前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >「R」dplyr 列式计算

「R」dplyr 列式计算

作者头像
王诗翔呀
发布于 2022-01-21 00:46:25
发布于 2022-01-21 00:46:25
2.6K00
代码可运行
举报
文章被收录于专栏:优雅R优雅R
运行总次数:0
代码可运行

❝在近期使用 「dplyr」 进行多列选择性操作,如 mutate_at() 时,发现文档提示一系列的 「dplyr」 函数变体已经过期,看来后续要退休了,使用 across() 是它们的统一替代品,所以最近抽时间针对性的学习和翻译下,希望给大家带来一些帮助。 本文是第一篇,介绍的是「列式计算」,后续还会有一篇介绍按行处理数据。 原文来自 [dplyr 文档](Column-wise operations • dplyr (tidyverse.org "dplyr 文档")) - 2021-01❞

同时对数据框的多列执行相同的函数操作经常有用,但是通过拷贝和粘贴的方式进行的话既枯燥就容易产生错误。

例如:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
df %>% 
  group_by(g1, g2) %>% 
  summarise(a = mean(a), b = mean(b), c = mean(c), d = mean(d))

(如果你想要计算每一行 a, b, c, d 的均值,请看行式计算一文)

本文将向你介绍 across() 函数,它可以帮助你以更加简洁的方式重写上述代码:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
df %>% 
  group_by(g1, g2) %>% 
  summarise(across(a:d, mean))

我们将从讨论 across() 的基本用法开始,特别是将其应用于 summarise() 中和展示如何联合多个函数使用它。然后我们将展示一些其他动词的使用。最后我们将简要介绍一下历史,说明为什么我们更喜欢 across() 而不是后一种方法(即 _if(), _at(), _all() 变体函数)以及如何将你的旧代码转换为新的语法实现。

载入包:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
library(dplyr, warn.conflicts = FALSE)

基本用法

across() 有两个主要的参数:

  • 第一个参数是 .cols ,它用来选择你想要操作的列。它使用 tidy 选择语法(像 select() 那样),因此你可以按照位置、名字和类型来选择变量。
  • 第二个参数是 .fns,它是应用到数据列上的一个函数或者是一个函数列表,它也可以是像 ~.x/2 这样 「purrr」 风格的公式语法。

下面是联合 across() 和它最喜欢的动词函数 summarise()的一些例子。但你也可以联合 across() 和任意其他的 「dplyr」 动词函数,我们后面会提及。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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))))
#> `summarise()` ungrouping output (override with `.groups` argument)
#> # 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
#> # … with 5 more rows

starwars %>% 
  group_by(homeworld) %>% 
  filter(n() > 1) %>% 
  summarise(across(where(is.numeric), ~ mean(.x, na.rm = TRUE)))
#> `summarise()` ungrouping output (override with `.groups` argument)
#> # 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
#> # … with 6 more rows

因为 across() 通过和 summarise() 以及 mutate() 结合使用,所以它不会选择分组变量以避免意外地修改它们。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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))
#> `summarise()` ungrouping output (override with `.groups` argument)
#> # A tibble: 2 x 3
#>       g     x     y
#>   <dbl> <dbl> <dbl>
#> 1     1     0    -5
#> 2     2     3    -9

多个函数

你可以通过对第二个参数传入一个函数(包括 lambda 函数)的命名列表来对每个变量同时执行多个函数操作。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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 参数使用 glue[1] 规范来控制新建列名的名字:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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

如果你更喜欢将所有具有相同函数的摘要放到在一起(就是下面的 min 结果都在左侧,而 max 都在右侧),你必须自己进行扩展调用:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
starwars %>% summarise(
  across(where(is.numeric), ~min(.x, na.rm = TRUE), .names = "min_{.col}"),
  across(where(is.numeric), ~max(.x, na.rm = TRUE), .names = "max_{.col}")
)
#> # A tibble: 1 x 9
#>   min_height min_mass min_birth_year max_height max_mass max_birth_year
#>        <int>    <dbl>          <dbl>      <int>    <dbl>          <dbl>
#> 1         66       15              8        264     1358            896
#> # … with 3 more variables: max_min_height <int>, max_min_mass <dbl>,
#> #   max_min_birth_year <dbl>

