假设我们有以下数据:
# 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 = 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)
}
我很高兴这样做,但是担心将某些东西放到用户环境中的“礼节”。如果用户在其环境中碰巧有一个名为“ add_ten_data”的对象,则还存在一个潜在的问题。
那么,实现此目标的最佳方法是什么?
提前致谢!
你当然应该避免将对象写入全局环境。如果你发现必须在多个不同功能的顶部重复执行相同的计算量大的任务,则意味着你执行该计算量大的任务为时已晚。
例如,你可以创建一个S3类,其中包含必要的分量以生成“便宜”图和系数的“便宜”提取。它甚至具有通用调度的优点:
add_ten <- function(model) model$model + 10
lm_tens <- function(formula, data)
{
model <- if(missing(data)) lm(formula) else lm(formula, data = data)
structure(list(data = data.frame(add_ten(model)), model = model),
class = "tens")
}
plot.tens <- function(tens) {
x = all.vars(formula(tens$data))[2]
y = all.vars(formula(tens$data))[1]
ggplot2::ggplot(tens$data, ggplot2::aes(x = x, y = y)) +
ggplot2::geom_point() +
ggplot2::geom_smooth()
}
coef.tens = function(tens) {
coef(lm(formula(tens$model), data = tens$data))
}
所以现在我们只需要做:
set.seed(21)
y = rnorm(100)
x = .5*y + rnorm(100, 0, sqrt(.75))
mod <- lm_tens(y ~ x)
coef(mod)
#> (Intercept) x
#> 4.3269914 0.5775404
plot(mod)
#> `geom_smooth()` using method = 'loess' and formula 'y ~ x'
请注意,我们add_ten
在这里只需要调用一次即可。
我见过函数可以做这样的事情(例如,mouses包和norm包),并且总是发现两阶段过程有些令人沮丧。但是,我认为一个不错的选择类似于您提出的建议:不需要lm_tens函数,但是如果他们已经调用了它,就可以使用它(否则,请重复add_ten)。
@dfife,这取决于您对该对象有什么可能的用途。我今天从
eulerr
包装中看到了一个例子。本euler
类包含,使得它便于将其打包成一个班上有几个不同的轻质领域,但工作的最多的是后进行; 该plot.euler
函数非常昂贵,因此绘制绘图所需的结构仅在plot
调用时生成。另一方面,大多数回归函数从一开始就在计算上花了很多钱,因此您可以将模型传递出去,因为知道以后进行任何工作将很便宜。让我给出更多细节(以防万一我忽略了一些明显的解决方案)。我的包裹接受lavaan包裹拟合的模型。(lavaan不是我的包裹)。我的某些功能需要计算标准误差(通过计算复杂度的多重插补估算)。我无法将这些标准错误附加到已经估算的lavaan模型上,因此我一直在为每个函数计算它们。但是,对于不同的功能,相同的标准误差计算可能会多次发生。因此,有关将它们放置在全球环境中的问题:)
@dfife那么为什么不创建一个包装lavaan对象并将其作为成员保存但又包含标准错误的类呢?假设您的类称为“ dfife_class”,并且包含一个成员“模型”(其为lavaan对象)和一个成员“ SE”(其具有计算得出的标准误差)。在每个函数的开头,检查是否已传递了lavaan对象或“ dfife_class”对象。如果是lavaan对象,则计算SE并将lavaan变成“ dfife_class”对象。然后编写您的函数来处理“ dfife_class”对象
我明白你在说什么。是的,这是一个好主意,它将节省额外的步骤(假设用户适合lm_tens而不是lm)。谢谢!