如何将管道链(magrittr)的结果传递给一个对象

23

这是一个相当简单的问题。但我在谷歌/stackexchange上找不到答案,并查看magrittr文档也无果。

如何将通过%>%连接的一系列函数的结果馈送到一个向量中?

我看到大多数人都是这样做的:

a <-
data.frame( x = c(1:3), y = (4:6)) %>%
sum()

但是是否有一种解决方案,可以将结果直接以管道链的方式传递给一个对象,可能是类似于别名或其他东西,就像这样:

data.frame( x = c(1:3), y = (4:6)) %>%
sum() %>%
a <- ()

这将有助于保持所有代码在相同的逻辑下向前“顺流而下”传递结果。

5个回答

27

试一下这个:

data.frame( x = c(1:3), y = (4:6)) %>% sum -> a

7
+1 我从来没有想过会有->的正当理由,现在有了! - Carlos Cinelli
7
但如果你只使用 ->,你就不能继续链式操作:例如 data.frame(x = c(1:3), y = (4:6)) %>% sum -> a %>% exp 会出错,你必须使用圆括号 (data.frame(x = c(1:3), y = (4:6)) %>% sum -> a) %>% (exp),如果不小心可能会导致意外的结果。 - Carlos Cinelli
7
(1) 继续链式操作不是个好主意。如果你真的想要在一半的过程中为变量赋值,最好将其拆成两个链。可以像这样写:data.frame(x = c(1:3), y = (4:6)) %>% sum -> a; a %>% exp来避免问题。(2) 在一半的过程中赋值是副作用,而函数式编程的风格是避免副作用。 - G. Grothendieck
1
如何尝试这个函数:tee=function(v,n){assign(n,v,.GlobalEnv);v} 然后使用管道符 d %>% whatever %>% tee("part1") %>% otherstuff %>% tee("part2") %>% etcetc - Spacedman
我认为通常希望将对象分配到当前环境而不是全局环境中,以便能够在函数中使用它。 - G. Grothendieck
显示剩余2条评论

13

你可以这样做:

data.frame( x = c(1:3), y = (4:6)) %>%  
sum %>%  
assign(x="a",value=.,pos=1)  

需要注意的几点:

您可以使用“.”告诉magrittr将要带入的对象属于哪个参数。默认情况下,它属于第一个参数,但在这里我使用 . 表示我想要它属于第二个value参数。

其次,我必须使用pos=1参数才能在全局环境中进行赋值。


你真的不应该在任何情况下使用assign。这是糟糕的编码实践。 - stanekam
12
你能详细说明一下吗?这里存在什么危险? - John Paul

5

您也可以使用<<-运算符:

data.frame( x = c(1:3), y = (4:6)) %>%
  sum() %>%
  `<<-`(a,.)

编辑:我认为John Paul的建议是最安全的,你可以继续使用链式编程来完成对部分结果的不同赋值。例如:

data.frame( x = c(1:3), y = (4:6)) %>%  
  sum %>%  
  assign(x="a",value=., pos=1)  %>% 
  exp %>%
  assign(x="b",value=., pos=1) %>% 
  sqrt %>%
  assign(x="c", value=., pos=1)

这将正确创建 abc

1
你确定它不能用 <- 吗?如果不是必须使用 <<-,最好不要使用。 - Carl Witthoft
1
@CarlWitthoft,这样不行,<- 操作符会分配到函数的本地环境中,因此它不会在全局环境中创建变量。 - Carlos Cinelli

4

3
我喜欢做的事情(我记不起来了,在某个地方发现了这个技巧)是在我的管道链末尾使用{.} -> obj。这样,我可以通过插入新行向链的末尾添加额外步骤,而不必重新定位到->赋值运算符。
您还可以使用(.)代替{.},但它看起来有点奇怪。
例如,可以这样做:
  iris %>% 
    ddply(.(Species), summarise, 
          mean.petal = mean(Petal.Length),
          mean.sepal = mean(Sepal.Length)) -> summary

请执行以下操作:

iris %>% 
    ddply(.(Species), summarise, 
          mean.petal = mean(Petal.Length),
          mean.sepal = mean(Sepal.Length)) %>% 
    {.} -> summary

这样做可以更容易地看到你的管道数据最终输出的位置。此外,虽然这似乎不是什么大问题,但是添加另一个最终步骤会更加容易,因为你不需要将->移动到新行,只需在{.}之前添加一个新行并添加该步骤即可。

就像这样:

iris %>% 
    ddply(.(Species), summarise, 
          mean.petal = mean(Petal.Length),
          mean.sepal = mean(Sepal.Length)) %>% 
    arrange(desc(mean.petal)) %>%   # just add a step here
    {.} -> summary

这并不能帮助节省中间结果。John Paul的使用assign()方法的答案很好,但是它有点长,需要使用".",因为数据不是第一个参数,所以必须将新参数的名称放在""中,并指定环境(pos = 1)。这似乎是我懒惰的表现,但使用%>%是关于速度的。因此,我将assign()方法包装在一个小函数中,以加快速度:
keep <- function(x, name) {assign(as.character(substitute(name)), x, pos = 1)}

现在你可以这样做:

  keep <- function(x, name) {assign(as.character(substitute(name)), x, pos = 1)}

  iris %>% 
    ddply(.(Species), summarise, 
          mean.petal = mean(Petal.Length),
          mean.sepal = mean(Sepal.Length)) %>% keep(unsorted.data) %>% # keep this step
    arrange(mean.petal) %>%
    {.} -> sorted.data

sorted.data
#     Species mean.petal mean.sepal
#1     setosa      1.462      5.006
#2 versicolor      4.260      5.936
#3  virginica      5.552      6.588

unsorted.data
#     Species mean.petal mean.sepal
#1     setosa      1.462      5.006
#2 versicolor      4.260      5.936
#3  virginica      5.552      6.588

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接