(可能有一天这种操作会通过 across() 的一个参数进行支持,但目前我们还没找到解决方案)

当前列

如果需要,你可以通过调用 cur_column() 来获取当前列的名字。如果你想执行一些语境依赖的相关转换,这可能会很有用:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
df <- tibble(x = 1:3, y = 3:5, z = 5:7)
mult <- list(x = 1, y = 10, z = 100)

# df 每列乘以 mult 对应列的值
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

陷阱

注意组合 is.numeric() 和数值汇总的使用:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
df <- data.frame(x = c(1, 2, 3), y = c(1, 4, 9))

df %>% 
  summarise(n = n(), across(where(is.numeric), sd))
#>    n x        y
#> 1 NA 1 4.041452

这里 n 变成 NA 是因为 n 是数值的,所以 across() 会计算它的标准差,3(常量) 的标准差是 NA,你可以最后计算 n() 来解决这个问题:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
df %>% 
  summarise(across(where(is.numeric), sd), n = n())
#>   x        y n
#> 1 1 4.041452 3

另外,你可以显式的将 n 排除掉来解决该问题:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
df %>% 
  summarise(n = n(), across(where(is.numeric) & !n, sd))
#>   n x        y
#> 1 3 1 4.041452

其他动词

到目前为止,我们聚焦于 across()summarise() 的组合使用,但它也可以和其他 「dplyr」 动词函数一起工作:

•重新缩放所有数值变量到范围 0-1:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
rescale01 <- function(x) {
  rng <- range(x, na.rm = TRUE)
  (x - rng[1]) / (rng[2] - rng[1])
}
df <- tibble(x = 1:4, y = rnorm(4))
df %>% mutate(across(where(is.numeric), rescale01))
#> # A tibble: 4 x 2
#>       x     y
#>   <dbl> <dbl>
#> 1 0     0.385
#> 2 0.333 1    
#> 3 0.667 0    
#> 4 1     0.903

查找所有没有变量缺失值的行:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
starwars %>% filter(across(everything(), ~ !is.na(.x)))
#> # A tibble: 29 x 14
#>   name  height  mass hair_color skin_color eye_color birth_year sex   gender
#>   <chr>  <int> <dbl> <chr>      <chr>      <chr>          <dbl> <chr> <chr> 
#> 1 Luke…    172    77 blond      fair       blue            19   male  mascu…
#> 2 Dart…    202   136 none       white      yellow          41.9 male  mascu…
#> 3 Leia…    150    49 brown      light      brown           19   fema… femin…
#> 4 Owen…    178   120 brown, gr… light      blue            52   male  mascu…
#> # … with 25 more rows, and 5 more variables: homeworld <chr>, species <chr>,
#> #   films <list>, vehicles <list>, starships <list>

对一些像 group_by()count()distinct() 这样的动词,你可以省略汇总函数:

寻找所有的唯一值:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
starwars %>% distinct(across(contains("color")))
#> # A tibble: 67 x 3
#>   hair_color skin_color  eye_color
#>   <chr>      <chr>       <chr>    
#> 1 blond      fair        blue     
#> 2 <NA>       gold        yellow   
#> 3 <NA>       white, blue red      
#> 4 none       white       yellow   
#> # … with 63 more rows

计算给定模式下所有变量的组合数目:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
starwars %>% count(across(contains("color")), sort = TRUE)
#> # A tibble: 67 x 4
#>   hair_color skin_color eye_color     n
#>   <chr>      <chr>      <chr>     <int>
#> 1 brown      light      brown         6
#> 2 brown      fair       blue          4
#> 3 none       grey       black         4
#> 4 black      dark       brown         3
#> # … with 63 more rows

across() 不能与 select() 或者 rename() 一起工作,因为后面两个函数已经支持 tidy 选择语法。如果你想要通过函数转换列名,可以使用 rename_with()

_if, _at, _all

「dplyr」 以前的版本允许以不同的方式将函数应用到多个列:使用带有_if_at_all后缀的函数。这些功能解决了迫切的需求而被许多人使用,但现在被取代了。这意味着它们会一直存在,但不会获得任何新功能,只会修复关键的bug。

