作为 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 %>% filter(b < 3) %>% . ->> tmp %>% mutate(b = b*2) %>% 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。
【讨论】:
请注意,这种方法似乎不适用于问题中的原始管道(即 `%>%) 对;%>>%
是 %>%
的扩展。从技术上讲,您可以用 pipeR 管道覆盖 magrittr 管道而不会破坏任何东西,但这会使弄清楚为什么代码很奇怪。【参考方案3】:
这不会在全局环境中创建对象:
df %>%
filter(b < 3) %>%
. -> tmp %>%
mutate(b = b*2) %>%
bind_rows(tmp)
如果您使用 . ->> tmp
而不是 . -> tmp
或将其插入管道,这也可用于调试:
browser(); . %>%
【讨论】:
为什么是右侧赋值->而不是传统的 这只是一个品味问题,分配的工作方式相同,但对我来说,右侧的分配似乎有点像管道的流动:-) 对这个很好的解决方案的重要警告:这仅适用于通过管道传输到 RHS 的原始对象.
。因此,如果您使用names(.) ->> 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", .) %>%
不起作用的原因是assign()
的默认 'envir' 参数是“当前环境”(参见documentation for assign),它在管道的每个阶段都不同。要查看这一点,请尝试将 print(environment()); . %>%
插入管道的各个点,并查看每次打印的地址不同。 (可能可以调整 to_var 的定义,以便默认为祖父母环境。)
【讨论】:
查看我在帖子末尾添加的关于如何使用它进行调试的信息。 @G.Grothendieck 非常棒。 . ->> tmp %>%
解决了我的问题并且更简单。 (我已经划掉了我的部分答案,并对你的答案投了赞成票。)【参考方案5】:
我经常发现需要在管道中保存中间产品。虽然我的用例通常是为了避免重复过滤器以供以后拆分、操作和重新组装,但该技术在这里可以很好地工作:
df %>%
filter(b < 3) %>%
. ->> intermediateResult %>% # this saves intermediate
mutate(b = b*2) %>%
bind_rows(intermediateResult)
【讨论】:
以上是关于作为 dplyr 管道的一部分,将中间输出分配给 temp 变量的主要内容,如果未能解决你的问题,请参考以下文章