作为 dplyr 管道的一部分,将中间输出分配给 temp 变量

Posted

技术标签:

【中文标题】作为 dplyr 管道的一部分,将中间输出分配给 temp 变量【英文标题】:Assign intermediate output to temp variable as part of dplyr pipeline 【发布时间】:2017-03-15 04:02:31 【问题描述】:

问:在 R dplyr 管道中,如何将一些中间输出分配给临时变量,以便在管道中进一步使用?

我下面的方法有效。但它分配到全局框架中,这是不可取的。必须有更好的方法,对吧?我认为涉及注释行的方法会得到预期的结果。没有骰子。很困惑为什么这不起作用。

df <- data.frame(a = LETTERS[1:3], b=1:3)
df %>%
  filter(b < 3) %>%
  assign("tmp", ., envir = .GlobalEnv) %>% # works
  #assign("tmp", .) %>% # doesn't work
  mutate(b = b*2) %>%
  bind_rows(tmp)
  a b
1 A 2
2 B 4
3 A 1
4 B 2

【问题讨论】:

只需使用 2 个管道。这是不必要的混淆。 您可能会喜欢pipeR,其中can assign (and a lot more) in the middle of a pipeline,但如果您打算与任何人共享您的代码,它可能会有点象形文字。 这并不比你的例子好,但语法可以说更好一点:df %&gt;% filter(b &lt; 3) %&gt;% . -&gt;&gt; tmp %&gt;% mutate(b = b*2) %&gt;% bind_rows(tmp) 这是一种强烈的代码气味,您不应该这样做。告诉我们为什么您要保存临时过滤结果tmp,即您最终要通过第二个管道实现什么目标?如果不保存 tmp 而只是重复filter() 步骤,会有什么问题? 好的,所以共识是“不要这样做,使用两条管道” 【参考方案1】:

您可以在管道中需要的位置生成所需的对象。例如:

df %>% filter(b < 3) %>% mutate(b = b*2) %>%
  bind_rows(df %>% filter(b < 3))

这种方法避免了两次过滤:

df %>%
  filter(b < 3) %>%
  bind_rows(., mutate(., b = b*2))

【讨论】:

这在技术上适用于我的玩具示例。但我认为,对于具有多个临时分配的更复杂、更长的管道来说,这是不可行的 如果您能提供一个更复杂的用例作为示例,将会很有帮助。我通常只是在管道中需要它的地方创建所需的对象。 是的,这就是我一直在做的事情。但我认为可能有一种更 dplyr-ey 的方式来处理它。那时也许不会。【参考方案2】:

pipeR 是一个在不添加不同管道的情况下扩展管道功能的包(就像magrittr 所做的那样)。要进行分配,您传递一个变量名,用括号括起来 ~ 作为管道中的一个元素:

library(dplyr)
library(pipeR)

df %>>%
  filter(b < 3) %>>%
  (~tmp) %>>% 
  mutate(b = b*2) %>>%
  bind_rows(tmp)
##   a b
## 1 A 2
## 2 B 4
## 3 A 1
## 4 B 2

tmp
##   a b
## 1 A 1
## 2 B 2

虽然语法描述性不强,但pipeR 是very well documented。

【讨论】:

请注意,这种方法似乎不适用于问题中的原始管道(即 `%>%) 对; %&gt;&gt;%%&gt;% 的扩展。从技术上讲,您可以用 pipeR 管道覆盖 magrittr 管道而不会破坏任何东西,但这会使弄清楚为什么代码很奇怪。【参考方案3】:

不会在全局环境中创建对象:

df %>% 
   filter(b < 3) %>% 
    
      . -> tmp  %>% 
     mutate(b = b*2) %>% 
     bind_rows(tmp) 
   

如果您使用 . -&gt;&gt; tmp 而不是 . -&gt; tmp 或将其插入管道,这也可用于调试:

 browser(); .  %>% 

【讨论】:

为什么是右侧赋值->而不是传统的 这只是一个品味问题,分配的工作方式相同,但对我来说,右侧的分配似乎有点像管道的流动:-) 对这个很好的解决方案的重要警告:这仅适用于通过管道传输到 RHS 的原始对象 .。因此,如果您使用names(.) -&gt;&gt; tmp,那么names(.) 将通过管道传递。【参考方案4】:

为了调试,我对这个问题很感兴趣(想要保存中间结果,以便我可以从控制台检查和操作它们,而不必将管道分成很麻烦的两部分。所以,为了我的目的, OP的解决方案原始解决方案的唯一问题是它有点冗长。

这可以通过定义一个辅助函数来解决:

to_var <- function(., ..., env=.GlobalEnv) 
  var_name = quo_name(quos(...)[[1]])
  assign(var_name, ., envir=env)
  .

然后可以按如下方式使用:

df <- data.frame(a = LETTERS[1:3], b=1:3)
df %>%
  filter(b < 3) %>%
  to_var(tmp) %>%
  mutate(b = b*2) %>%
  bind_rows(tmp)
# tmp still exists here

这仍然使用全局环境,但您也可以显式传递更本地的环境,如下例所示:

f <- function() 
    df <- data.frame(a = LETTERS[1:3], b=1:3)
    env = environment()
    df %>%
      filter(b < 3) %>%
      to_var(tmp, env=env) %>%
      mutate(b = b*2) %>%
      bind_rows(tmp)

f()
# tmp does not exist here

accepted solution 的问题在于它似乎无法与 tidyverse 管道一起使用。 G. Grothendieck's solution 根本不适用于调试用例。(更新:请参阅下面 G. Grothendieck 的评论和他的更新答案!)

最后,assign("tmp", .) %&gt;% 不起作用的原因是assign() 的默认 'envir' 参数是“当前环境”(参见documentation for assign),它在管道的每个阶段都不同。要查看这一点,请尝试将 print(environment()); . %&gt;% 插入管道的各个点,并查看每次打印的地址不同。 (可能可以调整 to_var 的定义,以便默认为祖父母环境。)

【讨论】:

查看我在帖子末尾添加的关于如何使用它进行调试的信息。 @G.Grothendieck 非常棒。 . -&gt;&gt; tmp %&gt;% 解决了我的问题并且更简单。 (我已经划掉了我的部分答案,并对你的答案投了赞成票。)【参考方案5】:

我经常发现需要在管道中保存中间产品。虽然我的用例通常是为了避免重复过滤器以供以后拆分、操作和重新组装,但该技术在这里可以很好地工作:

df %>%
  filter(b < 3) %>%
  . ->> intermediateResult %>%  # this saves intermediate 
  mutate(b = b*2) %>%
  bind_rows(intermediateResult)    

【讨论】:

以上是关于作为 dplyr 管道的一部分,将中间输出分配给 temp 变量的主要内容,如果未能解决你的问题,请参考以下文章

%.% (dplyr) 和 %>% (magrittr) 之间的差异

R语言基础知识|dplyr管道函数处理表格

将数据表单元格分配给类

R----dplyr包介绍学习

无法将命名管道名称分配给 LPTSTR 变量

应用的入口——Startup