跳至主要内容

R 中的长表与宽表

了解为何需要在长表与宽表之间转换,并学习如何在 R 中使用 melt() 与 dcast() 实现这一过程!
· 15分钟 阅读

使用 AI 探索

在数据分析中,您常常需要在长格式(也称为“长表”——其含义稍后会更清楚)与宽格式(“宽表”)之间相互转换。真实世界中的数据格式五花八门,但多数 R 函数只适配某一种特定格式。

例如,用于混合线性建模的 lme4::lmer()、用于可视化的 ggplot2::ggplot() 都更倾向于长表;而 mosaicplot() 等函数则偏好宽表。这意味着,除非数据已经预处理为正确的形态,否则您需要先进行格式转换,才能顺利开展分析或可视化。

R 中有多种格式转换方式,但在入门阶段往往容易混淆。

本教程将帮助您理解在长表与宽表之间转换的机制。您将学习以下内容:

  • 进一步认识长表;
  • 接着了解宽表;
  • 回顾长表与宽表的差异;
  • 随后了解从一种格式转换为另一种格式需要的步骤;
  • 最后,使用 R 动手实现:通过 melt() 与 dcast() 函数完成长转宽与宽转长。

数据

表 01
namegenderyear2011year2012year2013year2014year2015
Jackson SmithM74.6984.9991.73105.11111.04
Sophia JohnsonFNANANANA75.89
Emma WilliamsFNANA75.7486.5091.50
Aiden JonesMNANANA71.8981.42
Liam BrownM88.2496.91101.85108.13112.45
Lucas DavisM70.6083.7894.17100.03106.35
Olivia MillerF64.7880.7687.3097.13103.80
Ava WilsonF88.7796.45104.72112.84NA

该表展示了某地婴儿的身高测量数据,时间跨度为 2011 至 2015 年。您可能已经知道,NA 表示“数据不可用”。从 NA 出现的位置,您大致能推断婴儿的年龄,但表中并未直接给出精确年龄。虽然表格看起来井井有条,但对于研究婴儿生长的人来说,问题在于:关键并不是测量发生的绝对年份,而是测量时婴儿的年龄。您能把这条信息加入表格吗?下面是一种方式:

表 02
namegenderage0age1age2age3age4age5age6
Jackson SmithMNA76.6984.9197.06105.73107.32NA
Sophia JohnsonFNA76.05NANANANANA
Emma WilliamsF72.6582.4691.76NANANANA
Aiden JonesM69.7679.89NANANANANA
Liam BrownMNANA85.3493.22105.78105.84114.82
Lucas DavisM71.5984.8792.10100.73104.21NANA
Olivia MillerF62.7481.3992.0298.32106.44NANA
Ava WilsonFNANA88.39100.67107.58111.52NA

NA 的数量增加了。## 宽表数据 但如果您想表示“婴儿满了多少个月”呢?可能会是下面这样:

表 03
namegenderm4m6m7m10m12...m55m60m65m72
Jackson SmithMNANANANA76.69...NA107.32NANA
Sophia JohnsonFNANANANA76.05...NANANANA
Emma WilliamsFNANANA72.65NA...NANANANA
Aiden JonesMNA69.76NANANA...NANANANA
Liam BrownMNANANANANA...NA105.84NA114.82
Lucas DavisMNANA71.59NANA...104.21NANANA
Olivia MillerF62.74NANANANA...NANANANA
Ava WilsonFNANANANANA...NANA111.52NA

如上表所示,NA 成片出现。如果婴儿的相对测量时间不同,宽表会极其浪费空间。另一种办法是在 2015 年时记录婴儿的年龄,但这只在所有绝对测量时间相同的情况下可行。如果婴儿测量时间相差以“月”为单位,而您又想用月份来表示年龄,就行不通了。在这种情况下,上述示例似乎是唯一可行的方式。这时,您就可以考虑使用长表。## 长表数据 在宽表中,一个婴儿的所有测量集中在一行;而在长表中,一行只包含一次测量。获得长表的最简单方法,就是在一行中展示儿童姓名、测量时间和测量值(身高)。下面这张表正是如此:

