在 mutate 语句中动态引用列名 - dplyr
Refer to column names dynamically inside mutate statements - dplyr
对于这么长的问题,我深表歉意,但过了很长一段时间后,我自己也想不出解决办法。
我有这个玩具数据框
set.seed(23)
df <- tibble::tibble(
id = paste0("00", 1:6),
cond = c(1, 1, 2, 2, 3, 3),
A_1 = sample(0:9, 6, replace = TRUE), A_2 = sample(0:9, 6, replace = TRUE), A_3 = sample(0:9, 6, replace = TRUE),
B_1 = sample(0:9, 6, replace = TRUE), B_2 = sample(0:9, 6, replace = TRUE), B_3 = sample(0:9, 6, replace = TRUE),
C_1 = sample(0:9, 6, replace = TRUE), C_2 = sample(0:9, 6, replace = TRUE), C_3 = sample(0:9, 6, replace = TRUE)
)
# A tibble: 6 x 11
# id cond A_1 A_2 A_3 B_1 B_2 B_3 C_1 C_2 C_3
# <chr> <dbl> <int> <int> <int> <int> <int> <int> <int> <int> <int>
# 1 001 1 6 3 9 5 0 5 6 0 6
# 2 002 1 4 5 0 8 5 0 1 6 6
# 3 003 2 4 2 8 8 8 6 5 2 5
# 4 004 2 4 4 0 7 2 6 7 5 7
# 5 005 3 1 7 0 9 9 0 5 7 8
# 6 006 3 3 8 7 0 2 5 0 9 4
我想创建三个变量 A_def
、B_def
、C_def
,它们只取相应变量之一的值 <LETTER_NUMBER> 取决于它们的后缀等于变量 cond
.
的条件
例如,对于 cond == 1
的行,A_def
的值应来自 A_1
,B_def
的值应来自 B_1
,C_def
的值应该来自 C_1
。同样,如果 cond == 2
,*_def
列应具有来自相应 *_2
变量的值。
我设法通过两种方式实现了我想要的输出:一种是硬编码(如果 cond
包含许多值可能会避免这种情况),另一种是使用 tidyr
的旋转函数。
硬编码解决方案:
df %>%
mutate(
A_def = ifelse(cond == 1, A_1, ifelse(cond == 2, A_2, A_3)),
B_def = ifelse(cond == 1, B_1, ifelse(cond == 2, B_2, B_3)),
C_def = ifelse(cond == 1, C_1, ifelse(cond == 2, C_2, C_3))
) %>%
select(id, cond, contains("_def"))
tidyr
的解法:
df %>%
pivot_longer(cols = contains("_")) %>%
mutate(
number = gsub("[A-Za-z_]", "", name),
name = gsub("[^A-Za-z]", "", name)
) %>%
filter(cond == number) %>%
pivot_wider(id_cols = c(id, cond), names_from = name, values_from = value, names_glue = "{name}_def")
两种情况下的输出
# A tibble: 6 x 5
# id cond A_def B_def C_def
# <chr> <dbl> <int> <int> <int>
# 1 001 1 6 5 6
# 2 002 1 4 8 1
# 3 003 2 2 8 2
# 4 004 2 4 2 5
# 5 005 3 0 0 8
# 6 006 3 7 5 4
现在,我想知道是否有可能以动态方式使用 mutate
and/or across
获得相同的输出(也许在 mutate
?)。我尝试了以下代码片段,但结果不如预期。在其中一个中,我试图将变量名称作为 ifelse
语句中的符号,但出现错误。
df %>%
mutate(across(paste0(c("A", "B", "C"), "_1"),
~ifelse(cond == 1, cur_column(),
ifelse(cond == 2, cur_column(), paste0(gsub("[^A-Za-z]", "", cur_column()), "_3"))))) %>%
select(id, cond, contains("_1"))
df %>%
mutate_at(paste0(c("A", "B", "C"), "_1"),
~ifelse(cond == 1, ., ifelse(cond == 2, ., paste0(., "_2")))) %>%
select(id, cond, contains("_1"))
df %>%
mutate_at(paste0(c("A", "B", "C"), "_1"),
~ifelse(cond == 1, !!!rlang::syms(paste0(c("A", "B", "C"), "_1")),
ifelse(cond == 2, !!!rlang::syms(paste0(c("A", "B", "C"), "_2")),
!!!rlang::syms(paste0(c("A", "B", "C"), "_3")))))
问题:有没有一种方法可以使用 dplyr
的语句(例如 mutate
(或其被取代的作用域变体)and/or [=33] 来获得与上面相同的期望输出=]?
正如 Ronak 所说,您的 tidyr
解决方案似乎很好。
不过你可以稍微简化一下:
df %>%
pivot_longer(cols = contains("_"), names_to = c("name", "number"), names_sep = "_") %>%
filter(cond == number) %>%
pivot_wider(id_cols = c(id, cond), names_glue = "{name}_def")
## A tibble: 6 x 5
# id cond A_def B_def C_def
# <chr> <dbl> <int> <int> <int>
#1 001 1 7 8 1
#2 002 1 2 5 2
#3 003 2 4 2 3
#4 004 2 0 3 1
#5 005 3 9 0 7
#6 006 3 9 7 0
我同意 tidyr
使代码更具可读性的其他评论,但这是 pmap
的替代方法:
library(purrr)
library(rlang)
pmap_dfr(df, ~with(list(...),
set_names(c(id, cond,
map_dbl(c("A","B","C"),
~ eval_tidy(parse_expr(paste(.x,cond,sep = "_"))))),
c("id","cond","A_def","B_def","C_def"))
))
# A tibble: 6 x 5
id cond A_def B_def C_def
<dbl> <dbl> <dbl> <dbl> <dbl>
1 1 1 6 5 6
2 2 1 4 8 1
3 3 2 2 8 2
4 4 2 4 2 5
5 5 3 0 0 8
6 6 3 7 5 4
这是一个简短的基础 R 解决方案,使用 mapply
:
f <- function(x, i) df[-(1:2)][i, c(x, x+3, x+6)]
df <- cbind(df[1:2], t(mapply(f, df$cond, seq(nrow(df)))))
setNames(df, c("id", "cond", "A_def", "B_def", "C_def"))
#> id cond A_def B_def C_def
#> 1 001 1 7 8 1
#> 2 002 1 2 5 2
#> 3 003 2 4 2 3
#> 4 004 2 0 3 1
#> 5 005 3 9 0 7
#> 6 006 3 9 7 0
对于这么长的问题,我深表歉意,但过了很长一段时间后,我自己也想不出解决办法。
我有这个玩具数据框
set.seed(23)
df <- tibble::tibble(
id = paste0("00", 1:6),
cond = c(1, 1, 2, 2, 3, 3),
A_1 = sample(0:9, 6, replace = TRUE), A_2 = sample(0:9, 6, replace = TRUE), A_3 = sample(0:9, 6, replace = TRUE),
B_1 = sample(0:9, 6, replace = TRUE), B_2 = sample(0:9, 6, replace = TRUE), B_3 = sample(0:9, 6, replace = TRUE),
C_1 = sample(0:9, 6, replace = TRUE), C_2 = sample(0:9, 6, replace = TRUE), C_3 = sample(0:9, 6, replace = TRUE)
)
# A tibble: 6 x 11
# id cond A_1 A_2 A_3 B_1 B_2 B_3 C_1 C_2 C_3
# <chr> <dbl> <int> <int> <int> <int> <int> <int> <int> <int> <int>
# 1 001 1 6 3 9 5 0 5 6 0 6
# 2 002 1 4 5 0 8 5 0 1 6 6
# 3 003 2 4 2 8 8 8 6 5 2 5
# 4 004 2 4 4 0 7 2 6 7 5 7
# 5 005 3 1 7 0 9 9 0 5 7 8
# 6 006 3 3 8 7 0 2 5 0 9 4
我想创建三个变量 A_def
、B_def
、C_def
,它们只取相应变量之一的值 <LETTER_NUMBER> 取决于它们的后缀等于变量 cond
.
例如,对于 cond == 1
的行,A_def
的值应来自 A_1
,B_def
的值应来自 B_1
,C_def
的值应该来自 C_1
。同样,如果 cond == 2
,*_def
列应具有来自相应 *_2
变量的值。
我设法通过两种方式实现了我想要的输出:一种是硬编码(如果 cond
包含许多值可能会避免这种情况),另一种是使用 tidyr
的旋转函数。
硬编码解决方案:
df %>%
mutate(
A_def = ifelse(cond == 1, A_1, ifelse(cond == 2, A_2, A_3)),
B_def = ifelse(cond == 1, B_1, ifelse(cond == 2, B_2, B_3)),
C_def = ifelse(cond == 1, C_1, ifelse(cond == 2, C_2, C_3))
) %>%
select(id, cond, contains("_def"))
tidyr
的解法:
df %>%
pivot_longer(cols = contains("_")) %>%
mutate(
number = gsub("[A-Za-z_]", "", name),
name = gsub("[^A-Za-z]", "", name)
) %>%
filter(cond == number) %>%
pivot_wider(id_cols = c(id, cond), names_from = name, values_from = value, names_glue = "{name}_def")
两种情况下的输出
# A tibble: 6 x 5
# id cond A_def B_def C_def
# <chr> <dbl> <int> <int> <int>
# 1 001 1 6 5 6
# 2 002 1 4 8 1
# 3 003 2 2 8 2
# 4 004 2 4 2 5
# 5 005 3 0 0 8
# 6 006 3 7 5 4
现在,我想知道是否有可能以动态方式使用 mutate
and/or across
获得相同的输出(也许在 mutate
?)。我尝试了以下代码片段,但结果不如预期。在其中一个中,我试图将变量名称作为 ifelse
语句中的符号,但出现错误。
df %>%
mutate(across(paste0(c("A", "B", "C"), "_1"),
~ifelse(cond == 1, cur_column(),
ifelse(cond == 2, cur_column(), paste0(gsub("[^A-Za-z]", "", cur_column()), "_3"))))) %>%
select(id, cond, contains("_1"))
df %>%
mutate_at(paste0(c("A", "B", "C"), "_1"),
~ifelse(cond == 1, ., ifelse(cond == 2, ., paste0(., "_2")))) %>%
select(id, cond, contains("_1"))
df %>%
mutate_at(paste0(c("A", "B", "C"), "_1"),
~ifelse(cond == 1, !!!rlang::syms(paste0(c("A", "B", "C"), "_1")),
ifelse(cond == 2, !!!rlang::syms(paste0(c("A", "B", "C"), "_2")),
!!!rlang::syms(paste0(c("A", "B", "C"), "_3")))))
问题:有没有一种方法可以使用 dplyr
的语句(例如 mutate
(或其被取代的作用域变体)and/or [=33] 来获得与上面相同的期望输出=]?
正如 Ronak 所说,您的 tidyr
解决方案似乎很好。
不过你可以稍微简化一下:
df %>%
pivot_longer(cols = contains("_"), names_to = c("name", "number"), names_sep = "_") %>%
filter(cond == number) %>%
pivot_wider(id_cols = c(id, cond), names_glue = "{name}_def")
## A tibble: 6 x 5
# id cond A_def B_def C_def
# <chr> <dbl> <int> <int> <int>
#1 001 1 7 8 1
#2 002 1 2 5 2
#3 003 2 4 2 3
#4 004 2 0 3 1
#5 005 3 9 0 7
#6 006 3 9 7 0
我同意 tidyr
使代码更具可读性的其他评论,但这是 pmap
的替代方法:
library(purrr)
library(rlang)
pmap_dfr(df, ~with(list(...),
set_names(c(id, cond,
map_dbl(c("A","B","C"),
~ eval_tidy(parse_expr(paste(.x,cond,sep = "_"))))),
c("id","cond","A_def","B_def","C_def"))
))
# A tibble: 6 x 5
id cond A_def B_def C_def
<dbl> <dbl> <dbl> <dbl> <dbl>
1 1 1 6 5 6
2 2 1 4 8 1
3 3 2 2 8 2
4 4 2 4 2 5
5 5 3 0 0 8
6 6 3 7 5 4
这是一个简短的基础 R 解决方案,使用 mapply
:
f <- function(x, i) df[-(1:2)][i, c(x, x+3, x+6)]
df <- cbind(df[1:2], t(mapply(f, df$cond, seq(nrow(df)))))
setNames(df, c("id", "cond", "A_def", "B_def", "C_def"))
#> id cond A_def B_def C_def
#> 1 001 1 7 8 1
#> 2 002 1 2 5 2
#> 3 003 2 4 2 3
#> 4 004 2 0 3 1
#> 5 005 3 9 0 7
#> 6 006 3 9 7 0