避免与用户提供的变量名冲突的最佳实践 - R

Best practice to avoid conflict with user supplied variable names - R

我正在尝试为一个包创建一个 R 函数,它将获取用户数据和公式(的右侧),进行一些处理,然后 return 一个模型。但是,当用户数据或公式包含与我在内部使用的名称相同的变量时,我遇到了麻烦。一个可重现的例子,

(请注意,需要更新公式的环境以防止 R 在用户的 R_GlobalEnv 中查找我的变量 y。)

# R Version 3.6.2
my_function <- function(user_data, user_formula){
  y <- as.numeric(user_data[,1] > mean(user_data[,1]))

  my_formula <- update.formula(user_formula, y ~ .)
  environment(my_formula) <- environment()

  my_model <- lm(my_formula, data = user_data, model = TRUE)
  return(my_model)
}

some_data <- data.frame(x1 = c(1,2,3,3))
some_formula <- response ~ x1
my_function(some_data, some_formula)

以上是我想要的 运行,只要 user_formula 或 user_data 中没有名为 "y" 的变量,它就可以工作。但是当 user_data 包含同名变量时,模型将使用该变量而不是我的变量。

some_data <- data.frame(x1 = c(1,2,3,3), y = c(6,7,5,6))
some_formula <- response ~ x1 + y
my_function(some_data, some_formula)$model
#   y x1
# 1 6  1
# 2 7  2
# 3 5  3
# 4 6  3
# Warning messages:
# 1: In model.matrix.default(mt, mf, contrasts) :
#   the response appeared on the right-hand side and was dropped
# 2: In model.matrix.default(mt, mf, contrasts) :
#   problem with term 2 in model.matrix: no columns are assigned

我尝试使用 get() 强制 R 在函数的环境中搜索 y,

my_function <- function(user_data, user_formula){
  y <- as.numeric(user_data[,1] > mean(user_data[,1]))

  e1 <- environment()
  my_formula <- update.formula(user_formula, get("y", e1) ~ .)
  environment(my_formula) <- environment()

  my_model <- lm(my_formula, data = user_data, model = TRUE)
  return(my_model)
}

some_data <- data.frame(x1 = c(1,2,3,3), y = c(6,7,5,6))
some_formula <- response ~ x1 + y
my_function(some_data, some_formula)$model
#   get("y", e1) x1 y
# 1            0  1 6
# 2            0  2 7
# 3            1  3 5
# 4            1  3 6

但是如果用户数据有一个与我的内部环境名称同名的变量,这也会失败,

some_data <- data.frame(x1 = c(1,2,3,3), y = c(6,7,5,6), e1 = c(1,2,3,4))
some_formula <- response ~ x1 + y + e1
my_function(some_data, some_formula)$model
#  Error in get("y", e1) : invalid 'envir' argument 

避免内部变量与用户提供的变量名称重叠的正确方法是什么?如果可能的话,我更喜欢基于 R 的方法。

Per docs of lmdata 参数以两种方式处理公式中的变量,即不互斥:

data
an optional data frame, list or environment (or object coercible by as.data.frame to a data frame) containing the variables in the model. If not found in data, the variables are taken from environment(formula), typically the environment from which lm is called.

具体来说,函数中计算的 y 向量与数据框中潜在的 y 列发生名称冲突。正如您注意到的以及上面文档中强调的那样,lm 将默认为 y 向量之前的 y 列。

为避免命名冲突,请考虑添加合适的占位符,例如 responsedependent_variable 并引发 tryCatch 如果用户提供具有相同列名的数据框,则会发出警告。这种方法还允许您避免重置 environment(formula).

my_function <- function(user_data, user_formula){
  tryCatch({
       user_data$response <- as.numeric(user_data[,1] > mean(user_data[,1]))

       my_formula <- update.formula(user_formula, response ~ .)

       my_model <- lm(my_formula, data = user_data, model = TRUE)

       return(my_model)
  }, warning = function(w) message("Please rename column 'response' in your data frame")
   , error = function(e) print(e)
  )
}

输出

some_data <- data.frame(x1 = c(1,2,3,3))
some_formula <- response ~ x1
my_function(some_data, some_formula)

# Call:
# lm(formula = my_formula, data = user_data, model = TRUE)

# Coefficients:
# (Intercept)           x1  
#     -0.7273       0.5455  

some_data <- data.frame(x1 = c(1,2,3,3), y = c(6,7,5,6), e1 = c(1,2,3,4))
some_formula <- response ~ x1 + y + e1
my_function(some_data, some_formula)

# Call:
# lm(formula = my_formula, data = user_data, model = TRUE)

# Coefficients:
# (Intercept)           x1            y           e1  
#   1.667e+00    3.850e-16   -3.333e-01    3.333e-01  

some_data <- data.frame(x1 = c(1,2,3,3), y = c(6,7,5,6), response=c(1,1,1,1))
some_formula <- response ~ x1 + y + response
my_function(some_data, some_formula)
# Please rename column 'response' in your data frame