library(nadir)
#> Registered S3 method overwritten by 'future':
#> method from
#> all.equal.connection parallellyThis article contains some advice for writing and constructing new learners.
Weights
We recommend explicitly handling weights as an argument
to learners so that it is a protected argument. Some of the internals of
different algorithms may vary, using other names for
weights instead, so we recommend doing this to standardize
the weights argument across different learner algorithms. As a concrete
example, the ranger::ranger() function takes
case.weights as its argument rather than
weights.
A typical learner that supports weights might look
like:
lnr_supportsWeights <- function(data, formula, weights = NULL, ...) {
# train the model
model <- model_fit(data = data, formula = formula, weights = weights, ...)
return(function(newdata) {
predict(model, newdata = newdata)
})
}However, some model fitting procedures do not like passing
weights = NULL and so it may be necessary to be careful not
to pass the default weights = NULL to the model fitting
procedure.
As an example of this, we refer the curious reader to the source of
lnr_glm in https://github.com/ctesta01/nadir/blob/main/R/learners.R.
Attributes
It’s recommended that if you create learners, that you also give them a couple attributes for a couple of reasons:
- If a learner has a
sl_lnr_nameattribute, then this can be automatically used in the outputs if a name for the learner is left unspecified. - If a learner has a
sl_lnr_typeattribute, it will be checked against theoutput_typeargument tosuper_learner().
To set these attributes, when making a new learner, one should run something along the lines of
lnr_myNewLearner <- function(data, formula, ...) {
model <- # fit your learner given data, formula, ...
predictor_fn <- function(newdata) {
predict(model, newdata = newdata)
}
return(predictor)
}
attr(lnr_myNewLearner, 'sl_lnr_name') <- 'newLearnerName'
attr(lnr_myNewLearner, 'sl_lnr_type') <- 'continuous' # or c('continuous', 'binary') and similar
# see ?nadir_supported_typesModifying Predictions
Occasionally there are settings in which we would like to enforce some constraint on model behavior. One such example is in clipping the predicted values to some pre-specified range. This concept is often relevant for the logistic regression (i.e., binary outcomes) setting, but may be more broadly applicable.
Here we show how to truncate the predictions of a learner to a specified range as an illustrative example.
truncate_lnr <- function(lnr, min, max) {
truncate <- function(x, min, max) {
pmax(pmin(x, max), min)
}
# we want to return a modified learner: so in other words, we want to return a
# function that takes in a learner's inputs and returns a prediction function,
# just with the added truncation.
return(
function(...) {
predictor_fn <- lnr(...)
truncated_predictor_fn <- function(...) {
truncate(predictor_fn(...), min, max)
}
return(truncated_predictor_fn)
}
)
}
# create the new, truncated learner
lnr_truncated_hal <- truncate_lnr(lnr = lnr_hal, min = 0, max = 1)
# fit the learner
learned_hal_model <- lnr_truncated_hal(data = mtcars, am ~ .)
# produce predictions, for example
learned_hal_model(mtcars)
#> [1] 0.699742339 0.664010374 0.695709519 0.000000000 0.000000000 0.060095349
#> [7] 0.000000000 0.511040606 0.283893910 0.390855426 0.361627216 0.194663708
#> [13] 0.057058232 0.009506522 0.000000000 0.015485391 0.047048910 0.807818383
#> [19] 0.837018020 0.857143078 0.590793239 0.225453528 0.141459463 0.233407327
#> [25] 0.000000000 0.820616609 0.760721447 0.750925569 0.192624472 0.777093601
#> [31] 0.354637109 0.630647486
# in contrast, if we had not truncated this learner, we could have gotten
# predictions outside of [0, 1]:
learned_hal_not_truncated <- lnr_hal(data = mtcars, am ~ .)
range(learned_hal_not_truncated(mtcars))
#> [1] -0.4658581 1.0268739With many learners, it is possible to specify
family = binomial(link = 'logit') or a similar argument.
However, nonetheless there are settings where fitting a truncated
learner has benefits, including that sometimes a truncated linear
probability model has lower risk than a logistic regression model, and
sometimes truncated models are useful for manually expanding the variety
of candidate learners used in the super_learner()
algorithm, thereby making it more likely that the true data generating
mechanism is closely estimated by the fit
super_learner().
If you are wondering whether a truncated linear probability model for binary outcomes is misspecified, see the following section of the FAQ: FAQ: How should we think about “misspecified” learners?
