2. R基础数据结构:向量、矩阵、数组、数据框、列表的创建与操作
这一章是R语言的“地基”。搞质控的人,天天和这些数据结构打交道。说白了,R世界里的一切数据,都跑不出这五种容器:向量、矩阵、数组、数据框、列表。
我先讲个故事。去年我帮一家药企搭建OOS(超标结果)分析流程,现场工程师把检测结果堆在一个大列表里,想算均值结果报错了。我一看,列表里混着字符串和数值——典型的"数据结构用错"。所以别小看这章,基础不牢,后面全白费。
核心原则:每种数据结构有它天生的“逻辑”,用途单一。搞混了,代码跑不通,质控分析就瘫了。
2.1 向量:最基础的数据单元
向量是所有数据结构的“积木”。一个向量就是一组同质元素,类型必须统一。用c()函数创建,这是最常用的函数之一。
# 数值型向量
batch_id <- c(101, 102, 103, 104, 105)
assay_result <- c(98.5, 99.2, 97.8, 100.1, 99.5)
# 字符型向量
batch_label <- c("合格", "合格", "不合格", "合格", "合格")
# 逻辑型向量
is_pass <- c(TRUE, TRUE, FALSE, TRUE, TRUE)
我的习惯:质控数据里,我喜欢先用is.vector()检查数据是不是向量。以前遇上过数据框的一列,本质是因子,直接算均值报错。
向量操作:
- 索引:用方括号
batch_id[1:3]取出前3个批次ID。 - 向量运算:R对向量运算非常直接。
assay_result * 1.05会给每个值乘以1.05(模拟5%的偏差修正)。 - 标量回收:R会自动将标量“回收”到与向量等长。
# 索引:取第2个到第4个
batch_id[2:4] # 返回 102 103 104
# 向量运算:所有结果加一个常数
assay_result + 0.5 # 每个值加0.5
# 逻辑索引:提取合格批次
assay_result[is_pass]
我曾经踩过的坑:在拼接两个向量时,不小心把NA值带了进去。c(1, 2, NA, 4) + c(5, 6, 7, 8),结果是第三位成了NA。质控里NA会污染整个计算,所以一定要用na.omit()或na.rm参数提前处理。
2.2 矩阵:二维同质数据
矩阵是向量的“二维升级版”,所有元素还是同类型。质控里常用矩阵存放多个批次的平行检测结果,比如每行一个批次,每列一个平行样。
# 创建一个4行3列的矩阵,存放4个批次的3次平行检测
results_matrix <- matrix(
c(98.5, 99.2, 97.8,
100.1, 99.5, 100.3,
97.0, 96.8, 97.5,
99.8, 100.0, 99.3),
nrow = 4, ncol = 3, byrow = TRUE
)
rownames(results_matrix) <- c("Batch01", "Batch02", "Batch03", "Batch04")
colnames(results_matrix) <- c("Rep1", "Rep2", "Rep3")
| 操作 | 代码示例 | 说明 |
|---|---|---|
| 按行计算均值 | rowMeans(results_matrix) |
计算每批次的均值 |
| 按列计算标准差 | apply(results_matrix, 2, sd) |
计算每列的标准差 |
| 矩阵转置 | t(results_matrix) |
行变列,列变行 |
| 元素索引 | results_matrix[2, 3] |
第2行第3列的值 |
# 提取Batch03的所有平行样
results_matrix["Batch03", ]
# 提取所有批次的第1个平行样
results_matrix[, "Rep1"]
2.3 数组:超越二维
数组是矩阵的泛化,可以是任意维度。质控里,你可能会用三维数组存放“批次×样点×时间点”的数据。但说实话,日常分析中三维以上用得不多,遇到时我会考虑拆成列表。
# 生成一个三维数组:2个批次 × 3个样点 × 4个时间点
array_data <- array(1:24, dim = c(2, 3, 4))
dimnames(array_data) <- list(
c("BatchA", "BatchB"),
c("Site1", "Site2", "Site3"),
c("t1", "t2", "t3", "t4")
)
# 取出BatchA在Site2的全部时间点
array_data["BatchA", "Site2", ]
2.4 数据框:质控分析的主角
数据框是R里最重要的数据结构。它是个“聪明的矩阵”——每列类型可以不同,有行名和列名。质控中一张检测报告,一列日期,一列批次号,一列结果值,一列判定结论,用数据框存正合适。
创建方式有很多,我习惯用data.frame()直接定义每一列:
qc_records <- data.frame(
batch_id = c(101, 102, 103, 104, 105),
assay_mean = c(98.5, 99.2, 97.8, 100.1, 99.5),
std_dev = c(1.2, 1.5, 2.1, 1.8, 1.3),
result = c("PASS", "PASS", "FAIL", "PASS", "PASS"),
stringsAsFactors = FALSE # 重要:不自动转因子
)
我个人经验:从Excel或者CSV读数据时,很多列自动变因子。我总会加一句stringsAsFactors = FALSE,省得后面操作字符串时还要as.character()转回来。
数据框操作:
- 筛选行:
qc_records[qc_records$result == "PASS", ] - 选取列:
qc_records[, c("batch_id", "assay_mean")]或qc_records$batch_id - 新增列:
qc_records$cv <- qc_records$std_dev / qc_records$assay_mean * 100 - 查看结构:
str(qc_records)看每列的类型
# 排序:按均值降序
qc_records[order(qc_records$assay_mean, decreasing = TRUE), ]
# 删除不合格批次(先找出符合条件的行索引)
qc_records <- qc_records[qc_records$result != "FAIL", ]
2.5 列表:最强容器
列表可以“装万物”。质控分析中,一个项目可能包含多种数据:原始向量、统计摘要、控制限值和备注文本。列表可以把它们打包在一个对象里。
qc_project <- list(
project_name = "季度OOS分析",
batch_means = c(98.5, 99.2, 97.8, 100.1, 99.5),
summary = data.frame(
metric = c("mean", "sd", "min", "max"),
value = c(99.02, 1.58, 97.8, 100.1)
),
control_limits = c(LCL = 97.0, UCL = 102.0),
notes = "该分析基于FDA指南"
)
访问列表元素:
- 单层访问:
qc_project$batch_means - 递归访问:
qc_project[[1]]取出第一个元素 - 部分列表:
qc_project[c("summary", "notes")]仍然是列表
# 取出summary中的标准差
std_val <- qc_project$summary$value[2]
std_val # 输出 1.58
避坑提醒:列表用$和[[]]的区别,很多人弄混。$name相当于[["name"]],返回对象本身;["name"]返回一个只有这个元素的子列表。我建议多用$,代码可读性高。
2.6 数据结构转换
质控分析里,经常需要在不同结构间转换。下面这些是常用函数:
| 转换 | 函数 | 应用场景 |
|---|---|---|
| 数据框→矩阵 | as.matrix() |
当需要整矩阵运算时(但注意类型统一) |
| 列表→向量 | unlist() |
把列表里的值“拉平”成一维向量 |
| 数据框→列表 | as.list() |
按列拆分为列表(每列一个元素) |
| 矩阵→数据框 | as.data.frame() |
矩阵计算结果加上元数据列 |
# 把data.frame的数值列转成矩阵(用于计算相关系数矩阵)
numeric_mat <- as.matrix(qc_records[, c("assay_mean", "std_dev")])
cor(numeric_mat) # 计算相关系数
# 把一个列表里的多个批次均值拉平成向量
list_of_means <- list(Batch1=98.5, Batch2=99.2, Batch3=97.8)
combined <- unlist(list_of_means)
combined
我遇到过的情况:在把数据框转为矩阵时,如果数据框里混有字符列,as.matrix()会强制把数值也变成字符。结果相关系数算不了,全是NA。所以转矩阵前,一定要先筛选纯数值列。
好了,这五种数据结构就是R里“质控工具箱”的原材料。我心里很清楚:向量是砖,矩阵和数组是预制件,数据框是操作台,列表是文件柜。掌握它们,后面讲数据清洗、可视化、统计检验时才能顺手。今晚回去把每种类型的创建和基本操作敲两遍,代码熟能生巧。