Own Fit and Predict Functions as a Workflow Model
model_adapter.RdTurn a pair of your own functions into an unfitted parsnip model
specification for workflows and calibrated_workflow. You
do not register a parsnip engine. The workflow keeps preprocessing, data
roles, calibration splits and calibration, and calls your functions on each
local training and prediction step.
Arguments
- mode
"classification"or"regression".- fit
A
function(x, y)that trains a fresh model.xis a base data frame of the predictors after the workflow's preprocessing, for the local training rows only.yis the matching outcome: a factor with all stored levels for classification, or a numeric vector for regression. Return any object that holds everythingpredictneeds.- predict
A
function(object, new_data).objectis the value returned byfit.new_datais a base data frame with the same preprocessing applied. For classification, return a numeric matrix with one column per level ofy, named by the original class names. Binary classification also needs both columns. Rows must keep the input order. Values must be finite, in [0, 1] and sum to one within 1e-8 in every row. For regression, return a plain numeric vector with one finite value per row.
Value
An unfitted parsnip model specification of class
vennabers_adapter with engine "callbacks". Add it to a workflow
with workflows::add_model(). After fitting,
workflows::extract_fit_engine() returns a vennabers_adapter_fit
whose state element is the object your fit returned.
Lifecycle
Every local fit calls fit once, on the rows of that fit, after the
workflow learned its preprocessing on the same rows: each Inductive or Cross
source model, the Full1 refit of prediction_model = "full", and
each analysis set of an outer resample. Calibration rows never reach
fit. A fitted adapter workflow is an ordinary fitted workflow and can be
adopted frozen with calibrate_workflow.
Supported tasks are binary classification, shared multiclass OvO/OvR and regression, with Inductive or Cross calibration, matched or full prediction models and outer resampling. For shared multiclass, the callbacks must learn and predict all local classes. Encodings and transformations belong in the workflow; the adapter does not guess model-specific preprocessing.
The existing compositions work unchanged:
model_training = "pairwise": a binary callback is fitted on each class pair's rows, withyholding that pair's two original levels. This does not make it a shared multiclass model.postprocessing:fitlearns on B only, for a holdout and for every inner ensemble member; P and C never reach it. Sigmoid on probabilities, Tailor logistic and multinomial on class probabilities, and Tailor linear or numeric isotonic on regression predictions keep their task, method and convergence rules.tuning: Grid and Bayes searches tune calibration and declared recipe parameters, each local search fitting fresh callbacks.
The callbacks receive no case weights, additional data or tuning parameters.
Bind fixed model options inside the functions. An adapter has no tune()
parameters: a search grid column for it is rejected, and so is
update() with any argument. postprocessing$sigmoid_input = "glm_link"
requires the native GLM engine, also when a callback calls stats::glm().
Predictions and errors
Class probability columns are aligned to the stored class order by name, never
by position, and returned as .pred_<class>. Class predictions of a plain
adapter workflow take the first maximum in level order. Calibrated workflows
apply their own class rules. Regression returns .pred. Empty
new_data returns an empty result without calling predict.
Malformed output is an error, never repaired: a non-matrix or unnamed result,
missing, duplicate or unknown class columns, a wrong row count, or invalid
probabilities. For regression, a matrix, a wrong length or non-finite values
are errors. Errors from your functions start with
model_adapter() `fit` callback: or
model_adapter() `predict` callback:. Calibrated workflows add the fold
or refit, for example Inductive1, Fold2 or Full1, and the
class pair (pair_2 Fold1) or inner postprocessing fold
(Inductive1 / Fold2 sigmoid).
Reproducibility and saving
The local seed of calibrated_workflow also applies to
callbacks that use R's random number generator. Callbacks must seed other
native generators themselves.
A saved fit stores the functions themselves, not their names. To use it in a
fresh R process, define the functions in a factory that binds their helpers
and fixed settings locally, and call package functions as pkg::fun.
Values looked up later in the global environment, or state shared and changed
between fits, are not supported. Models whose state needs their own save and
restore procedure, such as external pointers, are not covered.
See also
model-adapter.R in vennabers_examples for GLM, multinomial
and linear regression adapters with saved fits, pairwise OvO, Sigmoid
postprocessing and a grid search.
Examples
packages <- c("parsnip", "workflows", "recipes", "rsample", "withr")
if (all(vapply(packages, requireNamespace, logical(1), quietly = TRUE))) {
glm_adapter <- model_adapter("classification",
fit = function(x, y) list(classes = levels(y),
model = stats::glm(y ~ ., data = cbind(x, y = y), family = stats::binomial())),
predict = function(object, new_data) {
p <- stats::predict(object$model, new_data, type = "response")
matrix(c(1 - p, p), ncol = 2L, dimnames = list(NULL, object$classes))
})
development <- data.frame(
x = sin(1:80) + rep(c(1, 0, 0, 1), 20),
truth = factor(rep(c("yes", "no", "no", "yes"), 20), levels = c("yes", "no"))
)
base <- workflows::workflow() |>
workflows::add_formula(truth ~ x) |>
workflows::add_model(glm_adapter)
fitted <- generics::fit(calibrated_workflow(base, outcome = "truth", seed = 42), development)
predict(fitted, data.frame(x = c(-0.5, 0.5)), type = "prob")
}
#> # A tibble: 2 × 2
#> .pred_yes .pred_no
#> <dbl> <dbl>
#> 1 0.25 0.75
#> 2 0.512 0.488