在Tidyverse中进行简单流水线操作时,assign()函数行为不一致。

3
通过简单地改变连接步骤中的参数顺序,我可以让下面的代码运行。在此帖子发布时,我刚刚安装了最新版本的Tidyverse(1.3.1),并使用了R版本4.1.1(2021-08-10),“Kick Things”。
更新:
- 如果您在没有加入语句的情况下运行该管道,则赋值工作正常(奇怪) - 我有一个旧版本的tidyverse(我愚蠢地没有记录),代码可以运行。现在它不能在最新版本的tidyverse上运行。不要太复杂化,但我在另一台机器上使用的是R版本R version 3.6.3(2020-02-29)。
library(dplyr)

#Doesn't run
if(exists("test")) rm("test")
iris%>%
  assign(x = "test",value = .,envir = .GlobalEnv)%>%
  left_join(x = test,y =. ,by="Species")

#Runs
if(exists("test")) rm("test")
iris%>%
  assign(x = "test",value = .,envir = .GlobalEnv)%>%
  left_join(x = .,y =test ,by="Species")


2
我不喜欢你在管道中赋值并尝试在同一管道中后续使用,但我也觉得很困惑,因为它这样做的方式和另一种方式不同。勉强点了个赞。 - Gregor Thomas
我绝对同意,我从来没有喜欢过它。看起来很不自然。我只是更喜欢它比中间步骤,因为这样可以防止混淆,如果我正在运行一些独立的代码块(很容易忘记运行中间步骤)。 - Aegis
1
完全不是重复,但相关阅读:https://dev59.com/vlkS5IYBdhLWcg3wHDDU - Gregor Thomas
1个回答

5

在这里,管道会使事情变得有点混乱,但如果我们将相同的代码编写为嵌套函数,就能达到同样的效果:

#Doesn't run
if(exists("test")) rm("test")
left_join(x = test, y = assign("test", iris, envir = .GlobalEnv), by = "Species")

#Runs
if(exists("test")) rm("test")
left_join(x = assign("test", iris, envir = .GlobalEnv), y = test, by = "Species")

当你看到像这样写出来的时候,第一个版本不运行现在就有了意义:你正在调用不存在的对象上的left_join; 由于left_join是一个S3通用函数,它只评估x以确定方法分派,并将所有其他参数作为未评估的承诺传递给left_join.data.frame。由于y还没有被评估,所以test没有写入,因此我们得到一个test not found错误。
在第二个版本中,y参数直到在left_join.data.frame内部需要它时才被评估,当它被评估时,test已经被写入。
因此,这种奇怪的行为是懒惰评估的结果。

你说得很对,表述得很好。根据我之前的帖子,这一定是在某个时候进行的更改。我认为它应该按照管道的顺序进行评估。这是用户的期望,但是你的逻辑是无可否认的。我在Cross Validated上将其作为错误打开,但也许会将其作为功能请求提出。 - Aegis
1
为了使评估变得迅速,使用magrittr的%!>%管道。 - Aegis
可以这样做。或者根据 docs 中对于急切管道(%!>%)的建议,您可以将 test 包装在 base::force() 中,这样它就可以与标准管道 (%>%) 一起使用了。 - Dan Adams

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