IMPACT is a joint venture of the University of North Carolina at Chapel Hill, Duke University, and North Carolina State University. The program project aims to improve the health and longevity of people by improving the clinical trial process. A key component of this research has been the development of public-use software packages that implement new statistical methods developed by the 30\(+\) investigators. Whenever possible, these methods have been developed in . The library was born from the need to create simple, general-use implementations of new statistical methods that do not limit the underlying regression method(s) and do not require continued upgrading as new regression methods become available.
When creating packages for statistical methods developed on the framework of traditional regression or classification methods, researchers and/or software developers often make choices regarding the types of models that can be specified by the user; hard-coding the regression method into the library and limiting or eliminating the ability of the user to modify regression control parameters. However, the choice of a specific regression method may not be fundamental constraint of the new method, and such choices can limit the general application of an implementation.
In addition, a new method may require multiple regression steps. For example, implements the Augmented Inverse Probability Weighted Estimators (AIPWE) for average treatment effects and requires multiple regression analyses. To implement this method generally without using the framework described herein would require that the procedure be artificially broken into multiple function calls, each for a specific regression step, or that the user interface to the method be cumbersome and/or confusing.
The library is built on the premise of a ``model object.” A model object contains all of the information needed to complete a standard regression analysis and subsequent prediction step: a formula object, the existing regression method to be used to obtain parameter estimates (the so-called solver method), any control arguments to be passed to the regression method, the method to be used to obtain predictions, and any arguments to be passed to the prediction method. This information is grouped into a single object of class by a call to . To use a package built on the model object framework, the user creates the prior to calling the statistical method and passes the model object as input. The library provides simple functions that developers can use to implement standard regression procedures, such as to obtain parameter estimates and to obtain predictions.
Users of packages that have been developed based on the model object framework will interface with the library through calls to . These calls create a model object for a single regression step and are passed as input to the method. The function takes as inputUnless modified through and , default settings are assumed for the methods specified in and .
As a simple example,
defines a model object for a linear model, the parameter estimates of which are to be obtained using , and predictions obtained using . The solver and prediction methods will use default settings.
As a more complex (though contrived) example, consider the following functions
mylm <- function(X,Y){
obj <- list()
obj$lm <- lm.fit(x=X, y=Y)
obj$var <- "does something neat"
class(obj) = "mylm"
return(obj)
}
predict.mylm <- function(obj,data=NULL){
if( is(data,"NULL") ) {
obj <- exp(obj$lm$fitted.values)
} else {
obj <- data %*% obj$lm$coefficients
obj <- exp(obj)
}
return(obj)
}which, for the sake of argument, represent a ``new” regression method that a user would like to utilize. These functions are chosen to illustrate solver methods and prediction methods that do not accept the standard formal arguments. They provide a simple illustration of how flexible the framework can be. In this circumstance, the user would define the following modeling object:
object2 <- buildModelObj(model = ~x1,
solver.method = mylm,
solver.args = list('X' = "x", 'Y' = "y"),
predict.method = predict.mylm,
predict.args = list('obj' = "object",
'data' = "newdata"))The function invoked by a user returns an object of class .
Developers that use this utility package should carefully document for users any required settings for and . For example, the scale of the response needed for predictions. Though the developer can access and modify the argument lists provided by users using methods and , there is no strict variable naming convention in , and some methods do not adhere to the ``usual” choices. Thus, identifying the formal argument to adjust may be tricky.
Once provided an object of class modelObj, developers can see all of the values contained in the object but can modify only the argument lists to be passed to methods. Specifically:
The primary utility method available for objects of class is , which implements the regression step. The inputs for are:
The method constructs and executes the function call to the specified solver method using the formula object and formal arguments provided by the user in solver.args. The method uses an internal naming convention for the response, and thus only the right-hand-side of the formula object is referenced.
returns an S4 object of class . Developers can access members of this class using the following methods:Note that predictor and predictorArgs only give you access to see what has been specified for the prediction method. Should changes need to be made to the arguments, one must apply these changes to the defining modelObj before creating the modelObjFit object.
Additional methods available for objects areAgain, the value object returned by the regression method can be retrieved using ; thereby providing access to any methods developed for the regression method. We have chosen to implement only the most common methods (, , etc.) for the object. Note that not all regression methods have these functions. If these functions are required in your implementation, additional checks must be incorporated into your code to ensure their availability.
We use a standard dataset to illustrate the implementation of the model object framework. The `pressure’ data frame contains ``data on the relation between temperature in degrees Celsius and vapor pressure of mercury in millimeters (of mercury).” The details of the datatset are not relevant for this illustration. However, the reader is referred to ?pressure for details.
## temperature pressure
## Min. : 0 Min. : 0.0002
## 1st Qu.: 90 1st Qu.: 0.1800
## Median :180 Median : 8.8000
## Mean :180 Mean :124.3367
## 3rd Qu.:270 3rd Qu.:126.5000
## Max. :360 Max. :806.0000
It is straightforward to implement a regression step. As an example, suppose we are developing a new package called . The primary function of this package is . In this function, we want to obtain a fit and return the square of the fitted response and the estimated coefficients in a list. Our function takes the following form
exampleFun <- function(modelObj, data, Y){
fitObj <- fit(object = modelObj, data = data, response = Y)
##Test that coef() is an available method
cfs <- try(coef(fitObj), silent=TRUE)
if(class(cfs) == 'try-error'){
warning("Provided regression method does not have a coef method.\n")
cfs <- NULL
}
fitted <- predict(fitObj)^2
return(list("fittedSq"=fitted, "coef"=cfs))
}To use this function, a user must create the object of class and provide it as input to . The user can implement a linear model as follows:
ylog <- log(pressure$pressure)
objlm <- buildModelObj(model = ~temperature,
solver.method = "lm",
predict.method = "predict.lm",
predict.args = list("type"="response"))
fitObjlm <- exampleFun(objlm, pressure, ylog)
print(fitObjlm$coef)## (Intercept) temperature
## -6.06814354 0.03979188
Or, the non-linear least squares method :
objnls <- buildModelObj(model = ~exp(a + b*temperature),
solver.method = "nls",
solver.args = list('start'=list(a=1, b=0.1)),
predict.method = "predict",
predict.args = list("type" = "response"))
fitObjnls <- exampleFun(objnls, pressure, pressure$pressure)
print(fitObjnls$coef)## a b
## -0.67814836 0.02052001
Or, even the previously defined ``new” method:
objectnew <- buildModelObj(model = ~temperature,
solver.method = mylm,
solver.args = list('X' = "x", 'Y' = "y"),
predict.method = predict.mylm,
predict.args = list('obj'="object",
'data'="newdata"))
fitObjnew <- exampleFun(objectnew, pressure, ylog)
print(fitObjnew$coef)## NULL
In the last example, the function returned NULL for the parameter estimates because there is no available method to retrieve the estimated parameters.
The same function, can be used to implement each of these models, and no development is required to extend the wow function to new regression methods as they become available.