2.6 列操作

在多列上执行相同的函数是常有的操作,但是通过复制和粘贴代码,麻烦并且容易错,如下所示:

df %>% 
  group_by(g1, g2) %>% 
  summarise(a = mean(a), b = mean(b), c = mean(c), d = mean(d))

通过across()函数可以更简洁地重写上面代码:

df %>% 
  group_by(g1, g2) %>% 
  summarise(across(a:d, mean))

假设我们要将表格中多列向上取整,代码如下:

dt <- tibble(a= rnorm(100,mean = 1),b=rnorm(100,mean = 1),d=rnorm(100,mean = 1))
dt %>% 
  mutate(across(a:d,ceiling))
#> # A tibble: 100 x 3
#>       a     b     d
#>   <dbl> <dbl> <dbl>
#> 1     2     2     1
#> 2     1     1     1
#> 3     1     3     1
#> 4     1     2     1
#> 5     1     2     1
#> 6     1     1     3
#> # ... with 94 more rows

函数across()通过与summarise()mutate()结合,很容易将某函数运用到多列上。函数across()取代了summarise_all(),summarise_at(),summarise_if()函数。

starwars %>%
  summarise_at(c("height", "mass"), mean, na.rm = TRUE)
#> # A tibble: 1 x 2
#>   height  mass
#>    <dbl> <dbl>
#> 1   174.  97.3

starwars %>% summarise(across(c("height", "mass"), ~ mean(.x, na.rm = TRUE))) # purrr风格函数
#> # A tibble: 1 x 2
#>   height  mass
#>    <dbl> <dbl>
#> 1   174.  97.3

2.6.1 基础用法

across() 有两个主要参数:

  • 第一个参数,.cols 选择要操作的列。它使用tidyr的方式选择(例如select()),因此您可以按位置,名称和类型选择变量。

  • 第二个参数,.fns是要应用于每一列的一个函数或函数列表。也可以是 purrr 样式的公式(或公式列表),例如~ .x / 2。

starwars %>% 
  summarise(across(where(is.character), ~ length(unique(.x))))
#> # A tibble: 1 x 8
#>    name hair_color skin_color eye_color   sex gender homeworld species
#>   <int>      <int>      <int>     <int> <int>  <int>     <int>   <int>
#> 1    87         13         31        15     5      3        49      38

starwars %>% 
  group_by(species) %>% 
  filter(n() > 1) %>% 
  summarise(across(c(sex, gender, homeworld), ~ length(unique(.x))))
#> # A tibble: 9 x 4
#>   species    sex gender homeworld
#>   <chr>    <int>  <int>     <int>
#> 1 Droid        1      2         3
#> 2 Gungan       1      1         1
#> 3 Human        2      2        16
#> 4 Kaminoan     2      2         1
#> 5 Mirialan     1      1         1
#> 6 Twi'lek      2      2         1
#> # ... with 3 more rows

starwars %>% 
  group_by(homeworld) %>% 
  filter(n() > 1) %>% 
  summarise(across(where(is.numeric), ~ mean(.x, na.rm = TRUE)))
#> # A tibble: 10 x 4
#>   homeworld height  mass birth_year
#>   <chr>      <dbl> <dbl>      <dbl>
#> 1 Alderaan    176.  64         43  
#> 2 Corellia    175   78.5       25  
#> 3 Coruscant   174.  50         91  
#> 4 Kamino      208.  83.1       31.5
#> 5 Kashyyyk    231  124        200  
#> 6 Mirial      168   53.1       49  
#> # ... with 4 more rows

~ .x / 2是purrr包提供的函数式编程风格,等效于 function(x) (x/2)

across() 不会选择分组变量,如下所示:group_by()中的变量g不会被选中执行sum()函数。

df <- data.frame(g = c(1, 1, 2), x = c(-1, 1, 3), y = c(-1, -4, -9))
df %>% 
  group_by(g) %>% 
  summarise(across(where(is.numeric), sum))
#> # A tibble: 2 x 3
#>       g     x     y
#>   <dbl> <dbl> <dbl>
#> 1     1     0    -5
#> 2     2     3    -9

2.6.2 多种函数功能

通过在第二个参数提供函数或 lambda 函数的命名列表,可是使用多个函数转换每个变量:

min_max <- list(
  min = ~min(.x, na.rm = TRUE), 
  max = ~max(.x, na.rm = TRUE)
)
starwars %>% summarise(across(where(is.numeric), min_max))
#> # A tibble: 1 x 6
#>   height_min height_max mass_min mass_max birth_year_min birth_year_max
#>        <int>      <int>    <dbl>    <dbl>          <dbl>          <dbl>
#> 1         66        264       15     1358              8            896

通过.names参数控制名称:

Note: 该参数的机制没有特别理解,需多练习体会,主要是运用到匿名函数时

以下是官方案例,但是报错(目前已修复):

starwars %>% summarise(across(where(is.numeric), min_max, .names = "{.fn}.{.col}"))
#> # A tibble: 1 x 6
#>   min.height max.height min.mass max.mass min.birth_year max.birth_year
#>        <int>      <int>    <dbl>    <dbl>          <dbl>          <dbl>
#> 1         66        264       15     1358              8            896

修改后正常运行:

starwars %>% summarise(across(where(is.numeric), min_max, .names = "{fn}.{col}"))
#> # A tibble: 1 x 6
#>   min.height max.height min.mass max.mass min.birth_year max.birth_year
#>        <int>      <int>    <dbl>    <dbl>          <dbl>          <dbl>
#> 1         66        264       15     1358              8            896

区别主要是.names参数的使用方式问题,.加不加的问题。


starwars %>% summarise(across(where(is.numeric), min_max, .names = "{fn}——{col}"))
#> # A tibble: 1 x 6
#>   `min——height` `max——height` `min——mass` `max——mass` `min——birth_year`
#>           <int>         <int>       <dbl>       <dbl>             <dbl>
#> 1            66           264          15        1358                 8
#> # ... with 1 more variable: max——birth_year <dbl>

2.6.3 当前列

如果需要,可以通过调用访问内部的“当前”列的名称cur_column(),仅在across()使用。和 Excel 中power pivot的“上下文”概念类似。

该函数不是特别容易理解,需要多尝试使用加深认识。

df <- tibble(x = 1:3, y = 3:5, z = 5:7)
mult <- list(x = 1, y = 10, z = 100)

df %>% mutate(across(all_of(names(mult)), ~ .x * mult[[cur_column()]]))
#> # A tibble: 3 x 3
#>       x     y     z
#>   <dbl> <dbl> <dbl>
#> 1     1    30   500
#> 2     2    40   600
#> 3     3    50   700

代码解释:代码实现的是数据框 df 中列和 mult 中同名元素相乘得到新列。mult[[cur_column()]]依次返回mult[[“x”]],mult[[“y”]],mult[[“z”]]。

以上部分是关于列操作的内容,详情查看vignette("colwise")