<- function(formula, ...)
lo
{## ensure it's a formula
if(!inherits(formula, "formula")) {
<- as.character(substitute(formula))
formula <- as.formula(paste("~", formula))
formula environment(formula) <- sys.frame(-1)
}
## list for setting up the special model term
<- list()
st
## control arguments
$control <- list(...)
st
## variables, label and data
$term <- all.vars(formula)
st$label <- paste0("lo(", paste0(gsub(" ", "",
stas.character(formula)), collapse = ""), ")")
$data <- model.frame(formula)
st
## New model formula used for fitting.
$formula <- update(formula, response_z ~ .)
st
## Assign the "special" class and the new class "n".
class(st) <- c("special", "lo")
return(st)
}
Special Model Terms
In order to add any new machine learning type algorithm in gamlss2
you need to define three special functions:
- A special term constructor function,
- a fitting function and,
- a predict function.
Here we demonstrate how this can be done using the local polynomial smoothing function loess()
in R, Cleveland, Grosse, and Shyu (1993).
Note that any regression type machine learning function is R can be easily incorporated in gamlss2
especially if there is a prior weights argument in the function. loess()
has the argument weights
for prior weights so it can be incorporated easily.
1 The special model term constructor
Any special model term constructor must be registered in the fake_formula()
function. If not yet registered, the user can provide a new special name in the specials
argument of fake_formula()
. Another option is to use the special model term constructor name ‘“user”’, which is already part of the special names list in fake_formula()
.
The definition function can take all relevant loess
and loess.control
arguments so it can pass them into the fitting function.
2 The fitting function
The fitting function takes the current working response, the iterative weights and the corresponding relevant term and creates a call to the loess
function to fit the relevant model. It then saves the fitted values and the fitted objects for later use.
<- function(x, z, w, control, ...)
special_fit.lo
{## assign current working response and weights
$data$response_z <- z
x$data$weights_w <- w
x
## set up loess call
<- "loess(formula = x$formula, data = x$data, weights = weights_w"
call
## add optional control parameters
if(!is.null(x$control)) {
for(j in names(x$control))
<- paste0(call, ", ", j, "= x$control$", j)
call
}<- paste0(call, ")")
call
## estimate model
<- list("model" = eval(parse(text = call)))
rval
## get the fitted.values
$fitted.values <- fitted(rval$model)
rval
## center fitted values
$shift <- mean(rval$fitted.values)
rval$fitted.values <- rval$fitted.values - rval$shift
rval
## degrees of freedom
$edf <- rval$model$trace.hat
rval
## assign class for predict method
class(rval) <- "lo.fitted"
return(rval)
}
3 The predict function
The prediction function shows how the predicted values of the model can be extracted.
<- function(x, data, se.fit = FALSE, ...)
special_predict.lo.fitted
{<- as.numeric(predict(x$model, newdata = data))
p <- p - x$shift
p if(se.fit)
<- data.frame("fit" = p)
p return(p)
}
4 Example: rent99 data
We use the rent99
data to demonstrate the use of the functions
## load the Munich rent data
data("rent99", package = "gamlss.data")
## scale covariates
$area <- scale(rent99$area)
rent99$yearc <- scale(rent99$yearc) rent99
Note that the continuous variables in the data area
and yearc
have been standardised. We defined four formulae for modelling the rent data. The first two use loess
and the third and fourth uses the additive smoothing function s()
for comparison. Formula f
uses main effect smoothing terms for area
and yearc
for parameters \(\mu\) and \(\sigma\), respectively, while the second, f1
, uses two dimensional smoothing functions for modelling one way interaction. The third formula uses one dimensional smoother for main effects and the fourth two dimensional cubic splines smoothers for interactions. Note that in this example we only use explanatory terms for the first two parameters \(\mu\) and \(\sigma\) and constants for the rest, \(\nu\) and \(\tau\).
<- rent ~ lo(~area)+lo(~yearc)+location+bath+kitchen|
f lo(~area)+lo(~yearc)+location+bath+kitchen|
1|1
<- rent ~ lo(~area*yearc)+location+bath+kitchen|
f1 lo(~area*yearc)+location+bath+kitchen|
1|1
<- rent ~ s(~area)+s(~yearc)+location+bath+kitchen|
sf s(~area)+s(~yearc)+location+bath+kitchen|
1|1
<- rent ~ te(area,yearc) + location + bath + kitchen |
sf1 te(area,yearc) + location + bath + kitchen |
1|1
5 Estimation
Below we use the package tictoc
to measure the time is taken to fit each model. The main effect fit for loess
is;
library("tictoc")
tic()
<- gamlss2(f, data = rent99, family = BCTo) b1
GAMLSS-RS iteration 1: Global Deviance = 38409.6672 eps = 0.286141
GAMLSS-RS iteration 2: Global Deviance = 38363.6237 eps = 0.001198
GAMLSS-RS iteration 3: Global Deviance = 38360.7687 eps = 0.000074
GAMLSS-RS iteration 4: Global Deviance = 38359.9016 eps = 0.000022
GAMLSS-RS iteration 5: Global Deviance = 38359.6113 eps = 0.000007
toc()
4.815 sec elapsed
The first order interaction fit for loess
is;
tic()
<- gamlss2(f1, data = rent99, family = BCTo) b2
GAMLSS-RS iteration 1: Global Deviance = 38410.5652 eps = 0.286125
GAMLSS-RS iteration 2: Global Deviance = 38359.84 eps = 0.001320
GAMLSS-RS iteration 3: Global Deviance = 38356.7695 eps = 0.000080
GAMLSS-RS iteration 4: Global Deviance = 38355.8814 eps = 0.000023
GAMLSS-RS iteration 5: Global Deviance = 38355.6119 eps = 0.000007
toc()
8.337 sec elapsed
Now the main effect model using s()
library(tictoc)
tic()
<- gamlss2(sf, data = rent99, family = BCT) a1
GAMLSS-RS iteration 1: Global Deviance = 38462.44 eps = 0.285216
GAMLSS-RS iteration 2: Global Deviance = 38414.9787 eps = 0.001233
GAMLSS-RS iteration 3: Global Deviance = 38411.2225 eps = 0.000097
GAMLSS-RS iteration 4: Global Deviance = 38409.6335 eps = 0.000041
GAMLSS-RS iteration 5: Global Deviance = 38409.0103 eps = 0.000016
GAMLSS-RS iteration 6: Global Deviance = 38408.8217 eps = 0.000004
toc()
1.203 sec elapsed
The interaction model using te()
library(tictoc)
tic()
<- gamlss2(sf1, data = rent99, family = BCT) a2
GAMLSS-RS iteration 1: Global Deviance = 38433.744 eps = 0.285749
GAMLSS-RS iteration 2: Global Deviance = 38391.1221 eps = 0.001108
GAMLSS-RS iteration 3: Global Deviance = 38388.3527 eps = 0.000072
GAMLSS-RS iteration 4: Global Deviance = 38387.4626 eps = 0.000023
GAMLSS-RS iteration 5: Global Deviance = 38387.1819 eps = 0.000007
toc()
1.286 sec elapsed
The cubic spline function is lot faster than the loess()
implementation lo()
in gamlss2
, but let us now compare the models using AIC.
## deviance
AIC(b1, b2, a1, a2, k = 0)
AIC df
b2 38355.61 36.77363
b1 38359.61 33.15714
a2 38387.18 33.61847
a1 38408.82 32.80415
## BIC
AIC(b1, b2, a1, a2, k = log(nrow(rent99)))
AIC df
b1 38625.97 33.15714
b2 38651.03 36.77363
a2 38657.25 33.61847
a1 38672.35 32.80415
It seems that the two lo()
models do better that the s()
as far as the AIC criteria are concern.
6 Visualise the fits
The standard plot()
function of gamlss2
can be used to visualises the smooth curves fits (under certain circumstances). For example for the main effect model using lo()
we have;
plot(b1)
Note that no standard errors are shown here compare to the s()
function model shown below;
plot(a1)
For the first order interaction model b2
and because the effects are not defined within gamlss2
calling plot()
produces the standard residual plots;
plot(b2)
For the first order interaction model a2
using tensor products the plot are more informative;
plot(a2)
One can use the function vis.lo()
of the package gamlss
to visualised the fitted terms fitted with lo()
. Here we show the area
fitted values for parameter \(\mu\) and model b
including the partial residuals from the model.
:::vis.lo(specials(b1, model="mu")[[1]]$model, partial = TRUE) gamlss
Next we show the year of construction yearc
fit for parameters \(\mu\) from model b
without partial residuals.
:::vis.lo(specials(b1, model = "mu")[[2]]$model, partial = FALSE) gamlss
Here we plot the fitted surface fit from model b2
and parameters \(\mu\);
:::vis.lo(specials(b2, model = "mu")$model, partial = FALSE) gamlss
Here we plot the same fitted surface as above adding a 95% confidence intervals;
:::vis.lo(specials(b2, model = "mu")$model, se = 1.97) gamlss
Finally we plot the fitted surface fit from the \(\mu\) model of b2
adding the partial residuals.
:::vis.lo(specials(b2, model = "mu")$model, partial = TRUE) gamlss
Note that similar plots are given in section 9.6.3 of Stasinopoulos et al. (2017), where the lo()
function within package gamlss
, is described.