为什么无法使用 lapply 在包装函数中调用子集

Tim*_*Fan 4 r

subset_base是 r 基函数的简单形式subset()。该示例取自 Advanced R 第 20.6.1 章。

这个函数本身工作得很好:

subset_base <- function(data, rows) {
  rows <- substitute(rows)
  rows_val <- eval(rows, data, parent.frame())
  data[rows_val, , drop = FALSE]
}

my_df <- data.frame(x = 1:3)
  
subset_base(my_df, x == 1)
#>   x
#> 1 1
Run Code Online (Sandbox Code Playgroud)

然而,当我们构建一个包装函数时apply_subset,在这个包装函数中定义一些值并在提供参数时zzz调用(在同一函数中)找不到它。lapply()subset_basezzz

我想更好地理解为什么zzz找不到。我的思维模型如下: subset_base被调用,lapply被调用apply_subset。在subset_base我们内部评估rows所提供的参数,data.frame data该参数会自动转换为环境。该环境由调用环境封装parent.frame()。这应该是lapply. 我认为这个环境再次有lapply作为其父级的调用环境。这将是 的执行环境apply_subset。但事实似乎并非如此,因为如果这是真的,zzz就应该被发现。

apply_subset <- function(){
  zzz <- 2
  dfs <- list(data.frame(x = 1:3), data.frame(x = 4:6))
  lapply(dfs, FUN = subset_base, x == zzz)
}

apply_subset()
#> Error in eval(rows, data, parent.frame()): object 'zzz' not found
Run Code Online (Sandbox Code Playgroud)

lapply更奇怪的是,当我们从subset_base直接提供对象/函数名称改为调用时,将其包装到匿名函数中时,zzz可以发现。

apply_subset2 <- function(){
  zzz <- 2
  dfs <- list(data.frame(x = 1:3), data.frame(x = 4:6))
  lapply(dfs, FUN = \(df) subset_base(df, x == zzz))
}

apply_subset2()
#> [[1]]
#>   x
#> 2 2
#> 
#> [[2]]
#> [1] x
#> <0 rows> (or 0-length row.names)
Run Code Online (Sandbox Code Playgroud)

创建于 2024-01-27,使用reprex v2.0.2

我非常感谢对以下问题的解释:(1)调用堆栈中每个执行环境的父环境是什么,(2)为什么我的假设它们对齐显然是错误的,以及(3)为什么当调用时这种情况会发生变化tolapply使用匿名函数。此外,很高兴知道(4)是否有任何方法可以在不使用匿名函数的情况下调用 lapply(Advanced R 似乎建议(也许)唯一的解决方案是使用 rlangs quosures)。

G. *_*eck 5

关键点是,如果我们相对于父框架计算表达式,它将查看调用堆栈的一层,如果没有找到,将不会进一步查看调用堆栈,而是会查看定义调用者的环境并递归地通过它的祖先。

有了这个背景我们就可以回答以下问题:

(1)调用栈中各个执行环境的父环境是什么,

apply_subset运行时,函数subset_base被传递到lapply,然后内部的代码lapply调用传递的代码subset_base,因此内部的执行环境lapply是父框架,而不是所在的subset_base执行环境。 不在执行环境中,因此它查找的下一个位置是定义位置的父环境(不是调用堆栈的更上方)在这种情况下,它会在基础包中查找,但也不在那里,然后它会查找全局环境和搜索路径上的所有包,但不在那里,因此会出现错误。apply_subsetzzzzzzlapplylapplylapplyzzzzzz

(2) 为什么我关于它们对齐的假设显然是错误的,以及

往上看。

(3) 当调用 lapply 使用匿名函数时,为什么会发生这种变化。此外,很高兴知道,如果

当匿名函数调用时,它会在匿名函数的执行环境中subset_base查找,但由于它不在那里,因此它会查找匿名函数的父环境(而不是调用堆栈的更上方),并且由于匿名函数是在父环境中定义的找到where的执行环境。zzzapply_subsetapply_subsetzzz

(4) 有什么方法可以在不使用匿名函数的情况下使对 lapply 的调用起作用

推广到其他情况的方法是通过envir=像这样的显式参数来传递环境:

subset_base <- function(data, rows, envir = parent.frame()) {   ##
  rows <- substitute(rows)
  rows_val <- eval(rows, data, envir)  ##
  data[rows_val, , drop = FALSE]
}

apply_subset <- function(){
  zzz <- 2
  dfs <- list(data.frame(x = 1:3), data.frame(x = 4:6))
  lapply(dfs, FUN = subset_base, x == zzz, envir = environment()) ##
}

apply_subset()
Run Code Online (Sandbox Code Playgroud)

给予

[[1]]
  x
2 2

[[2]]
[1] x
<0 rows> (or 0-length row.names)
Run Code Online (Sandbox Code Playgroud)