使用data.table::melt()处理拼接列名时如何单步拆分完成数据重塑
问题背景
我尝试使用data.table::melt()函数对包含拼接变量列的数据框进行长表重塑(即宽表转长表),所有变量列均由变量名与年份拼接命名 注:我使用的是data.table开发版本(1.14.3)。
首先构造测试数据集:
library(data.table) dt <- data.table( id = c(1, 2, 3), varA_2000 = c(2, 6, 1), varA_2001 = c(1, 1, 1), varA_2002 = c(1, 2, 3), varB_2000 = c(1, 0, 1), varB_2001 = c(1, 1, 1), varB_2002 = c(0, 0, 0) ) print(dt) #> id varA_2000 varA_2001 varA_2002 varB_2000 varB_2001 varB_2002 #> <num> <num> <num> <num> <num> <num> <num> #> 1: 1 2 1 1 1 1 0 #> 2: 2 6 1 2 0 1 0 #> 3: 3 1 1 3 1 1 0
需求为在使用melt()函数拉长数据框的同时拆分多个拼接列名,最终得到如下格式的结果:
desiredDT <- structure( list( id = c(1, 2, 3, 1, 2, 3, 1, 2, 3), year = c( 2020, 2020, 2020, 2021, 2021, 2021, 2022, 2022, 2022 ), varA = c( 2, 6, 1, 1, 1, 1, 1, 2, 3 ), varB = c(1, 0, 1, 1, 1, 1, 0, 0, 0) ), row.names = c(NA, -9L), class = c("data.table", "data.frame") ) head(desiredDT) #> id year varA varB #> 1 1 2020 2 1 #> 2 2 2020 6 0 #> 3 3 2020 1 1 #> 4 1 2021 1 1 #> 5 2 2021 1 1 #> 6 3 2021 1 1
同类问题在2014年被提出时尚无纯data.table的原生解决方案,本次处理的数据集涉及varA、varB等多个需要同时拉长的变量。
目前已通过两种多步骤方法生成目标格式结果,但两种方法均需要多步操作:
- 方法1(先执行melt,再使用fcase重标记变量值):
dx <- melt(dt, id.vars = "id", measure = patterns("^varA", "^varB"), value.name = c("varA", "varB"), variable.name = "year" ) first_twoStepApproach <- dx[, year := fcase( year == "1", 2020, year == "2", 2021, year == "3", 2022 )] head(first_twoStepApproach) #> id year varA varB #> <num> <num> <num> <num> #> 1: 1 2020 2 1 #> 2: 2 2020 6 0 #> 3: 3 2020 1 1 #> 4: 1 2021 1 1 #> 5: 2 2021 1 1 #> 6: 3 2021 1 1
- 方法2(先执行melt,第二步使用tstrsplit拆分变量名列):
dx <- melt(dt, id.vars = "id", variable.name = c("variable"), value.name = c("value"), verbose = TRUE) #> 'measure.vars' is missing. Assigning all columns other than 'id.vars' columns as 'measure.vars'. #> Assigned 'measure.vars' are [varA_2000, varA_2001, varA_2002, varB_2000, ...]. dx[, c("variable", "year") := tstrsplit(variable, "_")] second_twoStepApproach <- dcast(dx, id + year ~ variable, value.name = value) head(second_twoStepApproach) #> Key: <id, year> #> id year varA varB #> <num> <char> <num> <num> #> 1: 1 2000 2 1 #> 2: 1 2001 1 1 #> 3: 1 2002 1 0 #> 4: 2 2000 6 0 #> 5: 2 2001 1 1 #> 6: 2 2002 2 0
核心疑问:是否可以仅通过melt()函数单步完成上述数据转换操作?
解答
完全可以。你使用的1.14.3版本data.table已经内置了拼接列名的多值测量支持,不需要额外多步操作,直接在melt()中调用measure()指定列名匹配规则即可单步完成:
# 单步melt实现 result <- melt( dt, id.vars = "id", measure.vars = measure( value.name, year, pattern = "(varA|varB)_(\\d+)" ), variable.factor = FALSE ) # 若需要年份为数值格式,加一行转换即可 result[, year := as.integer(year)]
运行后输出的结果结构和预期完全一致:
head(result) #> id year varA varB #> <num> <int> <num> <num> #> 1: 1 2000 2 1 #> 2: 2 2000 6 0 #> 3: 3 2000 1 1 #> 4: 1 2001 1 1 #> 5: 2 2001 1 1 #> 6: 3 2001 1 1
注意:你给出的期望结果里年份写为2020/2021/2022属于笔误,和原始列名的2000/2001/2002后缀不匹配,如果确实需要年份偏移,直接在转换时对year列加20即可。
该实现的核心逻辑是measure()会按传入的正则表达式自动拆分列名:第一捕获组对应不同的取值列名(varA/varB),第二捕获组对应共同的维度列(year),自动完成列的分组匹配,不需要后续手动重编码或者dcast操作,是纯data.table的原生单步方案。
内容的提问来源于stack exchange,提问作者statu
相关产品推荐
相关产品推荐

