在dplyr的mutate_at调用中使用多列函数

65

我想使用dplyr的mutate_at函数来对数据框中的多列应用函数,其中该函数将直接应用于的列以及数据框中的另一列作为输入。

以一个具体的例子为例,我想要修改以下数据框:

# Example input dataframe
df <- data.frame(
    x = c(TRUE, TRUE, FALSE),
    y = c("Hello", "Hola", "Ciao"),
    z = c("World", "ao", "HaOlam")
)

使用类似于这样的mutate_at调用

df %>%
mutate_at(.vars = vars(y, z),
          .funs = ifelse(x, ., NA))

返回一个类似于这样的数据框

# Desired output dataframe
df2 <- data.frame(x = c(TRUE, TRUE, FALSE),
                  y_1 = c("Hello", "Hola", NA),
                  z_1 = c("World", "ao", NA))

所需的 mutate_at 调用将类似于对 mutate 的以下调用:

df %>%
   mutate(y_1 = ifelse(x, y, NA),
          z_1 = ifelse(x, z, NA))

我知道使用基本的R语言有几种方法可以完成这个任务,但出于可读性、与数据库的互联等方面的考虑,我特别希望使用dplyr的mutate_at函数来实现这个目标。

以下是一些类似的stackoverflow上的问题,它们并没有解决我在这里提出的问题:

在 dplyr 的 mutate 中添加多个列

dplyr::mutate 添加多个值

使用 dplyr 的 mutate() 函数内部的 sum() 函数中的列


18
df %>% mutate_at(vars(y, z), funs(ifelse(x, ., NA)))这行代码意思是对于数据框df中的变量y和z,如果x为真,则保留原值,否则将其设为NA,并返回处理后的数据框。 - eipi10
@eipi10 哦,好的。所以上面的代码如果我实际上将 ifelse(x, ., NA) 包装在 funs() 的调用中就可以工作了。谢谢!我已经检查了您的解决方案,它完美地解决了问题。您的解决方案正是我一直在寻找的! - bschneidr
2个回答

72

这个问题已经被 @eipi10 在评论中回答了,但我在这里写出来留作记录。

解决方案是使用:

df %>%
   mutate_at(.vars = vars(y, z),
             .funs = list(~ ifelse(x, ., NA)))

您还可以使用新的across()函数和mutate()一起使用,如下所示:

df %>%
   mutate(across(c(y, z), ~ ifelse(x, ., NA)))

在这里使用公式运算符(如〜ifelse(...))表示ifelse(x,。 ,NA)是一个匿名函数,它被定义在调用mutate_at()中。

这类似于在调用之外定义函数,例如:

temp_fn <- function(input) ifelse(test = df[["x"]],
                                  yes = input,
                                  no = NA)

df %>%
   mutate_at(.vars = vars(y, z),
             .funs = temp_fn)

dplyr中语法更改的注释:在dplyr 0.8.0版本之前,您只需要编写.funs = funs(ifelse(x, . , NA)),但是funs()函数正在被弃用,并将很快从dplyr中删除。


在这里使用 funs() 指示了 ifelse(x, ., NA) 是一个匿名函数。那么 funs() 和传统的匿名函数 function(x) 有什么区别呢? - coip
1
在我的经验中,最值得注意的是它需要更少的打字,同时也很容易阅读。然而,它还允许您提供一个匿名函数列表(例如 funs(avg = mean(.), total = sum(., na.rm = TRUE)))。请参见 https://www.rdocumentation.org/packages/dplyr/versions/0.7.6/topics/funs。 - bschneidr
如果在mutate之外定义函数的示例仅在函数定义和使用之间df未更改时才有效。这似乎是一种冒险的策略。例如,如果有人首先对数据进行分组,那该怎么办? - randy
同意,我不建议这样做。那个例子只是为了帮助解释实际解决方案的工作原理。 - bschneidr

19

为了补充之前的回答,如果您希望 mutate_at() 添加新变量(而不是替换),并使用类似于原始问题中的 z_1y_1 等名称,您只需要:

  • dplyr >=1 使用 across():添加 .names="{.col}_1",或者使用 list('1'=~ifelse(x, ., NA)(反引号!)
  • dplyr [0.8, 1[:使用 list('1'=~ifelse(x, ., NA)
  • dplyr <0.8:使用 funs('1'=ifelse(x, ., NA)
library(tidyverse)

df <- data.frame(
  x = c(TRUE, TRUE, FALSE),
  y = c("Hello", "Hola", "Ciao"),
  z = c("World", "ao", "HaOlam")
)

## Version >=1
df %>%
  mutate(across(c(y, z), 
                list(~ifelse(x, ., NA)),
                .names="{.col}_1"))
#>       x     y      z   y_1   z_1
#> 1  TRUE Hello  World Hello World
#> 2  TRUE  Hola     ao  Hola    ao
#> 3 FALSE  Ciao HaOlam  <NA>  <NA>


## 0.8 - <1
df %>%
  mutate_at(.vars = vars(y, z),
            .funs = list(`1`=~ifelse(x, ., NA)))
#>       x     y      z   y_1   z_1
#> 1  TRUE Hello  World Hello World
#> 2  TRUE  Hola     ao  Hola    ao
#> 3 FALSE  Ciao HaOlam  <NA>  <NA>

## Before 0.8
df %>%
  mutate_at(.vars = vars(y, z),
            .funs = funs(`1`=ifelse(x, ., NA)))
#> Warning: `funs()` is deprecated as of dplyr 0.8.0.
#> Please use a list of either functions or lambdas: 
#> 
#>   # Simple named list: 
#>   list(mean = mean, median = median)
#> 
#>   # Auto named with `tibble::lst()`: 
#>   tibble::lst(mean, median)
#> 
#>   # Using lambdas
#>   list(~ mean(., trim = .2), ~ median(., na.rm = TRUE))
#> This warning is displayed once every 8 hours.
#> Call `lifecycle::last_warnings()` to see where this warning was generated.
#>       x     y      z   y_1   z_1
#> 1  TRUE Hello  World Hello World
#> 2  TRUE  Hola     ao  Hola    ao
#> 3 FALSE  Ciao HaOlam  <NA>  <NA>

使用 reprex 包 (v0.3.0) 在 2020-10-03 创建

了解更多细节和技巧,请参阅:在保留原始变量的同时使用 mutate_at 创建新变量


当函数在调用 mutate_at之外定义时,这将如何工作? - randy
不确定我是否理解你的问题@randy,如果主函数在内部或外部定义(请注意ifelse本身是在调用之外定义的),则没有区别。 - Matifou

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