2. R基础数据结构:向量、矩阵、数组、数据框、列表的创建与操作

这一章是R语言的“地基”。搞质控的人,天天和这些数据结构打交道。说白了,R世界里的一切数据,都跑不出这五种容器:向量、矩阵、数组、数据框、列表。

我先讲个故事。去年我帮一家药企搭建OOS(超标结果)分析流程,现场工程师把检测结果堆在一个大列表里,想算均值结果报错了。我一看,列表里混着字符串和数值——典型的"数据结构用错"。所以别小看这章,基础不牢,后面全白费。

核心原则:每种数据结构有它天生的“逻辑”,用途单一。搞混了,代码跑不通,质控分析就瘫了。

R基础数据结构体系 R基础数据结构 向量 一维,同类型 矩阵 二维,同类型 数组 多维,同类型 数据框 二维,每列类型可不同 列表 容器,可嵌套任意类型

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里“质控工具箱”的原材料。我心里很清楚:向量是砖,矩阵和数组是预制件,数据框是操作台,列表是文件柜。掌握它们,后面讲数据清洗、可视化、统计检验时才能顺手。今晚回去把每种类型的创建和基本操作敲两遍,代码熟能生巧。