使用 dplyr::across 和两组变量

Using dplyr::across with two sets of variables

我有两组变量,例如变量a和变量a_avail。我正在尝试根据 a_avail 的值更改 a 的值,并且想知道是否可以使用 acrossglue.

来完成此操作

这是我试过的。没有产生错误,但是胶水似乎没有获取 .x_avail 的值,因为所有返回的值都是 NA:

library(tidyverse)

df <- tibble(a = c(0, 1, 0, 0, 0),
       a_avail = c(1, 1, 1, 0, 0),
       b = c(1, 1, 1, 0, 0),
       b_avail = c(1, 0, 0, 1, 0))

df2 <- df %>% 
  mutate(across(.cols = c(a, b),
                .fns = ~case_when(
                  glue::glue("{.x}_avail") == 1 ~ .x,
                  glue::glue("{.x}_avail") == 0 ~ as.numeric(NA)
                ),
                .names = "{.col}_new"))

df2
#> # A tibble: 5 x 6
#>       a a_avail     b b_avail a_new b_new
#>   <dbl>   <dbl> <dbl>   <dbl> <dbl> <dbl>
#> 1     0       1     1       1    NA    NA
#> 2     1       1     1       0    NA    NA
#> 3     0       1     1       0    NA    NA
#> 4     0       0     0       1    NA    NA
#> 5     0       0     0       0    NA    NA

reprex package (v0.3.0)

于 2021 年 2 月 12 日创建

不是一个 tidyverse 解决方案,但它应该可以工作

library(tidyverse)

df <- tibble(a = c(0, 1, 0, 0, 0),
             a_avail = c(1, 1, 1, 0, 0),
             b = c(1, 1, 1, 0, 0),
             b_avail = c(1, 0, 0, 1, 0))


v1 <- list('a','b')
v2 <- list('a_avail','b_avail')


v3 <- as.data.frame(mapply(function(x,y){ifelse(df[[y]] == 0, NA,df[[x]])} , v1,v2, 
                           SIMPLIFY = TRUE))

names(v3) <- paste0(v1,"_new")

df3 <- cbind(df, v3)

您遇到的主要问题是引用列本身,而不仅仅是将字符串(或 glue 对象)与数字进行比较。您可能会组合一个 tidyeval 函数,但(可能)更简单的方法是将数据重新整形为长格式,以包含原始值列和可用性列,在新列中添加比较,然后重新整形.这也将缩放,因此您不必指定要执行此操作的所有列,也不必手动将原始文件与可用的列准确匹配。

第一个技巧是使用某种方式标记原始列,这样您就可以拆分,例如"a" 来自 "avail"。为此,将另一个字符串附加到只有单个字符的名称上。 (您可以使用不同的方法来选择列。)使用 ID 来标记行——您可以稍后删除该列。第二个技巧是在枢轴函数中使用特殊的 ".value" 项。

我建议一个一个地完成重塑步骤,看看它们是如何工作的,并根据需要进行调整。

library(dplyr)
library(tidyr)
df %>%
  rename_with(~paste(., "orig", sep = "_"), matches("^[a-z]$")) %>%
  tibble::rowid_to_column() %>%
  pivot_longer(-rowid, names_to = c("col", ".value"), names_sep = "_") %>%
  mutate(new = if_else(avail == 1, orig, NA_real_)) %>%
  pivot_wider(id_cols = rowid, names_from = col, values_from = orig:new, 
              names_glue = "{col}_{.value}")
#> # A tibble: 5 x 7
#>   rowid a_orig b_orig a_avail b_avail a_new b_new
#>   <int>  <dbl>  <dbl>   <dbl>   <dbl> <dbl> <dbl>
#> 1     1      0      1       1       1     0     1
#> 2     2      1      1       1       0     1    NA
#> 3     3      0      1       1       0     0    NA
#> 4     4      0      0       0       1    NA     0
#> 5     5      0      0       0       0    NA    NA

我认为您可以通过 purrr 包轻松实现您想要的输出。在某种程度上,我们使用 map2 函数而不是使用 across 函数,因为我们同时处理 2 个变量,并且为了我们的目的,我们希望逐行迭代每一对变量:

library(dplyr)
library(purrr)

df <- tibble(a = c(0, 1, 0, 0, 0),
             a_avail = c(1, 1, 1, 0, 0),
             b = c(1, 1, 1, 0, 0),
             b_avail = c(1, 0, 0, 1, 0))


df %>%
  mutate(a_new = map2_dbl(a, a_avail, ~ ifelse(.y == 1, .x, NA)),
         b_new = map2_dbl(b, b_avail, ~ ifelse(.y == 1, .x, NA)))


# A tibble: 5 x 6
      a a_avail     b b_avail a_new b_new
  <dbl>   <dbl> <dbl>   <dbl> <dbl> <dbl>
1     0       1     1       1     0     1
2     1       1     1       0     1    NA
3     0       1     1       0     0    NA
4     0       0     0       1    NA     0
5     0       0     0       0    NA    NA

在这种情况下,最好仔细考虑一下什么函数最能满足您的目的,并且最符合您给出的参数集,您希望用它们做什么。在这里,因为我们正在处理逐行操作,所以我更愿意使用 purrr 包函数。

Ronak Shah 在他的 to a related 中提出了一个很棒的方法,我在下面复制了它。

实际上是两件事

  • mutate(across.. 中使用 column/variable 名称而不是值 cur_column() 应该用于反对 ..x
  • get() 也可以与 glue 一起使用,以便 R 将其识别为变量。

这样做

df %>% 
  mutate(across(.cols = c(a, b),
                .fns = ~case_when(
                  get(glue::glue("{cur_column()}_avail")) == 1 ~ .x,
                  get(glue::glue("{cur_column()}_avail")) == 0 ~ NA_real_
                ),
                .names = "{.col}_new"))

# A tibble: 5 x 6
      a a_avail     b b_avail a_new b_new
  <dbl>   <dbl> <dbl>   <dbl> <dbl> <dbl>
1     0       1     1       1     0     1
2     1       1     1       0     1    NA
3     0       1     1       0     0    NA
4     0       0     0       1    NA     0
5     0       0     0       0    NA    NA