表 04
namegendermonthheight
Olivia MillerF462.74
Aiden JonesM669.76
Lucas DavisM771.59
Emma WilliamsF1072.65
Jackson SmithM1276.69
............
Liam BrownM72114.82

如果按名字排序整理数据,您会得到下面这样的表。显然,这张表占用的空间比上面的表 3 要小得多!

表 05
namegendermonthheight
Jackson SmithM1276.69
Jackson SmithM2484.91
Jackson SmithM3697.06
Jackson SmithM48105.73
Jackson SmithM60107.32
Sophia JohnsonF1276.05
Emma WilliamsF1072.65
............
Ava WilsonF65111.52

长表 vs 宽表

请再次注意长表与宽表的根本差异:一个直观的区别是,宽表将某个个体的多次测量放在同一行,并通过列名标示测量是什么。

下表在一行中展示了 Liam Brown 在 2011–2015 年的身高:

表 06
namegenderyear2011year2012year2013year2014year2015
Liam BrownM88.2496.91101.85108.13112.45

而在长表中,Liam Brown 的身高出现在同一列:

表 07
namegender_
Liam BrownM85.34
Liam BrownM93.22
Liam BrownM105.78
Liam BrownM105.84
Liam BrownM114.82

如您所见,单看这一列并不知道这些数值对应哪次测量。如果把列名写出来,会变成这样:

表 08
namegenderyear2011, 2012, 2013, 2014, 2015
Liam BrownM85.34
Liam BrownM93.22
Liam BrownM105.78
Liam BrownM105.84
Liam BrownM114.82

但这样也有问题。Sophia Johnson 在 2011–2014 年没有测量值。如下所示,这一列的列名就很难统一设定:

表 09
namegender?
Liam BrownM85.34
Liam BrownM93.22
Liam BrownM105.78
Liam BrownM105.84
Liam BrownM114.82
Sophia JohnsonF76.05

从宽表到长表

将宽表转换为长表可分步进行。在把一行中的多个测量堆叠到一列之前,您可以先把表格改造成“每行只保留一次测量”。以这张表为例:

表 10
namegenderage0age1age2age3age4age5age6
Emma WilliamsF72.6582.4691.76NANANANA
Aiden JonesM69.7679.89NANANANANA

结果如下表所示:

表 11
namegenderage0age1age2age3age4age5age6
Emma WilliamsF72.65  NANANANA
Emma WilliamsF 82.46    NA
Emma WilliamsF  91.76    
Aiden JonesM69.76 NANANANANA
Aiden JonesM 79.89     

在把分散在多列的测量值收拢到一列之前,您需要先新增一列来标识这些测量的类别,如下:

表 12
namegendermeasureage0age1age2age3age4age5age6
Emma WilliamsFage072.65  NANANANA
Emma WilliamsFage1 82.46    NA
Emma WilliamsFage2  91.76    
Aiden JonesMage069.76 NANANANANA
Aiden JonesMage1 79.89     

新列名通常用 “measure” 或 “key”。您会注意到,上表中 “age0”、“age1”、“age2”、“age3”、“age4”、“age5” 同时出现在列名和某一列中。如果省略列名,表格如下。

表 13
namegendermeasure____________________________
Emma WilliamsFage072.65  NANANANA
Emma WilliamsFage1 82.46     
Emma WilliamsFage2  91.76    
Aiden JonesMage069.76 NANANANANA
Aiden JonesMage1 79.89     

由于每行只有一次测量,您可以将其收拢到一列,得到如下结果:

表 14
namegendermeasure 
Emma WilliamsFage072.65
Emma WilliamsFage182.46
Emma WilliamsFage291.76
Aiden JonesMage069.76
Aiden JonesMage179.89

