尽管这个问题是专门针对基础 R 的,但了解其他有助于实现相同类型结果的方法很有用。
一种替代方法reshape
是merged.stack
使用“dplyr”和“tidry”的组合,如下所示:
dadmom %>%
gather(variable, value, -famid) %>% ## Make the entire dataset long
separate(variable, into = c("var", "time"), ## Split "variable" column into two...
sep = "(?<=name|inc)", perl = TRUE) %>% ## ... using regex to split the values
spread(var, value, convert = TRUE) ## Make result wide, converting type
# famid time inc name
# 1 1 d 30000 Bill
# 2 1 m 15000 Bess
# 3 2 d 22000 Art
# 4 2 m 18000 Amy
# 5 3 d 25000 Paul
# 6 3 m 50000 Pat
另一种选择是使用melt
“data.table”,如下所示:
library(data.table)
melt(as.data.table(dadmom), ## melt here requres a data.table
measure = patterns("name", "inc"), ## identify columns by patterns
value.name = c("name", "inc"))[ ## specify the resulting variable names
## melt creates a numeric "variable" value. Replace with factored labels
, variable := factor(variable, labels = c("d", "m"))][]
# famid variable name inc
# 1: 1 d Bill 30000
# 2: 2 d Art 22000
# 3: 3 d Paul 25000
# 4: 1 m Bess 15000
# 5: 2 m Amy 18000
# 6: 3 m Pat 50000
这些方法与 相比如何merged.stack
?
- 这两个软件包都得到了更好的支持。他们比我更广泛地更新和测试他们的代码。
melt
正在快速燃烧。
- Hadleyverse 方法实际上更慢(在我的许多测试中,甚至比基本 R 的更慢
reshape
)可能是因为必须使数据变长,然后变宽,然后执行类型转换。但是,一些用户喜欢它的逐步方法。
- Hadleyverse 方法可能会产生一些意想不到的后果,因为需要在使数据变宽之前使数据变长。这会强制所有度量列被强制转换为相同的类型(通常是“字符”),如果它们的类型不同的话。
- 两者都没有
merged.stack
. 只需查看获得结果所需的代码;-)
merged.stack
但是,可能会从简化的更新中受益,类似于此功能
ReshapeLong_ <- function(indt, stubs, sep = NULL) {
if (!is.data.table(indt)) indt <- as.data.table(indt)
mv <- lapply(stubs, function(y) grep(sprintf("^%s", y), names(indt)))
levs <- unique(gsub(paste(stubs, collapse="|"), "", names(indt)[unlist(mv)]))
if (!is.null(sep)) levs <- gsub(sprintf("^%s", sep), "", levs, fixed = TRUE)
melt(indt, measure = mv, value.name = stubs)[
, variable := factor(variable, labels = levs)][]
}
然后可以用作:
ReshapeLong_(dadmom, stubs = c("name", "inc"))
这些方法与基础 R 相比如何reshape
?
- 主要区别在于
reshape
无法处理不平衡的面板数据集。例如,在下面的测试中,请参见“mydf2”而不是“mydf”。
测试用例
这是一些示例数据。“mydf”是平衡的。“mydf2”不平衡。
set.seed(1)
x <- 10000
mydf <- mydf2 <- data.frame(
id_1 = 1:x, id_2 = c("A", "B"), varAa = sample(letters, x, TRUE),
varAb = sample(letters, x, TRUE), varAc = sample(letters, x, TRUE),
varBa = sample(10, x, TRUE), varBb = sample(10, x, TRUE),
varBc = sample(10, x, TRUE), varCa = rnorm(x), varCb = rnorm(x),
varCc = rnorm(x), varDa = rnorm(x), varDb = rnorm(x), varDc = rnorm(x))
mydf2 <- mydf2[-c(9, 14)] ## Make data unbalanced
以下是一些要测试的功能:
f1 <- function(mydf) {
mydf %>%
gather(variable, value, starts_with("var")) %>%
separate(variable, into = c("var", "time"),
sep = "(?<=varA|varB|varC|varD)", perl = TRUE) %>%
spread(var, value, convert = TRUE)
}
f2 <- function(mydf) {
melt(as.data.table(mydf),
measure = patterns(paste0("var", c("A", "B", "C", "D"))),
value.name = paste0("var", c("A", "B", "C", "D")))[
, variable := factor(variable, labels = c("a", "b", "c"))][]
}
f3 <- function(mydf) {
merged.stack(mydf, var.stubs = paste0("var", c("A", "B", "C", "D")), sep = "var.stubs")
}
## Won't run with "mydf2". Should run with "mydf"
f4 <- function(mydf) {
reshape(mydf, direction = "long",
varying = lapply(c("varA", "varB", "varC", "varD"),
function(x) grep(x, names(mydf))),
sep = "", v.names = paste0("var", c("A", "B", "C", "D")),
timevar="time", times = c("a", "b", "c"))
}
测试性能:
library(microbenchmark)
microbenchmark(f1(mydf), f2(mydf), f3(mydf), f4(mydf))
# Unit: milliseconds
# expr min lq mean median uq max neval
# f1(mydf) 463.006547 492.073086 528.533319 514.189548 538.910756 867.93356 100
# f2(mydf) 3.737321 4.108376 6.674066 4.332391 4.761681 47.71142 100
# f3(mydf) 60.211254 64.766770 86.812077 87.040087 92.841747 262.89409 100
# f4(mydf) 40.596455 43.753431 61.006337 48.963145 69.983623 230.48449 100
观察:
- Base R
reshape
将无法处理重塑“mydf2”。
- “dplyr” + “tidyr” 方法会破坏生成的“varB”、“varC”和“varD”中的结果,因为值将被强制转换为字符。
- 正如基准所显示的,
reshape
给出了合理的性能。
注意:由于发布我最后一个答案的时间差异和方法的差异,我想我会分享这个作为一个新的答案。