R 的新原生管道 `|>` 和 magrittr 管道 `%>%` 有什么区别?

sie*_*ste 12 r pipe tidyverse

在 R 4.1 中引入了一个本地管道运算符,它比以前的实现“更加精简”。我已经注意到 native|>和 magrittr pipe之间的一个区别%>%,即2 %>% sqrt可以工作但2 |> sqrt不能,并且必须写为2 |> sqrt(). 使用新的管道运算符时是否有更多差异和陷阱需要注意?

GKi*_*GKi 106

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n \n\n\n\n\n\n
话题马格里特2.0.3基础4.3.0
操作员%>% %<>% %$% %!>% %T>%|>(自 4.1.0 起)
函数调用1:3 %>% sum()1:3 |> sum()
\xc2\xa01:3 %>% sum需要括号/圆括号
\xc2\xa01:3 %>% `+`(4)部分功能不支持
插入第一个空位置mtcars %>% lm(formula = mpg ~ disp)mtcars |> lm(formula = mpg ~ disp)
占位符._(自 4.2.0 起)
\xc2\xa0mtcars %>% lm(mpg ~ disp, data = . )mtcars |> lm(mpg ~ disp, data = _ )
\xc2\xa0mtcars %>% lm(mpg ~ disp, . )需要命名参数
\xc2\xa01:3 %>% setNames(., .)只能出现一次
\xc2\xa01:3 %>% {sum(sqrt(.))}不允许嵌套调用
提取呼叫mtcars %>% .$cyl
mtcars %>% {.$cyl[[3]]}或者
mtcars %$% cyl[[3]]
mtcars |> _$cyl(自 4.3.0 起)
mtcars |> _$cyl[[3]]

环境%>%附加功能环境
使用:"x" %!>% assign(1)
"x" |> assign(1)
创建函数top6 <- . %>% sort() %>% tail()不可能
速度由于函数调用的开销而较慢更快,因为语法转换
\n
\n

|>当与(匿名)函数结合使用时,许多差异和限制就会消失:
\n 1 |> (\\(.) .)()
\n-3:3 |> (\\(.) sum(2*abs(.) - 3*.^2))()

\n

