【问题标题】:R pipe, mget, and environmentsR 管道、mget 和环境
【发布时间】:2021-01-21 16:33:07
【问题描述】:

我发布这个是希望有人可以解释这里的行为。也许这可以为其他人节省一些时间来跟踪如何修复类似的错误。

答案可能在this vignette by Hadley Wickham and Lionel Henry 的某个地方。然而,像我这样的人需要数周的学习才能将这些点联系起来。

我正在从远程数据库运行多个查询,然后将它们组合到一个 data.table 中。我将“part_”前缀添加到每个查询结果的名称中,并将ls()mget() 与data.table 的rbindlist() 结合使用。

这行得通:

results_all <- rbindlist(mget(ls(pattern = "part_", )))

我可能从list data.tables in memory and combine by row (rbind) 学到了这种方法,知道如何做是很有帮助的。

为了可读性,我通常更喜欢使用 magrittr 管道(或与 data.table 链接),尤其是像这样的项目,因为我使用 dplyr 来查询数据库。然而这段代码会导致错误:

results_all <- ls(pattern = "part_", ) %>% 
 mget() %>%
 rbindlist()

错误为Error: value for ‘part_a’ not found,其中part_a 是ls() 返回的字符向量中的第一个对象名称。

搜索该错误消息,我遇到了in this data.table Github issue 的讨论。通过阅读,我尝试在 mget() 中设置“inherits = TRUE”,如下所示:

results_all <- ls(pattern = "part_", ) %>% 
 mget(inherits = TRUE) %>%
 rbindlist()

这行得通。因此,将ls() 的结果传送到mget() 时会发生错误。鉴于在 mget() 中嵌套 ls() 是可行的,我的猜测是它与管道和“环境的封闭框架”有关。

在写这篇文章时,我遇到了Unexpected error message while joining data.table with rbindlist() using mget()。从那里的讨论中我发现这也有效。

results_all <- ls(pattern = "part_", ) %>% 
 mget(envir = .GlobalEnv) %>%
 rbindlist()

再次,我希望有人可以为希望了解更多有关 R 环境如何工作的人们解释正在发生的事情。

编辑:添加可重现的示例

根据对可重现答案的请求,使用这三个 data.tables 运行上面的代码(data.frames 或 tibbles 的行为相同)应该可以做到。

part_a <- data.table(col1 = 1:10, col2 = sample(letters, 10))

part_b <- data.table(col1 = 11:20, col2 = sample(letters, 10))
  
part_c <- data.table(col1 = 21:30, col2 = sample(letters, 10)) 

【问题讨论】:

  • 请创建一个可重现的示例,包括所有输入和library 语句。有关发布说明,请参阅r 标签页顶部的信息。
  • 好的,我加了一个。看起来@bcarlsen 已经解释了正在发生的事情。我认为在将 ls() 返回的任何对象名称传递给 mget() 时,您会看到相同的行为。
  • 在您链接的论文中,您感兴趣的是有关词汇副作用的部分。阅读 Hadley 的 Advanced R 中有关环境的部分也会有所帮助。
  • 谢谢@Moody_Mudskipper。 Advanced R 的解释很好,并且使用简单的语言。感谢您指点我那里(我也从您的 Twitter 帖子中学到了很多东西)。这些年来,我从 SO 那里学到了很多技巧。考虑到我在 bcarlsen 的回答下的评论中引用的“建立理解”SO 博客文章,我想知道为什么我的问题到目前为止被两次否决。也许这个问题对许多人来说是显而易见的。想到人们从电子表格过渡到 R,我会非常怀疑。
  • 我很高兴能帮助科里。我认为你不应该投反对票,而且这个问题一点也不明显,所以不要难过。我相信你被否决了,因为你的例子不是最小的,关于数据库、rbindlist、数据框的所有部分都与问题无关。我知道您想设置上下文,但 x &lt;- 1; y &lt;-2; ls() %&gt;% mget() 在这里就足够了。

标签: r data.table magrittr mget


【解决方案1】:

管道运算符的rhs 参数(在您的示例中,表达式mget())永远不会被解释器评估为函数调用。管道运算符是一个中缀函数,它对其第二个参数 (rhs) 执行非标准评估。管道函数使用 RHS 表达式作为一种“模板”组合并执行一个新的函数调用。

这个新函数调用的调用环境是%&gt;%的函数环境,而不是lhs函数的调用环境或全局环境。 .GlobalEnvlhs 函数的调用环境在您的示例中恰好是相同的环境,并且该环境是 %&gt;% 的函数环境的父级,这就是为什么 inherits = TRUE 或将环境设置为 @ 987654332@ 为您工作。

【讨论】:

  • 谢谢@bcarlsen。这为未来的研究提供了一些线索。我想我不明白为什么这种情况不会发生更多。是不是因为大多数函数都继承了全局环境,而 mget() 的默认值没有? magrittr.tidyverse.org 的概述说 x %&gt;% f is equivalent to f(x)。然后它说由于非标准评估,这“在技术上并不准确”。然而,它也说它在许多情况下“没有实际意义”。我不明白为什么首先评估 ls() 会导致这里出现问题。我使用 %&gt;% 有一段时间了,以前没有遇到过这个问题。跨度>
  • 在 R 中,函数环境中的搜索路径包括所有父环境。 mget() 是不寻常的(但几乎不是唯一的),因为它专门设计为不使用默认搜索路径,并且默认仅在特定环境的命名空间中查找。使用getmget() 是一个相当深奥的设计选择。问题不在于评估内容的顺序。问题是管道运算符是一个函数。您可以使用%&gt;% 在其函数环境中编写对mget() 的函数调用。 mget() 的默认行为是只查看其调用环境。
  • 在评估变量时使用搜索路径,因此棘手的函数将是使用 NSE 并在意想不到的地方进行评估的函数。 mget 采用变量名称,而不是变量,并且它具有环境属性,因此它相当明确。然而,不明确的是管道评估其步骤的位置。默认情况下,mget 将在其父框架中查找变量,这不是您运行 ls() 的地方,尽管它很可能采用不同的设计(因此您链接的论文)。
  • 您也可以尝试"part_" %&gt;% ls(pattern =.),它会向您展示这种反直觉行为的另一个示例。
  • 谢谢你们。变量/对象及其名称之间的区别很难掌握。巧合的是,SO 正在为我展示[这篇关于建立理解与快速学习的博客文章](stackoverflow.blog/2020/10/05/…) 以及这篇文章。当 R 社区出来帮助像我这样的非程序员类型建立理解时,它真的很有帮助。所以谢谢你。
猜你喜欢
  • 1970-01-01
  • 2021-08-13
  • 1970-01-01
  • 2017-08-09
  • 2018-04-19
  • 2017-02-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多