给这一列测量值起个新列名,通常叫作 "value":

表 15
namegendermeasurevalue
Emma WilliamsFage072.65
Emma WilliamsFage182.46
Emma WilliamsFage291.76
Aiden JonesMage069.76
Aiden JonesMage179.89

观察上表,您会发现 "age" 在重复。我们可以去掉重复的“age”前缀,得到:

表 16
namegenderagevalue
Emma WilliamsF072.65
Emma WilliamsF182.46
Emma WilliamsF291.76
Aiden JonesM069.76
Aiden JonesM179.89

每一行中重复出现的 Age 现在成了一个列名。上表中的 value 还可以更具体些:把它替换为更明确的 height。结果如下:

表 17
namegenderageheight
Emma WilliamsF072.65
Emma WilliamsF182.46
Emma WilliamsF291.76
Aiden JonesM069.76
Aiden JonesM179.89

比较表 15 与表 17。表 15 中的列名 value 可以代表任何数值,而表 17 中的 height 则语义明确:只能表示身高;表 15 中的 value 可能是身高、体重、速度或颜色等任意值。另一种做法如下:

表 18
namegendermeasurevalue
Emma WilliamsFheight.age072.65
Emma WilliamsFheight.age182.46
Emma WilliamsFheight.age291.76
Aiden JonesMheight.age069.76
Aiden JonesMheight.age179.89

考虑测量项目多样化的情形:若既有身高、又有体重与 BMI,如下表所示。但此时 value 列混合了不同量纲的数值,读起来可能会困惑。此表中,身高单位为厘米,体重单位为千克:

表 19
namegendermeasurevalue
Emma WilliamsFheight72.65
Emma WilliamsFweight8.22
Emma WilliamsFBMI22.60
Aiden JonesMheight69.76
Aiden JonesMweight8.51

因此,在把宽表转换为长表时,首先要决定哪些数值要合并进同一列。随后再确定该列的列名。

小结一下:将宽表转为长表,您需要先决定要合并成一列的那些列。合并后,原来多列会变成一列,因此需要给这列取一个列名,如下表所示:

表 20
id1id2age0age1age2
Emma WilliamsFNA82.4691.76
Aiden JonesM69.7679.89NA
Emma WilliamsFNA82.4691.76
表 21
id1id2age0age1age2
Emma WilliamsFNA  
Emma WilliamsF 82.46 
Emma WilliamsF  91.76
Aiden JonesM69.76  
Aiden JonesM 79.89 
Aiden JonesM  NA
Emma WilliamsFNA  
Emma WilliamsF 82.46 
Emma WilliamsF  91.76
表 22
id1id2age_age0age1age2
Emma WilliamsFage0NA  
Emma WilliamsFage1 82.46 
Emma WilliamsFage2  91.76
Aiden JonesMage069.76  
Aiden JonesMage1 79.89 
Aiden JonesMage2  NA
Emma WilliamsFage0NA  
Emma WilliamsFage1 82.46 
Emma WilliamsFage2  91.76
表 23
id1id2age_ 
Emma WilliamsFage0NA
Emma WilliamsFage182.46
Emma WilliamsFage291.76
Aiden JonesMage069.76
Aiden JonesMage179.89
Aiden JonesMage2NA
Emma WilliamsFage0NA
Emma WilliamsFage182.46
Emma WilliamsFage291.76
表 24
id1id2keyvalue
Emma WilliamsFage0NA
Emma WilliamsFage182.46
Emma WilliamsFage291.76
Aiden JonesMage069.76
Aiden JonesMage179.89
Aiden JonesMage2NA
Emma WilliamsFage0NA
Emma WilliamsFage182.46
Emma WilliamsFage291.76
表 25
id1id2keyvalue
Emma WilliamsFage182.46
Emma WilliamsFage291.76
Aiden JonesMage069.76
Aiden JonesMage179.89
Emma WilliamsFage182.46
Emma WilliamsFage291.76

