【问题标题】:Is there a efficient way to mutate only on rows that meet a condition? Think mutate(when(condition))是否有一种有效的方法可以仅在满足条件的行上进行变异?认为变异(何时(条件))
【发布时间】:2022-01-02 09:23:00
【问题描述】:

我希望仅在满足某些条件时应用变异。

我知道我能做到……

data2 <- data1 %>%
   group_by(a, b) %>%
   mutate(
      var1 = case_when(
         condition ~ TRUE,
         TRUE ~ FALSE,
         NA
         ),
      var2 = case_when(
         condition ~ TRUE,
         max(var28),
         var2
         ),
      var3 = case_when(
         condition ~ TRUE,
         "happy",
         var3
         ),
...more vars here....
)

我想要的是这样的东西……

data2 <- data1 %>%
   group_by(a, b) %>%
   mutate(
      when(condition),
      var1 = FALSE,
      var2 = max(var28),
      var3 = "happy",
...more vars here....
)

很遗憾,mutate(across(when(condition))) 不起作用。

有什么建议吗?

【问题讨论】:

  • 一些示例数据(作为纯文本)和预期输出会有所帮助。

标签: r dplyr conditional-statements tidyverse


【解决方案1】:

另一个可能的解决方案是使用 cross() 和 ifelse(),例如如果 x 列中的值小于 4,则执行突变:

library(tidyverse)
tibble(x = c(1:2, 4:5), y = 1:4) %>% 
  mutate(across(everything(), ~ ifelse(x < 4, -.x, .x)))
#> # A tibble: 4 × 2
#>       x     y
#>   <int> <int>
#> 1    -1    -1
#> 2    -2    -2
#> 3     4     3
#> 4     5     4

reprex package (v2.0.1) 于 2021 年 11 月 24 日创建

您也可以将 ifelse 嵌套在一起,例如如果 x 列中的值小于 4,或者任何列中的值等于 3,则变异:

library(tidyverse)
tibble(x = c(1:2, 4:5), y = 1:4) %>% 
  mutate(across(everything(), ~ ifelse(x < 4, -.x,
                                       ifelse(.x == 3, .x + 10, .x))))
#> # A tibble: 4 × 2
#>       x     y
#>   <int> <dbl>
#> 1    -1    -1
#> 2    -2    -2
#> 3     4    13
#> 4     5     4

reprex package (v2.0.1) 于 2021 年 11 月 24 日创建

等等:

library(tidyverse)
tibble(x = c(1:2, 4:5), y = 1:4) %>% 
  mutate(across(everything(), ~ ifelse(x < 4, -.x,
                                       ifelse(.x == 3, .x + 10,
                                              ifelse(.x >= 5, "outlier", .x)))))
#> # A tibble: 4 × 2
#>   x           y
#>   <chr>   <dbl>
#> 1 -1         -1
#> 2 -2         -2
#> 3 4          13
#> 4 outlier     4

reprex package (v2.0.1) 于 2021 年 11 月 24 日创建

--

要更有效地进行突变,请不要使用 dplyr::mutate。 ifelse() 函数是矢量化的(更多细节:https://swcarpentry.github.io/r-novice-gapminder/09-vectorization/),如果你有一个大数据框,单独的 ifelse 很可能比 tidyverse 函数更快,例如

library(tidyverse)
df <- tibble(x = c(1:2, 4:5), y = 1:4)
df$y <- ifelse(df$x < 4, -df$y, df$y)
df
#> # A tibble: 4 × 2
#>       x     y
#>   <int> <int>
#> 1     1    -1
#> 2     2    -2
#> 3     4     3
#> 4     5     4

编辑

另一个可能的选择是通过赋值替换值:df$y[df$x &lt; 4] &lt;- -(df$y); df$x[df$x &lt; 4] &lt;- -(df$x)(速度快,但有限制)。

这里是对 100 万行建议方法的快速基准测试:

library(tidyverse)
df <- tibble(x = sample(1:10, 1000000, replace = TRUE),
             y = sample(1:10, 1000000, replace = TRUE))

mutate_func <- function(df){
  df %>%
    mutate(across(everything(), ~ ifelse(x < 4, -.x, .x)))
}

ifelse_func <- function(df){
  df$y <- ifelse(df$x < 4, -df$y, df$y)
}

replacement_func <- function(df) {
  df$y[df$x < 4] <- -(df$y)
  df$x[df$x < 4] <- -(df$x)
}

mutate_when_func <- function(df) {
  mutate_when <- function(.data, when, ...) {
    dots <- enquos(...)
    names <- names(dots)
  
    mutate(.data, {
      test <- {{ when }}
    
      changed <- data.frame(!!!dots, stringsAsFactors = FALSE)
      out <- across(all_of(names))
      # assuming `changed` and `out` have the same data frame type
    
      out[test, ] <- changed[test, ]
      out
    })
  }

df %>% 
  mutate_when(x < 4, x = -x, y = -y)
}

library(microbenchmark)
result <- microbenchmark(mutate_func(df), ifelse_func(df),
                         mutate_when_func(df), replacement_func(df),
                         times = 10)
autoplot(result)

【讨论】:

  • 这真是很棒的分析。在我的特殊情况下,我有少量数据,其中输出是使用流程图从变量中得出的,代码可能会被技术含量较低的观众查看。所以 mutate_when 函数这次符合要求,但如果我或其他人正在做一些计算量更大的事情,这很高兴。
【解决方案2】:

在 mutate 中没有执行此操作的功能,但 Romain Francois has shared 您可以定义自己的函数来执行此操作:

library(dplyr, warn.conflicts = F)

mutate_when <- function(.data, when, ...) {
  dots <- enquos(...)
  names <- names(dots)
  
  mutate(.data, {
    test <- {{ when }}
    
    changed <- data.frame(!!!dots, stringsAsFactors = FALSE)
    out <- across(all_of(names))
    # assuming `changed` and `out` have the same data frame type

    out[test, ] <- changed[test, ]
    out
  })
  
}

tibble(x = 1:4, y = 1:4) %>% 
  mutate_when(x < 4, x = -x, y = -y)
#> # A tibble: 4 × 2
#>       x     y
#>   <int> <int>
#> 1    -1    -1
#> 2    -2    -2
#> 3    -3    -3
#> 4     4     4

reprex package (v2.0.1) 于 2021 年 11 月 23 日创建

【讨论】:

  • 这正是我正在寻找的简化功能。谢谢!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2021-03-22
  • 2018-05-04
  • 2021-04-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多