Postprocessing Before Workflow Calibration
workflow_postprocessing.RdConfiguration and reference rules for the postprocessing argument of
calibrated_workflow. Start with the worked Sigmoid example in
vignette("workflows", package = "vennabers") before using this reference.
These options learn a postprocessor before the Venn-Abers calibrator.
Data roles
Each VA source training portion Q is split into B for recipe and engine
learning and P for postprocessor learning. Held-out C fits the VA calibrator.
Outer assessment A is excluded from all learning. When calibration is tuned,
selection rows S are excluded from the final fit domain F. With
prediction_model = "full", F gets its own complete B/P composition.
An inner pair is a model/postprocessor pair; an OvO class pair is a separate
two-class training domain.
Configuration
NULL preserves the existing unprocessed training path and
does not remove a supplied postprocessor. Otherwise a named list with explicit
method = "tailor_logistic", "tailor_multinomial", "tailor_linear", "sigmoid", or "isotonic_numeric",
strategy = "holdout", and either
prop (the P fraction, strictly between zero and one) or splits
(a local rsplit or serializable function of local Q data). Analysis is B;
assessment is P. A supplied plan cannot be combined with prop or v. Unknown,
duplicate, irrelevant and conflicting fields are errors. Currently binary, shared OvO/OvR and pairwise OvO
Inductive/Cross VA and regression IVAR/CVAR with matched or full models are implemented,
with fixed engine/recipe settings or local selection (see venn_abers_tuning);
Binary and pairwise OvO tailor_logistic/sigmoid, shared OvO/OvR
tailor_logistic (two classes)/tailor_multinomial (three or more), and
regression tailor_linear/isotonic_numeric support
strategy = "ensemble", with an explicit integer v >= 2 or
splits (a local unrepeated rset or serializable function of Q).
Ensembles reject prop; explicit splits reject v.
Method, Sigmoid input, strategy, prop/v and prepared plans stay fixed per run.
Supported compositions and costs
The postprocessing scope is a finite matrix of 80 fixed compositions:
method x holdout/inner ensemble x Inductive/Cross x matched/full over binary
(Sigmoid, Tailor logistic), pairwise OvO (Sigmoid, Tailor logistic), shared
OvO/OvR with three or more classes (Tailor multinomial), shared two-class
OvO/OvR (Tailor logistic) and regression (reference isotonic, Tailor linear).
Every composition also accepts joint or two-stage Grid/Bayes selection of
engine/recipe settings inside each B. B trains recipe and engine, P the
postprocessor, C the unchanged VA calibrator; F is the permitted final fit
data of a full composition. Without tuning, J inner pairs cost J (Inductive
matched), 2J (Inductive full), KJ (Cross matched with K folds) or (K+1)J (Cross full)
engine and postprocessor fits per domain, retaining J, J, KJ or J pairs;
class pairs add up. Out of scope: out-of-fold postprocessing followed by a
complete engine refit, foreign engines beyond model_adapter
callbacks, Tailor step sequences, further postprocessing methods, and other new
public constructors. Frozen workflows
(calibrate_workflow) keep their existing contract.
Binary Sigmoid holdouts
For binary matched or full Inductive/Cross workflows with fixed parameters, set
postprocessing = list(method = "sigmoid", strategy = "holdout", prop = .3,
sigmoid_input = "probability") or explicitly select sigmoid_input = "glm_link".
The base workflow must have no postprocessor. Recipe and engine learn only on B;
Sigmoid learns only on P; the unchanged VA core learns on held-out C. Both B and
P must contain both factor levels, and P needs at least two rows. Constant P
inputs are valid; coefficients are not identifiable in that case.
probability uses the final named probability of the second stored factor
level from the complete public base workflow. glm_link requires a
logistic_reg(engine = "glm") binomial model with logit link and calls
predict(workflow, new_data, type = "raw", opts = list(type = "link")),
including recipe processing. It never reconstructs links from probabilities.
Both VA event choices are supported by named probability selection after Sigmoid.
The mapping minimizes unregularized logistic loss with targets
\((N_1+1)/(N_1+2)\) for the second class and \(1/(N_0+2)\) for the first.
It follows scikit-learn's Platt calibration, including its start values,
L-BFGS-B tolerances (relative loss reduction of 64 machine epsilons, gradient
1e-6) and SciPy's ten correction pairs. These tolerances stop short of the exact
optimum, so the optimizer path determines the result. Stored coefficients are
a, b on the stored input scale: the second-class output is
plogis(-(a * (x / scale) + b)). The scale is the maximum absolute P
input when at least 30, otherwise one. Constant inputs use the exact zero-slope
solution (the mean target). This is an R choice, not Python parity:
scikit-learn's iterative estimate stops within its gradient tolerance and, for
very small P, can differ from it by more than 1e-7. A line-search stop is kept
only when its mean gradient per P row is at most 1e-6 in each coefficient, as at
a machine-precision optimum.
Nonfinite or incomplete inputs, every other unsuccessful optimization, and incomplete
probability outputs fail with method, role and local fold context.
The fitted state retains the complete base workflow, mapping and package
versions. Details expose B/P/C memberships, input, sigmoid_class,
scale, coefficients, convergence, warnings and fit counts. Empty batches retain their schema without engine calls.
No Tailor, probably or Python installation is required for Sigmoid. Python is
used only by the separate checked-in reference generator; regular tests read
its fixed CSV outputs. See system.file("examples", "sigmoid-holdout.R",
package = "vennabers") for both inputs and native outer evaluation.
Tailor classification holdouts
Each VA source training portion Q is partitioned into disjoint B and P. The
recipe and engine learn only on B through the public workflow fit. A
package-owned composition validates its P predictions and fits an unchanged
canonical Tailor through its public fit, using
tailor::adjust_probability_calibration(method = "logistic", smooth = FALSE)
for two classes, or method = "multinomial", smooth = FALSE for three or
more classes. The requested method must match the class count; no automatic
exchange is performed. Shared two-class OvO/OvR retains multiclass class and
tie rules, not the binary event contract.
One exactly matching untrained Tailor adjustment may already be configured;
it is adopted once. Foreign, trained or multiple adjustments are rejected.
The stored composition predicts held-out VA data C and new predictors through
the base workflow, input validation and the public Tailor prediction path.
The VA core, event alignment, loss and rounding rules are unchanged.
Both B and P must contain all K model classes. P needs at least K+1 rows and
a full-rank design: the logistic intercept and one probability for two classes,
or the multinomial intercept and K-1 probabilities for K classes. Rank and
convergence are checked on the actual learned model after the necessary B
predictions. Failed fits, absent learned state and method switches to none
are errors. Tailor multinomial uses the nnet::multinom() defaults of
probably (at most 100 iterations), and the fixed Tailor arguments offer
no iteration setting: weakly informative class probabilities can stop there
unconverged even when the B engine converges. Setting maxit on the B
engine does not change the P postprocessor's limit. The composition fails
with fold, inner-pair and B/P context, without retry, method switch or partial
ensemble. This is an accepted limit of the 1.x contract, not a pending
option. This R composition limit does not change
numerical VA parity on identical valid inputs. Warnings from successful fits are retained with source/fold context.
The existing VA rules for missing C classes remain unchanged (including valid
one-class Cross subproblems); B/P requirements do not apply to C. Use venn_abers_schema() for nonsyntactic outcome
or class names that the underlying Tailor/probably formula interface cannot use.
Tailor linear holdouts
Linear regression postprocessing requires method = "tailor_linear" explicitly and uses
tailor::adjust_numeric_calibration(method = "linear", smooth = FALSE)
through the same package-owned composition. Recipe/engine learning uses B;
the intercept and slope are learned only on P. Numeric 0/1 targets remain
regression. Automatic B/P splits are unstratified. P needs at least three rows,
finite predictions and truth, full actual design rank two, finite coefficients
and convergence; constant predictions, absent fits and switches to none fail.
The complete composition's numeric output enters the unchanged IVAR/CVAR core
with the requested valid m or epsilon. CVAR averages fold points
and both bounds separately. No confidence or prediction intervals are added.
See installed examples/tailor-regression-holdout.R for both paths,
encoded names, complete native evaluation and data-role checks.
Numeric isotonic holdouts
method = "isotonic_numeric" accepts a regression base workflow without
a postprocessor. The recipe and engine learn on B, and unweighted increasing
least-squares isotonic regression learns on finite P predictions and truths.
At least two P rows are needed; tied predictions retain their frequencies.
Fitted support values are linearly interpolated, extended constantly beyond
the support, and are not restricted to [0,1]. A single support predicts the
mean P truth everywhere. No full-rank or Ridge condition applies. The complete
saved B workflow and isotonic fit enter the same IVAR/CVAR cores. See installed
examples/isotonic-regression-holdout.R for both native evaluation paths.
Local plans and seeds
All source B/P plans are validated before any source is trained. Static plans must
match local selected columns in order, values, types, levels and row order.
Automatic classification splits use the local outcome with pool = 0, without retries
or size adjustments; ordered valid explicit holdouts are allowed. The enclosing
seed scope covers VA splitting, B/P splitting and fitting in stored fold order;
NULL consumes the caller's stream and an explicit seed restores it.
Inner ensembles
For inner ensembles, P folds partition Q exactly once and each B is its
own P complement. B/P disjointness is local to a pair: a P row can belong to
another pair's B. Gaps, overlap, bootstrap duplicates, repeated folds and
incorrect local data bindings fail before learning. Each pair uses the same
complete holdout fitting rules. Aligned final probability vectors are averaged
with equal pair weights before VA, without inner VA calibrators or size weights.
Shared OvO/OvR averages complete class vectors in stored class order before
binary VA aggregation and subsequent OvO coupling or OvR normalization.
Regression averages finished numeric predictions from the retained complete
model/postprocessor pairs before IVAR/CVAR. It does not average coefficients,
isotonic supports or separately VA-calibrated inner results. Each pair retains
its method's P-size and rank rules, including valid constant isotonic inputs.
CVAR's outer fold bounds remain descriptive VA bounds, not confidence or
prediction intervals. See installed examples/regression-ensemble.R
for both numeric methods, native evaluation, schema and held-out data checks.
All plans are resolved first, followed by fits in stored VA/inner-fold order in
one RNG stream; the seed is never restarted for each inner B/P pair. Local plan functions
must be pure functions of their supplied data, not call counters or global row
positions. Different Q domains may have different inner fold counts.
Pairwise OvO compositions
For pairwise OvO, restrict the data and outcome levels to each class pair before
resolving any VA or B/P plan. Each pair supports tailor_logistic or
sigmoid with either explicit input type; no foreign class reaches its
recipe, model, postprocessor or plan function. Pair-local complete predictions
are averaged before the binary VA core, and only the resulting pair points enter
global OvO coupling. Different pairs and VA folds can have different local sizes
and inner fold counts. Explicit B/P plans must bind to their own Q, using a pure
local function or local_calibration_splits() for multiple prepared domains.
Global position plans are rejected. Existing isolated VA pair-plan seeds remain
unchanged; all B/P plans are then resolved in class-pair/VA-fold order, followed
by fits in class-pair/VA-fold/inner-fold order in the enclosing RNG stream.
Pairwise diagnostics are stored at
extract_fit_engine(fitted)$pair_models[[pair_id]]$postprocessing.
venn_abers_details()$postprocessing exposes these records under unique
pair_N/VA_fold keys. Each record includes pair_id and
pair_rows (positions in the enclosing fit data); Q/B/P/C positions are
relative to that class pair. Inner-fold names identify model/postprocessor pairs,
not class pairs. The decoder preserves the two-class outcome domain and translates
its names. Costs sum the actual inner counts across every class pair and VA fold;
all required paths remain stored. Missing members and failed fits/predictions
abort the complete result with pair/fold context. See installed
examples/pairwise-postprocessing.R for native resampling and last_fit.
Shared diagnostics and serialization
For shared models, extract_fit_engine(fitted)$postprocessing and the postprocessing
element of venn_abers_details() store actual Q/B/P/C row memberships,
Q-local analysis/assessment positions, sizes, class/event and schema bindings,
RNG settings, method-specific fit state, warnings and observed fit counts. Prediction
details add the inner fold, method, input and B/P sizes. Package versions are
stored in the fitted engine's versions. Each nonempty prediction calls
each retained base workflow and Tailor once; empty batches call none. Predict needs
no truth or plan resolution and never learns. RDS retains the full trained state.
Ensemble records contain a named inner_folds list of complete pair
diagnostics and summed observed fit counts. Their prediction details use
list-columns for inner_fold, B_size and P_size, retaining
one VA result row and the ordered identities/sizes of every contributing pair.
An Inductive ensemble fits J engines and J postprocessors; a K-fold Cross
ensemble with J pairs per fold fits K*J of each. Missing or failed pairs abort
the whole composition with fold context. See installed
examples/binary-ensemble.R and examples/multiclass-ensemble.R
for complete native evaluation.
Tailor and probably are optional and required only for this path.
See installed examples/tailor-holdout.R for native fit, complete repeated
outer resampling, last_fit, diagnostics and checks of held-out data roles.
examples/tailor-multiclass-holdout.R adds shared two-, three- and four-class
OvO/OvR, encoded names and the yardstick Brier factor of one half. Single-row
multinomial predictions use a duplicated prediction row at the composition
boundary, then discard that extra result. The base workflow is predicted once;
the canonical Tailor and its learned nnet model remain unchanged. Learned-state
compatibility checks are confined to the package's Tailor boundary because
Tailor has no public learned-model extractor.
Full postprocessing compositions
All fixed-parameter holdout and inner-ensemble methods also accept
prediction_model = "full". First every original Q composition predicts
its held-out C and builds its VA calibrator. Only then does each permitted F
domain resolve its own B/P plan and fit an additional complete composition.
This ordering preserves the source RNG stream, C values and calibrators between
matched and full fits with the same seed. F excludes outer assessment A and
any reserved selection S; for pairwise OvO it contains only the local class pair.
C can influence this extra composition, never its own original Q composition.
Full refers to the whole composition: no inner engine is trained on all F rows.
After successful fitting and prediction validation, only the F composition
remains in models$Full1. Source workflows are discarded, while their
held C values, VA calibrators, plans and postprocessing diagnostics remain.
The additional postprocessing$Full1 record uses Q for the F row
membership, has empty C, and records its own B/P memberships and fit counts.
Pairwise details expose it as pair_N/Full1. Prediction-row inner-fold names
and sizes describe the retained F composition; source diagnostic records remain
available without claiming new predictions from discarded workflows.
If a caller's VA fold already uses Full1, the final ID gets the next free
numeric suffix (Full1.1, etc.); sources records the actual ID.
For constant inner count J (J=1 for holdout), full Inductive performs 2J engine and postprocessor fits; full Cross with K VA folds performs (K+1)J. Only J pairs are retained per domain. Different local counts are summed, including F's own J. Three class pairs with K=5 and J=3 perform 54 engine and 54 postprocessor fits, retaining nine pairs. Each nonempty batch predicts those retained pairs once, then reuses the composed F prediction for every original VA calibrator. Empty batches call no engine. A failed final plan, fit or prediction aborts the result; there is no fallback, retention switch or prediction-time model-mode switch.
RDS retains complete F workflows and all diagnostics. Prediction needs neither
truth, plan functions nor additional learning. See installed
examples/full-postprocessing.R for all 80 fixed matched/full combinations,
including shared two-class OvO/OvR, native outer evaluation and last_fit.
The full-model transfer limitations in workflow_training apply to these
complete compositions as well.
See also
vennabers_examples for annotated installed scripts.
calibrated_workflow for the constructor,
venn_abers_details for returned diagnostics,
venn_abers_tuning for searches inside each B, and
workflow_training for VA splits and full-model transfer.