观察上表中的 id1 与 id2 列。id2 并不标识个体,而是个体的属性(性别)。换言之,它也可以被视为测量项,因此亦可继续转为“更长”的形态。最“长”的形态由三列组成:id、key 与 value。

表 26
idkeyvalue
Emma WilliamsgenderF
Emma Williamsage182.46
Emma Williamsage291.76
Aiden JonesgenderF
Aiden Jonesage069.76
Aiden Jonesage179.89
Emma WilliamsgenderF
Emma Williamsage182.46
Emma Williamsage291.76

上表展示了表 25 的“最长形态”。但不同的人可能同名。

此时,长表会引发歧义,因为 id 无法唯一标识个人。您可以在姓名后加序号(例如使用 R 函数 make.unique())加以区分,但若有人本名就带有数字(比如真叫 “John.2”),则无法区分修改过的名字与原名。

显而易见的解决方案是为不同的人分配不同的数字 ID。在这种情况下,姓名也可被视为另一项“测量”。

下表展示了连姓名都作为测量项处理。在某种意义上,这才是“真正的最长形态”。

表 27
idkeyvalue
1nameEmma Williams
1genderF
1age182.46
1age291.76
2nameAiden Jones
2genderF
2age069.76
2age179.89
3nameEmma Williams
3genderF
3age182.46
3age291.76

“最长形态”是生成宽表最容易的起点。反向执行表 20 到表 25 所示的步骤,就能从长表回到宽表。下面的表展示了这一过程:

表 28. 将 key 列中的测量项重复到列名上
idkeynamegenderage0age1age2
1nameEmma Williams    
1gender F   
1age1   82.46 
1age2    91.76
2nameAiden Jones    
2gender F   
2age0  69.76  
2age1   79.89 
3nameEmma Williams    
3gender F   
3age1   82.46 
3age2    91.76
表 29. 可删除 key 列
idnamegenderage0age1age2
1Emma Williams    
1 F   
1   82.46 
1    91.76
2Aiden Jones    
2 F   
2  69.76  
2   79.89 
3Emma Williams    
3 F   
3   82.46 
3    91.76
表 30. 将相同 id 的行合并
idnamegenderage0age1age2
1Emma WilliamsF 82.4691.76
1     
1     
1     
2Aiden JonesF69.7679.89 
2     
2     
2     
3Emma WilliamsF 82.4691.76
3     
3     
3     
表 31. 删除没有任何测量值的行
idnamegenderage0age1age2
1Emma WilliamsF 82.4691.76
2Aiden JonesF69.7679.89 
3Emma WilliamsF 82.4691.76

将宽表转为长表的关键,在于决定要把哪些列合并为一列。

但也不一定只能合并为“一列”。必要时,您可以合并成两列,如下表所示:

表 32
namegenderh2011h2012w2011w2012
Jackson SmithM74.6984.999.6012.0
Olivia MillerFNA80.767.1510.7
Ava WilsonF88.7796.45NA15.0

您能把它转换成长表吗?

h2011、h2012、w2011、w2012 分别表示 2011 与 2012 年的身高与体重。height 与 weight 的量纲不同(厘米、千克)。您可以将二者合并到同一列,也可以保留为两列,如下:

表 33
namegenderyearheightweight
Jackson SmithM201174.699.60
Jackson SmithM201284.9912.00
Olivia MillerF2011NA80.76
Olivia MillerF20127.1510.70
Ava WilsonF201188.7796.45
Ava WilsonF2012NA15.00

这到底是长表还是宽表呢?

R 中的长表转宽表

让我们用 R 来进行长表与宽表之间的转换。完成该任务的函数与包有很多。

