我有一个模型构建函数,其中公式可以包含一些函数,我希望它工作,如果用户多次输入函数,只有第一次使用警告。例如,在lm
中如果我们使用相同的变量两次,则删除第二个变量:
y<-1:3
x<-1:3
lm(y~x+x)
Call:
lm(formula = y ~ x + x)
Coefficients:
(Intercept) x
0 1
这是有效的,因为terms
中使用的函数model.frame
删除了具有相同名称的变量。但在我的情况下,我正在使用公式中的函数,这些函数不一定具有相同的参数列表,我希望这种行为能够扩展,以便这些函数的参数无关紧要:
model(y~x+fn("x"))
(Intercept) x temp
1 1 1 1
2 1 2 1
3 1 3 1
model(y~x+fn("x")+fn("x")) #identical function calls
(Intercept) x temp
1 1 1 1
2 1 2 1
3 1 3 1
model(y~x+fn("x")+fn("z")) #function with different argument value
Error in attr(all_terms, "variables")[[1 + ind_fn]] :
subscript out of bounds
这是我上面使用的示例函数(高度简化):
model <- function(formula, data) {
#the beginning is pretty much copied from lm function
mf <- match.call(expand.dots = FALSE)
mf <- mf[c(1L, match(c("formula", "data"), names(mf), 0L))]
mf[[1L]] <- as.name("model.frame")
mf$na.action <- as.name("na.pass")
all_terms <- if (missing(data)){
terms(formula, "fn")
} else terms(formula, "fn", data = data)
#find the position of the function call in the formula
ind_fn <- attr(all_terms, "specials")$fn
#update the formula by removing the "fn" part
if(!is.null(ind_fn)){
fn_term <- attr(all_terms, "variables")[[1 + ind_fn]]
formula <- update( formula, paste(". ~ .-", deparse(fn_term,
width.cutoff = 500L, backtick = TRUE)))
mf$formula<-formula
}
# build y and X
mf <- eval(mf, parent.frame())
y <- model.response(mf, "numeric")
mt <- attr(mf, "terms")
X <- model.matrix(mt, mf)
#if fn was in formula do something with it
if (!is.null(ind_fn)){
foobar<-function(type=c("x","z")){
if(type=="x"){
rep(1,nrow(X))
} else rep(0,nrow(X))
}
fn_term[[1]]<-as.name("foobar")
temp<-eval(fn_term)
X<-cbind(X,temp)
}
X
}
我可以检查特殊名称(函数调用)并将它们重命名为与第一次出现相同,但我想知道是否会有更聪明的方法处理它?
答案 0 :(得分:1)
我无法让您的代码正常工作,但假设我已经理解了您的任务,也许这样的事情可以实现您所追求的目标。
f <- y ~ x + fn("x") + fn("z") + z + fn('a')
# get list of terms
vars <- as.list(attr(terms(f), 'variables'))
# get those terms that are duplicate calls
redundant <- vars[sapply(vars, is.call) & duplicated(sapply(vars, function(x) as.list(x)[[1]]))]
# remove the duplicate calls from the formula
update(f, paste(". ~ .", paste(sapply(redundant, deparse), collapse='-'), sep='-'))
# y ~ x + fn("x") + z