命令式编程容易出现大量的中间变量,污染全局环境。
可如果写成一系列函数嵌套的形式,又会造成可读性下降
pipeline operator 能够解决这个痛点,使代码更加清晰易读
|>
这是 4.1.0 版本新发布的 base R 管道操作符。
4.2.0 为其设计了 placeholder _
,但这个 placeholder
不能单独使用,必须在其出现的参数位置写上 paramter = _
|> lm(mpg ~ disp, data = _) # data = 不可省略,否则会报错 mtcars
#>
#> Call:
#> lm(formula = mpg ~ disp, data = mtcars)
#>
#> Coefficients:
#> (Intercept) disp
#> 29.59985 -0.04122
|>
和 _
这一套的推广,需要 Rstudio 和
VSCode-R 等 IDE 中快捷键和语法检查的匹配——但它们的更新往往要慢于 R
base
%>%
右向管道操作符传递对象给右边的函数或表达式。当对象在右边函数的第一个参数时,可以省略这个参数;不是第一个参数时,则可以用.
代替。如果是一元函数,甚至可以连()
也省掉,只保留函数名。
管道操作符
set.seed(123)
<- rnorm(10)
a a
#> [1] -0.56047565 -0.23017749 1.55870831 0.07050839 0.12928774 1.71506499
#> [7] 0.46091621 -1.26506123 -0.68685285 -0.44566197
# 由此可见:`*`和`+`其实是两个函数!接收传来的数据流作为自己的第一个参数
%>%
a `*`(5) %>%
`+`(5)
#> [1] 2.197622 3.849113 12.793542 5.352542 5.646439 13.575325 7.304581
#> [8] -1.325306 1.565736 2.771690
%T>%
左向管道操作符必须与%>%
配合使用。
T管道操作符
例1:在数据处理的中间过程,需要打印输出或图片输出(如观察代码是否达到了预设的目的),这时整个过程就会被中断,用向左操作符就可以解决这样的问题。
# 为了提高可读性,%T>% 不换行
rnorm(100) %>%
matrix(ncol = 2) %T>% plot %>%
str()
#> num [1:50, 1:2] 1.224 0.36 0.401 0.111 -0.556 ...
%$%
爆炸操作符一些基础函数只接受向量作为参数,而不接受数据框,爆炸操作符可以绑定数据框,让右侧的函数可以直接使用列名
%$% cor(disp, mpg) mtcars
#> [1] -0.8475514
相当于
cor(mtcars$disp, mtcars$mpg)
#> [1] -0.8475514
%<>%
复合赋值操作符功能与%>%
基本一样,多了一项额外的操作,就是把结果写回左侧对象。所以其功能就是运算且更新。
比如,我们需要对一个数据集进行排序,同时需要获得排序的结果,用%<>%
就是非常方便的。
现实原理如下图所示,使用%<>%
把左侧的数据集A传递右侧的B函数,B函数的结果数据集再向右侧传递给C函数,C函数结果的数据集再重新赋值给A,完成整个过程。
复合赋值操作符
例:定义符合正态分布的100个随机数,计算绝对值,并按从小到大的顺序排序,获得并取前10个数字赋值给x。
set.seed(1)
<- rnorm(100)
x <- x %>%
a abs() %>%
sort() %>%
head(10)
a
#> [1] 0.001105352 0.016190263 0.028002159 0.039240003 0.044933609 0.053805041
#> [7] 0.056128740 0.059313397 0.074341324 0.074564983
%<>% abs %>%
x sort() %>%
head(10)
x
#> [1] 0.001105352 0.016190263 0.028002159 0.039240003 0.044933609 0.053805041
#> [7] 0.056128740 0.059313397 0.074341324 0.074564983
%<>%
的主要作用是省略一步<-
,但会造成变量的改变,程序的稳健性会因此削弱,最好不要用。
匿名函数function(parameter){...}
单独出现时,外面要用括号()
括起来
%>%
iris function(x) {
(if (nrow(x) > 2) {
bind_rows(head(x, 1), tail(x, 1)) %>% return()
else {
} return(x)
} })
#> Sepal.Length Sepal.Width Petal.Length Petal.Width Species
#> 1 5.1 3.5 1.4 0.2 setosa
#> 2 5.9 3.0 5.1 1.8 virginica
# 本质上,R中的运算符都是函数,故以下两行伪代码等价
fun(sym) <- value
`fun<-`(sym, value) # 上一行是这一行的语法糖
新函数 | 原操作符1 |
---|---|
extract(x, index or 'col_name') |
[ ,即[ (x, index/‘col_name’) |
extract2(x, index or 'col_name') |
[[ ,即[[ (x, index’col_name’) |
inset() |
[<- |
inset2() |
[[<- |
set_colnames() |
colnames()<- |
set_rownames() |
rownames()<- |
magrittr::use_series() (可用于list),
dplyr::pull() (不适用于list) |
$ |
add | + |
subtract | - |
multiply_by | * |
raise_to_power | ^ |
multiply_by_matrix | %*% |
divide_by | / |
divide_by_int | %/% |
mod | %% |
is_in() |
%in% |
and | & |
or | | |
equals | == |
is_greater_than | > |
is_weakly_greater_than | >= |
is_less_than | < |
is_weakly_less_than | <= |
not | ! |
set_names() |
names()<- |
set.seed(1)
<- rnorm(10)
x x
#> [1] -0.6264538 0.1836433 -0.8356286 1.5952808 0.3295078 -0.8204684
#> [7] 0.4874291 0.7383247 0.5757814 -0.3053884
%>%
x multiply_by(5) %>%
add(5)
#> [1] 1.8677309 5.9182166 0.8218569 12.9764040 6.6475389 0.8976581
#> [7] 7.4371453 8.6916235 7.8789068 3.4730581
%>%
x `*`(5) %>%
`+`(5)
#> [1] 1.8677309 5.9182166 0.8218569 12.9764040 6.6475389 0.8976581
#> [7] 7.4371453 8.6916235 7.8789068 3.4730581
<- 1:10
x %<>% set_names(letters[1:10])
x # 作为参数传入函数时,会自动生成副本,x不会被修改,所以必须加赋值命令
x
#> a b c d e f g h i j
#> 1 2 3 4 5 6 7 8 9 10
<- 1:10
x %<>% `names<-`(letters[1:10]) # names()<- 在R中执行的真正形式
x x
#> a b c d e f g h i j
#> 1 2 3 4 5 6 7 8 9 10
<- list(c(1, 2), c("a", "b"))
y %>% `[`(2) # 不能直接用extract(),已被其他包同名函数覆盖 y
#> [[1]]
#> [1] "a" "b"
%>% magrittr::extract(2) y
#> [[1]]
#> [1] "a" "b"
%>% extract2(2) y
#> [1] "a" "b"
%>%
y extract2(2) %>%
class()
#> [1] "character"
%>%
y extract2(2) %>%
extract2(2)
#> [1] "b"
%>%
y map(extract2, 2) %>%
unlist()
#> [1] "2" "b"
# unlist()拆分成vector,必须是同样的数据类型,那只能是数字退化为字符串
<- tibble(v1 = 1:26, v2 = letters)
df # 完成了列名赋值工作
%<>% set_colnames(c("id", "name")) %>%
df filter(id %>% is_in(1:10)) # 替代 %in%
df
#> # A tibble: 10 × 2
#> id name
#> <int> <chr>
#> 1 1 a
#> 2 2 b
#> 3 3 c
#> 4 4 d
#> 5 5 e
#> 6 6 f
#> 7 7 g
#> 8 8 h
#> 9 9 i
#> 10 10 j
例:对一个包括10个随机数的向量的先*5再+5,求出向量的均值和标准差,并从小到大排序后返回前5条。
set.seed(1)
<- rnorm(10)
a %>%
a multiply_by(5) %>%
add(5) %>%
{cat("Mean:", mean(.), "\nVar:", var(.), "\n")
sort(.) %>% head(5)
}
#> Mean: 5.661014
#> Var: 15.23286
#> [1] 0.8218569 0.8976581 1.8677309 3.4730581 5.9182166
# 等价于定义一个函数。还是应该定义函数,增强可读性
<- function(d) {
display cat("Mean:", mean(d), "\nVar:", var(d), "\n")
sort(d) %>%
head(5) %>%
print()
}%>%
a multiply_by(5) %>%
add(5) %>%
display()
#> Mean: 5.661014
#> Var: 15.23286
#> [1] 0.8218569 0.8976581 1.8677309 3.4730581 5.9182166
R 中的操作符其实都是函数,只不过是写法上特殊一点的无括号函数。所以在管道操作中,左边函数对右边运算符的替换并不是必须的,因为管道也可以和操作符直接配合,只不过都要用反引号将操作符括起来,显式指明这是一个函数。↩︎