【问题标题】:Refer to column names dynamically inside mutate statements - dplyr在 mutate 语句中动态引用列名 - dplyr
【发布时间】:2020-10-14 19:14:44
【问题描述】:

我为这个冗长的问题道歉,但过了一段时间我自己也想不出解决办法。

我有这个玩具数据框

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_defB_defC_def,它们只取对应变量 LETTER_NUMBER> 之一的值,具体取决于它们的后缀是等于变量cond

例如,对于cond == 1A_def 的值应该来自A_1B_def 的值应该来自B_1C_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 和/或across 以动态方式获得相同的输出(可能在mutate 中使用ifelse 语句?)。我尝试了以下代码 sn-ps 但结果并不如预期。在其中一个中,我尝试将变量名称作为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(或其被取代的作用域变体)和/或across)获得与上述相同的所需输出?

【问题讨论】:

  • 我认为您的tidyr 解决方案是解决此类问题的标准方法。
  • 正如@RonakShah 所说,比您的tidyr 解决方案更通用的解决方案并不明显。 [顺便说一句,在pivot_longer 中使用names_sep 可以使它更优雅一点...]
  • 谢谢大家的意见,不知道有没有简单的方法使用mutate或者我只是在找暗物质..

标签: r dplyr across


【解决方案1】:

我同意 tidyr 使代码更具可读性的其他 cmets,但这是 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

【讨论】:

  • 我必须研究它,因为我对 purrr 不是很熟悉,但它可能是最好的非 tidyr 解决方案之一!也许我的“使用mutate”的想法太难实现了,也不可取
【解决方案2】:

正如 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

【讨论】:

  • 感谢names_to 的简化,没想到!
【解决方案3】:

这是使用mapply 的简短基本 R 解决方案:

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-10-03
    • 2022-06-13
    • 1970-01-01
    • 2016-09-17
    • 2016-05-22
    • 1970-01-01
    相关资源
    最近更新 更多