表 34
funcpackageto_long_formto_wide_form
stack/unstackutilsstackunstack
reshapestatsreshape(direction="long", ...)reshape(direction="wide", ...)
melt/dcastreshape2meltdcast
gather/spreadtidyrgatherspread

这里将讲解 reshape2 包中的 melt() 与 dcast() 函数。原因是 stack() 与 unstack() 虽是基础函数,但通常需要对结果再做后处理。需要说明的是,gather() 与 spread() 是 tidyr 中同样流行的函数,但本教程不做覆盖。

另一个可用函数是 stats 包中的 reshape()。别被它与同名包 reshape 搞混!后者与 reshape2 是为帮助使用 reshape() 感到吃力的人而开发的。另外,reshape() 似乎默认数据具有纵向特征(即随时间重复测量)。

让我们把表 35 的数据转成长表。假设下表以 data.frame 形式存为 dat。

表 35
namegenderyear2011year2012year2013
Jackson SmithM74.6984.9991.73
Emma WilliamsFNANA75.74
Liam BrownM88.24NA101.85
Ava WilsonF88.7796.45NA

babies = data.frame(name= c("Jackson Smith", "Emma Williams", "Liam Brown", "Ava Wilson"), gender = c("M", "F", "M", "F"), year2011= c(74.69, NA, 88.24, 88.77), year2012=c(84.99, NA, NA, 96.45), year2013=c(91.73, 75.74, 101.83, NA)) babies

##            name gender year2011 year2012 year2013
## 1 Jackson Smith      M    74.69    84.99    91.73
## 2 Emma Williams      F       NA       NA    75.74
## 3    Liam Brown      M    88.24       NA   101.83
## 4    Ava Wilson      F    88.77    96.45       NA

您需要首先回答的问题是:“哪些列要合并为一列?”

这里就是 year2011、year2012、year2013。使用 melt() 时,把这些列写在 measure.vars 中:

melt(babies, measure.vars = c("year2011", "year2012", "year2013"))

##             name gender variable  value
## 1  Jackson Smith      M year2011  74.69
## 2  Emma Williams      F year2011     NA
## 3     Liam Brown      M year2011  88.24
## 4     Ava Wilson      F year2011  88.77
## 5  Jackson Smith      M year2012  84.99
## 6  Emma Williams      F year2012     NA
## 7     Liam Brown      M year2012     NA
## 8     Ava Wilson      F year2012  96.45
## 9  Jackson Smith      M year2013  91.73
## 10 Emma Williams      F year2013  75.74
## 11    Liam Brown      M year2013 101.83
## 12    Ava Wilson      F year2013     NA

year2011、year2012、year2013 分别位于第 3、4、5 列,因此也可以这样写:

melt(babies, measure.vars = 3:5)

##             name gender variable  value
## 1  Jackson Smith      M year2011  74.69
## 2  Emma Williams      F year2011     NA
## 3     Liam Brown      M year2011  88.24
## 4     Ava Wilson      F year2011  88.77
## 5  Jackson Smith      M year2012  84.99
## 6  Emma Williams      F year2012     NA
## 7     Liam Brown      M year2012     NA
## 8     Ava Wilson      F year2012  96.45
## 9  Jackson Smith      M year2013  91.73
## 10 Emma Williams      F year2013  75.74
## 11    Liam Brown      M year2013 101.83
## 12    Ava Wilson      F year2013     NA

结果见上表。注意列名 variable 与 value。表 23 与表 24 已展示:需要为新列命名。melt() 的默认新列名是 variable 与 value。您可以通过 variable.name= 与 value.name= 参数覆盖默认值,如下:

melt(babies, measure.vars=3:5, variable.name="year", value.name="height")

