使用多个值列将宽改造成长[重复]
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
我有使用melt
和reshape
的基本知识,但我不清楚如何在我的案例中应用这种重塑。
【问题讨论】:
这应该是一个相当标准的reshape
,从“宽”到“长”——见这里:***.com/questions/14638747/…
【参考方案1】:
reshape
使用适当的参数执行此操作。
varying
列出以宽格式存在但以长格式分为多行的列。 v.names
是长格式等效项。在两者之间创建了一个映射。
来自?reshape
:
此外,如果明确给出 v.names,则不会尝试猜测。注意变量的顺序是 x.1,y.1,x.2,y.2。
鉴于这些 varying
和 v.names
参数,reshape
足够聪明,可以看到我已经指定索引在此处的点之前(即顺序 1.x、1.y、2.x , 2.y)。注意原始数据的列顺序是这样的,所以我们可以为这个示例数据指定varying=2:5
,但这通常是不安全的。
给定times
和v.names
的值,reshape
将varying
列拆分为.
字符(默认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
而不是timevar
和times
之外,您所做的调用几乎与上一个答案相同 - 我会解决差异...谢谢!
@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 的 reshape
和 merged.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
【讨论】:
以上是关于使用多个值列将宽改造成长[重复]的主要内容,如果未能解决你的问题,请参考以下文章