为什么我们喜欢 across()

为什么我们决定从上面的函数迁移到 across()?理由如下:

across() 使它能够表达以前不可能表达的有用的汇总:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
df %>%
  group_by(g1, g2) %>% 
  summarise(
    across(where(is.numeric), mean), 
    across(where(is.factor), nlevels),
    n = n(), 
  )

across() 减少 「dplyr」 需要提供的函数数量。这使 「dplyr」 更容易使用(因为需要记住的函数更少),也使我们更容易实现新的动词(因为我们只需要实现一个函数,而不是四个)。

across() 统一了 _if_at 的语义让我们可以随心按照位置、名字和类型选择变量,甚至是随心所欲地组合它们,这在以前是不可能的。例如,你现在可以转换以 x 开头的数值列:across(where(is.numeric) & starts_with("x")).

across() 不需要使用 vars()_at() 函数是 「dplyr」 中唯一你需要手动引用变量名的地方,这让它们比较奇怪且难以记忆。

为什么过了这么久才发现 across()

令人失望的是,我们没有早点发现 across(),而是经历了几个错误的尝试(首先没有意识到这是一个常见的问题,然后是使用_each()函数,最后是使用_if()/_at()/_all()函数)。但是 across() 的开发工作离不开以下三个最新发现:

  • 你可以有一个数据框的列,它本身就是一个数据框。这是由 base R 提供的,但它并没有很好的文档,我们花了一段时间才发现它是有用的,而不仅仅是理论上的好奇。
  • 我们可以使用数据框让汇总函数返回多列。
  • 我们可以使用没有外部名称作为将数据框列解包为单独列的约定。

你如何转移已经存在的代码?

幸运的是,将已有的代码转换为使用 across() 实现通常是非常直观的:

  • 去掉函数 _if(), _at() and _all() 后缀
  • 调用 across(),第一个参数如下: 后面如果还有参数,保持原样即可。
    1. 对于 _if(),原来的第二个参数包裹进 where()
    2. 对于 _at(),原来的参数,如果有 vars() 包裹则移除
    3. 对于 _all(),使用everything()

例如:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
df %>% mutate_if(is.numeric, mean, na.rm = TRUE)
# ->
df %>% mutate(across(where(is.numeric), mean, na.rm = TRUE))

df %>% mutate_at(vars(c(x, starts_with("y"))), mean)
# ->
df %>% mutate(across(c(x, starts_with("y")), mean, na.rm = TRUE))

df %>% mutate_all(mean)
# ->
df %>% mutate(across(everything(), mean))

这个规则有些意外情况:

rename_*()select_*() 遵循不同的模式。它们已经有选择语义,所以通常以与 across() 不同的方式使用,我们需要使用新的 rename_with() 代替。

先前 filter()all_vars()any_vars() 帮助函数配对使用。现在,across() 等价于 all_vars(),然而没有 any_vars() 的直接替代品,不过你可以自己创建一个:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
df <- tibble(x = c("a", "b"), y = c(1, 1), z = c(-1, 1))

# 找到满足每一个数值列都大于 0 的所有的行
df %>% filter(across(where(is.numeric), ~ .x > 0))
#> # A tibble: 1 x 3
#>   x         y     z
#>   <chr> <dbl> <dbl>
#> 1 b         1     1

# 找到满足任何一个数值列都大于 0 的所有的行
rowAny <- function(x) rowSums(x) > 0
df %>% filter(rowAny(across(where(is.numeric), ~ .x > 0)))
#> # A tibble: 2 x 3
#>   x         y     z
#>   <chr> <dbl> <dbl>
#> 1 a         1    -1
#> 2 b         1     1

当在 mutate() 中使用时,所有 across() 执行的转换都一次性完成。这与 mutate_if()mutate_at()mutate_all() 不同,后者一次只完成一个转换。我们希望大家不会对这种新行为感到惊讶:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
df <- tibble(x = 2, y = 4, z = 8)
df %>% mutate_all(~ .x / y)
#> # A tibble: 1 x 3
#>       x     y     z
#>   <dbl> <dbl> <dbl>
#> 1   0.5     1     8

