当时尚有 R base,何事纷纷说 tidyverse

来自 Peng Zhao | May 15, 2019

1

R 语言的”极乐净土“ tidyverse 包,对我而言是个奇葩的存在。就像它的颜值担当成员”哥哥画图呃“ ggplot2 一样,这个东西我学起来用起来特别吃力。

网上到处都是对 tidyverse 一片喝彩,几乎听不到任何负面的声音,这个现象我觉得很奇怪。比如搜一下"tidyverse 缺点 不足 问题",是搜不到结果的。这让我有点警惕。不过,这是个数据科学领域的工具,我是外行,而且年岁大了,可能是我自己的问题吧。R 基础包已经够我了此残生了,tidyverse 学不会就算了吧。

然而见得多了,发现不学不行了。比如有人在 Stack Overflow 提了个问题,得到的是 tidyverse 版的回答,不懂 %>% 是啥的话还真看不懂,虽然明明是基础函数就能解决。

原来,国王连锄地都用金锄头啊!

我隐隐担忧起来。万一世界被 tidyverse 一统江湖,我遇见基础包的问题可咋办,问都没处问去,人家解答了我也看不懂……

不止我一个人有这种困惑。

比如,一个朋友就私下向我抱怨:

如果在论坛提问如何用 R 做个什么图,那么回帖最多的肯定是用 ggplot。然而 ggplot 各种诡异和思想颠覆,太难记,要是一点不懂 R 的基础绘图还好点。有的图,基础包两三句就行, ggplot 要隆重地写上很大一段,满满的仪式感。

的确如此。

举两个例子。

比如,我想为 diamonds 这个数据框的钻石品质数据做个柱状图,看看各种品质的钻石有多少个。

用基础命令,思路很正常也单纯:先用 table() 算出 cut 列每种品质的钻石有多少个,再用 barplot() 把数据用图表达出来:

barplot(table(diamonds$cut))

而 tidyverse,需要先加载包,然后把数据映射过去,最后添加个 x 轴的美学映射——这几个术语我吃不准用得对不对,反正代码就是这样复杂:

require('tidyverse')
ggplot(data = diamonds) + 
  geom_bar(mapping = aes(x = cut))

再比如说,我想计算一下各种品质钻石的平均价格。

用基础命令,思路很正常也很单纯:一条 tapply() 函数搞定,只需指定一下“算谁,按谁算,咋算”就行了:

tapply(diamonds$price,      # 算 价格
       diamonds$cut,        # 按 cut 分类算
       mean, na.rm = TRUE)  # 求平均

##      Fair      Good Very Good   Premium     Ideal 
##  4358.758  3928.864  3981.760  4584.258  3457.542

而 tidyverse,加载包那一步就不提了,需要先用 group_by()cut列来分一下组,再用 summarize()来做总结,里面指定一下总结方式是mean(),光是说起来都累:

diamonds %>% 
  group_by(cut) %>% 
  summarise(mean(price, na.rm = TRUE))

## # A tibble: 5 x 2
##   cut       `mean(price, na.rm = TRUE)`
##   <ord>                           <dbl>
## 1 Fair                            4359.
## 2 Good                            3929.
## 3 Very Good                       3982.
## 4 Premium                         4584.
## 5 Ideal                           3458.

R 基础包多好啊,为什么那么多初学者会纠结是学 R 基础命令,还是学 tidyverse?

当时尚有周天子,何事纷纷说魏齐?

2

这个问题困扰了我好久。

有一天,我决定好好思考一下这个问题。

了解 tidyverse 的最好办法,就是去读官方综合教程:R for Data Science

原以为大神的书都艰深晦涩,没想到这本书写得非常通俗易懂,就是函数名字难记。我学会了好多动词,比如 mutate 这个词,我一直以为是 X 战警生造的,就像哈利波特里的麻瓜(Muggle)一样。我从来没在任何其他场合听说读写过它,这回算是长知识了。

读着读着,不知怎么,晴空里一道闪电,我突然开了窍,说服了自己:tidyverse 用法的奇葩之处,是有理由的。

tidyverse 的世界里,有一只指南针。有它在手,我此前关于 tidyverse 的困惑一扫而空。

这个指南针就是“管道”。

(可以阅读 yihui 的管道时代一文。)

