绑定完请刷新页面
取消
刷新

分享好友

×
取消 复制
用tidyfst的vars族函数取缔dplyr的across
2020-07-13 13:38:49

作者:黄天元,复旦大学博士在读,热爱数据科学与开源工具(R),致力于利用数据科学迅速积累行业经验优势和科学知识发现,涉猎内容包括但不限于信息计量、机器学习、数据可视化、应用统计建模、知识图谱等,著有《R语言数据高效处理指南》(《R语言数据高效处理指南》(黄天元)【摘要 书评 试读】- 京东图书《R语言数据高效处理指南》(黄天元)【简介_书评_在线阅读】 - 当当图书)。知乎专栏:R语言数据挖掘邮箱:huang.tian-yuan@qq.com.欢迎合作交流。

dplyr的1.0.0版本中推出了across函数,以对多列进行同时的汇总或更新。tidyfst包在这之前已经构造了vars族的两个函数对其进行实现,本来应该去学习dplyr去构造一组across的函数,但是发现构造出来在语法结构上反而更复杂,不利于用户使用,因此就不再把问题复杂化。下面,我们给出例子进行对比,并用代码进行实现。这里的例子主要参考了:

Across (dplyr 1.0.0): applying dplyr functions simultaneously across multiple columnswww.rebeccabarter.com

首先先加载数据和环境:

remotes::install_github("allisonhorst/palmerpenguins")
library(palmerpenguins)
library(dplyr)
library(tidyfst)

我们要分析的是一份企鹅的数据:

penguins
#> # A tibble: 344 x 7
#>    species island bill_length_mm bill_depth_mm flipper_length_~ body_mass_g
#>    <fct>   <fct>           <dbl>         <dbl>            <int>       <int>
#>  1 Adelie  Torge~           39.1          18.7              181        3750
#>  2 Adelie  Torge~           39.5          17.4              186        3800
#>  3 Adelie  Torge~           40.3          18                195        3250
#>  4 Adelie  Torge~           NA            NA                 NA          NA
#>  5 Adelie  Torge~           36.7          19.3              193        3450
#>  6 Adelie  Torge~           39.3          20.6              190        3650
#>  7 Adelie  Torge~           38.9          17.8              181        3625
#>  8 Adelie  Torge~           39.2          19.6              195        4675
#>  9 Adelie  Torge~           34.1          18.1              193        3475
#> 10 Adelie  Torge~           42            20.2              190        4250
#> # ... with 334 more rows, and 1 more variable: sex <fct>

我们要完成一个很简单的任务,看每一列中有多少个独立的值(数值、因子都一样)。比如我们对species列来做这个操作:

# dplyr
penguins %>%
  summarise(distinct_species = n_distinct(species))
#> # A tibble: 1 x 1
#>   distinct_species
#>              <int>
#> 1                3

# tidyfst
penguins %>%
  summarise_dt(distinct_species = uniqueN(species))
#>    distinct_species
#>               <int>
#> 1:                3

只做一个的时候,两者差异不大,只是dplyr用n_distinct函数,而tidyfst用了data.table内置的uniqueN函数。下面,我们对species, island, sex三列同时来做这个操作,进行一下比较。

# dplyr
penguins %>%
  summarise(across(c(species, island, sex), 
                   n_distinct))
#> # A tibble: 1 x 3
#>   species island   sex
#>     <int>  <int> <int>
#> 1       3      3     3

# tidyfst
penguins %>% 
  summarise_vars("species|island|sex",uniqueN)
#>    species island   sex
#>      <int>  <int> <int>
#> 1:       3      3     3

我们可以看到,dplyr又要嵌套across又要用c来做向量,其实是非常不便于操作的。而tidyfst利用正则表达式选择列名称,不仅便于减少代码量,在实际的使用中也方便代码自动生成(构造字符)。这只是其一,那么我们试试,求除了这三列以外别的列有多少个特殊值。

# dplyr
penguins %>%
  summarise(across(!c(species, island, sex), 
                   n_distinct))
#> # A tibble: 1 x 4
#>   bill_length_mm bill_depth_mm flipper_length_mm body_mass_g
#>            <int>         <int>             <int>       <int>
#> 1            165            81                56          95

# tidyfst
penguins %>% 
  summarise_vars(!"species|island|sex",uniqueN)
