R 有时无法评估从字符串解析的表达式

R sometimes fails to evaluate expressions parsed from strings

我有一个庞大的数据框,我需要在其中创建“滞后”变量并将它们与以前的时间点进行比较。由于这个过程需要可变,我选择编写自己的函数来创建这些滞后变量(此处未包含)。

当我使用 GLM 时,我想使用 stepAIC 函数,在开始编写“lag01 + lag02 ...”的十分之一之前,我想创建另一个函数(modelfiller),它根据我的参数创建这些字符串,然后我用 string2lang 使它们成为表达式。

这大部分都有效,但有一个问题我无法解决。

正如你在 reprex 中看到的那样,当我只使用 y~x+lag01+lag02 时可以创建 full.model。如果我在位置 1 和 3 使用 modelfiller("y", 2, "x", "lag") 它也可以。但是当我将 modelfiller("y", 2, "x", "lag") 放在代码中的位置 2 时(在 stepAIC glm 中),它会创建以下错误消息:

Error: Problem with `mutate()` input `GLM_AIC`.
x object '.x' not found
i Input `GLM_AIC` is `purrr::map(...)`.
i The error occurred in group 1: group = "a".

我也试过 as.formula 有和没有 eval,但它导致了同样的问题。

group <- c(rep("a", 10), rep("b", 10),  rep("c", 10))
order <- c(seq(1:10), seq(1:10), seq(1:10))
x <- c(runif(30))
y <- c(runif(30))

df <- data.frame(group, order, x, y)

df <- df  %>% 
  dplyr::group_by(group) %>% 
  dplyr::arrange(group, order) %>% 
  dplyr::mutate(lag01 = dplyr::lag(x, n=1),
                lag02 = dplyr::lag(x, n=2)) %>% 
  tidyr::drop_na() 


modelfiller = function(depPar, maxlag, indepPar, str) {
  varnames = list()
  for (i in seq(1:maxlag)) {
    varnames[i] = paste0(str, stringr::str_pad(i, width = 2, pad = "0"))
  }
  varnames = paste0(varnames, collapse="+")
  varnames = paste(indepPar, varnames, sep = "+")
  return(paste(depPar, varnames, sep = "~"))
}


full.model <- df %>%
  tidyr::nest(- group) %>%
  dplyr::mutate(
    # Perform GLM calculation on each group and then a step-wise model selection based on AIC
    GLM = purrr::map(
      data, ~ lm(data = .x, 
                 # Location 1 - Working
                 str2lang(modelfiller("y", 2, "x", "lag"))
                 #y~x+lag01+lag02
                 )),
    GLM_AIC = purrr::map(
      data, ~ MASS::stepAIC(glm(data = .x,
                                # Location 2 - NOT Working
                                str2lang(modelfiller("y", 2, "x", "lag"))
                                #y~x+lag01+lag02
                                )
                            ,direction = "both", trace = FALSE, k = 2,
                            scope = list(
                              lower = lm(data = .x, 
                                         y ~ 1),
                              upper = glm(data = .x,
                                          # Location 3 - Working
                                          str2lang(modelfiller("y", 2, "x", "lag"))
                                          #y~x+lag01+lag02
                                          )
                            )))
  )

问题是 glm 存储了用于引用数据的变量名称,然后 stepAIC 尝试检索该名称并对其求值以访问数据,但对定义变量的环境。为了演示,我将把你的代码简化为

mdl <- str2lang(modelfiller("y", 2, "x", "lag"))      # This is your y~x+lag01+lag02
dfn <- df %>% tidyr::nest( data = c(-group) )         # First step of your %>% chain
glms <- purrr::map( dfn$data, ~glm(data = .x, mdl) )  # Construct the models

# Examine glms to observe that
# Call:  glm(formula = mdl, data = .x)    <--- glm() remembers that the data is in .x

# but stepAIC is not properly aware of where .x
# is defined and behaves effectively as
MASS::stepAIC( glms[[1]] )                            # Error: object '.x' not found

选项 1

一种解决方法是 :

glm2 <- function(.df, ...) {
  eval(rlang::expr(glm(!!rlang::enexpr(.df),!!!list(...)))) }

glms2 <- purrr::map( dfn$data, ~glm2(data = .x, mdl) )  # Same as above, but with glm2
MASS::stepAIC( glms2[[1]] )                             # Now works

在有问题的地方将 glm 更改为 glm2 也会使您的代码正常工作。缺点是 Call: 然后会记住整个数据框,如果它们非常大,这可能会有问题。

选项 2

另一种方法是用 for 循环替换 purrr 调用,这有助于维护 stepAIC 假定的调用帧,从而将其引导至定义数据的位置

# This fails with Error: object '.x' not found
purrr::map( dfn$data, ~MASS::stepAIC(glm(data=.x, mdl), direction="both") )

# This works
for( mydata in dfn$data )
    MASS::stepAIC(glm(data=mydata, mdl), direction="both")

这里的优点是不需要在调用中存储整个数据帧。缺点是您实际上无法访问 purrr 为简化代码所做的工作。