管道的意思是,管道前面得到的输出,不落地,直接作为管道后面的输入。

打个比方。

不使用管道的时候,好比吃中餐,合餐制。从厨房输出的每道菜,先各自占用一个盘子,再端到大圆桌上,每个人把菜输入到自己碗里。

合餐的好处是你可以自由搭配,想从哪个盘子夹多少菜多少肉都行,到你的碗里就是独一无二的组合,还可以随便放调料。坏处是不仅占桌子占盘子,而且洗碗特别麻烦,因为除了洗每人的碗筷和锅,你还得洗每道菜的盘子这种中间步骤。

西餐就省事多了,分餐制,只需一步,直接将厨房输出的菜输入到每人的盘子里,省却了中间步骤。这就是管道的妙处。

以上是一个家里没有洗碗机而经常从事洗碗劳动的非程序员的日常心得。

正是为了能够全程使用管道,tidyverse 用了一根筋的办法:各个函数,输入的必须是数据框,输出的也必须是数据框。而且最好是超级数据框( tibble)。

这就好比吃西餐,从锅里输出的必须是比较干的菜,例如香肠土豆什么的,这样才能用刀叉(管道)把他们直接输入到用户的盘子里。如果输出的是汤,对不起,你得把里面的水先煮干,再把其中的干货用刀叉(管道)输出到用户的盘子里。当然,后续可以由用户往里加水复原成汤——奇葩吧?对,这就是为啥 tidyverse 搞出那么多奇葩函数的根本原因。都是为了适应管道的需求。管道两头都必须是类型相同的干货:数据框。

上文举的两个例子,R 基础包用的 table()tapply(),一个要求输入的是一个向量,输出的是一个表格,另一个要求输入的两个向量,输出的是一个数组。输入和输出这两头自己都不一致,跟别的函数更不一致,没法统一用管道。

表面上,R 基础包更省事,然而 zoom out 一下,从总体上看,放到前后相继的一串代码里,却是管道更省事。这算是舍小弊而取大利,为了宏观而牺牲微观吧。

管道符号 %>% 已经普及到了 tidyverse 的大片领地,偏偏是带头大哥的 ggplot2 没有使用,而用了加号。这是因为 %>% 诞生较晚,是个后生。然而后生长过先生,ggplot2 的下一代版本”格格巫师“(ggvis)全面使用%>% 代替加号了,颇有秦朝车同轨书同文的意味。

有了管道,数据处理从最初到最终成品一气呵成,畅快淋漓。可以设想,习惯之后再学 R 基础函数得需要多大勇气和耐心。

3

我接触 R 比较晚。2010 年整整一年,我都在用 Visual Basic 吭哧吭哧跟数据死磕,2011 年才开始真正接触 R(此前只是听说过),不知道此前发生过什么,只知道有 R 基础命令这种周天子,没听说过 ggplot2。

R 基础命令作图能满足我的绝大部分需求。不过,当时我已经知道 lattice 包的存在,震惊的程度不亚于后来遇见 ggplot2。lattice 让我联想到齐桓公,虽然称雄与 R 语言绘图界,但并没有像今天的 ggplot2 给人”你到底是用我还是用 R 基础包?“的选择压力——至少我没有。不用 lattice?没关系,能出结果就行。用 lattice?好啊,画图更方便。齐桓公尊王攘夷,得到了周天子的认可,lattice 包也被 R 基础包认可,常年作为 R 的默认安装包。不知道 lattice 是怎么做到的。

后来,ggplot2 崛起,作为新一代的霸主,在江湖地位上,显然取代了齐桓公 lattice。我不止在一处看到有人建议,R 安装包里应该用 ggplot2 取代 lattice,从周天子层面确认 ggplot2 的霸主地位。只是不知道周天子在犹豫什么。

后来的后来,ggplot2 的周边生态完善,联合了一大群彼此兼容的包,成立了联盟,这便是虎狼之邦的 tidyverse。

如果你是个新居民,刚刚搬家踏上 R 语言的土地,可能会遇到一个奇怪的问题:

你是想住进极乐净土的秦国 tidyverse,还是住进纷繁扰攘的周朝 R base?

新居民可能摸不着头脑:这是什么逻辑?秦国不是就在周朝里吗?

别人可能神秘一笑:生活在秦国的人,不需要知道周天子。

comments powered by Disqus