Rob
Rob

Reputation: 1490

Trouble passing on an argument to function within own function

I am writing a function in which I want to pass some arguments to the crrstep-function ('crrstep' package), but I encountered a problem: somehow the argument 'event' in my function is not recognized when I enter it in crrstep. I guess that crrstep looks in a different environment than the one I want it to look, but even after hours of searching for solutions on the web, I cannot seem to figure out how to solve this (I am quite inexperienced in programming..). Any help would be greatly appreciated!

Here is some simulation data (adjusted example from crrstep-documentation) and an example of my code:

n <- 500
ftime <- rexp(n)
fstatus <- sample(0:2,n,replace=TRUE)
testdata <- matrix(runif(8*n),nrow=n)
testdata <- cbind(ftime,fstatus,testdata)
dimnames(testdata)[[2]] <- c('ftime','fstatus','x1','x2','x3','x4','x5','x6','x7','x8')
testdata <- as.data.frame(testdata)
formula1 <- ftime ~ 1 + x1 + x2 + x3 + x4 + x5 + x6 + x7 + x8
rm(fstatus,ftime,n)

test.fun <- function(x,data,event){
require(crrstep)
select.mod<- crrstep(formula=x,,etype=event, failcode=1, cencode=0,data=data, 
                   direction = "backward", criterion = "AIC", crr.object = TRUE, 
                   trace = FALSE)
#Rest of function omitted for now
print(select.mod)
}

#Test
test.fun(x=formula1,data=testdata,event=fstatus) 
#I get: Error in eval(expr, envir, enclos) : object 'event' not found"

Many thanks! Rob

Upvotes: 3

Views: 5396

Answers (2)

Aaron - mostly inactive
Aaron - mostly inactive

Reputation: 37744

When calling functions within functions that depend on evaluating names within a data frame, I use do.call, which evaluates its arguments before passing to the function and so makes both debugging and writing the code simpler, and I feel like I can be more sure about what it's doing. (For debugging, just use call instead of do.call, which will show what the function will try to run; the syntax is also a little different so when doing this remove the list structure within the call as well.)

(Credit to Josh O'Brien's answer here for this idea: https://stackoverflow.com/a/7668846/210673)

In this case, it would look like this:

test.fun <- function(x, data, event){
  require(crrstep)
  select.mod <- do.call("crrstep", 
         list(formula=x, etype=substitute(event), failcode=1, cencode=0,
              data=as.name("data"), direction = "backward", criterion = "AIC", 
              crr.object = TRUE, trace = FALSE))
  print(select.mod)
}

test.fun(x=formula1, data=testdata, event=fstatus) 

The substitute(event) tells it to use the name that was given to the function, not the name event. The as.name("data") tells it to look for data within the function instead of passing the actual data frame. Another option is substitute(data) which will look for the actual data frame you have.

an example using lm

Here's an example of very similar behavior using lm and the weights argument:

Here's an example data set and the call to lm, not within another function. I print the call element of the response to see what it actually did.

> set.seed(5)
> dd <- data.frame(x=1:10,y=round(rnorm(10,mean=10),1), z=round(runif(10,1,4),1))
> lm(y~x, weights=z, data=dd)$call
lm(formula = y ~ x, data = dd, weights = z)

The natural way, which doesn't work because it's looking for w in the data frame:

> f1 <- function(f,w,d){
+   lm(formula=f,weights=w, data=d)
+ }
> f1(y~x, z, dd)
Error in eval(expr, envir, enclos) : object 'w' not found

One can build the call with strings; this is a little more straightforward:

> f2 <- function(f,w,d){
+   do.call("lm", list(formula=as.formula(f), weights=as.name(w), data=as.name(d)))
+ }
> f2("y~x", "z", "dd")$call
lm(formula = y ~ x, data = dd, weights = z)

Or one can use substitute; here I'm calling the function on my actual data set dd, not the d within the function. This might be useful later if I want to use update.

> f3 <- function(f,w,d){
+   do.call("lm", list(formula=f, weights=substitute(w), data=substitute(d)))
+ }
> f3(y~x, z, dd)$call
lm(formula = y ~ x, data = dd, weights = z)

But I could also use d within the function; this time notice that data = d in the call instead of data = dd.

> f4 <- function(f,w,d){
+   do.call("lm", list(formula=f, weights=substitute(w), data=as.name("d")))
+ }
> f4(y~x, z, dd)$call
lm(formula = y ~ x, data = d, weights = z)

It works to put in the actual data frame as well, but the call is longer. This however, might be desired if you're changing the data frame programatically before each call and want to have a record of what that data frame is. (My preference, though, would be to save that data frame in a more explicit way, if you really do want it later.)

> f5 <- function(f,w,d){
+   do.call("lm", list(formula=f, weights=substitute(w), data=d))
+ }
> f5(y~x, z, dd)$call
lm(formula = y ~ x, data = list(x = 1:10, y = c(9.2, 11.4, 8.7, 
10.1, 11.7, 9.4, 9.5, 9.4, 9.7, 10.1), z = c(3.7, 3.2, 1.6, 1.7, 
1.4, 2.4, 2.3, 3.9, 1.4, 3.9)), weights = z)

One more to show that you can't just use substitute without do.call as the substitute is executed within the call to lm.

> f6 <- function(f,w,d){
+   lm(formula=f,weights=substitute(w), data=d)
+ }
> f6(y~x, z, dd)
Error in model.frame.default(formula = f, data = d, weights = substitute(w),  : 
  invalid type (symbol) for variable '(weights)'

Upvotes: 6

juba
juba

Reputation: 49033

I don't clearly understand why, but it seems to fail if you pass your etype argument as an indexed data frame. However it seems to work if you create the vector beforehand :

test.fun <- function(x,data,event){
  require(crrstep)
  etype <- data[,event]
  select.mod<- crrstep(formula=x,data=data,etype=etype, failcode=1, cencode=0, 
                   direction = "backward", criterion = "AIC", crr.object = TRUE, 
                   trace = FALSE)
#Rest of function omitted for now
print(select.mod)
}

Upvotes: 0

Related Questions