我正在尝试将R中的data.table从宽变长为整形。我需要减少几组变量,但是我一次做一组的最初方法看起来很容易出错,我想要替代。在这个可重现的示例中,我以与原始数据相似的方式创建了两组变量(XX
和YYY
)。
我的解决方案在此示例中有效,但是原始数据表具有如此多的列,以至于我不信任此代码。我不确定问题出在我的实现中还是方法本身-如果可能的话,我希望使用keep it simple。
问:是否有更好的方法来解决此问题?
示例数据
library(data.table)
dt.orig <- data.table(ID= 1:3,
a = c("Y", "Y", "N"),
b = c("N", "Y", "Y"),
XXa=c(101, 102, 103),
XXb=c(110, 120, 130),
YYYa=c(201, 202, 203),
YYYb=c(210, 220, 230))
dt.goal <- data.table(ID=c(1,1,2,2,3,3),
obs=c("a", "b"),
outcome = c("Y", "N", "Y", "Y", "N", "Y"),
XX=c(101, 110, 102, 120, 103, 130),
YYY=c(201, 210, 202, 220, 203, 230))
> dt.orig
ID a b XXa XXb YYYa YYYb
1: 1 Y N 101 110 201 210
2: 2 Y Y 102 120 202 220
3: 3 N Y 103 130 203 230
> dt.goal
ID obs outcome XX YYY
1: 1 a Y 101 201
2: 1 b N 110 210
3: 2 a Y 102 202
4: 2 b Y 120 220
5: 3 a N 103 203
6: 3 b Y 130 230
dt.orig
代表原始数据,dt.goal
是我要实现的目标。在tidyr
封装插图之后,我的初步尝试如下:
尝试1:tidyr/dplyr
方法
library(tidyr)
library(dplyr)
dt.orig[, .(ID, a, b)] %>%
pivot_longer(
cols = c("a", "b"),
names_to = "obs",
values_to = "outcome"
) %>% data.table -> dt.tidyr1
dt.orig[, .(ID, XXa, XXb, YYYa, YYYb)] %>%
pivot_longer(
cols = XXa:YYYb,
names_to = c(".value", "obs"),
names_pattern = "(XX|YYY)(.)",
) %>% data.table -> dt.tidyr2
dt.tidyr1[, .(ID, obs, outcome)] == dt.goal[, .(ID, obs, outcome)] # test passes
dt.tidyr2[, .(ID, obs, XX, YYY)] == dt.goal[, .(ID, obs, XX, YYY)] # test passes
> merge(dt.tidyr1, dt.tidyr2)
ID obs outcome XX YYY
1: 1 a Y 101 201
2: 1 b N 110 210
3: 2 a Y 102 202
4: 2 b Y 120 220
5: 3 a N 103 203
6: 3 b Y 130 230
在上面的代码中,我首先为obs
和a
和b
的结果创建一对名称/值。由于所有变量组在其命名方案中都包含a
和b
,因此我可以使用这一事实通过单个 regex 遍历所有组。
然后我可以将两个数据表合并或合并为最终数据表。
尝试2:data.table
方式
按照相同的原理,我可以将原始的a和b融化为obs和结果,然后为每个var组进行第二步(为简洁起见,此处未显示)。在这种情况下,我一次成功融化了一个var组,因此在此示例中,首先进行所有XX,然后进行所有YYY。优点/缺点:优点是我不需要创建多个步骤表即可完成该过程。缺点:世界上没有足够的咖啡来完成这种方法,而实际数据中的所有var组都没有这种咖啡(并相信结果)。
dt.melt1 <- melt(dt.orig,
id.vars = c("ID", "XXa", "XXb", "YYYa", "YYYb"),
measure = c("a", "b"),
variable.name = "obs",
value.name = "outcome")
答案 0 :(得分:1)
我认为dplyr版本很好。您可以只使pivot_longer中的正则表达式更通用,以扩大列数。另外,您可以将a和b列展平到列表中,这样就不必处理第二个数据框并合并。
# flatten cols a,b
outcome <- c(t(select(dt.orig, c(a, b))))
# pivot longer on regex and add outcome list
dt.orig %>%
pivot_longer(-c(ID, a, b),
names_to = c(".value", "obs"),
names_pattern = "(.*)(.)") %>%
mutate(outcome = outcome) %>%
select(-c(a, b))
ID obs XX YYY outcome
1 1 a 101 201 Y
2 1 b 110 210 N
3 2 a 102 202 Y
4 2 b 120 220 Y
5 3 a 103 203 N
6 3 b 130 230 Y
答案 1 :(得分:1)
我不认为data.table::melt
具有将您的“ XXa”自动拆分为“ XX”和“ a”的机制,因此您可能别无选择,只能通过{ {1}}。但是,这是获得结果的两种替代方法,它们是针对@LRRR的漂亮tidyverse解决方案的快速基准测试。
数据和库:
data.table
第一个library(data.table)
library(tidyverse)
library(microbenchmark)
dt.orig = data.table(ID= 1:3,
a = c("Y", "Y", "N"),
b = c("N", "Y", "Y"),
XXa=c(101, 102, 103),
XXb=c(110, 120, 130),
YYYa=c(201, 202, 203),
YYYb=c(210, 220, 230))
解决方案(用于基准测试的函数包装):
data.table
第二个dt_1 <- function() {
dt = melt(dt.orig,
id.vars=c("a", "b", "ID"),
measure.vars=patterns("XX|YYY"),
variable.factor=FALSE)
dt = melt(dt,
id.vars=c("ID", "variable", "value"),
value.name="outcome",
variable.name="obs",
variable.factor=FALSE)
dt = dt[substr(variable, nchar(variable), nchar(variable)) == obs]
dt[, variable := substr(variable, 1, nchar(variable)-1)]
dcast(dt, ID + obs + outcome ~ variable)
}
解决方案:
data.table
dt_2 <- function() {
# ID-obs-outcome
dt1 = melt(dt.orig[, .(ID, a, b)],
id.vars="ID",
value.name="outcome",
variable.name="obs",
variable.factor=FALSE)
# ID-obs-XX-YYY
dt2 = melt(dt.orig[, !c("a", "b")],
id.vars="ID",
variable.factor=FALSE)
dt2[, obs := substr(variable, nchar(variable), nchar(variable))]
dt2[, variable := substr(variable, 1, nchar(variable)-1)]
dt2 = dcast(dt2, ID + obs ~ variable)
# merge
merge(dt1, dt2, by=c("ID", "obs"))
}
解决方案被LRRR发布为工作答案:
tidyverse
基准:
tidy_1 <- function(){
# flatten cols a,b
outcome <- c(t(select(dt.orig, c(a, b))))
# pivot longer on regex and add outcome list
dt.orig %>%
pivot_longer(-c(ID, a, b),
names_to = c(".value", "obs"),
names_pattern = "(.*)(.)") %>%
mutate(outcome = outcome) %>%
select(-c(a, b))
}
答案 2 :(得分:1)
您可以通过两行来实现:
dt.res <- melt(dt.orig, "ID", patterns("^[ab]$", "^XX", "^YYY"), "obs", c("outcome", "XX", "YYY"))
setorder(dt.res, ID)[, obs := unname(c("1" = "a", "2" = "b")[obs])]
输出
> dt.res[]
ID obs outcome XX YYY
1: 1 a Y 101 201
2: 1 b N 110 210
3: 2 a Y 102 202
4: 2 b Y 120 220
5: 3 a N 103 203
6: 3 b Y 130 230
这是上面相同代码的稍长版本
dt.res <-
melt(
dt.new2,
id.vars = "ID", measure.vars = patterns("^[ab]$", "^XX", "^YYY"),
variable.name = "obs", value.name = c("outcome", "XX", "YYY")
)
setorder(dt.res, ID)[, obs := unname(c("1" = "a", "2" = "b")[obs])]
其他说明
似乎除了ID
列外,您还有三组列需要同时进行透视:没有前缀的列(即a
和b
),前缀XX
和带有前缀YYY
的前缀。如果每个组中的后缀为a
的列始终出现在后缀为b
的列之前,则可以同时melt
将这些列组作为{{ 1}},并在本机之后支持此类操作。您需要使用data.table v1.9.6
指定每个列组。
这就是我们拥有regex
的原因,它捕获了我们尝试patterns("^[ab]$", "^XX", "^YYY")
的三个列组。经过melt
操作后,您将获得一个melt
,如下所示:
data.table
我们在 ID obs outcome XX YYY
1: 1 1 Y 101 201
2: 2 1 Y 102 202
3: 3 1 N 103 203
4: 1 2 N 110 210
5: 2 2 Y 120 220
6: 3 2 Y 130 230
中得到了a
和b
,而不是1
和2
,因为obs
操作会自动在其中设置第一个匹配项每个组分别为melt
,第二个组为"1"
,依此类推。稍后,我们可以通过指定"2"
和"1" = "a"
重置此列。但是,您可能已经知道,如果后缀为"2" = "b"
的列出现在后缀为a
的列之后,那么我们将无法再使用此映射b
。这就是为什么我们必须确保每个列组的顺序正确。
有关此订购问题的更好说明,请参见下面的代码:
c("1" = "a", "2" = "b")
因此,如果您不能确保每个组中的顺序,则可以进行预处理以固定列顺序。这样,您也可以获得正确的结果。
# Assume that your data.table looks like this
> dt.unordered
ID b a XXa YYYb XXb YYYa
1: 1 N Y 101 210 110 201
2: 2 Y Y 102 220 120 202
3: 3 Y N 103 230 130 203
# See the difference now?
> dt.wrong <- melt(dt.unordered, "ID", patterns("^[ab]$", "^XX", "^YYY"), "obs", c("outcome", "XX", "YYY"))
> dt.wrong[]
ID obs outcome XX YYY
1: 1 1 N 101 210
2: 2 1 Y 102 220
3: 3 1 Y 103 230
4: 1 2 Y 110 201
5: 2 2 Y 120 202
6: 3 2 N 130 203
总而言之,如果您已对所有列进行了预排序,请执行以下操作:
> setcolorder(dt.unordered, sort(names(dt.unordered)))
> dt.fixed <- melt(dt.unordered, "ID", patterns("^[ab]$", "^XX", "^YYY"), "obs", c("outcome", "XX", "YYY"))
> dt.fixed[]
ID obs outcome XX YYY
1: 1 1 Y 101 201
2: 2 1 Y 102 202
3: 3 1 N 103 203
4: 1 2 N 110 210
5: 2 2 Y 120 220
6: 3 2 Y 130 230
如果没有,请执行以下操作:
dt.res <- melt(dt.orig, "ID", patterns("^[ab]$", "^XX", "^YYY"), "obs", c("outcome", "XX", "YYY"))
setorder(dt.res, ID)[, obs := unname(c("1" = "a", "2" = "b")[obs])]