使用rowwise()、mutate()、across()组合多个函数

7
这与这个问题有些相关:question: 原则上,我试图理解如何在应用多个函数(例如mean()sum()min()等)的情况下通过mutate进行跨多列的rowwise操作。 我已经学到,across可以完成这项工作,而不是c_across。 我了解到函数mean()和函数min()不同之处在于mean()不能在数据框上工作,我们需要将其更改为向量,这可以通过unlist或as.matrix完成——从Ronak ShahhereUnderstanding rowwise() and c_across()学到。 现在,对于我的实际情况:我能够完成这个任务,但我失去了一个列d。在此情况下如何避免丢失列d。 我的df:
df <- structure(list(a = 1:5, b = 6:10, c = 11:15, d = c("a", "b", 
"c", "d", "e"), e = 1:5), row.names = c(NA, -5L), class = c("tbl_df", 
"tbl", "data.frame"))

不起作用:

df %>% 
  rowwise() %>% 
  mutate(across(a:e), 
         avg = mean(unlist(cur_data()), na.rm = TRUE),
         min = min(unlist(cur_data()), na.rm = TRUE), 
         max = max(unlist(cur_data()), na.rm = TRUE)
  )

# Output:
      a     b     c d         e   avg min   max  
  <int> <int> <int> <chr> <int> <dbl> <chr> <chr>
1     1     6    11 a         1    NA 1     a    
2     2     7    12 b         2    NA 12    b    
3     3     8    13 c         3    NA 13    c    
4     4     9    14 d         4    NA 14    d    
5     5    10    15 e         5    NA 10    e 

这样做是可行的,但我会失去d列:

df %>% 
  select(-d) %>% 
  rowwise() %>% 
  mutate(across(a:e), 
         avg = mean(unlist(cur_data()), na.rm = TRUE),
         min = min(unlist(cur_data()), na.rm = TRUE), 
         max = max(unlist(cur_data()), na.rm = TRUE)
  )

      a     b     c     e   avg   min   max
  <int> <int> <int> <int> <dbl> <dbl> <dbl>
1     1     6    11     1  4.75     1    11
2     2     7    12     2  5.75     2    12
3     3     8    13     3  6.75     3    13
4     4     9    14     4  7.75     4    14
5     5    10    15     5  8.75     5    15
4个回答

7
使用purrrpmap()可能更可取,因为您只需选择一次数据即可,并且可以使用选择助手。
df %>% 
 mutate(pmap_dfr(across(where(is.numeric)),
                 ~ data.frame(max = max(c(...)),
                              min = min(c(...)),
                              avg = mean(c(...)))))

      a     b     c d         e   max   min   avg
  <int> <int> <int> <chr> <int> <int> <int> <dbl>
1     1     6    11 a         1    11     1  4.75
2     2     7    12 b         2    12     2  5.75
3     3     8    13 c         3    13     3  6.75
4     4     9    14 d         4    14     4  7.75
5     5    10    15 e         5    15     5  8.75

或者加入 tidyr

df %>% 
 mutate(res = pmap(across(where(is.numeric)),
                   ~ list(max = max(c(...)),
                          min = min(c(...)),
                          avg = mean(c(...))))) %>%
 unnest_wider(res)

谢谢tmfmnk。您能简要评论一下c(...)吗? - TarJae
1
请查看此问题。这可能会有所帮助。 - AnilGoyal
1
如果使用 purrr 没有问题,那就再好不过了。+1。 - AnilGoyal
1
@TarJae 这两个也可能有帮助: https://dev59.com/aMDqa4cB1Zd3GeqPZkIghttps://dev59.com/h8Dqa4cB1Zd3GeqPZT98 - Anoushiravan R

6

编辑:

最佳解决方案如下:

df %>%
  rowwise() %>% 
  mutate(min = min(c_across(a:e & where(is.numeric)), na.rm = TRUE),
         max = max(c_across(a:e & where(is.numeric)), na.rm = TRUE), 
         avg = mean(c_across(a:e & where(is.numeric)), na.rm = TRUE)
  )

# A tibble: 5 x 8
# Rowwise: 
      a     b     c d         e   min   max   avg
  <int> <int> <int> <chr> <int> <int> <int> <dbl>
1     1     6    11 a         1     1    11  4.75
2     2     7    12 b         2     2    12  5.75
3     3     8    13 c         3     3    13  6.75
4     4     9    14 d         4     4    14  7.75
5     5    10    15 e         5     5    15  8.75

早期答案 如果您更改输出顺序,您的this will work甚至无法正常工作,请参见

df %>% 
  select(-d) %>% 
  rowwise() %>% 
  mutate(across(a:e), 
         min = min(unlist(cur_data()), na.rm = TRUE),
         max = max(unlist(cur_data()), na.rm = TRUE), 
         avg = mean(unlist(cur_data()), na.rm = TRUE)
  )

# A tibble: 5 x 7
# Rowwise: 
      a     b     c     e   min   max   avg
  <int> <int> <int> <int> <int> <int> <dbl>
