第2章 R语言数据结构:从向量到列表,一栋数据大厦

做特征工程,说白了就是在跟数据结构打交道。你拿到的原始数据可能是表格,中间处理结果可能是向量,最后模型需要的又可能是矩阵——这五种结构就是你的工具箱。我个人习惯先把它们摸透,后面写管道、建特征才不会卡壳。

R的数据结构可以分成两大类:同质结构(所有元素类型一样)和异质结构(元素可以不同类型)。向量、矩阵、数组属于前者,数据框和列表属于后者。下面逐个聊。

2.1 向量(Vector)——最基础的数据单元

向量是R里最核心的结构,其他很多结构本质上都是向量的组合。创建向量最简单的方式就是用 c() 函数,中文叫“组合”。

# 数值向量
num_vec <- c(3.14, 2.71, 1.618)

# 字符向量
char_vec <- c("房价", "户型", "面积")

# 逻辑向量
log_vec <- c(TRUE, FALSE, TRUE)

注意R里面没有标量(单个值),写 x <- 5 其实创建的是一个长度为1的向量。这一点我刚开始学的时候老忘。

索引方式:R是1-based索引(从1开始),这个跟Python不一样。

num_vec[2]   # 返回2.71
num_vec[-1]  # 去掉第一个元素,返回剩余

避坑指南:我曾经在特征筛选时用 which() 找符合条件的索引,结果写成了 num_vec[num_vec > 3] 返回的是元素本身,不是位置——用 which(num_vec > 3) 才拿到索引。这两个容易搞混。

向量还有一个“循环”特性,做运算时短向量会自动补长,这在矩阵乘法和加偏差时非常有用,但如果你没搞清楚,可能会出悄悄错误。

2.2 矩阵(Matrix)——行和列的战场

矩阵本质上是加了维度属性的向量。用 matrix() 创建。

mat <- matrix(1:12, nrow = 3, ncol = 4)
mat
#      [,1] [,2] [,3] [,4]
# [1,]    1    4    7   10
# [2,]    2    5    8   11
# [3,]    3    6    9   12

默认按列填充(先填满第一列),如果想按行,加 byrow = TRUE。矩阵的维度用 dim(mat) 查看,返回两个数字:行数和列数。

矩阵运算:

  • 逐元素乘除:直接用 * 和 /
  • 矩阵乘法:用 %*%,这是你算协方差矩阵、特征工程中做降维时绕不开的符号
  • 转置:t(mat)

实际经验:在特征工程里,很多时候我们先把数据框转换成矩阵,再丢给线性代数函数。比如做PCA时,原始数据矩阵要中心化,R里写 scale(mat, center = TRUE, scale = FALSE) 就完事。

2.3 数组(Array)——维度再升级

数组可以看作是矩阵的推广:矩阵是二维,数组可以是任意维度。用 array() 创建,需要指定数据和维度向量。

arr <- array(1:24, dim = c(2, 3, 4))
dim(arr)   # 2 3 4

上面创建了一个有4个“层”的数组,每层是2行3列的矩阵。你可以在特征工程中把不同时间切片的数据堆到数组里,比如每日特征的月份序列。

索引多维数组时,哪个维度对应什么含义,建议写注释。我曾经在写时空特征提取时,把时间维度和空间维度搞反,结果模型怎么调都不对——检查了半天才发现索引顺序错了。

2.4 数据框(Data Frame)——日常工作的主力

数据框是表格型数据,每一列可以不同类型(一列全是数字、另一列全是字符串),这是它和矩阵的最大区别。创建方法:

df <- data.frame(
  id = 1:4,
  city = c("北京", "上海", "深圳", "广州"),
  price = c(50000, 60000, 45000, 32000),
  is_sale = c(TRUE, FALSE, TRUE, FALSE)
)

常用操作:

  • 查看列名:names(df)
  • 提取列:df$price 或 df[["price"]]
  • 行筛选:df[df$price > 40000, ]
  • 增加新列:df$price_per_sqm <- df$price / 100

注意:默认情况下,data.frame() 会把字符串列转为因子(factor),如果你不想要这个行为,记得加 stringsAsFactors = FALSE。我在第一次做回归时,分类变量被当成因子编码方式搞乱了特征,后来改用 tibble::tibble() 省心多了。

在特征工程中,数据框是最常用的容器:原始数据、预处理后的特征、衍生特征都可以放在里面,用 dplyr 管道操作非常顺手。

2.5 列表(List)——装下一切

列表是最灵活的结构,它可以包含任意类型、任意长度的元素,甚至可以装另一个列表。

my_list <- list(
  title = "特征工程手册",
  data = mtcars,               # 数据框
  coeffs = c(0.3, -1.2, 0.8),  # 向量
  sub_model = list(            # 嵌套列表
    weight = 0.5,
    intercept = 2.1
  )
)

访问方式:

  • my_list$title —— 通过名字访问
  • my_list[[2]] —— 通过位置索引,返回元素本身
  • my_list[2] —— 返回一个子列表(注意:仍是一个列表)

我个人经验:列表常用来保存模型训练中间产物,比如特征工程流程的各个阶段输出。如果想把多个类型不同的结果打包返回,列表就是最佳选择。

2.6 五种数据结构速览对比

结构维度元素类型典型用途
向量1维同质存储单列数据、系数、索引
矩阵2维同质线性代数运算、距离矩阵
数组多维(≥2)同质图像数据、时间序列堆叠
数据框2维每列异质日常表格数据、特征存储
列表递归结构完全异质包裹多个对象、复杂返回

你想想看,这五种结构其实可以看作是一个模型——从最简单的向量开始,慢慢往上堆维度、堆灵活性,最后得到列表这种“万能袋”。掌握了它们,你在R里写特征工程代码就有了骨架。

R语言数据结构族谱 原子向量(同质) 矩阵(2维) 数组(多维) 数据框(列异质) 列表(任意嵌套) 说明 实线箭头表示“由向量添加维度/结构得到” 虚线箭头表示“数据框由多个向量列组合而成” 所有结构最终都能放进列表这个超级容器

这张图把五种结构的衍生关系串起来了。你在写代码时,心里最好有个这样的谱:从向量出发,叠加维度变成矩阵/数组;为每一列赋予不同类型,就成了数据框;如果要装更复杂的东西,就上列表。

避坑二:我曾经为了图省事,把所有特征放在一个列表里,结果后续想转成数据框时用了 as.data.frame(),发现列表里元素长度不一样就报错——这时需要先用 do.call(cbind, ...) 或者 dplyr::bind_cols() 对齐长度。