使用字符后缀而不是数字后缀重新整形为长到长

时间:2012-05-06 07:41:39

标签: r reshape

受到来自@ gsk3的a comment的关于重塑数据的问题的启发,我开始对重组数据进行一些实验,其中变量名称具有字符后缀而不是数字后缀。

例如,我将从UCLA ATS Stata learning webpages之一加载dadmomw数据集(请参阅网页上的“示例4”)。

以下是数据集的外观:

library(foreign)
dadmom <- read.dta("https://stats.idre.ucla.edu/stat/stata/modules/dadmomw.dat")
dadmom
#   famid named  incd namem  incm
# 1     1  Bill 30000  Bess 15000
# 2     2   Art 22000   Amy 18000
# 3     3  Paul 25000   Pat 50000

当试图从这种宽格式重塑为长时,我遇到了一个问题。以下是我重塑数据的方法。

reshape(dadmom, direction="long", idvar=1, varying=2:5, 
        sep="", v.names=c("name", "inc"), timevar="dadmom",
        times=c("d", "m"))
#     famid dadmom  name  inc
# 1.d     1      d 30000 Bill
# 2.d     2      d 22000  Art
# 3.d     3      d 25000 Paul
# 1.m     1      m 15000 Bess
# 2.m     2      m 18000  Amy
# 3.m     3      m 50000  Pat

请注意“name”和“inc”的交换列名称;将v.names更改为c("inc", "name")并不能解决问题。

reshape似乎非常挑剔想要以相​​当标准的方式命名列。例如,如果我首先重命名列,我可以正确地(并且容易地)重新整形数据:

dadmom2 <- dadmom # Just so we can continue experimenting with the original data
# Change the names of the last four variables to include a "."
names(dadmom2)[2:5] <- gsub("(d$|m$)", "\\.\\1", names(dadmom2)[2:5])
reshape(dadmom2, direction="long", idvar=1, varying=2:5, 
        timevar="dadmom")
#     famid dadmom name   inc
# 1.d     1      d Bill 30000
# 2.d     2      d  Art 22000
# 3.d     3      d Paul 25000
# 1.m     1      m Bess 15000
# 2.m     2      m  Amy 18000
# 3.m     3      m  Pat 50000

我的问题是:

  1. 为什么R在我提供的示例中交换列?
  2. 我可以使用基础R reshape获得此结果,而无需在重新整形前更改变量名称吗?
  3. 是否有其他方法可以考虑而不是reshape

3 个答案:

答案 0 :(得分:11)

这是有效的(指定改变哪些列与谁一起):

reshape(dadmom, direction="long",  varying=list(c(2, 4), c(3, 5)), 
        sep="", v.names=c("name", "inc"), timevar="dadmom",
        times=c("d", "m"))

所以你实际上已经在这里嵌套了重复的措施;妈妈和爸爸的名字和公司。由于您有多个系列的重复措施,因此您必须提供list变量,以告知reshape哪个组在另一个组上堆叠。

因此,解决这个问题的两种方法是像我一样提供一个列表,或者按照R野兽喜欢的方式重命名列。

有关详情,请参阅我最近关于reshape的博客(特别是第二个链接处理此内容):

reshape (part I)

reshape (part II)

答案 1 :(得分:8)

虽然这个问题是专门针对基础R的,但了解其他可以帮助您实现相同类型结果的方法很有用。

reshapemerged.stack的一种替代方法是使用&#34; dplyr&#34;和&#34; tidry&#34;,像这样:

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

另一种选择是使用来自&#34; data.table&#34;的melt,如下所示:

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方法可能会产生一些意想不到的后果,因为需要在使数据变宽之前将数据做好。这会强制所有度量列被强制转换为相同类型(通常为#34;字符&#34;),如果它们的类型不同,则会被强制使用。
  • 两者都没有merged.stack的便利性。只需查看获得结果所需的代码; - )
然而,

merged.stack可能会受益于简化的更新,类似于this function

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无法处理不平衡的面板数据集。例如,参见&#34; mydf2&#34;而不是&#34; mydf&#34;在下面的测试中。

测试用例

这是一些示例数据。 &#34;是myDF&#34;是平衡的。 &#34; mydf2&#34;是不平衡的。

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&#39; reshape将无法处理重塑&#34; mydf2&#34;。
  • &#34; dplyr&#34; +&#34; tidyr&#34;方法会破坏结果&#34; varB&#34;,&#34; varC&#34;和&#34; varD&#34;因为价值会被强迫给人物。
  • 正如基准所示,reshape给出了合理的表现。

注意 :由于发布我的上一个答案与方法上的差异之间的时间差异,我想我会将此作为一个新答案分享。

答案 2 :(得分:2)

merged.stack来自我的&#34; splitstackshape&#34;通过使用sep = "var.stubs"构造来处理这个:

library(splitstackshape)
merged.stack(dadmom, var.stubs = c("inc", "name"), sep = "var.stubs")
#    famid .time_1   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

请注意,由于堆叠的变量中没有真正的分隔符,我们只需从名称中删除var.stubs即可创建&#34; time&#34;变量。使用sep = "var.stubs"相当于执行sep = "inc|name"

这是因为&#34; .time_1&#34;是通过去除&#34; var.stubs&#34;之后删除剩下的内容来创建的。来自列名。