我想将数据帧从宽格式转换为长格式。
这是一个玩具示例:
mydata <- data.frame(ID=1:5, ZA_1=1:5,
ZA_2=5:1,BB_1=rep(3,5),BB_2=rep(6,5),CC_7=6:2)
ID ZA_1 ZA_2 BB_1 BB_2 CC_7
1 1 5 3 6 6
2 2 4 3 6 5
3 3 3 3 6 4
4 4 2 3 6 3
5 5 1 3 6 2
有些变量将保持原样(此处仅为ID),有些变量将转换为长格式(此处所有其他变量均以_1,_2或_7结尾)
为了将其转换为长格式,我使用data.table melt和dcast,这是一种能够自动检测变量的通用方法。其他解决方案也是受欢迎的。
library(data.table)
setDT(mydata)
idvars = grep("_[1-7]$",names(mydata) , invert = TRUE)
temp <- melt(mydata, id.vars = idvars)
nuevo <- dcast(
temp[, `:=`(var = sub("_[1-7]$", '', variable),
measure = sub('.*_', '', variable), variable = NULL)],
... ~ var, value.var='value')
ID measure BB CC ZA
1 1 3 NA 1
1 2 6 NA 5
1 7 NA 6 NA
2 1 3 NA 2
2 2 6 NA 4
2 7 NA 5 NA
3 1 3 NA 3
3 2 6 NA 3
3 7 NA 4 NA
4 1 3 NA 4
4 2 6 NA 2
4 7 NA 3 NA
5 1 3 NA 5
5 2 6 NA 1
5 7 NA 2 NA
正如您所看到的那样,列按字母顺序重新排序,但我希望尽可能保留原始顺序,例如考虑变量首次出现的顺序。
ID ZA_1 ZA_2 BB_1 BB_2 CC_7
应该是
ID ZA BB CC
我不介意idvars列是否在开始时全部完成,或者它们是否也保持在原始位置。
ID ZA_1 ZA_2 TEMP BB_1 BB_2 CC_2 CC_1
将是
ID ZA TEMP BB CC
或
ID TEMP ZA BB CC
我更喜欢最后一个选项。
另一个问题是一切都变成了角色。
答案 0 :(得分:2)
如果将列名列表传递给参数measure =
,则可以同时熔化多个列。以可扩展的方式执行此操作的一种方法是:
提取列名和相应的前两个字母:
measurevars <- names(mydata)[grepl("_[1-9]$",names(mydata))]
groups <- gsub("_[1-9]$","",measurevars)
将groups
转换为系数对象,并确保不按字母顺序排序关卡。我们将在下一步中使用它来创建具有正确结构的列表对象。
split_on <- factor(groups, levels = unique(groups))
使用带有measurevars
的{{1}}创建列表,并为split()
中的value.name =
参数创建向量。
melt()
将所有这些结合在一起:
measure_list <- split(measurevars, split_on)
measurenames <- unique(groups)
答案 1 :(得分:1)
以下是使用基本R函数split.default
和do.call
的方法。
# split the non-ID variables into groups based on their name suffix
myList <- split.default(mydata[-1], gsub(".*_(\\d)$", "\\1", names(mydata[-1])))
# append variables by row after setting the regularizing variable names, cbind ID
cbind(mydata[1],
do.call(rbind, lapply(myList, function(x) setNames(x, gsub("_\\d$", "", names(x))))))
ID ZA BB
1.1 1 1 3
1.2 2 2 3
1.3 3 3 3
1.4 4 4 3
1.5 5 5 3
2.1 1 5 6
2.2 2 4 6
2.3 3 3 6
2.4 4 2 6
2.5 5 1 6
第一行将data.frame变量(减去ID)拆分为同意其变量名称的最终字符的列表。该标准使用gsub
确定。第二行使用do.call
在此变量列表上调用rbind
,使用setNames
进行修改,以便从名称中删除最后的数字和下划线。最后,cbind
将ID附加到生成的data.frame。
请注意,数据必须定期构建,不会丢失变量等。
答案 2 :(得分:1)
当一半列为melt()
时,OP已经更新了他自己的问题的答案,抱怨中间id.vars
步骤的内存消耗。他要求 data.table
需要一种直接的方法,而不需要创建巨大的中间步骤。
嗯,data.table
已经具备了这种能力,它被称为 join 。
给定来自Q的样本数据,通过仅使用一个id.var进行重新整形,然后将重新整形的结果与原始data.table连接,可以以更少的内存消耗方式实现整个操作。表:
setDT(mydata)
# add unique row number to join on later
# (leave `ID` col as placeholder for all other id.vars)
mydata[, rn := seq_len(.N)]
# define columns to be reshaped
measure_cols <- stringr::str_subset(names(mydata), "_\\d$")
# melt with only one id.vars column
molten <- melt(mydata, id.vars = "rn", measure.vars = measure_cols)
# split column names of measure.vars
# Note that "variable" is reused to save memory
molten[, c("variable", "measure") := tstrsplit(variable, "_")]
# coerce names to factors in the same order as the columns appeared in mydata
molten[, variable := forcats::fct_inorder(variable)]
# remove columns no longer needed in mydata _before_ joining to save memory
mydata[, (measure_cols) := NULL]
# final dcast and right join
result <- mydata[dcast(molten, ... ~ variable), on = "rn"]
result
# ID rn measure ZA BB CC
# 1: 1 1 1 1 3 NA
# 2: 1 1 2 5 6 NA
# 3: 1 1 7 NA NA 6
# 4: 2 2 1 2 3 NA
# 5: 2 2 2 4 6 NA
# 6: 2 2 7 NA NA 5
# 7: 3 3 1 3 3 NA
# 8: 3 3 2 3 6 NA
# 9: 3 3 7 NA NA 4
#10: 4 4 1 4 3 NA
#11: 4 4 2 2 6 NA
#12: 4 4 7 NA NA 3
#13: 5 5 1 5 3 NA
#14: 5 5 2 1 6 NA
#15: 5 5 7 NA NA 2
最后,如果result[, rn := NULL]
不再需要,您可以删除行号。
此外,您可以按molten
删除中间rm(molten)
。
我们已经开始使用data.table
组成的1个id列,5个度量列和5个行。重新整形的结果有1个id列,3个度量列和15个行。因此,存储在id列中的数据量有效地增加了三倍。但是,中间步骤只需要一个id.var rn
。
编辑如果内存消耗是至关重要的,可能值得考虑将id.vars和measure.vars保存在两个单独的data.tables中并加入只需要带有measure.vars的必要id.var列。
请注意,measure.vars
的{{1}}参数允许使用特殊功能melt()
。有了这个,patterns()
的调用可以写成
melt()
答案 3 :(得分:1)
使用data.table
的替代方法:
melt(mydata, id = 'ID')[, c("variable", "measure") := tstrsplit(variable, '_')
][, variable := factor(variable, levels = unique(variable))
][, dcast(.SD, ID + measure ~ variable, value.var = 'value')]
给出:
ID measure ZA BB CC 1: 1 1 1 3 NA 2: 1 2 5 6 NA 3: 1 7 NA NA 6 4: 2 1 2 3 NA 5: 2 2 4 6 NA 6: 2 7 NA NA 5 7: 3 1 3 3 NA 8: 3 2 3 6 NA 9: 3 7 NA NA 4 10: 4 1 4 3 NA 11: 4 2 2 6 NA 12: 4 7 NA NA 3 13: 5 1 5 3 NA 14: 5 2 1 6 NA 15: 5 7 NA NA 2
答案 4 :(得分:0)
最后,我找到了方法,修改了我的初始解决方案
mydata <- data.table(ID=1:5, ZA_2001=1:5, ZA_2002=5:1,
BB_2001=rep(3,5),BB_2002=rep(6,5),CC_2007=6:2)
idvars = grep("_20[0-9][0-9]$",names(mydata) , invert = TRUE)
temp <- melt(mydata, id.vars = idvars)
temp[, `:=`(var = sub("_20[0-9][0-9]$", '', variable),
measure = sub('.*_', '', variable), variable = NULL)]
temp[,var:=factor(var, levels=unique(var))]
dcast( temp, ... ~ var, value.var='value' )
它为您提供了适当的测量值。 无论如何,这个解决方案需要大量的内存。
诀窍是将var变量转换为因子指定我想要的水平顺序,正如mtoto所做的那样。 mtoto解决方案很好,因为它不需要转换和融化,只能融化,但在我更新的示例中不起作用,只有在每个单词的数字变化数量相同时才有效。
PD: 我正在解析每一步,发现在使用大型数据表时,熔化步骤可能是一个大问题。如果你有一个只有100000行x 1000列的data.table并且使用一半列作为id.vars,那么输出大约是50000000 x 500,只是太多而不能继续下一步。 data.table需要一种直接的方法,而不需要创建巨大的中间步骤。