使用多个值列将宽改造成长[重复]

Posted

技术标签:

【中文标题】使用多个值列将宽改造成长[重复]【英文标题】:Reshaping wide to long with multiple values columns [duplicate] 【发布时间】:2014-07-19 16:01:33 【问题描述】:

我需要将我的宽表重塑为长格式,但为每条记录保留多个字段,例如:

dw <- read.table(header=T, text='
 sbj f1.avg f1.sd f2.avg f2.sd  blabla
   A   10    6     50     10      bA
   B   12    5     70     11      bB
   C   20    7     20     8       bC
   D   22    8     22     9       bD
 ')

# Now I want to melt this table, keeping both AVG and SD as separate fields for each measurement, to get something like this:

 #    sbj var avg  sd  blabla
 #     A   f1  10  6     bA
 #     A   f2  50  10    bA
 #     B   f1  12  5     bB
 #     B   f2  70  11    bB
 #     C   f1  20  7     bC
 #     C   f2  20  8     bC
 #     D   f1  22  8     bD
 #     D   f2  22  9     bD

我有使用meltreshape 的基本知识,但我不清楚如何在我的案例中应用这种重塑。

【问题讨论】:

这应该是一个相当标准的reshape,从“宽”到“长”——见这里:***.com/questions/14638747/… 【参考方案1】:

reshape 使用适当的参数执行此操作。

varying 列出以宽格式存在但以长格式分为多行的列。 v.names 是长格式等效项。在两者之间创建了一个映射。

来自?reshape

此外,如果明确给出 v.names,则不会尝试猜测。注意变量的顺序是 x.1,y.1,x.2,y.2。

鉴于这些 varyingv.names 参数,reshape 足够聪明,可以看到我已经指定索引在此处的点之前(即顺序 1.x、1.y、2.x , 2.y)。注意原始数据的列顺序是这样的,所以我们可以为这个示例数据指定varying=2:5,但这通常是不安全的。

给定timesv.names 的值,reshapevarying 列拆分为. 字符(默认sep 参数)以在输出中创建列。

times 指定要在创建的var 列中使用的值,v.names 被粘贴到这些值上以获取宽格式的列名称以映射到结果。

最后,idvar 被指定为sbj 列,它以宽格式标识单个记录(感谢@thelatemail)。

reshape(dw, direction='long', 
        varying=c('f1.avg', 'f1.sd', 'f2.avg', 'f2.sd'), 
        timevar='var',
        times=c('f1', 'f2'),
        v.names=c('avg', 'sd'),
        idvar='sbj')

##      sbj blabla var avg sd
## A.f1   A     bA  f1  10  6
## B.f1   B     bB  f1  12  5
## C.f1   C     bC  f1  20  7
## D.f1   D     bD  f1  22  8
## A.f2   A     bA  f2  50 10
## B.f2   B     bB  f2  70 11
## C.f2   C     bC  f2  20  8
## D.f2   D     bD  f2  22  9

【讨论】:

添加idvar='sbj' 将避免将id 列添加到结果中。 令人惊讶的是(至少对我而言),列的顺序很重要。如果对列 dw = dw[,c(1,4,3,2,5,6)] 重新排序,然后运行此答案,则结果不正确。 @BrianD 原来如此,谢谢!我默认使用数据的假设(即使没有意识到),并将很快解决答案。 为了让这个答案在处理多对“avg”和“sd”列时更加健壮,请将varying 参数替换为varying=list(grep("avg", colnames(dw), value=T), grep("sd", colnames(dw), value=T))【参考方案2】:

这似乎可以满足您的要求,只是从 time 中的元素中删除了 f

reshape(dw, idvar = "sbj", varying = list(c(2,4),c(3,5)), v.names = c("ave", "sd"), direction = "long")

    sbj blabla time ave sd
A.1   A     bA    1  10  6
B.1   B     bB    1  12  5
C.1   C     bC    1  20  7
D.1   D     bD    1  22  8
A.2   A     bA    2  50 10
B.2   B     bB    2  70 11
C.2   C     bC    2  20  8
D.2   D     bD    2  22  9

【讨论】:

aha,除了使用idvar 而不是timevartimes 之外,您所做的调用几乎与上一个答案相同 - 我会解决差异...谢谢! @VasilyA varying 的结构在答案之间也有很大不同。事实上,他们似乎唯一的共同点是他们都使用相同的v.names,并且都使用direction="long" 确实,现在我明白了...感谢您的指出! 比较两个正确答案的有趣之处在于,这个答案使用 list 参数的结构来表示“变化”,而 Lundberg 的答案使用“times”和“v.names”来表示传达一些结构。我从来没有在脑海中理清过这些选项,通常会反复试验。 为了澄清,此答案的代码不会从列名中删除 f 以创建元素。如果没有 times 参数,甚至不会考虑列名。创建数据框dw时,我可以将“f1”替换为“f1.alpha”,将“f2”替换为“f2.beta”,运行此答案中的代码,“时间”列将与在这个答案中。它不会是“1.alpha”或“2.beta”。如果有人想将列名保留为元素,则需要在times 参数中指定列名(与varying 参数中的顺序相同)。【参考方案3】:

使用 Hadley 的新 tidyr 包的另一种选择。

library(tidyr)
library(dplyr)

dw <- read.table(header=T, text='
 sbj f1.avg f1.sd f2.avg f2.sd  blabla
   A   10    6     50     10      bA
   B   12    5     70     11      bB
   C   20    7     20     8       bC
   D   22    8     22     9       bD
 ')

dw %>% 
  gather(v, value, f1.avg:f2.sd) %>% 
  separate(v, c("var", "col")) %>% 
  arrange(sbj) %>% 
  spread(col, value)

【讨论】:

关于 tidyr 的有用信息以及上面的代码如何在这里工作:blog.rstudio.org/2014/07/22/introducing-tidyr【参考方案4】:

要添加此处可用的选项,您还可以考虑我的“splitstackshape”包中的merged.stack

library(splitstackshape)
merged.stack(dw, var.stubs = c("avg", "sd"), sep = "var.stubs", atStart = FALSE)
#    sbj blabla .time_1 avg sd
# 1:   A     bA     f1.  10  6
# 2:   A     bA     f2.  50 10
# 3:   B     bB     f1.  12  5
# 4:   B     bB     f2.  70 11
# 5:   C     bC     f1.  20  7
# 6:   C     bC     f2.  20  8
# 7:   D     bD     f1.  22  8
# 8:   D     bD     f2.  22  9

您还可以像这样对".time_1" 变量进行更多清理。

merged.stack(dw, var.stubs = c("avg", "sd"), 
             sep = "var.stubs", atStart = FALSE)[, .time_1 := sub(
               ".", "", .time_1, fixed = TRUE)][]
#    sbj blabla .time_1 avg sd
# 1:   A     bA      f1  10  6
# 2:   A     bA      f2  50 10
# 3:   B     bB      f1  12  5
# 4:   B     bB      f2  70 11
# 5:   C     bC      f1  20  7
# 6:   C     bC      f2  20  8
# 7:   D     bD      f1  22  8
# 8:   D     bD      f2  22  9

您会注意到atStart = FALSE 参数的使用。这是因为您的名称与重塑相关函数似乎喜欢的顺序有点不同。一般来说,“stub”应该先出现,然后是“times”,像这样:

dw2 <- dw
setnames(dw2, gsub("(.*)\\.(.*)", "\\2.\\1", names(dw2)))
names(dw2)
# [1] "sbj"    "avg.f1" "sd.f1"  "avg.f2" "sd.f2"  "blabla"

如果名称采用该格式,则基本 R 的 reshapemerged.stack 都受益于更直接的语法:

merged.stack(dw2, var.stubs = c("avg", "sd"), sep = ".")
reshape(dw2, idvar = c("sbj", "blabla"), varying = 2:5, 
        sep = ".", direction = "long")

【讨论】:

【参考方案5】:

melt 来自 >=1.9.6 版本的 data.table,通过将 measure.vars 中的列索引指定为 list 来实现这一点。

 melt(setDT(dw), measure.vars=list(c(2,4), c(3,5)), 
     variable.name='var', value.name=c('avg', 'sd'))[, 
      var:= paste0('f',var)][order(sbj)]
#   sbj blabla var avg sd
#1:   A     bA  f1  10  6
#2:   A     bA  f2  50 10
#3:   B     bB  f1  12  5
#4:   B     bB  f2  70 11
#5:   C     bC  f1  20  7
#6:   C     bC  f2  20  8
#7:   D     bD  f1  22  8
#8:   D     bD  f2  22  9

或者您可以使用新的patterns 函数:

melt(setDT(dw), 
     measure = patterns("avg", "sd"),
     variable.name = 'var', value.name = c('avg', 'sd'))
#    sbj blabla var avg sd
# 1:   A     bA   1  10  6
# 2:   B     bB   1  12  5
# 3:   C     bC   1  20  7
# 4:   D     bD   1  22  8
# 5:   A     bA   2  50 10
# 6:   B     bB   2  70 11
# 7:   C     bC   2  20  8
# 8:   D     bD   2  22  9

【讨论】:

以上是关于使用多个值列将宽改造成长[重复]的主要内容,如果未能解决你的问题,请参考以下文章

通过多个步骤将宽数据集转换为长数据集

将多个值列重塑为宽格式

具有多个值列的数据透视表

使用 INNER JOIN LATERAL 和 postgresql 将宽表转换为长表

如何创建具有一个索引键列和多个值列的字典

具有匹配索引列的多个表的数据框连接值列