##             name gender     year height
## 1  Jackson Smith      M year2011  74.69
## 2  Emma Williams      F year2011     NA
## 3     Liam Brown      M year2011  88.24
## 4     Ava Wilson      F year2011  88.77
## 5  Jackson Smith      M year2012  84.99
## 6  Emma Williams      F year2012     NA
## 7     Liam Brown      M year2012     NA
## 8     Ava Wilson      F year2012  96.45
## 9  Jackson Smith      M year2013  91.73
## 10 Emma Williams      F year2013  75.74
## 11    Liam Brown      M year2013 101.83
## 12    Ava Wilson      F year2013     NA

上述示例是按测量项来指定的。在某些场景中,Id 也可间接指定测量项。因此您也可以选择用 id 列来指定,而不是用测量列。

下面两种写法等价。第一种选择 id 列,第二种选择要合并的列:

melt(babies, id.vars=c("name","gender"))

melt(babies, measure.vars = c("year2011", "year2012", "year2013"))

##             name gender variable  value
## 1  Jackson Smith      M year2011  74.69
## 2  Emma Williams      F year2011     NA
## 3     Liam Brown      M year2011  88.24
## 4     Ava Wilson      F year2011  88.77
## 5  Jackson Smith      M year2012  84.99
## 6  Emma Williams      F year2012     NA
## 7     Liam Brown      M year2012     NA
## 8     Ava Wilson      F year2012  96.45
## 9  Jackson Smith      M year2013  91.73
## 10 Emma Williams      F year2013  75.74
## 11    Liam Brown      M year2013 101.83
## 12    Ava Wilson      F year2013     NA

如果同时指定 id.vars 与 measure.vars,未被包含在两者中的列将被排除。例如:

melt(babies, id.vars=c("name"), measure.vars=c("year2011", "year2012"))

##            name variable value
## 1 Jackson Smith year2011 74.69
## 2 Emma Williams year2011    NA
## 3    Liam Brown year2011 88.24
## 4    Ava Wilson year2011 88.77
## 5 Jackson Smith year2012 84.99
## 6 Emma Williams year2012    NA
## 7    Liam Brown year2012    NA
## 8    Ava Wilson year2012 96.45

此外,使用 na.rm=T 可去除 NA:

melt(babies, id.vars=c("name"), measure.vars=c("year2011", "year2012"), na.rm=T)

##            name variable value
## 1 Jackson Smith year2011 74.69
## 3    Liam Brown year2011 88.24
## 4    Ava Wilson year2011 88.77
## 5 Jackson Smith year2012 84.99
## 8    Ava Wilson year2012 96.45

dcast() 与 melt() 相反。id、variable、value 是“最长形态”的三列。dcast(data, id ~ variable, value.var="value") 可将最长形态转换为宽表。

babiesLong <- melt(babies, id.vars=c("name"), measure.vars=c("year2011", "year2012"), na.rm=T)
babiesLong

##            name variable value
## 1 Jackson Smith year2011 74.69
## 3    Liam Brown year2011 88.24
## 4    Ava Wilson year2011 88.77
## 5 Jackson Smith year2012 84.99
## 8    Ava Wilson year2012 96.45

babiesLong 中,name 对应 id,variable 对应 variable,value 对应 value。因此可用 dcast() 将其变回宽表:

dcast(babiesLong, name ~ variable, value.var="value")

##            name year2011 year2012
## 1    Ava Wilson    88.77    96.45
## 2 Jackson Smith    74.69    84.99
## 3    Liam Brown    88.24       NA

结语

以上就是全部内容。本教程着重于理解数据重塑的逻辑与过程。通常,长表更适合分析,但也有例外。在合适的场景下,宽表更省空间;然而当缺失值增多时,转换为长表往往更合适。

在格式转换时,请聚焦“什么是测量项”。长表的口号是“一行一个测量”,而宽表是一行包含多个测量。当您把分布在多列的测量合并到一列时,需要用新增的一列来保留原列名;当您把一列中的测量展开到多列时,则需要从 variable 列推断并设定新的列名。

理解了这一过程后,使用 dcast() 与 melt() 等函数就会顺畅许多!

主题