#>    bill_length_mm bill_depth_mm flipper_length_mm body_mass_g
#>             <int>         <int>             <int>       <int>
#> 1:            165            81                56          95

友情提示,tidyfst中惊叹号(!)和减号(-)都可以用来表达除了这些以外的列,表排斥。

下面,我们来对以bill开头的列进行这个操作:

# dplyr
penguins %>%
  summarise(across(starts_with("bill"), n_distinct))
#> # A tibble: 1 x 2
#>   bill_length_mm bill_depth_mm
#>            <int>         <int>
#> 1            165            81

# tidyfst
penguins %>% 
  summarise_vars("^bill",uniqueN)
#>    bill_length_mm bill_depth_mm
#>             <int>         <int>
#> 1:            165            81

dplyr又是across又是starts_with,这个代码量就太大了。而tidyfst利用正则表达式,只需要用“^”表明是以什么开头即可(熟悉正则的小伙伴应该还知道,“$”就表示以此为结尾)。如果是包含某个字符串就匹配,dplyr需要用contains,而tidyfst直接什么都不需要,利用字符串就能匹配。例子如下:

# dplyr
penguins %>%
  summarise(across(contains("length"), n_distinct))
#> # A tibble: 1 x 2
#>   bill_length_mm flipper_length_mm
#>            <int>             <int>
#> 1            165                56

# tidyfst
penguins %>% 
  summarise_vars("length",uniqueN)
#>    bill_length_mm flipper_length_mm
#>             <int>             <int>
#> 1:            165                56

如果要对所有列进行计算,dplyr还要everything一下,而tidyfst直接缺省列信息,但是显式传递参数.func即可,例子如下:

# dplyr
penguins %>%
  summarise(across(everything(), n_distinct))
#> # A tibble: 1 x 7
#>   species island bill_length_mm bill_depth_mm flipper_length_~ body_mass_g   sex
#>     <int>  <int>          <int>         <int>            <int>       <int> <int>
#> 1       3      3            165            81               56          95     3

# tidyfst
penguins %>% 
  summarise_vars(.func = uniqueN)
#>    species island bill_length_mm bill_depth_mm flipper_length_mm body_mass_g
#>      <int>  <int>          <int>         <int>             <int>       <int>
#> 1:       3      3            165            81                56          95
#>      sex
#>    <int>
#> 1:     3

后,实名反对引入的所谓简洁的变成规范,即这种代码:

# dplyr
penguins %>%
  group_by(species, island) %>%
  mutate(across(where(is.numeric), 
                ~if_else(condition = is.na(.), 
                         true = mean(., na.rm = T), 
                         false = as.numeric(.)))) %>%
  ungroup()

上面的代码是要先按照物种和岛屿分组,然后对数值型的变量,进行一个函数操作,这个函数本质是:

replace0 <- function(x) {
  if_else(condition = is.na(x), 
          true = mean(x,na.rm = T), 
          false = as.numeric(x))
}

上面的函数,是编程的规范所在。当然为了临时的自由而选择之前包含“~”和“.”的写法,只能作为平时尝试的“便捷”,但是不建议作为编程规范作为推广。里面一定有雷,不便于维护。此外,关于在select函数中使用across的情况,之前已经在帖子(HopeR:为什么你要使用tidyfst的select_dt)中进行过说明,这里不再赘述。感兴趣的小伙伴,可以去给dplyr包pull一个request,让dplyr的设计更加人性化和规范化一些。tidyfst是dplyr与data.table两个老师教导下的学生,一定再接再厉去学习的特性,为用户提供更便捷的服务。

分享好友

分享这个小栈给你的朋友们,一起进步吧。

R语言
创建时间:2020-06-15 11:46:51
R是用于统计分析、绘图的语言和操作环境。R是属于GNU系统的一个自由、免费、源代码开放的软件,它是一个用于统计计算和统计制图的工具。
展开
订阅须知

• 所有用户可根据关注领域订阅专区或所有专区

• 付费订阅:虚拟交易,一经交易不退款;若特殊情况,可3日内客服咨询

• 专区发布评论属默认订阅所评论专区(除付费小栈外)

技术专家

查看更多
  • 小雨滴
    专家
戳我,来吐槽~