在R中将数据框从“宽”格式转换为“长”格式

5

I have the following dataframe:

 df = data.frame(A_1 = c(1,2,3), A_2 = c(4,5,6), A_3 = c(7,8,9), B_1 = c(10, 11, 12), B_2 = c(13, 14, 15), B_3 = c(16, 17, 18))

 #> df
 #  A_1 A_2 A_3 B_1 B_2 B_3
 #1   1   4   7  10  13  16
 #2   2   5   8  11  14  17
 #3   3   6   9  12  15  18

列名包含字母和数字。字母代表特定变量(例如,A是一个因子,B是一个因子),而列名中的数字代表个体。换句话说,每个个体都有A和B的值:A_1和B_1是个体1的列,A_2和B_2是个体2的列,以此类推。
我想要实现以下结果:请注意,所有的“A”列都合并成一个“A”列,所有的“B”列也一样,等等。
   A  B
 # 1 10
 # 2 11
 # 3 12
 # 4 13
 # 5 14
 # 6 15
 # 7 16
 # 8 17
 # 9 18

有没有简单的方法可以实现这个?请注意,我的真实数据框包含超过20个不同的字母列(A、B、C...),每个字母都有三个子列(例如:A_1、A_2、A_3)。谢谢!

你已经打上了 [tag:data.table] 的标签。你是在使用 "data.table" 包吗?还是你想用的是 "data.frame"?此外,请注意 [tag:dataframes] 标签与 R 中的数据结构无关,因此我已编辑该标签并将其替换为 "data.frame"。 - A5C1D2H2I1M1N2O1R2T1
我添加了data.table标签,以便在使用数据表更容易实现解决方案。但感谢您的编辑! - Mayou
2
在这种情况下,我也为您添加了一个data.table选项 :) - A5C1D2H2I1M1N2O1R2T1
谢谢!我已经根据您的建议修改了标题! - Mayou
3个回答

12

这被称为将数据从"宽"格式转换成"长"格式。在基础R中,有一个工具叫做reshape

reshape(df, direction = "long", varying = names(df), sep = "_")
#     time A  B id
# 1.1    1 1 10  1
# 2.1    1 2 11  2
# 3.1    1 3 12  3
# 1.2    2 4 13  1
# 2.2    2 5 14  2
# 3.2    2 6 15  3
# 1.3    3 7 16  1
# 2.3    3 8 17  2
# 3.3    3 9 18  3

如果需要,您可以删除其他列。


为了好玩,这里介绍另一种方法,使用“reshape2”包(从原始数据开始):

library(reshape2)
dfL <- melt(as.matrix(df))
dfL <- cbind(dfL, colsplit(dfL$Var2, "_", c("Factor", "Individual")))
dcast(dfL, Individual + Var1 ~ Factor, value.var="value")
#   Individual Var1 A  B
# 1          1    1 1 10
# 2          1    2 2 11
# 3          1    3 3 12
# 4          2    1 4 13
# 5          2    2 5 14
# 6          2    3 6 15
# 7          3    1 7 16
# 8          3    2 8 17
# 9          3    3 9 18

如果你喜欢尝试最新的技术,“data.table”版本1.8.11现在已经实现了“melt”和“dcast”。我还没有深入使用过它,但它也很简单。与迄今为止提供的所有解决方案一样,都需要一个“id”。

library(reshape2)
library(data.table)
packageVersion("data.table") ## Must be at least 1.8.11 to work
# [1] ‘1.8.11’

DT <- data.table(cbind(id = sequence(nrow(df)), df))
DTL <- melt(DT, id.vars="id")
DTL[, c("Fac", "Ind") := colsplit(variable, "_", c("Fac", "Ind"))]
dcast.data.table(DTL, Ind + id ~ Fac)
#    Ind id A  B
# 1:   1  1 1 10
# 2:   1  2 2 11
# 3:   1  3 3 12
# 4:   2  1 4 13
# 5:   2  2 5 14
# 6:   2  3 6 15
# 7:   3  1 7 16
# 8:   3  2 8 17
# 9:   3  3 9 18

更新

另一个选项是使用我的 "splitstackshape" 包中的 merged.stack。如果您还使用 as.data.table(df, keep.rownames = TRUE),它将可以很好地工作,这将创建相当于 "data.table" 方法中的 data.table(cbind(id = sequence(nrow(df)), df)) 步骤的等效内容。

library(splitstackshape)
merged.stack(as.data.table(df, keep.rownames = TRUE), 
             var.stubs = c("A", "B"), sep = "_")
#    rn .time_1 A  B
# 1:  1       1 1 10
# 2:  1       2 4 13
# 3:  1       3 7 16
# 4:  2       1 2 11
# 5:  2       2 5 14
# 6:  2       3 8 17
# 7:  3       1 3 12
# 8:  3       2 6 15
# 9:  3       3 9 18

为了公平和完整起见,这里介绍一种使用 "tidyr" + "dplyr" 的方法。

library(tidyr)
library(dplyr)
df %>%
  gather(var, value, A_1:B_3) %>%
  separate(var, c("var", "time")) %>%
  group_by(var, time) %>%
  mutate(grp = sequence(n())) %>%
  ungroup() %>%
  spread(var, value)
# Source: local data frame [9 x 4]
# 
#   time grp A  B
# 1    1   1 1 10
# 2    1   2 2 11
# 3    1   3 3 12
# 4    2   1 4 13
# 5    2   2 5 14
# 6    2   3 6 15
# 7    3   1 7 16
# 8    3   2 8 17
# 9    3   3 9 18

好的。谢谢。您能解释一下“id”的作用吗?谢谢。 - Mayou
@Mariam,你可以考虑一种方法来识别每个唯一的记录(在这种情况下,是一行)。 - A5C1D2H2I1M1N2O1R2T1
+1 很好的列分组方式。我一定要记住这个。 - Simon O'Hanlon

3

我会 unlist 一个 data.frame 中相关的列。有很多方法可以把这些列分组成唯一的人 (我非常喜欢 Ananda 的方法),但使用正则表达式是另一种方式...

#  Find unique persons
IDs <- unique( gsub( "([A-Z]).*" , "\\1" , names( df ) ) )
[1] "A" "B"

# Unlist columns relevant to that person
out <- sapply( IDs , function(x) unlist( df[ , grepl( x , names( df ) ) ] , use.names = FALSE ) )

#  Change from matrix to data.frame
data.frame( out )
#  A  B
#1 1 10
#2 2 11
#3 3 12
#4 4 13
#5 5 14
#6 6 15
#7 7 16
#8 8 17
#9 9 18

1
我也喜欢这种方法!谢谢! - Mayou
+1 表示 “unlist”。当然还有“stack”和“melt” 。我已经在我的答案中添加了“melt”+“dcast”的版本。 - A5C1D2H2I1M1N2O1R2T1

1
您可以通过以下方式以所需的形式获取数据:
> m<-as.matrix(df)
> dim(m)<-c(nrow(m)*3,ncol(m)/3)
> m
      [,1] [,2]
 [1,]    1   10
 [2,]    2   11
 [3,]    3   12
 [4,]    4   13
 [5,]    5   14
 [6,]    6   15
 [7,]    7   16
 [8,]    8   17
 [9,]    9   18

只要每个人有三列,那么相同的代码应该适用于大型数据框。然后您只需要分配列名称即可。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接