你能使用dplyr的across()函数来迭代处理一对列吗?

9
我有18对变量,想对它们进行成对数学计算,以计算出18个新变量。在对一列应用公式时,dplyr中的across()函数非常方便。有没有一种方法可以将across()应用于成对的列?
这里有一个简单的除法示例(我的实际代码会更复杂,包括一些ifelse等条件判断):
library(tidyverse)
library(glue)

# filler data
df <- data.frame("label" = c('a','b','c','d'),
                 "A" = c(4, 3, 8, 9),
                 "B" = c(10, 0, 4, 1),
                 "error_A" = c(0.4, 0.3, 0.2, 0.1),
                 "error_B" = c(0.3, 0, 0.4, 0.1))

# what I want to have in the end 
# instead of just 2 (A, B), I have 18
df1 <- df %>% mutate(
  'R_A' = A/error_A,
  'R_B' = B/error_B
)

# what I'm thinking about doing to use both variables A and error_A to calculate the new column
df2 <- df %>% mutate(
  across(c('A','B'),
         ~.x/{HOW DO I USE THE COLUMN WHOSE NAME IS glue('error_',.x)}
         .names = 'R_{.col}'
)
4个回答

8

一种选择是map/reduce。指定感兴趣的列('nm1'),在map中循环遍历它们,从数据集中select这些列,通过除法reduce,在列绑定后rename这些列(_dfc),然后将其与原始数据集进行绑定。

library(dplyr)
library(purrr)
library(stringr)
nm1 <- c('A', 'B')
map_dfc(nm1, ~ df %>% 
                select(ends_with(.x)) %>% 
                reduce(., `/`) ) %>%
    rename_all(~ str_c('R_', nm1)) %>%
    bind_cols(df, .)

-输出
#  label A  B error_A error_B R_A      R_B
#1     a 4 10     0.4     0.3  10 33.33333
#2     b 3  0     0.3     0.0  10      NaN
#3     c 8  4     0.2     0.4  40 10.00000
#4     d 9  1     0.1     0.1  90 10.00000

或者使用 across 的另一种选择

df %>% 
    mutate(across(c(A, B), ~ 
     ./get(str_c('error_', cur_column() )), .names = 'R_{.col}' ))
#  label A  B error_A error_B R_A      R_B
#1     a 4 10     0.4     0.3  10 33.33333
#2     b 3  0     0.3     0.0  10      NaN
#3     c 8  4     0.2     0.4  40 10.00000
#4     d 9  1     0.1     0.1  90 10.00000    

6

一个选择可能是:

df %>%
 mutate(across(c(A, B), .names = "R_{col}")/across(starts_with("error")))

  label A  B error_A error_B R_A      R_B
1     a 4 10     0.4     0.3  10 33.33333
2     b 3  0     0.3     0.0  10      NaN
3     c 8  4     0.2     0.4  40 10.00000
4     d 9  1     0.1     0.1  90 10.00000

这真的很奇怪,但很酷。我想知道为什么这甚至能工作,但看起来你使用across()创建了两个tibbles,然后将它们相除。赞。 - TimTeaFan

3

我喜欢akruns在上面的回答,特别是使用cur_column()的方法。有趣的是,cur_column()不能与{rlang}的评估(!! sym(paste0("error_", cur_column())))一起使用,但get是一个不错的解决方法。

只是再添加一种方法,在dpylr < 1.0.0下也适用。我通常使用一个mutate自定义函数和purrr::reduce()一起使用。在这个函数中,x是您的字符串正体,并且您使用!! sym(paste0(...))构造您想要访问的所有变量。在左边,您可以简单地使用{rlang}的粘合语法。

您通过在字符串向量上调用reduce()并将您的data.frame传递给.init =.参数来应用此自定义函数。

library(tidyverse)
library(glue)


# filler data
df <- data.frame("label" = c('a','b','c','d'),
                 "A" = c(4, 3, 8, 9),
                 "B" = c(10, 0, 4, 1),
                 "error_A" = c(0.4, 0.3, 0.2, 0.1),
                 "error_B" = c(0.3, 0, 0.4, 0.1))

gen_vars1 <- function(df, x) {
  
  mutate(df,
         "R_{x}" := !! sym(x) / !! sym(paste0("error_", x)))
}

df %>% 
  reduce(c("A", "B"), gen_vars1, .init = .)
#>   label A  B error_A error_B R_A      R_B
#> 1     a 4 10     0.4     0.3  10 33.33333
#> 2     b 3  0     0.3     0.0  10      NaN
#> 3     c 8  4     0.2     0.4  40 10.00000
#> 4     d 9  1     0.1     0.1  90 10.00000

本文由reprex软件包(版本0.3.0)于2021-01-02创建。

曾经我为这种问题提交了一个功能请求,但显然对于{dplyr}来说太特殊了。当您访问链接时,您也可以找到另一种解决此类操作的方法。


我认为此功能在需要对纵向数据进行相同转换的情况下非常有用。用户要么像上面所示编写语法复杂的函数,要么将数据按浪动轴长格式旋转,执行一次变换,然后再旋转回宽格式。 - D. Bontempo

2
对于这种情况,我认为基本的 R 解决方案也很简单和高效。它不需要循环遍历列或唯一值。您可以定义两组列并直接将它们划分。
对于您分享的示例,我们可以通过查找列名称中只有一个字符的列名来识别"A"和"B"列。
cols <- grep('^.$', names(df), value = TRUE)
error_cols <- grep('error', names(df), value = TRUE)

df[paste0('R_', cols)] <- df[cols]/df[error_cols]
df

#  label A  B error_A error_B R_A  R_B
#1     a 4 10     0.4     0.3  10 33.3
#2     b 3  0     0.3     0.0  10  NaN
#3     c 8  4     0.2     0.4  40 10.0
#4     d 9  1     0.1     0.1  90 10.0

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接