另请参阅:如何在基本 R 中纯粹进行管道传输(\'基本管道\')?以及5 个 Magrittr 管道 %>%、%<>%、%$%、%!>% 和 %T>% 的区别和用例是什么?

\n
\n

需要括号

\n
library(magrittr)\n\n1:3 |> sum\n#Error: The pipe operator requires a function call as RHS\n\n1:3 |> sum()\n#[1] 6\n\n1:3 |> approxfun(1:3, 4:6)()\n#[1] 4 5 6\n\n1:3 %>% sum\n#[1] 6\n\n1:3 %>% sum()\n#[1] 6\n\n1:3 %>% approxfun(1:3, 4:6)  #But in this case empty parentheses are needed\n#Error in if (is.na(method)) stop("invalid interpolation method") :\n1:3 %>% approxfun(1:3, 4:6)()\n#[1] 4 5 6\n
Run Code Online (Sandbox Code Playgroud)\n

有些函数不受支持,\n有些函数仍然可以通过将它们放在括号中、通过函数调用它们::、使用占位符、在函数中调用或定义函数的链接来调用。

\n
1:3 |> `+`(4)\n#Error: function \'+\' not supported in RHS call of a pipe\n\n1:3 |> (`+`)(4)\n#[1] 5 6 7\n\n1:3 |> base::`+`(4)\n#[1] 5 6 7\n\n1:3 |>  `+`(4, e2 = _)\n#[1] 5 6 7\n\n1 |> (`+`)(2) |> (`*`)(3) #(1 + 2) * 3  or `*`(`+`(1, 2), 3) and NOT 1 + 2 * 3\n#[1] 9\n\n1:3 |> (\\(.) . + 4)()\n#[1] 5 6 7\n\nfun <- `+`\n1:3 |> fun(4)\n#[1] 5 6 7\n\n1:3 %>% `+`(4)\n#[1] 5 6 7\n
Run Code Online (Sandbox Code Playgroud)\n

占位符需要命名参数

\n
2 |> setdiff(1:3, _)\n#Error: pipe placeholder can only be used as a named argument\n\n2 |> setdiff(1:3, y = _)\n#[1] 1 3\n\n2 |> (\\(.) setdiff(1:3, .))()\n#[1] 1 3\n\n2 %>% setdiff(1:3, .)\n#[1] 1 3\n\n2 %>% setdiff(1:3, y = .)\n#[1] 1 3\n
Run Code Online (Sandbox Code Playgroud)\n

此外,对于具有...(点-点-点)参数的可变参数函数,占位符_需要用作命名参数。

\n
"b" |>  paste("a", _, "c")\n#Error: pipe placeholder can only be used as a named argument\n\n"b" |>  paste("a", . = _, "c")\n#[1] "a b c"\n\n"b" |>  (\\(.) paste("a", ., "c"))()\n#[1] "a b c"\n
Run Code Online (Sandbox Code Playgroud)\n

占位符只能出现一次

\n
1:3 |> setNames(nm = _)\n#1 2 3 \n#1 2 3 \n\n1:3 |> setNames(object = _, nm = _)\n#Error in setNames(object = "_", nm = "_") : \n#  pipe placeholder may only appear once\n\n1:3 |> (\\(.) setNames(., .))()\n#1 2 3 \n#1 2 3 \n\n1:3 |> list() |> setNames(".") |> with(setNames(., .))\n#1 2 3 \n#1 2 3 \n\n1:3 |> list(. = _) |> with(setNames(., .))\n#1 2 3\n#1 2 3\n\n1:3 %>% setNames(object = ., nm = .)\n#1 2 3\n#1 2 3\n\n1:3 %>% setNames(., .)\n#1 2 3 \n#1 2 3\n
Run Code Online (Sandbox Code Playgroud)\n

不允许嵌套调用

\n
1:3 |> sum(sqrt(x=_))\n#Error in sum(1:3, sqrt(x = "_")) : invalid use of pipe placeholder\n\n1:3 |> (\\(.) sum(sqrt(.)))()\n#[1] 4.146264\n\n1:3 %>% {sum(sqrt(.))}\n#[1] 4.146264\n
Run Code Online (Sandbox Code Playgroud)\n

提取调用
\n自 4.3.0 起的实验功能。占位符_现在还可以在正向管道表达式的 rhs 中用作|>提取调用中的第一个参数,例如_$coef。更一般地,它可以用作提取链的头部,例如_$coef[[2]]*

\n
mtcars |> _$cyl\nmtcars |> _[["cyl"]]\nmtcars |> _[,"cyl"]\n# [1] 6 6 4 6 8 6 8 4 4 6 6 8 8 8 8 8 8 4 4 4 4 8 8 8 8 4 4 4 8 6 8 4\n\nmtcars |> _$cyl[[4]]\n#[1] 6\n\nmtcars %>% .$cyl\nmtcars %>% .[["cyl"]]\nmtcars %>% .[,"cyl"]\n# [1] 6 6 4 6 8 6 8 4 4 6 6 8 8 8 8 8 8 4 4 4 4 8 8 8 8 4 4 4 8 6 8 4\n\n#mtcars %>% .$cyl[4] #gives mtcars[[4]]\nmtcars %>% .$cyl %>% .[4]\n#[1] 6\n
Run Code Online (Sandbox Code Playgroud)\n

无需额外环境

\n
assign("x", 1)\nx\n#[1] 1\n\n"x" |> assign(2)\nx\n#[1] 2\n\n"x" |> (\\(x) assign(x, 3))()\nx\n#[1] 2\n\n1:3 |> assign("x", value=_)\nx\n#[1] 1 2 3\n\n"x" %>% assign(4)\nx\n#[1] 1 2 3\n\n4 %>% assign("x", .)\nx\n#[1] 1 2 3\n\n"x" %!>% assign(4) #Use instead the eager pipe\nx\n#[1] 4\n\n5 %!>% assign("x", .)\nx\n#[1] 5\n
Run Code Online (Sandbox Code Playgroud)\n

创建一个函数

\n
top6 <- . %>% sort() %>% tail()\ntop6(c(1:10,10:1))\n#[1]  8  8  9  9 10 10\n
Run Code Online (Sandbox Code Playgroud)\n
\n

其他可能性: \n可以使用Bizarro 管道
实现不同的管道运算符和不同的占位符,这不是管道(请参阅缺点),而是覆盖 ->.;.

\n
1:3 ->.; sum(.)\n#[1] 6\n\nmtcars ->.; .$cyl\n# [1] 6 6 4 6 8 6 8 4 4 6 6 8 8 8 8 8 8 4 4 4 4 8 8 8 8 4 4 4 8 6 8 4\n\nmtcars ->.; .$cyl[4]\n#[1] 6\n\n1:3 ->.; setNames(., .)\n#1 2 3 \n#1 2 3 \n\n1:3 ->.; sum(sqrt(x=.))\n#[1] 4.146264\n\n"x" ->.; assign(., 5)\nx\n#[1] 5\n\n6 ->.; assign("x", .)\nx\n#[1] 6\n\n1:3 ->.; . + 4\n#[1] 5 6 7\n\n1 ->.; (`+`)(., 2) ->.; (`*`)(., 3)\n#[1] 9\n\n1 ->.; .+2 ->.; .*3\n#[1] 9\n
Run Code Online (Sandbox Code Playgroud)\n

并且评价不同。

\n
x <- data.frame(a=0)\nf1 <- \\(x) {message("IN 1"); x$b <- 1; message("OUT 1"); x}\nf2 <- \\(x) {message("IN 2"); x$c <- 2; message("OUT 2"); x}\n\nx ->.; f1(.) ->.; f2(.)\n#IN 1\n#OUT 1\n#IN 2\n#OUT 2\n#  a b c\n#1 0 1 2\n\nx |> f1() |> f2()\n#IN 2\n#IN 1\n#OUT 1\n#OUT 2\n#  a b c\n#1 0 1 2\n\nf2(f1(x))\n#IN 2\n#IN 1\n#OUT 1\n#OUT 2\n#  a b c\n#1 0 1 2\n
Run Code Online (Sandbox Code Playgroud)\n

或者定义一个自定义管道运算符,该运算.符在新环境中设置为 lhs 的值并在其中评估 rhs。但这里无法创建或更改调用环境中的值。

\n
`:=` <- \\(lhs, rhs) eval(substitute(rhs), list(. = lhs))\n\nmtcars := .$cyl[4]\n#[1] 6\n\n1:3 := setNames(., .)\n#1 2 3 \n#1 2 3 \n\n1:3 := sum(sqrt(x=.))\n#[1] 4.146264\n\n"x" := assign(., 6)\nx\n#Error: object \'x\' not found\n\n1 := .+2 := .*3\n#[1] 9\n
Run Code Online (Sandbox Code Playgroud)\n

因此,另一种尝试是将 lhs 分配给调用环境中的占位符.,并在调用环境中评估 rhs。但.如果它已经存在,这里将从调用环境中删除。

\n
`?` <- \\(lhs, rhs) {\n  on.exit(if(exists(".", parent.frame())) rm(., envir = parent.frame()))\n  assign(".", lhs, envir=parent.frame())\n  eval.parent(substitute(rhs))\n}\n\nmtcars ? .$cyl[4]\n#[1] 6\n\n1:3 ? setNames(., .)\n#1 2 3 \n#1 2 3 \n\n1:3 ? sum(sqrt(x=.))\n#[1] 4.146264\n\n"x" ? assign(., 6)\nx\n#[1] 6\n\n1 ? .+2 ? .*3\n#[1] 9\n
Run Code Online (Sandbox Code Playgroud)\n

另一种可能性是将 all 替换.为 lhs,以便在评估过程中.不再作为名称存在。

\n
`%|>%` <- \\(lhs, rhs)\n  eval.parent(eval(call(\'substitute\', substitute(rhs), list(. = lhs))))\n\nmtcars %|>% .$cyl[4]\n[1] 6\n\n1:3 %|>% setNames(., .)\n1 2 3 \n1 2 3\n\n1:3 %|>% sum(sqrt(x=.))\n[1] 4.146264\n\n"x" %|>% assign(., 6)\nx\n#[1] 6\n\n1 %|>% .+2 %|>% .*3\n#[1] 7\n
Run Code Online (Sandbox Code Playgroud)\n

使用的运算符的名称会影响运算符优先级:请参阅相同的函数,但使用名称 %>% 与使用名称 := 相比会导致不同的结果
\n有关更高级的选项,请参阅:编写自己的/自定义管道运算符

\n
\n

速度

\n
library(magrittr)\n\n`:=` <- \\(lhs, rhs) eval(substitute(rhs), list(. = lhs))\n\n`?` <- \\(lhs, rhs) {\n  on.exit(if(exists(".", parent.frame())) rm(., envir = parent.frame()))\n  assign(".", lhs, envir=parent.frame())\n  eval.parent(substitute(rhs))\n}\n\n`%|>%` <- \\(lhs, rhs)\n  eval.parent(eval(call(\'substitute\', substitute(rhs), list(. = lhs))))\n\n\nx <- 42\nbench::mark(min_time = 0.2, max_iterations = 1e8\n, x\n, identity(x)\n, "|>" = x |> identity()\n, "|> _" = x |> identity(x=_)\n, "->.;" = {x ->.; identity(.)}\n, "|> f()" = x |> (\\(y) identity(y))()\n, "%>%" = x %>% identity\n, ":=" = x := identity(.)\n, "list." = x |> list() |> setNames(".") |> with(identity(.))\n, "%|>%" = x %|>% identity(.)\n, "?" = x ? identity(.)\n)\n
Run Code Online (Sandbox Code Playgroud)\n

结果

\n
   expression       min   median `itr/sec` mem_alloc `gc/sec`   n_itr  n_gc\n   <bch:expr>  <bch:tm> <bch:tm>     <dbl> <bch:byt>    <dbl>   <int> <dbl>\n 1 x            31.08ns   48.2ns 19741120.        0B     7.46 2646587     1\n 2 identity(x) 491.04ns 553.09ns  1750116.        0B    27.0   323575     5\n 3 |>          497.91ns 548.08ns  1758553.        0B    27.3   322408     5\n 4 |> _        506.87ns 568.92ns  1720374.        0B    26.9   320003     5\n 5 ->.;        725.03ns 786.04ns  1238488.        0B    21.2   233864     4\n 6 |> f()      972.07ns   1.03\xc2\xb5s   929926.        0B    37.8   172288     7\n 7 %>%           2.76\xc2\xb5s   3.05\xc2\xb5s   315448.        0B    37.2    59361     7\n 8 :=            3.02\xc2\xb5s   3.35\xc2\xb5s   288025.        0B    37.0    54561     7\n 9 list.         5.19\xc2\xb5s   5.89\xc2\xb5s   166721.        0B    36.8    31752     7\n10 %|>%          6.01\xc2\xb5s   6.86\xc2\xb5s   143294.        0B    37.0    27076     7\n11 ?             30.9\xc2\xb5s  32.79\xc2\xb5s    30074.        0B    31.3     5768     6\n
Run Code Online (Sandbox Code Playgroud)\n

  • 令人惊讶的全面。注意 `1:3 |&gt; list() |&gt; setNames(".") |&gt; with(setNames(., .))` 可以写成 `1:3 |&gt; list(. = _) |&gt; with (setNames(., .))` 甚至 `1:3 |&gt; setNames(nm = _)` (3认同)

Maë*_*aël 18

一个区别是它们的占位符,_在基数 R 中,.magrittr.


R 4.2.0开始,基础 R 管道有一个用于管道输入值的占位符 ,_类似于%>%'s .,但其使用仅限于命名参数,并且每次调用只能使用一次。

现在可以在 rhs 调用中使用带有占位符 _ 的命名参数来指定 lhs 的插入位置。占位符只能在右侧出现一次。

重申Ronak Shah的示例,您现在可以用作_右侧的命名参数来引用公式的左侧:

c("dogs", "cats", "rats") |> 
    grepl("at", x = _)
#[1] FALSE  TRUE  TRUE
Run Code Online (Sandbox Code Playgroud)

但它必须被命名为:

c("dogs", "cats", "rats") |> 
    grepl("at", _)
#Error: pipe placeholder can only be used as a named argument
Run Code Online (Sandbox Code Playgroud)

并且不能出现多次(为了解决这个问题,仍然可以使用Ronak Shah提供的解决方案):

c("dogs", "cats", "rats") |> 
  expand.grid(x = _, y = _)
# Error in expand.grid(x = "_", y = "_") : pipe placeholder may only appear once
Run Code Online (Sandbox Code Playgroud)

虽然这是可能的magrittr

library(magrittr)
c("dogs", "cats", "rats") %>% 
  expand.grid(x = ., y = .)
#     x    y
#1 dogs dogs
#2 cats dogs
#3 rats dogs
#4 dogs cats
#5 cats cats
#6 rats cats
#7 dogs rats
#8 cats rats
#9 rats rats
Run Code Online (Sandbox Code Playgroud)

  • 限制使用一次是否有意义?他们有计划消除这一限制吗? (4认同)
  • 如果我不得不猜测(我不是 R 核心),那是因为这些运算符(`|&gt;` 等)重写了语法,使得 `longcalc() |&gt; quux(x = _)` 变成了 `quux(x = longcalc())`,并且他们不希望将 `longcalc() |&gt; quux(x=_, y=)` 转换为 `quux(x=longcalc(), y=longcalc()) 的双倍计算)` (其中第二个是冗余且双倍时间的调用)。不过,这只是一个猜测。@GitHunter0 (3认同)

Ron*_*hah 12

它们两者之间的另一个区别是在值管道.可被用作在一个占位符magrittr的管

c("dogs", "cats", "rats") %>% grepl("at", .)
#[1] FALSE  TRUE  TRUE
Run Code Online (Sandbox Code Playgroud)

但这对于 R 的原生管道是不可能的。

c("dogs", "cats", "rats") |> grepl("at", .)
Run Code Online (Sandbox Code Playgroud)

grepl(c("dogs", "cats", "rats"), "at", .) 中的错误:对象 '.' 未找到

以下是引用它们的不同方法 -

  1. 编写一个单独的函数 -
find_at = function(x) grepl("at", x)
c("dogs", "cats", "rats") |> find_at()
#[1] FALSE  TRUE  TRUE
Run Code Online (Sandbox Code Playgroud)

2 一个。使用匿名函数 -

c("dogs", "cats", "rats") |> {function(x) grepl("at", x)}()
Run Code Online (Sandbox Code Playgroud)

2 湾。使用新的匿名函数语法

c("dogs", "cats", "rats") |> {\(x) grepl("at", x)}()
Run Code Online (Sandbox Code Playgroud)

示例取自 - https://www.jumpingrivers.com/blog/new-features-r410-pipe-anonymous-functions/


Dir*_*tel 12

|>R 4.1.0 中添加的基础 R 管道“只是”执行功能组合。即我们可以看到它的使用真的和函数调用一样:

> 1:5 |> sum()             # simple use of |>
[1] 15
> deparse(substitute( 1:5 |> sum() ))
[1] "sum(1:5)"
> 
Run Code Online (Sandbox Code Playgroud)

这有一些后果:

  • 它使它更快一点
  • 它使它更简单,更健壮
  • 它使限制性更强:sum()这里需要括号才能正确调用
  • 它限制了“隐式”数据参数的使用

这导致可能使用=>当前“可用但未激活”(为此您需要设置环境变量_R_USE_PIPEBIND_,并且可能会在 R 4.2.0 中更改)。

(这首先是作为一个问题的答案在这里复制的,我只是按照建议复制了它。)

编辑:随着关于“是什么=>”的后续问题出现,这里是一个快速跟进。请注意,此运算符可能会发生变化。

> Sys.setenv("_R_USE_PIPEBIND_"=TRUE)
> mtcars |> subset(cyl == 4) |> d => lm(mpg ~ disp, data = d)

Call:
lm(formula = mpg ~ disp, data = subset(mtcars, cyl == 4))

Coefficients:
(Intercept)         disp  
     40.872       -0.135  

> deparse(substitute(mtcars |> subset(cyl==4) |> d => lm(mpg ~ disp, data = d)))
[1] "lm(mpg ~ disp, data = subset(mtcars, cyl == 4))"
> 
Run Code Online (Sandbox Code Playgroud)

deparse(substitute(...))这里特别漂亮。


sie*_*ste 11

本机管道是作为语法转换实现的,因此2 |> sqrt()与 相比没有明显的开销sqrt(2),而2 %>% sqrt()有一个小的惩罚。

microbenchmark(sqrt(1), 
               2 |> sqrt(), 
               3 %>% sqrt())
# Unit: nanoseconds
#          expr  min     lq    mean median   uq   max neval
#       sqrt(1)  117  126.5  141.66  132.0  139   246   100
#       sqrt(2)  118  129.0  156.16  134.0  145  1792   100
#  3 %>% sqrt() 2695 2762.5 2945.26 2811.5 2855 13736   100
Run Code Online (Sandbox Code Playgroud)

您会看到2 |> sqrt()传递给的表达式如何microbenchmark被解析为sqrt(2). 这也可以在

quote(2 |> sqrt())
# sqrt(2)
Run Code Online (Sandbox Code Playgroud)