df %>% mutate(across(everything(), ~ .x / y))
#> # A tibble: 1 x 3
#>       x     y     z
#>   <dbl> <dbl> <dbl>
#> 1   0.5     1     2

小结

「dplyr」 的开发者们通过 across() 简化了 「dplyr」 对于一些数据复杂操作的处理逻辑,提高了整体的学习和使用效率,让我们使用者更关注于逻辑而非实现上。点个赞!

Reference

[1]

glue: https://glue.tidyverse.org/

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2021-02-18,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 优雅R 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
Spring Boot 中的静态资源到底要放在哪里?
当我们使用 SpringMVC 框架时,静态资源会被拦截,需要添加额外配置,之前老有小伙伴在微信上问松哥Spring Boot 中的静态资源加载问题:“松哥,我的HTML页面好像没有样式?”,今天我就通过一篇文章,来和大伙仔细聊一聊这个问题。
江南一点雨
2019/08/30
2.1K0
Spring Boot 中的静态资源到底要放在哪里?
Spring MVC和springboot静态资源处理详细总结
优雅REST风格的资源URL不希望带 .html 或 .do 等后缀.由于早期的Spring MVC不能很好地处理静态资源,所以在web.xml中配置DispatcherServlet的请求映射,往往使用 *.do 、 * .xhtml等方式。这就决定了请求URL必须是一个带后缀的URL,而无法采用真正的REST风格的URL
大忽悠爱学习
2021/11/15
2.4K0
8.8 Spring Boot静态资源处理小结
当使用Spring Boot来开发一个完整的系统时,我们往往需要用到前端页面,这就不可或缺地需要访问到静态资源,比如图片、css、js等文件。
一个会写诗的程序员
2018/08/20
1K0
Spring Boot 静态资源处理
以前做过web开发的同学应该知道,我们以前创建的web工程下面会有一个webapp的目录,我们只要把静态资源放在该目录下就可以直接访问。
IT大咖说
2020/03/25
7240
Spring Boot实战:静态资源处理
  前两章我们分享了Spring boot对Restful 的支持,不过Restful的接口通常仅仅返回数据。而做web开发的时候,我们往往会有很多静态资源,如html、图片、css等。那如何向前端返回静态资源呢?以前做过web开发的同学应该知道,我们以前创建的web工程下面会有一个webapp的目录,我们只要把静态资源放在该目录下就可以直接访问。但是,基于Spring boot的工程并没有这个目录,那我们应该怎么处理? 一、最笨的方式   我们首先来分享一种最笨的办法,就是将静态资源通过流直接返回给前端,
