
Cross-Fit a Super Learner and Retain Fold-Specific Predictors
Source:R/crossfit_super_learner.R
crossfit_super_learner.RdCross-fitting proceeds by:
splitting the data into
n_foldsouter folds,fitting a full
super_learner()(with its own internalinner_n_folds-fold cross-validation to determine ensemble weights) on each outer training split,predicting on the corresponding outer held-out fold,
retaining each fold-specific prediction function for later modified-data (e.g. intervention) prediction.
Usage
crossfit_super_learner(
data,
learners,
formulas,
y_variable = NULL,
n_folds = 5,
inner_n_folds = 5,
determine_super_learner_weights = NULL,
ensemble_or_discrete = c("ensemble", "discrete"),
cv_schema = NULL,
inner_cv_schema = NULL,
outcome_type = c("continuous", "binary", "density", "multiclass"),
extra_learner_args = NULL,
cluster_ids = NULL,
strata_ids = NULL,
weights = NULL,
rowids = NULL,
loss_metric = NULL,
use_complete_cases = FALSE
)Arguments
- data
Data to use in training a
super_learner.- learners
A list of predictor/closure-returning-functions. See Details.
- formulas
Either a single regression formula or a vector of regression formulas.
- y_variable
Optional character name of the outcome variable; inferred from
formulaswhen omitted.- n_folds
Number of outer cross-fitting folds.
- inner_n_folds
Number of folds used by the inner
super_learner()on each outer training split to estimate ensemble weights. Deliberately decoupled fromn_folds.- determine_super_learner_weights
Function used by the inner
super_learner()calls to determine ensemble weights. IfNULL(default), inferred fromoutcome_typeviadefault_determine_weights(), matchingsuper_learner()'s own inference.- ensemble_or_discrete
Defaults to
'ensemble', but can be set to'discrete'. Discretesuper_learner()chooses only one of the candidate learners to have weight 1 in the resulting prediction algorithm, whileensemblesuper_learner()combines predictions from 1 or more candidate learners, with respective weights adding up to 1.- cv_schema
Function taking
(data, n_folds)and returning a list withtraining_dataandvalidation_data, each of lengthn_folds, used for the outer split. If omitted andcluster_idsorstrata_idsare supplied,cv_origami_schemais used so the outer split respects them; otherwisecv_random_schema.- inner_cv_schema
Optional
cv_schemafor the innersuper_learner()calls. IfNULL(default),super_learner()'s own defaults apply (which respectcluster_ids/strata_ids, subset to each outer training fold).- outcome_type
One of 'continuous', 'binary', 'multiclass', or 'density'.
outcome_typeis used to infer the correctdetermine_super_learner_weightsfunction if it is not explicitly passed.- extra_learner_args
A list of equal length to the
learnerswith additional arguments to pass to each of the specified learners.- cluster_ids
(default: null) If specified, clusters will either be entirely assigned to training or validation (not both) in each cross-validation split.
- strata_ids
(default: null) If specified, strata are balanced across training and validation splits so that strata appear in both the training and validation splits.
- weights
If specified, (per observation) weights are used to indicate that risk minimization across models (i.e., the meta-learning step) should be targeted to higher weight observations.
- rowids
(default: null) If specified, rowids are stored for use in out-of-fold prediction (
$oof_predict()) and can also be used to validate/observe the behavior of the cross-validation folds construction.- loss_metric
Optional loss function used only to report
cv_loss, the empirical loss of the cross-fitted out-of-fold predictions. IfNULL(default), inferred fromoutcome_typeviadefault_loss_metric().- use_complete_cases
(default: FALSE) If the
datapassed have any NA or NaN missing data, restrict thedatatodata[complete.cases(data),].
Value
An object of class "nadir_crossfit_sl", a list with:
$oof_predict(newdata, rowids = NULL)A prediction function that takes newdata with the same columns and rows as the training dataset and optionally rowids. If rowids are specified, the rows can be out of order compared to the original training data, but if rowids are not passed, then the ordering is assumed to be the same as the training
datapassed.$oof_predictionsNumeric vector of out-of-fold predictions in the original row order of
data(after any complete-case filtering; see$complete_rows). Rows never held out bycv_schemaareNA.$oof_predict_modified(modify)Full-length out-of-fold predictions after applying
modify(newdata)to each held-out fold, e.g.modify = function(d) { d$treatment <- 1; d }to obtain cross-fitted \(m_1\).$oof_predict_fold(newdata_list = NULL, modify = NULL)List of per-fold prediction vectors; entry
iis produced by the Super Learner trained without outer foldi.$fold_assignmentsInteger vector giving each row's outer validation fold (
NAif never held out).$fold_rowsList of row indices (into the analysis data) comprising each validation fold.
$sl_fitsList of the
n_foldsfittedsuper_learner()objects, e.g. for inspecting per-fold ensemble weights.$cv_lossEmpirical loss of the cross-fitted predictions on held-out rows, or
NAif it could not be computed.$complete_rowsIndices into the originally supplied
dataretained after complete-case filtering (identity when no filtering occurred).$warnings_from_fold_predictionsPresent only if non-empty: warnings captured while predicting on the outer held-out folds.
$warnings_from_inner_super_learnersPresent only if non-empty: a per-fold list of the warning fields captured by each inner
super_learner()(see?super_learner).$warning_learnersPresent only if non-empty: names of learners that signaled warnings in any fold.
plus y_variable, outcome_type, n_folds,
inner_n_folds, training_data, and validation_data.
Details
Each observation's out-of-fold prediction therefore comes from an ensemble
whose base learners and ensemble weights were estimated entirely
without that observation, which is the property required of cross-fitted
nuisance estimators in semiparametric workflows (AIPW, TMLE, DML). Typical
use is to obtain out-of-fold fits of an outcome model \(m\), its
interventional counterparts \(m_1, m_0\) (via $predict_modified()),
and a propensity model \(g\), each from one crossfit_super_learner()
call per nuisance.
Parallelism
Make sure to load the future package first using library(future).
Fitting is parallelized over outer folds through
future.apply::future_lapply(). With plan(multisession) only
the outer folds run in parallel and each inner super_learner() runs
sequentially inside its worker. To also
parallelize within each outer fold, declare a nested topology, e.g.
plan(list(tweak(multisession, workers = 2),
tweak(multisession, workers = I(4)))); the product across levels should
not exceed future::availableCores().
Examples
# \donttest{
cf <- crossfit_super_learner(
data = mtcars,
formulas = mpg ~ disp + hp + am,
learners = list(mean = lnr_mean, lm = lnr_lm, rf = lnr_rf),
n_folds = 5,
rowids = seq_len(nrow(mtcars))
)
# basic usage
coef(cf)
#> mean lm rf
#> fold_1 0 0.6813384 0.3186616
#> fold_2 0 0.4418869 0.5581131
#> fold_3 0 0.1793374 0.8206626
#> fold_4 0 0.2721632 0.7278368
#> fold_5 0 0.3290426 0.6709574
head(fitted(cf))
#> [1] 21.98114 24.06128 26.18548 19.42724 15.65984 19.93665
head(residuals(cf))
#> [1] -0.9811405 -3.0612757 -3.3854840 1.9727577 3.0401641 -1.8366513
formula(cf)
#> mpg ~ disp + hp + am
#> <environment: 0x32dc9beb0>
nobs(cf)
#> [1] 32
summary(cf)
#> Summary of Cross-fitted Super Learner
#> outcome: mpg (continuous); n = 32
#> outer folds: 5; inner CV folds: 5
#>
#> Held-out loss by outer fold:
#> fold n_validation loss
#> 1 6 7.402
#> 2 7 5.448
#> 3 6 4.279
#> 4 7 9.886
#> 5 6 18.670
#> Overall cross-fitted loss: 9.046
#>
#> Ensemble weight stability across outer folds:
#> learner mean_weight sd_weight min_weight max_weight n_folds_present
#> rf 0.6192 0.1931 0.3187 0.8207 5
#> lm 0.3808 0.1931 0.1793 0.6813 5
#> mean 0.0000 0.0000 0.0000 0.0000 5
# but what cf is really for is out of fold predictions:
cf$oof_predict(mtcars, rowids = seq_len(nrow(mtcars)))
#> [1] 21.98114 24.06128 26.18548 19.42724 15.65984 19.93665 13.56866 22.78791
#> [9] 21.19578 20.09891 19.81215 17.34134 16.16197 17.34134 15.82786 15.15485
#> [17] 11.98064 28.30233 27.17797 27.05730 21.87528 17.05990 16.84988 13.63369
#> [25] 15.55110 28.29871 23.61663 23.32871 17.92060 22.38309 17.52743 24.02416
cf$oof_predictions # cross-fitted \hat m(X_i) prediction models
#> [1] 21.98114 24.06128 26.18548 19.42724 15.65984 19.93665 13.56866 22.78791
#> [9] 21.19578 20.09891 19.81215 17.34134 16.16197 17.34134 15.82786 15.15485
#> [17] 11.98064 28.30233 27.17797 27.05730 21.87528 17.05990 16.84988 13.63369
#> [25] 15.55110 28.29871 23.61663 23.32871 17.92060 22.38309 17.52743 24.02416
m1 <- cf$oof_predict_modified(function(d) {
d$am <- 1
d
}) # \hat m(1, X_i)
m0 <- cf$oof_predict_modified(function(d) {
d$am <- 0
d
}) # \hat m(0, X_i)
cf$cv_loss # honest empirical loss
#> [1] 9.045843
lapply(cf$sl_fits, `[[`, "learner_weights") # per-fold ensemble weights
#> [[1]]
#> mean lm rf
#> 0.0000000 0.6813384 0.3186616
#>
#> [[2]]
#> mean lm rf
#> 0.0000000 0.4418869 0.5581131
#>
#> [[3]]
#> mean lm rf
#> 0.0000000 0.1793374 0.8206626
#>
#> [[4]]
#> mean lm rf
#> 0.0000000 0.2721632 0.7278368
#>
#> [[5]]
#> mean lm rf
#> 0.0000000 0.3290426 0.6709574
#>
# }