在R函数中创建可重用对象的推荐方法

在R函数中创建可重用对象的推荐方法,r,environment-variables,R,Environment Variables,假设我们有以下数据: # simulate data to fit set.seed(21) y = rnorm(100) x = .5*y + rnorm(100, 0, sqrt(.75)) 我们还假设用户已经安装了一个模型: # user fits a lm mod = lm(y~x) 现在假设我有一个R包,它设计用来对对象mod执行多个操作。为了简单起见,假设我们有两个函数,一个用于绘制数据,另一个用于计算系数。但是,作为中介,假设我们想要对数据执行一些操作(在本例中,添加十) 例如

假设我们有以下数据:

# simulate data to fit
set.seed(21)
y = rnorm(100)
x = .5*y + rnorm(100, 0, sqrt(.75))
我们还假设用户已经安装了一个模型:

# user fits a lm
mod = lm(y~x)
现在假设我有一个R包,它设计用来对对象
mod
执行多个操作。为了简单起见,假设我们有两个函数,一个用于绘制数据,另一个用于计算系数。但是,作为中介,假设我们想要对数据执行一些操作(在本例中,添加十)

例如:

# function that adds ten to all scores
add_ten = function(model) {
  data = model$model
  data = data + 10
  return(data)
}

# functions I defined that do something to the "add_ten" dataset
plot_ten = function(model) {
  new_data = data.frame(add_ten(model))
  x = all.vars(formula(model))[2]
  y = all.vars(formula(model))[1]
  ggplot2::ggplot(new_data, aes_string(x=x, y=y)) + geom_point() + geom_smooth()
}

coefs_ten = function(model) {
  new_data = data.frame(add_ten(model))
  coef(lm(formula(model), new_data))
}
(显然,这样做很愚蠢。实际上,我想要执行的操作是多重插补,这是计算密集型的)

注意,在上面的示例中,我必须调用
add_ten
函数两次,一次用于plot_ten,一次用于coefs_ten。这是低效的

那么,现在我的问题是,在函数中创建可重用对象的最佳方法是什么?

当然,我可以:

我很高兴这样做,但我担心的是在用户环境中放置东西的“网络礼仪”。如果用户的环境中碰巧有一个名为“add_ten_data”的对象,那么还有一个潜在的问题

那么,实现这一目标的最佳方式是什么


提前谢谢

您当然应该避免将对象写入全局环境。如果您发现必须在许多不同函数的顶部重复相同的计算代价高昂的任务,这意味着您执行计算代价高昂的任务太晚了

例如,您可以创建一个S3类,该类包含生成“廉价”绘图和系数“廉价”提取所需的组件。它甚至还具有通用调度的优点:


add_ten您当然应该避免将对象写入全局环境。如果您发现必须在许多不同函数的顶部重复相同的计算代价高昂的任务,这意味着您执行计算代价高昂的任务太晚了

例如,您可以创建一个S3类,该类包含生成“廉价”绘图和系数“廉价”提取所需的组件。它甚至还具有通用调度的优点:


add_ten调用
add_ten()
函数一次,然后将结果传递到
plot_ten
coefs_ten
函数中。函数不应该创建全局变量,函数假设存在某些全局变量不是一个好主意。同意。通常认为函数是自包含的最佳实践。输入被传入,结果被返回。这可以防止出现奇怪的行为,并使以后更容易更新代码。因此,将
new\u data=data.frame(add\u ten(model))
移动到函数之外。然后通过传入
新的\u数据来运行函数,而不是传入
模型
。如果需要,您可以在处理完新的_数据后将其删除。谢谢您的评论。我本来希望避免在用户端执行额外的步骤,但在不违反最佳实践的情况下,这似乎是不可避免的。请调用
add______________________________________________。函数不应该创建全局变量,函数假设存在某些全局变量不是一个好主意。同意。通常认为函数是自包含的最佳实践。输入被传入,结果被返回。这可以防止出现奇怪的行为,并使以后更容易更新代码。因此,将
new\u data=data.frame(add\u ten(model))
移动到函数之外。然后通过传入
新的\u数据来运行函数,而不是传入
模型
。如果需要,您可以在处理完新的_数据后将其删除。谢谢您的评论。我希望避免在用户端增加一个步骤,但在不违反最佳实践的情况下,这似乎是不可避免的。我见过函数做过类似的事情(例如,mice包和norm包),并且总是觉得两阶段的过程有点令人沮丧。但是,我认为一个很好的替代方法与您的建议类似:不需要lm_tens函数,但是如果他们调用了它,就使用它(否则,重复add_ten)。@dfife这取决于您对对象的可能用途。我今天遇到了一个来自
eulerr
包的示例。
euler
类包含几个不同的轻量级字段,可以方便地将它们捆绑到一个类中,但大部分工作都是稍后完成的;
plot.euler
函数非常昂贵,因此绘制绘图所需的结构仅在调用
plot
时生成。另一方面,大多数回归函数在一开始就做了计算量大的部分,你可以把模型传给别人,知道以后做任何工作都会很便宜。让我再详细一点(以防我忽略了一些明显的解决方案)。我的产品包接受从Lavan产品包安装的型号。(拉万不是我的包裹)。我的一些函数需要计算标准误差(通过计算密集的多重插补估计)。我无法将这些标准误差附加到已经估计的Lavan模型,因此我一直在为每个函数计算它们。但是,对于不同的函数,相同的标准误差计算可能会发生多次。因此,关于将它们放置在全局环境中的问题是:)@dfife那么为什么不创建一个类来包装对象并将其作为成员保存,但同时保存标准错误呢?假设您的类称为“Dfie_类”,包含一个成员“model”,该成员是一个对象,以及一个成员“SE”,该成员具有计算出的标准错误。在每个函数的开头,检查是否已向其传递了lavaan对象或“Dfie_类”对象。如果是拉万对象,则计算SE并转动拉万
add_ten = function(model) {
  # check for add_ten_data in the global environment
  if (exists("add_ten_data", where = .GlobalEnv)) return(get("add_ten_data", envir = .GlobalEnv))
  data = model$model
  data = data + 10
  # assign add_ten_data to the global environment
  assign('add_ten_data', data, envir = .GlobalEnv)
  return(data)
}