ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

R语言核心数据结构详解:矩阵、数组、列表与数据框一次讲透

R语言核心数据结构详解:矩阵、数组、列表与数据框一次讲透 说实话学R语言绕来绕去最后发现最核心的还是那几样基础数据结构。这期笔记继续我的R语言学习系列前两篇写了向量、因子、条件与循环这篇集中把矩阵matrix、数组array、列表list和数据框data.frame一次讲透。对刚入门R、或者被各种数据类型搞晕的朋友来说这四样东西就是R的骨架分析工作中八成以上的数据操作都发生在它们身上。如果结构选错后面所有代码都会绕远路所以值得花点时间把概念和用法都掰扯清楚。1. 内容整体设计与思路拆解1.1 为什么把矩阵、数组、列表和数据框放一起讲很多人学R的时候习惯一个函数一个函数地背今天学matrix明天学data.frame后天学list结果学完还是懵遇到数据不知道到底该用哪个。这很正常因为这几个对象不是孤立存在的它们本质上都是“容器”只是维度不同、规则不同、适用场景不同。先理清R里最底层的一个概念向量vector是R的一等公民。你在R里看到的几乎一切东西底层都是一个向量或者由向量组合出来的对象。单个数值是长度为1的向量一列数据是向量甚至一个数据框在底层也是由若干等长向量拼起来的。理解了这一点再看矩阵、数组、列表和数据框其实就是几种不同的“包装方式”矩阵二维的、所有元素类型一致的容器可以理解为“带行列维度的向量”。数组三维及以上的同质容器可以理解为“把向量折叠成多个维度”。列表一个可以装任何东西的“万能收纳箱”每个元素可以是向量、矩阵、数据框甚至另一个列表而且类型可以不同。数据框长得像矩阵但每一列可以存不同类型的数据本质是一个“特殊形态的列表”。把它们放在一起学是因为你可以从“维度”和“同质性”两个维度去记忆。矩阵和数组要求所有元素类型一致数据框和列表允许异构矩阵是二维的数组是多维的数据框是二维表格列表是任意结构的集合。这套逻辑串起来之后你在实际工作中看到数据就能很快判断该用哪种结构。1.2 选型逻辑遇到数据先问三个问题我实操中总结了一套选型思路每次拿到新数据都先问三个问题这份数据是几维的只有行和列还是有多层结构所有单元格的类型是否一致全是数值还是混合了字符、数值、日期我要对数据做什么做矩阵运算、按层汇总还是按列做统计分析如果数据是典型的二维表格行是记录、列是变量而且各列类型不同直接选数据框。如果数据是一份纯数值的矩阵要做矩阵乘法、求逆、特征值分解这类线性代数运算用矩阵。如果你的数据有多层比如“年份 x 地区 x 指标”这类三维结构用数组最方便。如果你的数据形态很散比如模型结果、不同长度的向量、一组数据框用列表把它们装起来统一管理。这套选型逻辑想清楚之后写代码会顺手很多。我见过不少新手把所有数据都塞进data.frame连纯数值矩阵也用data.frame存结果做矩阵运算时还要先转换白白浪费时间。2. 核心细节解析与实操要点2.1 矩阵从创建到运算先看矩阵的创建。最基本的方式是matrix()函数# 创建3行4列的矩阵按列填充默认 m1 - matrix(1:12, nrow 3) # 创建3行4列的矩阵按行填充 m2 - matrix(1:12, nrow 3, byrow TRUE) # 给行列起名字 m3 - matrix(1:9, nrow 3, byrow TRUE, dimnames list(c(r1, r2, r3), c(c1, c2, c3)))注意一点R的矩阵默认是按列填充的这可能不符合很多人的直觉。如果你的数据是按行读进来的一定记得加byrow TRUE否则数据顺序会全部错位。早期我在这里吃过不少亏。矩阵创建完之后最常用的操作是拼维度、取子集、做运算。# 查看维度 dim(m1) nrow(m1) ncol(m1) # 取子集 m1[1, ] # 第一行 m1[, 2] # 第二列 m1[2, 3] # 第二行第三列元素 # 行列拼接 rbind(m1, m2) # 按行拼 cbind(m1, m2) # 按列拼矩阵运算也值得单独说一嘴。R里的*是对应元素相乘也就是Hadamard积不是矩阵乘法。矩阵乘法要用%*%。这个区别新手很容易忽略我见过有人算矩阵乘法直接写A * B结果得到一堆莫名其妙的数。A - matrix(1:4, nrow 2) B - matrix(4:1, nrow 2) A * B # 对应元素相乘 A %*% B # 真正的矩阵乘法 t(A) # 转置 solve(A) # 求逆 eigen(A) # 特征值分解eigen()返回的是特征值和特征向量组成的列表这一块在多元统计、主成分分析里经常用到。很多人以为特征值分解很难其实R里只是两行代码的事关键是你得知道结果长什么样ev - eigen(A) ev$values # 特征值 ev$vectors # 特征向量矩阵2.2 数组三维以上的数据怎么存数组和矩阵的关系一句话就能说清矩阵是二维数组数组是多维矩阵。也就是说matrix其实是array在二维情况下的特例。# 创建一个2x3x4的三维数组 arr - array(1:24, dim c(2, 3, 4)) # 给维度命名 arr - array(1:24, dim c(2, 3, 4), dimnames list(c(a, b), c(x, y, z), c(t1, t2, t3, t4)))数组在R里最典型的应用场景是重复测量的多维数据。比如你有4个时间点、3个处理组、每组2个重复样本这就是一个三维结构用数组存最合理。取数据的时候索引顺序和维度对应arr[1, 2, 3] # 第一个样本、第二个组、第三个时间点的值 arr[1, , ] # 第一个样本在所有组和时间点的数据返回一个矩阵 arr[, , 4] # 第四个时间点的所有数据返回一个矩阵数组的一个易错点在于维度索引的匹配尤其当维度多起来之后很容易对错位置。我个人的习惯是创建数组时一定把dimnames设置好这样取数、查看结果都清晰很多。数组操作里最实用的是apply()函数可以在指定维度上批量应用函数# 按第一个维度样本求和结果是长度为2的向量 apply(arr, 1, sum) # 按第二和第三维度求均值返回一个3x4的矩阵 apply(arr, c(2, 3), mean)2.3 列表R的“万能收纳箱”列表是R里最灵活、也最容易被忽视的结构。很多人学R时列表一带而过觉得用处不大其实列表在真实数据分析里无处不在——lm()的结果是列表t.test()的结果是列表建模的中间输出、多重比较的结果、循环迭代中不规整的数据全都靠列表来装。创建列表很简单my_list - list( name 张三, score c(85, 92, 78), info data.frame(city 北京, age 26), matrix_data matrix(1:4, nrow 2) )取列表里的元素有两个关键符号[ ]和[[ ]]。这个区别我必须单独强调因为新手在这里特别容易迷糊。my_list[1]返回的是一个子列表它里面还套着原来的那个元素my_list[[1]]返回的是元素本身my_list$name和my_list[[name]]效果一样都是直接取那个名字对应的值。str(my_list[1]) # List of 1仍然是列表 str(my_list[[1]]) # chr 张三直接是字符向量如果需要把列表中的所有元素拼接成一个向量用unlist()unlist(my_list[c(score)]) # 如果score本身是向量unlist之后也是向量但注意如果列表里有不同类型的元素unlist()会自动做类型转换比如把字符和数值混在一起数值会被强制转成字符。这个坑我踩过一次处理数据清洗时差点把数值型数据全变成字符串。2.4 数据框最像Excel的对象数据框是R里最常用的数据结构因为它和Excel表格的思维几乎一致行是观测列是变量。创建数据框用data.frame()df - data.frame( id 1:4, name c(Alice, Bob, Charlie, David), score c(88, 95, 79, 92), stringsAsFactors FALSE )这里有一个R的经典坑老版本R默认把字符列转成因子factor很多新手读取CSV后发现数据类型全变成factor然后各种计算报错。现在新版本的R默认stringsAsFactors FALSE但如果你在用较旧的版本或者读取某些数据时遇到莫名奇妙的因子列第一时间检查这个参数。数据框的常用操作包括查看结构、筛选行、选列和排序str(df) # 查看每一列的类型 head(df) # 查看前几行 summary(df) # 各列统计摘要 df[df$score 90, ] # 筛选score大于90的行 df[, c(name, score)] # 选取两列 df[order(df$score, decreasing TRUE), ] # 按score降序排列注意df[df$score 90, ]中的逗号不能省。数据框是两个维度的对象df[行条件, 列选择]逗号左边是行右边是列。初学者容易漏掉逗号结果得到的是一个列表而不是数据框。数据框和列表的关系也比较微妙数据框本质上是一个列表它的每一列是一个向量这些向量长度相同。这也解释了为什么数据框的每一列可以有不同的类型因为列表本来就允许异构元素存在。3. 实操过程与核心环节实现3.1 场景设定一份区域销售汇总数据单纯罗列函数没什么意思我拿一个实际场景过一遍完整流程。假设你拿到一份区域销售数据包含3个区域、4个季度、2个指标销售额和利润数据是三维的。这种数据很适合用数组存然后转成数据框做进一步分析。# 模拟数据3个区域、4个季度、2个指标 sales - array( c(120, 135, 142, 118, 156, 168, 171, 159, 89, 102, 98, 115, 32, 41, 47, 35, 45, 52, 49, 58, 21, 27, 30, 33), dim c(3, 4, 2), dimnames list( c(东区, 西区, 南区), c(Q1, Q2, Q3, Q4), c(销售额, 利润) ) )创建数组之后先验证一下数据是否准确定位sales[东区, Q2, 销售额] # 东区第二季度销售额 sales[, Q4, 利润] # 各区域第四季度利润这个场景下数组的优势是索引直观区域、季度、指标三个维度很清晰不太容易搞混。3.2 用apply按维度汇总现在需要分别看各区域的年度总销售额、总利润以及每个季度所有区域的合计。# 各区域年度合计按第二和第三维度求和得到3x2矩阵 apply(sales, c(1, 3), sum) # 各季度所有区域合计按第一和第三维度求和得到4x2矩阵 apply(sales, c(2, 3), sum)得到结果之后把数组转成数据框。转换过程中先用as.data.frame()sales_df - as.data.frame(sales)用一个真实场景看数组适合做“按维度批量计算”数据框适合做“按行筛选、按列建模”。需要把二维结果交给后续分析时两者之间切换是很自然的事情。3.3 矩阵在线性回归中的实际应用矩阵运算不光是在理论上绕来绕去在线性回归中就有直接应用。举个例子用R内置数据mtcars做简单线性回归手工用矩阵计算回归系数。X - cbind(1, mtcars$wt) # 加一列截距项 y - mtcars$mpg # 正规方程beta (XX)^{-1} Xy beta - solve(t(X) %*% X) %*% t(X) %*% y beta # 对比lm()的结果 lm(mpg ~ wt, data mtcars)$coefficients这两组结果完全一致。这里的t(X) %*% X就是XXsolve()用来求逆。我之所以一直建议别用数据框替代矩阵去处理这类问题就是因为矩阵运算写出来干净利落语义也清晰。3.4 列表批量管理模型结果实际项目中我经常要把多个模型的结果放在同一个对象里统一管理。这时候列表就派上用场了model1 - lm(mpg ~ wt, data mtcars) model2 - lm(mpg ~ wt hp, data mtcars) model3 - lm(mpg ~ wt hp cyl, data mtcars) models - list(m1 model1, m2 model2, m3 model3)然后批量提取每个模型的AIC值或者提取系数汇总lapply(models, AIC) lapply(models, function(m) coef(m)[wt])lapply()返回的还是一个列表如果所有结果类型一致可以unlist()转成向量。列表加lapply()这个组合是我个人在R里用得最多的模式之一比手动写三个coef()再拼接到一起舒服得多。4. 常见问题与排查技巧实录4.1 问题速查表下面这些都是我实际使用中碰到过、或者被问过很多次的问题整理成一张速查表现象可能原因解决办法矩阵运算结果错乱用了*而不是%*%确认运算类型矩阵乘法用%*%matrix()数据顺序不对默认按列填充加byrow TRUE数据框字符列变成factor老版本R默认stringsAsFactors TRUE读取时加stringsAsFactors FALSE列表取出来不是想要的值用了[ ]而不是[[ ]]要取元素本身用[[ ]]或$数组索引维度对不上忘记维度顺序或没有设置dimnames创建时设置维度名取数时按名称索引apply()结果维度意外对dim参数的顺序理解有偏差确认apply(x, 1, fun)是沿第一个维度操作unlist()后数值变了类型列表内元素类型不一致先确认列表元素类型必要时分开处理筛选数据框少了一列忘了逗号检索结果变成列表数据框子集用df[行, 列]格式这张表我建议收藏一下大部分R新手遇到的“数据结构相关的玄学报错”根源都能在这张表里找到。4.2 排查思路与两个必查函数遇到R对象相关的报错先别慌把下面两行代码跑一遍大多数问题都能定位class(你的对象) # 对象类型是什么矩阵数据框列表 str(你的对象) # 对象的内部结构长什么样str()是我认为R里最被低估的函数之一。它能清晰地告诉你这个对象是几维的、每一列是什么类型、列表里有哪些元素、元素长度多少。排查数据框列类型异常、列表嵌套过深等问题时str()基本就是“第一神器”。再分享一个小技巧如果你在RStudio里看到对象旁边显示的“Values”列提示比如num [1:3, 1:4]这表示一个3行4列的数值矩阵如果是List of 3说明是包含3个元素的列表。学会看这些提示很多问题在敲入名称回车之前就能提前发现。最后再分享一点个人经验。学R语言的数据结构别指望一次看完就记住一定要边学边写拿真实数据练。我当时就是用mtcars数据集反复创建矩阵、转数据框、拆列表折腾了两三天才把这些结构的切换变成肌肉记忆。后面学dplyr、ggplot2、tidyverse的时候发现基础数据结构理解得越透越能体会那些包的设计逻辑。这篇笔记只是一个起点建议你自己动手把每个示例跑一遍再想想如果换成自己的数据你会选哪种结构来存
返回列表