作者:黄天元,复旦大学博士在读,热爱数据科学与开源工具(R),致力于利用数据科学迅速积累行业经验优势和科学知识发现,涉猎内容包括但不限于信息计量、机器学习、数据可视化、应用统计建模、知识图谱等,著有《R语言数据高效处理指南》。知乎专栏:R语言数据挖掘。
分组汇总是常用功能之一,dplyr中有summarise函数,经常配合group_by来使用。既然如此,为何不直接送给summarise一个by参数呢?
既然tidyfst是自己的工具,就自己做主,现在我们宣布,summarise_dt中有by参数,如果不设置就跟dplyr的summarise一样用,但是设置了就可以直接在内部进行汇总计算。举个例子:
library(tidyfst)
#>
#> Life's short, use R.
mtcars %>% summarise_dt(avg = mean(hp))
#> avg
#> <num>
#> 1: 146.6875
mtcars %>% summarise_dt(avg = mean(hp),by = .(cyl,vs))
#> cyl vs avg
#> <num> <num> <num>
#> 1: 6 0 131.6667
#> 2: 4 1 81.8000
#> 3: 6 1 115.2500
#> 4: 8 0 209.2143
#> 5: 4 0 91.0000
上面例子中,我们可以看到,by参数可以设置分组。需要把分组结果放在一点加括号中,这其实是data.table的简化表达法,它相当于放在一个列表(list)中。所以也可以这样表达:
mtcars %>% summarise_dt(avg = mean(hp),by = list(cyl,vs))
谈到分组问题,因为汇总函数经常需要分组,所以给了by参数,但是其他的函数都没有给。如果需要分组来进行计算,那么就需要使用group家族。group家族非常灵活,能够对任意的操作进行分组操作,与dplyr不同的是,它从来不需要ungroup来取消分组。比如,我们如果想要完成上面的操作,还可以使用group_dt来做:
mtcars %>%
group_dt(
by = .(cyl,vs),
summarise_dt(avg = mean(hp))
)
它是可以使用管道操作的,比如我们汇总后再给avg变量限制保留2位小数:
mtcars %>%
group_dt(
by = .(cyl,vs),
summarise_dt(avg = mean(hp)) %>%
mutate_dt(avg = round(avg,2))
)
#> cyl vs avg
#> <num> <num> <num>
#> 1: 6 0 131.67
#> 2: 4 1 81.80
#> 3: 6 1 115.25
#> 4: 8 0 209.21
#> 5: 4 0 91.00
只要在group_dt中的操作,都是按照cyl和vs进行分组后的组内操作。data.table的分组计算快是得到很多实证的事实,而tidyfst则允许用户在保留最高可读性的情况下轻松地使用这种分组汇总操作。
后面考虑到,有的时候大家还是习惯使用group_by,因此又设计了一个函数,这是因为我发现分组操作与data.table给key先分组排序后计算的特征具有很高的相性。这不仅仅在明面上告诉大家如何分组,而且在底层先经过排序,分组计算也就更加快速。这里举个例子展示如何实现:
mtcars %>%
group_by_dt(cyl,vs) %>% # 给数据分组,实质是根据cyl和vs进行升序排序(data.table中标key)
group_exe_dt( # 根据所标志的键值key进行分组计算
summarise_dt(avg = mean(hp)) %>%
mutate_dt(avg = round(avg,2))
)
#> cyl vs avg
#> <num> <num> <num>
#> 1: 4 0 91.00
#> 2: 4 1 81.80
#> 3: 6 0 131.67
#> 4: 6 1 115.25
#> 5: 8 0 209.21
exe就是按照之前分组来执行计算的意思,先分组排序后执行,可以提高运算的效率。
至此,如果dplyr再不优化,tidyfst在这一点的特性上已经超越了它。当然,这都归功于data.table的高性能。dtplyr也可以分组计算,但是它们能够支持的函数实在太有限,而tidyfst的group家族受益于data.table的灵活特性,对于后面管道上要使用的函数没有任何限制。