1     1     6    11     1     1    11  5.17
2     2     7    12     2     2    12  6.17
3     3     8    13     3     3    13  7.17
4     4     9    14     4     4    14  8.17
5     5    10    15     5     5    15  9.17


因此,建议按照以下方式进行操作 -
df %>% 
  select(-d) %>% 
  rowwise() %>% 
  mutate(min = min(c_across(a:e), na.rm = TRUE),
         max = max(c_across(a:e), na.rm = TRUE), 
         avg = mean(c_across(a:e), na.rm = TRUE)
  )

# A tibble: 5 x 7
# Rowwise: 
      a     b     c     e   min   max   avg
  <int> <int> <int> <int> <int> <int> <dbl>
1     1     6    11     1     1    11  4.75
2     2     7    12     2     2    12  5.75
3     3     8    13     3     3    13  6.75
4     4     9    14     4     4    14  7.75
5     5    10    15     5     5    15  8.75

另一个选择是:

cols <- c('a', 'b', 'c', 'e')
df %>%
  rowwise() %>% 
  mutate(min = min(c_across(cols), na.rm = TRUE),
         max = max(c_across(cols), na.rm = TRUE), 
         avg = mean(c_across(cols), na.rm = TRUE)
  )

# A tibble: 5 x 8
# Rowwise: 
      a     b     c d         e   min   max   avg
  <int> <int> <int> <chr> <int> <int> <int> <dbl>
1     1     6    11 a         1     1    11  4.75
2     2     7    12 b         2     2    12  5.75
3     3     8    13 c         3     3    13  6.75
4     4     9    14 d         4     4    14  7.75
5     5    10    15 e         5     5    15  8.75

即使@Sinh提出的按组分组的方法,在这些情况下也不能正常工作。


非常感谢AnilGoyal。我非常感激您的努力,我理解您的所有想法并同意您的观点。但是,我不满意重复三次c_across(a:e & where(is.numeric)), na.rm = TRUE)。我想象中应该可以避免这种重复,并使用一次across - TarJae
TarJae,我担心你试图用dplyr做的事情可能是不可能的,mutate(across..的工作方式略有不同。它会改变所有现有列。即使您使用cur_data,它也将包括上面显示的新添加的列。此外,您仍然必须重复cur_data..bla..blan次,而不是像mutate(across那样。然而,这可能可以通过purrr中的许多方法之一实现-如答案所示。 - AnilGoyal

2

这里有一种方法,可以在使用mutate函数时保留data.frame属性,如果我们想将特定列设置为行名称属性(column_to_rownames),然后在转换后返回该属性。

library(dplyr)
library(tibble)
library(purrr)
df %>% 
   column_to_rownames('d') %>%
   mutate(max = reduce(., pmax), min = reduce(., pmin), 
         avg = rowMeans(.)) %>% 
   rownames_to_column('d')
#  d a  b  c e max min  avg
#1 a 1  6 11 1  11   1 4.75
#2 b 2  7 12 2  12   2 5.75
#3 c 3  8 13 3  13   3 6.75
#4 d 4  9 14 4  14   4 7.75
#5 e 5 10 15 5  15   5 8.75

亲爱的阿伦,可以使用 invoke_mapexec 将所有三个函数应用于每一行吗? - Anoushiravan R
是的,谢谢。我也将 mean 包含在函数列表中,但由于它是逐列操作,所以无法得到期望的结果。然而,由于其功能,pminpmax 使得按行获取最大值和最小值成为可能。我只是好奇。 - Anoushiravan R
1
@AnoushiravanR 函数 pmin/pmaxmean 的行为不同。pmin/pmax 并行地作用于行,而 mean 期望一个向量并且它没有矢量化。你可能期望 rowMeans 起作用,但是该函数的参数只有一个,即 x,它可以是矩阵或数据框/表格。而在 pmax/pmin 中,它是 ...,即它可以变化。因此,invoke_map(list(pmax, pmin), list(df %>% select(-d))) 可以工作,因为参数是数据框列。 - akrun
1
非常感谢。参数匹配是我需要在这种时刻考虑的重要因素。很高兴最终熟悉了并行极值。 - Anoushiravan R

1
我认为创建一个按行排列的tibble列可以创建一个易读、优雅的解决方案。由于mean函数不接受带有省略号(...)的值,所以需要额外的工作。
library(dplyr)

df |>
  rowwise() |>
  mutate(x = pick(where(is.numeric)),
         avg = mean(unlist(x)),
         min = min(x),
         max = max(x)) |>
  select(-x) |>
  ungroup()

输出

      a     b     c d         e   avg   min   max
  <int> <int> <int> <chr> <int> <dbl> <int> <int>
1     1     6    11 a         1  4.75     1    11
2     2     7    12 b         2  5.75     2    12
3     3     8    13 c         3  6.75     3    13
4     4     9    14 d         4  7.75     4    14
5     5    10    15 e         5  8.75     5    15

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