用户2140019
2018/05/18
1K0
深入Spring Boot (七):静态资源使用详解
Web应用经常需要使用大量的静态资源,如图片、css、js等,Spring Boot对这些静态资源的使用提供了默认配置。本篇将详细介绍如何使用默认配置和如何修改这些默认配置,主要包含以下5部分内容: 1.静态资源存储路径; 2.静态资源访问路径; 3.最佳实践; 4.应用欢迎页; 5.应用图标。 1.静态资源存储路径 默认配置下,使用Spring Boot可以将静态资源存储在/static或/public或/resources或/META-INF/resources目录下,这四个目录的根目录都是classp
JavaQ
2018/04/08
2.6K0
深入Spring Boot (七):静态资源使用详解
Spring Boot 静态资源处理
Spring Boot 默认为我们提供了静态资源处理,使用 WebMvcAutoConfiguration 中的配置各种属性。
全栈程序员站长
2022/08/24
8010
Spring Boot 静态资源处理
Springboot系列(三)web静态资源配置
引言: SpringBoot web项目开发中往往会涉及到一些静态资源的使用,比如说图片,css样式,js等等,今天我们来讲讲这些常见的静态资源应该放在哪个位置,怎么放在自己想放的位置。
全栈学习笔记
2022/03/31
6810
Springboot系列(三)web静态资源配置
springBoot静态资源配置及其原理
给出了很多的默认的Spring资源 : Beans 、 静态资源 、 自动注册等等
用户11097514
2024/05/30
1750
springBoot静态资源配置及其原理
【WEB 系列】WebFlux 静态资源配置与访问
上一篇博文介绍 SpringMVC 的静态资源访问,那么在 WebFlux 中,静态资源的访问姿势是否一致呢
一灰灰blog
2020/06/16
2.1K0
【WEB系列】静态资源配置与读取
SpringWeb项目除了我们常见的返回json串之外,还可以直接返回静态资源(当然在现如今前后端分离比较普遍的情况下,不太常见了),一些简单的web项目中,前后端可能就一个人包圆了,前端页面,js/css文件也都直接放在Spring项目中,那么你知道这些静态资源文件放哪里么
一灰灰blog
2020/06/13
1.3K0
Spring Boot 设置静态资源访问
问题描述 当使用spring Boot来架设服务系统时,有时候也需要用到前端页面,当然就不可或缺地需要访问其他一些静态资源,比如图片、css、js等文件。那么如何设置Spring Boot网站可以访问得到这些静态资源,以及静态资源如何布局? 解决方案 这里引用stackoverflow网站的问题截图:[http://stackoverflow.com/questions/27381781/java-spring-boot-how-to-map-my-my-app-root-to-index-html]
hbbliyong
2018/03/06
9410
Spring Boot 设置静态资源访问
【SpringBoot WEB系列】WebFlux静态资源配置与访问
上一篇博文介绍SpringMVC的静态资源访问,那么在WebFlux中,静态资源的访问姿势是否一致呢
一灰灰blog
2020/06/19
1.4K0
【SpringBoot WEB系列】WebFlux静态资源配置与访问
从SpringBoot源码看资源映射原理
很多的小伙伴刚刚接触SpringBoot的时候,可能会遇到加载不到静态资源的情况。
HUC思梦
2022/05/11
5060
从SpringBoot源码看资源映射原理
写在前面:2020年面试必备的Java后端进阶面试题总结了一份复习指南在Github上,内容详细,图文并茂,有需要学习的朋友可以Star一下! GitHub地址:https://github.com/abel-max/Java-Study-Note/tree/master
用户5546570
2020/09/28
7870
(四) SpringBoot起飞之路-Web静态资源处理
这是第四篇,关于如何处理第三方静态资源以及自己的静态资源的小结,其实如果仅仅想要知道将静态资源放在哪里,或者说怎么直接用,其实几句话就说完了,但是我在文中是循着源码或者官网/Github,诱导到这几个点,虽然算不得什么源码分析,不过起码静态资源处理的有关问题,起码不算空口而说,算是简单的引导吧
BWH_Steven
2020/05/20
1K1
Springboot多种方法处理静态资源:设置并访问静态资源目录
静态资源,一般是网页端的:HTML文件、JavaScript文件和图片。尤其是设置图片的静态资源,尤其重要:
Mintimate
2021/10/24
6.5K2
Springboot多种方法处理静态资源:设置并访问静态资源目录
Spring Boot 2.X(四):Spring Boot 自定义 Web MVC 配置
Spring Boot 不仅提供了相当简单使用的自动配置功能,而且开放了非常自由灵活的配置类。Spring MVC 为我们提供了 WebMvcConfigurationSupport 类和一个注解 @EnableWebMvc 以帮助我们减少配置 Bean 的声明。本文简单说明如何自定义 Web MVC 配置。 首先需要使用 @Configuration 将 WebMvcConfig 类标注为 Spring 配置类,示例代码如下:
朝雾轻寒
2019/10/18
1.4K0
SpringBoot2---静态资源映射规则
当前项目 + static-path-pattern + 静态资源名 = 静态资源文件夹下找
大忽悠爱学习
2021/11/15
1.2K0
Spring Boot 静态资源处理
摘要:spring Boot 默认的处理方式就已经足够了,默认情况下Spring Boot 使用WebMvcAutoConfiguration中配置的各种属性。 但是如果你想要自己配置一些项目的设置,你可以在@Configuration注解的配置类上增加@EnableWebMvc或者继承WebMvcConfigurationSupport和WebMvcConfigurationAdapter 正文: 首先解析@EnableWebMvc 、WebMvcConfigurationSupport和WebMvcC
itliusir
2018/05/21
1.6K0
推荐阅读
相关推荐
Spring Boot 中的静态资源到底要放在哪里?
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档