前言
刚开始学习R语言的时候,经常模仿别人的代码,囫囵吞枣。如今,在自己研究的领域中,经常使用一些操作,R语言的使用也相对熟练了,所以,需要静下心,补一补脑子里的洞,简单整理一下R语言 Magrittr 相关的管道操作。在代码整洁和可维护性方面,Magrittr 管道符(%>%)作用显著😑
一个简单的例子
刚开始使用R的时候,我为了减少赋值的频次,就把好多个函数套一起使用,经常少了某个括号,看起来也有些”奇怪“,或者说复杂、难理解…比如:
round(cos(exp(sin(log10(sqrt(25))))), 2)
# -0.33
后来,偶然看见师兄的代码,发现了这个符号%>%,才认识了管道符(开始用bash管道符的时候,想过为啥R没有,后来才知道是我太菜…),有了管道符之后就有了下面这样的操作:
library(magrittr)
sqrt(25) %>%
log10() %>%
sin() %>%
exp() %>%
cos() %>%
round(2)
# -0.33
这样看起来,是不是更好理解、更整洁?那么,管道符做了什么,比如%>%,它把前边的输出结果,呈递给下一个函数,作为输入,例子中就是把sqrt(25)
的结果递给log10()
,类推。所以,管道符做的,就是把其左边的结果递给右边,作为输入。这会使代码更加的整洁、可读,并且维护性也会提高。
管道符及占位、花括号 {} 、美元符号$
占位
很多时候,我们使用的函数,数据输入的位置并不是第一个参数,或者,我想要放到我想放的位置,比如gsub
等,这类函数在管道符右边的时候,就需要一个东西,指定前面(管道符左边)的输出该放在后面函数的哪个位置。这个东西就是:.
。没错,就是这个句点,这样用:
argument2 %>% function(argument1, .)
argument1 %>% function(argument2)
例子:
1:5 %>%
paste(., letters[.])
# [1] "1 a" "2 b" "3 c" "4 d" "5 e"
# install.packages("gapminder")
library(gapminder)
gapminder %>%
dplyr::pull(continent) %>%
gsub("Europe", "EUROPE", .) %>%
head(20)
# [1] "Asia" "Asia" "Asia" "Asia" "Asia" "Asia" "Asia" "Asia"
# [9] "Asia" "Asia" "Asia" "Asia" "EUROPE" "EUROPE" "EUROPE" "EUROPE"
# [17] "EUROPE" "EUROPE" "EUROPE" "EUROPE"
先上一段代码:
gapminder %>%
dplyr::filter(continent == "Asia") %>%
dplyr::pull(gdpPercap) %>% round(2) %>%
head(10)
# [1] 779.45 820.85 853.10 836.20 739.98 786.11 978.01 852.40 649.34 635.34
针对数据的continent 列,筛选出包含Asia的数据,然后拿出gdpPercap列,保留两位小数,然后取前十个。很简单的一段代码,但是如果为了缩短函数,像下面这样,把简单的函数head()
和round()
嵌套在一起,得到了不一样的结果:
gapminder %>%dplyr::filter(continent == "Asia") %>%
dplyr::pull(gdpPercap) %>%
head(round(2), 10)
# [1] 779.4453 820.8530
gapminder %>%
dplyr::filter(continent == "Asia") %>%
dplyr::pull(gdpPercap) %>%
head(., round(2), 10)
# [1] 779.4453 820.8530
看了head(., round(2), 10)
,应该就会明白上边代码的输出。首先,从管道符左边过来的数据,并没有直接作为round
函数的输入,而是给了head
函数。第二点,round
函数有两个参数,第一个位输入的vector,第二位是整数,用来指定保留的数位,默认是0,round(2)
返回的结果是2。head
函数只接受两个参数,第一位是输入,第二位是指定打印多少,它接收了从管道左边传来的数据,作为第一个参数,round(2)
的输出成了它的第二位参数,后面那个10就没用了……所以,一串代码只返回了两个数字,而不是我们想要的效果。想要嵌套一下,可以这样:
gapminder %>%
dplyr::filter(continent == "Asia") %>%
dplyr::pull(gdpPercap) %>%
head(round(., 2), 10)
# [1] 779.45 820.85 853.10 836.20 739.98 786.11 978.01 852.40 649.34 635.34
在使用管道符的时候,需要特别注意数据输入的位置,合适正确的使用.
占位
all_equal(
gapminder %>%
dplyr::filter(continent == "Asia") %>%
dplyr::pull(gdpPercap) %>%
round(2) %>%
head(10),
gapminder %>%
dplyr::filter(continent == "Asia") %>%
dplyr::pull(gdpPercap) %>%
{head(round(., 2), 10)}
)
[1] TRUE
花括号 {}
不多说,直接上代码:
gapminder %>%
dplyr::filter(continent == "Asia") %>%
stats::cor(.$lifeExp, .$gdpPercap)
gapminder %>%
dplyr::filter(continent == "Asia") %>%
stats::cor(lifeExp, gdpPercap)
报错:
Error in if (is.na(na.method)) stop(“invalid ‘use’ argument”) : the condition has length > 1
Error in pmatch(use, c(“all.obs”, “complete.obs”, “pairwise.complete.obs”, : object ‘gdpPercap’ not found
加上花括号:
gapminder %>%
dplyr::filter(continent == "Asia") %>%
{stats::cor(.$lifeExp, .$gdpPercap)}
# Equivalent to the code above
gapminder %>%
dplyr::filter(continent == "Asia") -> only_asia
stats::cor(only_asia$lifeExp, only_asia$gdpPercap)
不报错。相对比较容易理解,stats::cor
接受两个参数,我们想要的是.$lifeExp, .$gdpPercap
,但是管道左边来的数据占了一个位置,并且还是个data.frame
,自然就报错了,加上花括号 {},告诉管道符,我不想按照默认的,把左边的输出给右边,这样,stats::cor
就只接受.$lifeExp, .$gdpPercap
,正常运行。
美元符号 $
两段代码:
library(dplyr)
gapminder %>%
dplyr::group_by(continent, year) %>%
dplyr::summarise(count = n()) %>%
dplyr::mutate(total = sum(count),
prop = count / total) %>%
head()
# continent year count total prop
# Africa 1952 52 624 0.08333333
# Africa 1957 52 624 0.08333333
# Africa 1962 52 624 0.08333333
# Africa 1967 52 624 0.08333333
# Africa 1972 52 624 0.08333333
# Africa 1977 52 624 0.08333333
gapminder %>%
dplyr::group_by(continent, year) %>%
dplyr::summarise(count = n()) %>%
dplyr::mutate(total = sum(.$count),
prop = count / total) %>%
head()
# continent year count total prop
# Africa 1952 52 1704 0.03051643
# Africa 1957 52 1704 0.03051643
# Africa 1962 52 1704 0.03051643
# Africa 1967 52 1704 0.03051643
# Africa 1972 52 1704 0.03051643
# Africa 1977 52 1704 0.03051643
第一段代码的输出并不是我想要的结果,第二段代码才是。.$
允许我使用初始的数据也就是gapminder来计数,而不是dplyr::group_by
分组之后的分组数据。所以,在使用dplyr::group_by
之后,要注意我们的目的是什么,合理地使用.$
,得到期望的结果。
其他管道符 %<>%、%T>%、%$%
对于其他的管道符,这里就举几个例子,不再一一展开讨论了
%<>%
mtcars <- mtcars %>%
transform(cyl = cyl * 2)
mtcars %<>% transform(cyl = cyl * 2)
对数据集mtcars操作,输出又赋给了变量mtcars
%T>%
rnorm(200) %>%
matrix(ncol = 2) %T>%
plot %>% # plot usually does not return anything.
colSums
# [1] -4.018676 -27.018219
分支操作,随机生成200个数组成两列的矩阵之后,数据分别流向了两个方向,一是画个图,二是算一下列的和
%$%
iris %>%
subset(Sepal.Length > mean(Sepal.Length)) %$%
cor(Sepal.Length, Sepal.Width)
mtcars %$%
cor(disp, mpg)
这个看起来和上边的花括号{}类似,但是并不一样,它把前面的数据暴露出来,可以任意使用数据中变量,而不需要.$
获取。
总结
在使用管道操作的时候,要理解管道的输出形式,正确的使用占位等……不罗嗦了,慢慢探索吧😑
参考
https://magrittr.tidyverse.org/