吃(蘸(涮(挤(捶打(切(宰(牛))), 成丸), 牛骨清汤), 沙茶))
5 magrittr 管道
5.1 %>%
定义
在继续讲解 dplyr 函数前,我想介绍一个非常特殊的函数,来自 magrittr 包的管道(pipe)函数 %>%
。 它的用途是连通函数,美化代码。
它的定义如下
f(a)
等于a %>% f()
f(a, b)
等于a %>% f(b)
f(a, b, c, ...)
等于a %>% f(b, c, ...)
比方说,names(diamonds)
等于 diamonds %>% names()
。
从下一章开始,为了帮助大家养成习惯,我会把几乎所有的函数都写成管道 %>%
的形式。
5.2 %>%
优点
为什么要多此一举呢?在数据分析中,我们通常要用到大量的函数,比如我们要对一头牛做复杂的处理:
【错误】写法一
【错误】写法二
吃(
蘸(
涮(
挤(
捶打(
切(
宰(牛)
)
), 成丸
), 牛骨清汤
), 沙茶
) )
【正确】写法三(使用管道)
%>%
牛 %>%
宰() %>%
切() %>%
捶打() %>%
挤(成丸) %>%
涮(牛骨清汤) %>%
蘸(沙茶) 吃()
第三种写法,明显更自然,更清晰。原来这一系列函数,把牛变成了手打牛肉丸,下清汤锅,蘸沙茶酱,填了肚子——想必是在潮汕地区吃的。
当我们有连续多个函数连在一起的时候,%>%
能让代码更加整洁、易读。
5.3 %>%
范例
任务:先把 price 放到开头,再对它降序排列(从高到低)
# 【错误】写法一
arrange(relocate(diamonds, price), -price)
# 【错误】写法二
arrange(
relocate(diamonds, price),
-price
)
# 【正确】写法三,整洁、清晰、易读
%>%
diamonds relocate(price) %>%
arrange(-price)
# A tibble: 53,940 × 10
price carat cut color clarity depth table x y z
<int> <dbl> <ord> <ord> <ord> <dbl> <dbl> <dbl> <dbl> <dbl>
1 18823 2.29 Premium I VS2 60.8 60 8.5 8.47 5.16
2 18818 2 Very Good G SI1 63.5 56 7.9 7.97 5.04
3 18806 1.51 Ideal G IF 61.7 55 7.37 7.41 4.56
4 18804 2.07 Ideal G SI2 62.5 55 8.2 8.13 5.11
5 18803 2 Very Good H SI1 62.8 57 7.95 8 5.01
6 18797 2.29 Premium I SI1 61.8 59 8.52 8.45 5.24
7 18795 2.04 Premium H SI1 58.1 60 8.37 8.28 4.84
8 18795 2 Premium I VS1 60.8 59 8.13 8.02 4.91
9 18791 1.71 Premium F VS2 62.3 59 7.57 7.53 4.7
10 18791 2.15 Ideal G SI2 62.6 54 8.29 8.35 5.21
# ℹ 53,930 more rows
5.4 %>%
和 |>
在 R 4.1 的版本及以后,R 推出了所谓的自然管道(natural pipe)|>
。它有三个优点,
- 比较简洁,仅使用了两个字符;
- 和 Julia 等编程语言统一了语法;
- 随时可用,使用前不需要导入 tidyverse, dplyr 或 magnittr 包。
鉴于上述优点,甚至 tidyverse 的奠基人 Hadley Wickham 都改用了 |>
,几乎抛弃了 %>%
。但是需要注意,|>
也有很多缺点,具体什么我就不赘述了,你们以后遇到了自然知道。
就我个人习惯而言,我还是用 %>%
更多一点。而且我也更推荐初学者用 %>%
,因为它更简单、更少报错。
5.5 为所欲为
我们已经学了一些 dplyr
函数。可以发现,几乎所有 dplyr
函数输入和输出的格式都是 tibble,这是一个非常精妙的设计。
章节 1.11 说到,想要学会成语接龙,学“为所欲为”是最省时省力的。同理,
- 使用 dplyr 函数,初学者可以“为所欲为”地处理 tibble。
- dplyr 函数非常适配管道函数
%>%
。
下面是一个简单的证明:
代码
# 定义识别函数
<- function(object) {
read_object <- substitute(object) %>%
input as.character()
<- type_sum(object) %>%
type as.character() %>%
str_extract("^\\w+")
<- str_glue(
judgement "{input} 的类型是 {type}。"
)return(judgement)
}
read_object(diamonds) # 识别 diamonds
diamonds 的类型是 tibble。
<- diamonds %>%
diamonds2 relocate(price)
read_object(diamonds2) # 识别 diamonds2
diamonds2 的类型是 tibble。
relocate()
输入 diamonds,输出 diamonds2,二者都是 tibble。
<- diamonds2 %>%
diamonds3 arrange(-price)
read_object(diamonds3) # 识别 diamonds3
diamonds3 的类型是 tibble。
同样,arrange()
输入 diamonds2,输出 diamonds3,二者都是 tibble。
值得一提的是,tidyverse 底下的其他包,通常也有“为所欲为”的性质:
对象 | 函数 | 例子 | |
---|---|---|---|
成语接龙 | “为” | 特定成语 | “为所欲为” |
数据 | tibble | dplyr 函数 | arrange(), relocate() 等 |
图形 | gg | ggplot 函数 | geom_col(), ggtitle() 等 |
文本变量 | character | 部分 stringr 函数 | str_replace(), str_extract() 等 |
类别变量 | factor | 部分 forcats 函数 | fct_reorder(), fct_recode() 等 |
列表 | list | 部分 purrr 函数 | map(), map2() 等 |