Transform from Wide to Long without sorting columns
我想将数据帧从宽格式转换为长格式。
这是一个玩具示例:
1 2 3 4 5 6 7 8 9 | 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.tablemelt和dcast,这是一种能够自动检测变量的通用方法。也欢迎其他解决方案。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | 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
应该是
1 | ID ZA BB CC |
我不介意idvars列开头是否全部在一起,或者它们是否也保持其原始位置。
ID ZA_1 ZA_2 TEMP BB_1 BB_2 CC_2 CC_1
将是
1 | ID ZA TEMP BB CC |
或
1 | ID TEMP ZA BB CC |
我更喜欢最后一个选择。
另一个问题是,一切都变成了角色。
如果将列名列表传递给参数
提取列名和相应的前两个字母:
1 2 | measurevars <- names(mydata)[grepl("_[1-9]$",names(mydata))] groups <- gsub("_[1-9]$","",measurevars) |
将
1 | split_on <- factor(groups, levels = unique(groups)) |
使用
1 2 | measure_list <- split(measurevars, split_on) measurenames <- unique(groups) |
将所有内容放在一起:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | melt(setDT(mydata), measure = measure_list, value.name = measurenames, variable.name ="measure") # ID measure ZA BB # 1: 1 1 1 3 # 2: 2 1 2 3 # 3: 3 1 3 3 # 4: 4 1 4 3 # 5: 5 1 5 3 # 6: 1 2 5 6 # 7: 2 2 4 6 # 8: 3 2 3 6 # 9: 4 2 2 6 #10: 5 2 1 6 |
使用
的替代方法
1 2 3 | melt(mydata, id = 'ID')[, c("variable","measure") := tstrsplit(variable, '_') ][, variable := factor(variable, levels = unique(variable)) ][, dcast(.SD, ID + measure ~ variable, value.var = 'value')] |
给出:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 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
OP更新了他对自己的问题的回答,抱怨一半列为
好吧,
考虑到来自Q的样本数据,可以通过仅使用一个id.var进行整形,然后将整形后的结果与原始数据结合起来,从而以较少的内存消耗方式实现整个操作。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 | 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 |
最后,如果
此外,您可以通过
我们从
EDIT如果内存消耗至关重要,那么可能有必要考虑将id.vars和measure.vars保留在两个单独的data.tables中,并根据需要仅将必要的id.var列与measure.vars连接起来。
请注意,
1 | molten <- melt(mydata, id.vars ="rn", measure.vars = patterns("_\\\\d$")) |
这是使用基本R函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | # 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)拆分为与变量名的最后一个字符一致的列表。使用
请注意,数据必须定期组织,没有丢失的变量等。
最后,我找到了方法,修改了我的初始解决方案
1 2 3 4 5 6 7 8 9 | 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需要一种直接的方法来执行此操作,而无需创建巨大的中间步骤。