Scope and refusals#

cleverly refuses a composition it has not derived, rather than returning a convenient approximation to a different estimand. This page says how to read such a refusal. It also tabulates the one axis where the answer is least obvious: which surfaces take more than two treatment arms.

How to read a refusal#

A refusal is always by name. The keyword is accepted and rejected with a stated reason. It does not arrive as an unexpected keyword argument that names none.

Every CleverlyError carries the record of its refusal in error.refusal. The record is a frozen Refusal from cleverly.exceptions. str(error) prints error.refusal.message().

field

holds

reason

the RefusalReason code. The next table lists the five codes

condition

the sentences that state the failed condition

alternative

the sentence that names a working call, or NO_ALTERNATIVE when no call in the package answers the request. Only invalid_declaration may leave it empty

roadmap

the id of the roadmap item that tracks a missing capability, or None

The reason of the record tells you where the problem is. That decides what to do about it. A RefusalReason is a string enumeration, so error.refusal.reason == "inference_gap" is true for an inference gap. The error class does not fix the code. Read error.refusal.reason and not the class.

code

where the problem is

what the message holds

what to do about it

invalid_declaration

in the declared sample or design

the failed condition

Correct the declaration, the data, or the design. The request is not well posed as declared

different_estimand

in the question

both questions: the one you asked and the one the option answers

Decide which question you mean. No flag produces the other, and one that quietly did would answer something nobody asked

implementation_gap

in this package

the alternatives and the roadmap item that tracks the gap

The parameter is well defined and nobody has written it here. Use the alternative, ask for it, compute it elsewhere, or contribute it. Proposed work is on the roadmap

inference_gap

in the uncertainty

the supported inference

The estimate exists, and the requested interval, p-value, or band is not derived. Report the supported inference

api_redirection

in the call

a working call

The feature exists through another argument or object. Make the call that the message names. This code does not mark a missing capability

A capability row carries the same record before any call. A row of result.diagnostics.capabilities, an AssessmentItem, and a MethodAvailability row of CausalStudy.available_methods() each hold it in refusal. For these rows refusal is None when no refusal applies. The call, when you make it, raises an error with the same code and text. The row and the raise therefore state one reason in one text.

A built-in ValueError or TypeError means a malformed argument, such as a wrong type or a value out of range. It is an invalid declaration, and it carries no record. A misspelled keyword raises TypeError for the same reason.

Some refused requests would run and return a plausible number whose target or interval is wrong. The Wrong by construction section lists them. Read them as warnings about the analysis, and not about this package’s coverage.

A fit whose data cannot support what you declared is an invalid_declaration that the package refuses where the problem arises. Examples are a regimen nobody followed and two absorbing causes firing at one node. A known policy that can draw a level no fitted row of its node received is another, and known stochastic policies states why. Those are statements about the sample.

A horizon at which no follower of a regimen had the event is not refused. Its regression is zero, the maximum-likelihood hazard, and a node with no event gives the rule.

Cross-fitting narrows what the sample supports. Each outer fold fits its regressions and its mechanism on its training rows alone, so every fold must carry the followers and treatment levels that each node needs. The split reads no treatment and no outcome, so it cannot protect a rare level. The package checks the realized draw instead, before the first learner, and the refusal names no redraw. The fold and outcome-scale rules give every such message.

The sections below list refusals by topic. Each table has a code column. The column holds the code that the raise site assigns, so a section can hold rows of several codes.

section

what it lists

Not written yet

refusals of the request, mostly implementation_gap and inference_gap

Targeting and fitting option refusals

the refusals of stopping=, step_bound=, iterate=, outcome_by_arm=, patience= and treatment_adjustment=

Experimental compositions

requests that run and carry the experimental label, and the ones that stay refused

Replay-only unavailability

the omission codes of a result that cannot replay, all invalid_declaration

A different question

requests that ask for another estimand, mostly different_estimand

Wrong by construction

mistakes that would run and return a wrong number

Not written yet#

Nothing is wrong with wanting any of these. Most rows are gaps in coverage, and the message says so rather than implying the request was ill-posed. A row marked implementation_gap names an alternative and the roadmap item that tracks the gap. tests/unit/test_refusal_codes.py checks that each such refusal names an item. A row marked inference_gap names an alternative, and the roadmap item when one tracks the gap. A row marked invalid_declaration or api_redirection names the declaration to correct or the call to make.

refused

code

where

missing-outcome NaturalCourseMean, PAR or PAF outside their two TMLE contracts

implementation_gap, inference_gap, invalid_declaration. The subsection gives the code of each check

missing-outcome natural-course contracts lists every refusal. Observed-data extensions defines both estimators, and PAR and PAF with missing outcomes defines the stack

cross-fitted arm-indexed means and contrasts with missing outcomes outside the stacked CV-TMLE contract

implementation_gap, inference_gap, invalid_declaration. The subsection gives the code of each check

missing-outcome arm-indexed contract lists every refusal. Stacked CV-TMLE for arm-indexed targets defines the estimator

a cross-fitted shift, incremental, regime, MSM, or controlled-direct-effect fit with missing outcomes (delta=)

implementation_gap

the refusals a caller can meet. The hard-stop review reads Díaz et al. (2023), Theorem 3, for the shift, regime, MSM and controlled-direct-effect targets. The incremental target waits on a source read. The package implements neither. The fit raises CapabilityError before any learner is fitted. The in-sample fit with delta= remains available, except for a continuous-dose MSM, which the next row refuses. X53 part (d) reopens it

a continuous-dose MSM with missing outcomes (delta=, or PointTreatment(missingness=...)) or with intermediate=, in sample or cross-fitted

implementation_gap

MSM projections. The clever covariate that the package builds divides by the treatment density at each grid dose. In that construction, this composition also needs the response or intermediate mechanism at each grid dose, and the package does not write it. TMLE raises CapabilityError before any learner. CausalStudy.identify raises it for MSMProjection with a missing outcome. tests/unit/test_continuous_msm_mechanism_refusals.py checks both. X10 reopens it

DRTMLE with cross-fitted missing outcomes at every guard including guard=(), intermediate=, fold-wise targeting, composition with CTMLE, reduction="bivariate" composed with randomized=True, evaluation= or reduced_crossfit="nested" on the composite indicator, or targeting="one_step" with reduced_crossfit="nested" at a non-empty guard

implementation_gap. api_redirection for reduction="bivariate" with randomized=True, and for targeting="one_step" with reduced_crossfit="nested"

method presets, and the DR-TMLE refusals

a declared missing treatment (treatment_delta=) with att, atc, ey_obs, par, paf, incremental=, policies=, learned_rule=, intermediate=, cross_fit=True, randomized=True, a declared known mechanism, a selector-based CTMLE, or a shared-design oat fit

invalid_declaration for att, atc, ey_obs, par, paf, incremental=, policies=, randomized=True, and a declared known mechanism. implementation_gap for the rest

the composite indicator. The recording may depend on the treatment, so P(A = a | W) is not identified, and the first seven read it. No composite derivation is written for a learned rule (F27) or a controlled direct effect. One gate in the shared preflight refuses each one by name before any learner. CausalData refuses treatment_delta= beside a continuous treatment as invalid_declaration for the same reason. tests/unit/test_refusals_before_the_nuisance_fit.py pins each refusal and a mutation that removes the gate. A cross-fitted TMLE waits on X53 part (c), and a cross-fitted DRTMLE on F21. A selector-based CTMLE waits on X51 part (d). A cross-fitted per-arm oat fit waits on F19, which holds the cross-fitted outcome-adaptive construction. An in-sample per-arm oat fit reports a point estimate under the per-arm contract

an undeclared missing treatment value

invalid_declaration

the package does not infer a missing treatment from a missing value. CausalData raises DataError before any learner and names the declaration

a declared known treatment mechanism beside a continuous treatment, a vector treatment with a dose, or policies= with ratio="classifier", and a declared LTMLE design that leaves a regimen unidentified

implementation_gap for a known density. api_redirection for ratio="classifier". invalid_declaration for a regimen that no unit follows

known treatment mechanism. A known density is not supported, and the classifier learns the ratio that the declaration fixes. A declared zero on the arm a regimen needs leaves no unit able to follow it. Each refuses before any learner. tests/unit/test_known_treatment_mechanism.py and tests/unit/test_known_node_mechanisms.py pin the texts

baseline strata (strata=) with targeting_scheme="fold" or cv_evaluation=True, an incremental target with a stratum that lacks a treatment arm, an MSM whose design is singular inside a stratum, or a DRTMLE stratum with no trainable rows in some training complement

implementation_gap for fold targeting and fold evaluation. invalid_declaration for the three data conditions

weights, strata, and clusters. Fold targeting fits the fluctuation on the validation rows. Montoya et al. (2023), Section 3.2, Step 2(c), publish that update, and the package does not implement it with strata (X53 and X11 part (h) hold its other compositions). X61 part (b) holds fold targeting with strata. Fold evaluation needs stratum shares and a stratum-indexed fold average in each validation fold (X28). The other three are data conditions where the root may not exist or no regression is fittable. Each refuses before any learner. tests/unit/test_refusals_before_the_nuisance_fit.py, tests/unit/test_stratified_incremental_exact.py and tests/unit/test_stratified_msm_exact.py check them

the MNAR tilt, missingness_tilt() and tipping_gamma(), on a shift, incremental, regime, MSM, ratio-only, or PAR-and-PAF-only fit with missing outcomes

implementation_gap

missingness tilt and tipping gamma. The tilt re-mixes the arm-indexed means and their linear contrasts. No derivation here covers the tilt of these parameters. Both entry points raise the sentence of fit_wide_tilt_refusal. Both capability rows read unavailable with that sentence before any call. TestTheTiltRowsReadTheCallsPredicate in tests/unit/test_capability_row_predicates.py checks each kind

a learned-rule value from an in-sample or one-fold fit, the stacked report, fold-specific targeting, a categorical treatment, or a contrast with a known regime or an arm, reference= included

implementation_gap

learned rules. Each request has a published source and no implementation here. TMLE raises CapabilityError before any learner. CausalStudy.identify raises it for a categorical treatment, and IdentifiedEffect.estimate for a setting. CrossFitting(n_folds=1) raises MethodConfigurationError at construction, before the estimand is known. The refusals a caller can meet quotes each remedy. X11 reopens them

a learned-rule value with a continuous treatment, intermediate=, weights=, id=, strata=, repeats above 1, the full-refit bootstrap, CTMLE or DRTMLE; and every refutation and sensitivity analysis on a learned-rule fit

implementation_gap. inference_gap for the full-refit bootstrap

learned rules. No reviewed source gives the result. The fit refuses before any learner. refute reads unavailable because a refit relearns the rules, and each sensitivity row reads unavailable because no derivation for this target was reviewed. A learned-rule fit with missing outcomes refuses too. X11 parts (i) to (o) hold the repeats, the intermediate variable, weights, clusters, strata, the omitted-variable bound, simulated confounding and refute. F27 holds the continuous treatment, the bootstrap, CTMLE, a fold-evaluated DRTMLE rule, missing outcomes, a missing treatment, the E-value and the tilt

intermediate= with incremental=

api_redirection

incremental interventions

a hazard-based survival fit outside a held baseline categorical treatment with no covariate after baseline, and the isotonic projection on an end-of-study fit or a working model

implementation_gap, invalid_declaration

hazard and isotonic survival refusals lists every refusal. On a design that H1, H2, H7 or H11 refuses, LTMLE with survival_model="iterated_mean" and isotonic=True reports a monotone curve

an odds tilt on LTMLE with an estimated treatment mechanism, or incremental= on LTMLE

api_redirection

incremental interventions over time. LTMLE raises CapabilityError with code api_redirection for incremental= at construction. It raises CapabilityError before any learner for a tilt node whose mechanism is estimated. Both quote LTMLE_TILT_REFUSAL: “an odds tilt reads the estimated treatment mechanism, so its influence function has a mechanism term at every node (Kennedy 2019, Theorem 2), and LTMLE fits no mechanism fluctuation over time. Fit IPSI(regimens=[Incremental(2.0), …]), which runs Kennedy’s cross-fitted estimator. With a known treatment mechanism, LTMLE fits the tilt as a known stochastic policy”. tests/unit/test_ipsi_e2e.py pins the text

an odds tilt on SDR

api_redirection

incremental interventions over time. SDR raises CapabilityError before any learner: “the SDR estimator needs a policy that does not depend on the observed-data law (Díaz, Williams, Hoffman and Schenck 2023, Section 4). An odds tilt reads the treatment mechanism, so its estimator is not doubly robust (Kennedy 2019, Section 3.3). Fit IPSI(regimens=[…])”. tests/unit/test_ipsi_e2e.py pins the text

an IPSI plan that sets a node by a modified treatment policy or a RiskRatioTilt

implementation_gap

incremental interventions over time. IPSI raises CapabilityError before any learner. For a Shift at node A2 the message reads: “regimen ‘tilt then shift’ sets node ‘A2’ by a modified treatment policy (Shift). IPSI estimates no density ratio for a modified treatment policy or a risk-ratio tilt, so it has no estimator for a plan with such a node. F33 in docs/roadmap.md tracks this. Fit that plan without an odds tilt by LTMLE or SDR”. tests/unit/test_ipsi_e2e.py pins the text. F33 holds the request

alpha=, max_iter=, tol=, stopping=, step_bound=, step_search=, iterate=, targeting= or fluctuation= on IPSI, or a non-default submodel_alpha, max_iter, tol, stopping, step_bound, step_search or iterate on IPSIMethod

invalid_declaration

incremental interventions over time. The estimator fits no fluctuation. IPSI and IPSIMethod raise MethodConfigurationError with code invalid_declaration, IPSI at construction. tests/unit/test_ipsi_e2e.py pins both

ratio() of an IPSI level at or below 0 on the log scale, or outside \((0, 1)\) on the odds scale, and an IPSI working model under a logit or log link whose projection does not converge

invalid_declaration

incremental interventions over time. An average of influence-function values can leave the outcome range. CapabilityError names the level or the link. The difference scale and the identity link stay available

SDR on a repeated outcome

implementation_gap

repeated outcomes. SDR needs its own per-horizon reduction and evidence. The allowlist REPEATED_OUTCOME_KINDS names the estimator kinds that fit a repeated outcome. It holds tmle and ipsi. SDR.fit raises CapabilityError before any learner. available_methods() lists sdr as unavailable with the same reason. The message reads: “SDR has no per-horizon reduction for a repeated outcome (outcome=Repeated([…])). X32b in docs/roadmap.md tracks this. IPSI and LTMLE fit one. Each horizon is the end-of-study fit of the panel truncated after it.”. tests/unit/test_repeated_outcome_subclasses.py and tests/unit/test_repeated_outcome_study_route.py pin the text. X32b owns the SDR reduction

baseline= with no column on LongitudinalTreatment, LongPanel or TimeToEvent

implementation_gap

what the reader refuses. The package fits no intercept-only nuisance design. The three layouts share one container, so LongitudinalData raises DataError before any learner, and the point-treatment covariate check refuses the same way. tests/unit/test_empty_baseline_refusal.py pins the code on all three layouts. X63 part (c) reopens it

n_folds above 1 on SequentialGComputation or SequentialIPW

implementation_gap

sequential g-computation and IPW. Neither baseline has a root-n result with a data-adaptive learner, in sample or cross-fitted. The fit raises CapabilityError before any learner. F35 tracks the missing result

msm=, or an odds tilt with an estimated mechanism, on SequentialGComputation or SequentialIPW; and a modified treatment policy on SequentialIPW

api_redirection

sequential g-computation and IPW. The fit raises CapabilityError before any learner and names LTMLE, SDR, IPSI or SequentialGComputation. tests/unit/test_sequential_baselines.py pins each text

a fluctuation setting on SequentialGComputation or SequentialIPW, including stopping=, step_bound=, step_search= and iterate=, an outcome learner or a density setting on SequentialIPW, and a continuous outcome with a binomial GLM and q_bounds=None on SequentialGComputation

invalid_declaration

sequential g-computation and IPW. A fluctuation setting raises MethodConfigurationError, and the others raise CapabilityError, before any learner. The scale refusal names q_bounds= and GLM(family="gaussian")

SequentialGComputation or SequentialIPW on a repeated outcome

implementation_gap

repeated outcomes. The fit raises the shared repeated-outcome refusal before any learner. X71 owns the reduction

a static level other than the reference, a contrast, or check_model on StructuralNestedMean when a blip feature reads a time-varying covariate

implementation_gap

structural dynamic effects. The fit reports the blips and ey_reference. The static value needs the law of the covariate under the regimen. X72 owns it

a rule, a modified treatment policy, a known stochastic plan, an incremental policy, censoring, a survival outcome, a repeated outcome, a held decision, a declared mechanism, or msm= on StructuralNestedMean

api_redirection

structural dynamic effects. The fit raises CapabilityError before any learner and names LTMLE, SDR or IPSI. tests/unit/test_structural_nested_mean.py (W7) pins each code

a static dose outside the observed range, a dose node whose range excludes the reference dose 0, a singular diagonal Jacobian block, a blip feature that reads a later covariate or an unknown column, and check_model on a continuous node, on StructuralNestedMean

invalid_declaration

structural dynamic effects. The message names the range, the block or the node

ParametricGFormula on a repeated outcome, or n_folds above 1

implementation_gap

parametric g-formula. The fit raises CapabilityError before any learner. X71 owns the repeated outcome, and F35 owns cross-fitting

msm= or a risk-ratio tilt on ParametricGFormula, and a truncation curve of its result

api_redirection

parametric g-formula. The fit raises CapabilityError and names LTMLE or SequentialIPW

an undeclared time-varying column that is not coded 0/1, a learner that is not a GLM for a Gaussian column, a learner that nothing reads, and a declared treatment mechanism with a missing value at a tilt or policy node, on ParametricGFormula

invalid_declaration

parametric g-formula. The fit raises CapabilityError before any learner. The message names covariate_types= where that applies

sample sensitivity-bound estimation for LTMLE and SDR

implementation_gap

longitudinal diagnostics. See F16 for the contracts Tan (2025) leaves open

alpha=, max_iter=, tol=, stopping=, step_bound=, step_search= or iterate= on SDR, or a non-default submodel_alpha, max_iter, tol, stopping, step_bound, step_search or iterate on SDRMethod

invalid_declaration

the sequentially doubly robust estimator. SDR fits no fluctuation, so it has no logistic shrink, iteration limit or score tolerance. SDR and SDRMethod raise MethodConfigurationError, SDR at construction. The message names the LTMLE keyword that takes the setting. tests/unit/test_sdr_e2e.py pins both texts

method="sdr" on a point-treatment functional

api_redirection

the sequentially doubly robust estimator. The estimator is the sequential regression of a longitudinal fit. available_methods() lists it as unavailable with the reason, and estimate() raises CapabilityError before any learner

ratio() of an SDR level at or below 0 on the log scale, or outside \((0, 1)\) on the odds scale

invalid_declaration

the sequentially doubly robust estimator. An average of transforms can leave the outcome range (Díaz et al. 2023, page 854), and the scale is undefined there. CapabilityError names the level and its value. The difference scale stays available

an SDR working model under a logit or log link whose projection does not converge

invalid_declaration

MSM projections. A transform mean outside the link’s range has no finite solution. The fit raises CapabilityError and names the identity link and LTMLE(msm=...)

blocked-temporal and rolling-origin splits

implementation_gap

two fold layers

a confidence interval, a p-value, or a standard error on a CTMLE fit whose strategy is "greedy", "ordered", "discrete", "lasso", or "learners"

inference_gap

collaborative TMLE. No result shows the reported curve is the estimator’s influence curve when the selected working mechanism is not consistent for the treatment law. ci, pvalue, and std_error raise CapabilityError. The point estimate, the selection path, and the curve remain, and plugin_std_error and plugin_interval report the retained diagnostic. A discrete fit with one declared candidate, equal to the full adjustment set, selects nothing and keeps the TMLE interval. A learners fit with an empty treatment_candidates= list does the same. F18 reopens it

a confidence interval, a p-value, or a standard error on every CTMLE fit with strategy="oat", of either oat_design=, including a fit with strata=, delta= or treatment_delta= and a fit that requests one arm mean

inference_gap

collaborative TMLE. The fit takes the "generated_design_plugin" status. No result covers the shared design. The per-arm design applies Theorem 1 of Benkeser, Cai and van der Laan (2020) to each arm. That theorem’s condition (v) fails for an estimated outcome regression, and the registered study ctmle-oat-per-arm measured standard errors 8 to 24 percent too small. The point estimate remains, and plugin_std_error and plugin_interval report the retained diagnostic. F19 reopens it

a confidence interval, a p-value, or a standard error on a DRTMLE fit with a non-empty guard and varying weights declared estimated (weights_estimated=True)

inference_gap

DR-TMLE refusals. The fit takes the "estimated_weight_plugin" status. The argument that an interval conditions on the weights concerns \(D^*\), and not the reduced regressions. The point estimate remains, and plugin_std_error and plugin_interval report the retained diagnostic. guard=() fits the ordinary TMLE and keeps its interval. Constant weights fit the unweighted estimator and keep it too. X50 part (f) reopens it

a confidence interval, a p-value, or a standard error on a TMLE or DRTMLE fit with id= and fewer than 10 positive-mass clusters in the fit or one reported baseline stratum, or an LTMLE or SDR fit with fewer than 20, in sample or cross-fitted

inference_gap

clusters. The fit takes the "few_cluster_plugin" status, in sample or cross-fitted. One such stratum withholds every interval. From that floor to 39 such clusters the fit reports intervals on a Student \(t\) reference with \(J - 2\) degrees of freedom, as Nugent et al. (2024), Section 2.2, recommend. Each floor is the smallest count that the registered few-cluster study measures for that fit. The point estimate remains, and plugin_std_error and plugin_interval retain the diagnostic on the normal reference. F28 owns the counts below each floor. X54 holds the measurements from 4 clusters to each floor, and F28 keeps fits below 4 clusters

simultaneous_bands() over an estimate with a Student \(t\) reference, below 40 positive-mass clusters

inference_gap

clusters. No source read here gives a \(t\)-calibrated joint band. simultaneous_bands() raises CapabilityError, and a fit skips its default band and names the reason in summary(). The pointwise intervals remain. tests/unit/test_few_cluster_reference.py checks both

a contrast, a simultaneous band, any E-value branch, variable_importance(), or tipping_gamma(use_ci=True) on the fits of the four status rows above

inference_gap

inference status. Each one is a confidence statement, or reads an interval that these fits do not supply. variable_importance() refuses at its entry point, because it adjusts one p-value per candidate. The E-value capability reports unavailable rather than raising inside the computation. truncation_curve(), missingness_tilt(), and the default tipping_gamma() still run. On a point-treatment fit the two frames rename their three spread columns. The longitudinal truncation_curve() publishes no spread. LongitudinalResult.curve() and incidence_total() rename their spread columns

stratify_by="treatment" and stratify_by="treatment+outcome" on any fit that draws a split, including selector-based C-TMLE at every setting

implementation_gap

fold and outcome-scale rules. No shipped result covers a partition read off the data the fit then conditions on

a cross-fitted continuous outcome with q_bounds=None, for TMLE, DRTMLE, CTMLE, LTMLE and SDR above one fold

invalid_declaration

fold and outcome-scale rules. The sample outcome range would take the scale from held-out rows

CTMLE with id=, at every cross_fit setting, on every strategy except "derivative"

implementation_gap

fold and outcome-scale rules. No clustered result covers the outcome-adaptive mechanism. Selector-based fits also lack a result for grouped selection and nested folds or candidate-selection variance. A "derivative" fit selects nothing and takes the clustered result of TMLE

cv_evaluation=True or targeting_scheme="fold" with id=, when a validation fold holds 1 cluster

invalid_declaration

the algorithm as implemented. Each fold’s variance takes the variance of the cluster totals inside the fold, and 1 total has none. TMLE raises CapabilityError before any learner, for a generated or a supplied split. A generated split passes at no more folds than half the cluster count. tests/unit/test_fold_evaluated_cluster_variance.py checks it

the subset and bootstrap_measurement_error refutations on a fit that declared split_plan=

invalid_declaration

reusable outer split plans. Each one refits on rows the fit never ran, and no rule here says which fold labels those rows inherit. refute() raises CapabilityError before it refits anything. The refute capability row reads deferred on tests, with the same sentence. A request whose tests= names neither operation runs

replicate weights (BRR, jackknife)

invalid_declaration

observation weights. These are a set of designs rather than one weight vector, so the shape they want is a refit per replicate outside the estimator

omitted-variable sensitivity after repeats=

implementation_gap

validation and sensitivity methods. The median bound needs an influence function; a coordinatewise median of per-draw influence terms is not one. The five omitted-variable capability rows declare this refusal as unavailable before any call, and test_the_five_declared_rows_carry_that_same_reason checks them

omitted-variable sensitivity on a DRTMLE or CTMLE fit, except a CTMLE fit with strategy="derivative"

implementation_gap

validation and sensitivity methods. Neither estimator assumes a consistent treatment mechanism, and no derivation gives \(\nu^2\) or the bound’s standard error without that assumption. A "derivative" fit assumes a consistent mechanism as TMLE does, so the bound admits it

omitted-variable sensitivity on a fit with a response mechanism

implementation_gap

validation and sensitivity methods. The bound is well posed, and the implementation is missing. The implemented representer omits the response indicator, and \(\sigma^2\) averages the respondents only. Use the missingness tilt for response on the arm means. The tilt does not cover ey_obs, PAR or PAF. An explicit tilt request that names one of them raises a sentence that names them. On a fit that reports one of them, the tipping_gamma row reads deferred and requires estimand, and the bare call raises the row’s reason. The default tilt sweep skips them

omitted-variable sensitivity on a fit with a declared missing treatment

implementation_gap

validation and sensitivity methods. The Riesz representer of the bound is derived for a recorded treatment. This fit divides by the composite mechanism, whose representer and confounding strengths are not derived

omitted-variable sensitivity on a fit with an intermediate variable

implementation_gap

validation and sensitivity methods. The bound is well posed, and the implementation is missing. The representer carries the intermediate weight, so the treatment strength \(c_D\) would also measure the intermediate mechanism

omitted-variable sensitivity on a regime, shift, or msm parameter axis

implementation_gap

validation and sensitivity methods. Each parameter has a Riesz representer, so the bound is well posed. Only the implementation is missing

omitted-variable sensitivity on an ipsi parameter axis

invalid_declaration

validation and sensitivity methods. An incremental intervention tilts the mechanism, so the mechanism is part of the estimand rather than a nuisance

omitted-variable sensitivity on an arm-indexed fit that reports no counterfactual mean and no linear contrast. A ratio-only fit, a PAR or PAF fit, and a complete-outcome ey_obs fit are such fits

invalid_declaration

validation and sensitivity methods. One bound is the second moment of one contrast’s Riesz representer, and the fit reports no such contrast. The five capability rows read unavailable with the sentence that each call raises. On a fit that reports rr or or, the sentence names sensitivity.evalue(). TestTheBoundRowsReadTheCallsTable in tests/unit/test_capability_row_predicates.py checks four such fits

benchmark() with every covariate of the fit named

implementation_gap

validation and sensitivity methods. The short model would adjust for nothing, and no estimator here fits without a covariate. benchmark() raises CapabilityError before the refit. On a fit with one covariate, the benchmark capability row reads unavailable, because no value of covariates runs. On a wider fit, a request that names every covariate reads unavailable. An unknown name raises DataError before this refusal

omitted-variable sensitivity when the doubly robust estimate of \(\nu^2\) is not positive

invalid_declaration

validation and sensitivity methods. This row reads the data, not the fit class. The other omitted-variable rows refuse a whole class of fit before any computation, and their capability rows report unavailable. This one leaves the capability row available, and the call raises CapabilityError only when the fitted representer gives a nonpositive second moment. An ordinary TMLE script that received a bound before can therefore raise now, because the package substitutes no plug-in value for the refused one

the one-sided limits and the confidence-limit robustness value under nu2_estimator="plugin"

inference_gap

validation and sensitivity methods. No derivation in a source this package cites gives the standard error of a bound built on the plug-in \(\nu^2\), which moves at first order with the fitted treatment mechanism. The bounds, rv, max_bias, benchmark() and contour() remain. F26 reopens it. The limits under the default nu2_estimator="doubly_robust" remain available

omitted-variable limits when \(\sigma^2=0\)

inference_gap

the bound’s standard error needs a positive maximal-bias scale. The point bounds remain, but the limit accessors refuse. An unreachable point robustness threshold is None

any E-value request on a fit with an intermediate variable and a discrete treatment

implementation_gap

the E-value paths. This package has no implemented bound for the fitted controlled direct effect and its confounding model. The ordinary conversion does not establish sensitivity for that target. The capability row reports unavailable before any computation. A continuous-treatment fit with an intermediate variable reports not_applicable, because that check runs first. X49 tracks the support gap

the standardized Gaussian E-value on a fit with a response mechanism

implementation_gap

the E-value paths. The conversion needs the population outcome standard deviation, and the respondents’ one estimates a different quantity. A ratio E-value on the same fit stays available

correction diagnostics after ordinary TMLE or collaborative TMLE

invalid_declaration

those methods do not use the DR-TMLE correction system. A DR-TMLE fit with an empty guard also subtracts no correction term

a binomial ATT or ATC E-value conversion

implementation_gap

the analysis needs a conditional baseline risk and a supported conditional risk-ratio target. The library implements neither

a fixed-baseline E-value on a reference-arm mean that its own standard error does not separate from zero

invalid_declaration

the E-value paths. The conversion divides by that mean, so the ratio has no stable denominator

a fixed-baseline E-value whose risk difference is at or below the negative of the reference-arm mean

invalid_declaration

the E-value paths. The difference implies a nonpositive risk in the contrast arm, and no risk ratio describes one

a cached-nuisance risk ratio after CV evaluation

implementation_gap

the estimator refuses nonlinear ratio targets on this evaluation path. Post-fit retargeting keeps that boundary

a controlled direct risk ratio from cached nuisances

implementation_gap

the fitted result can report a controlled direct risk ratio. The post-fit retarget path does not pass the fitted intermediate intervention level, so it cannot derive one from cached nuisances

Which multi-arm surfaces are covered, and which are not, is tabulated in one place: where a multi-valued treatment is supported.

Several former gaps have landed. cleverly now supports multi-valued longitudinal treatment nodes. It supports multi-valued selector-based C-TMLE, outcome-adaptive C-TMLE, DR-TMLE, ATT, and ATC. LTMLE supports observation weights and a working model over regimens. IPSI fits incremental interventions over time. Shift fits support intermediate= and weights. They support delta= in sample. cleverly also supports multi-arm omitted-variable and MNAR sensitivity analyses. TMLE supports the scalar missing-outcome natural-course mean, and PAR and PAF, with ordinary fitting or one generated split of stacked CV-TMLE. Missing-outcome natural-course contracts gives the boundaries of each path.

TMLE also cross-fits arm-indexed means and contrasts with missing outcomes under one stacked CV-TMLE contract. Missing-outcome arm-indexed contract gives its boundaries.

The remaining shift gap is narrower than it was. The tilt itself is written. The missing derivation must establish whether the tilted parameter is still the shift parameter.

Targeting and fitting option refusals#

These options are Targeting(stopping=, step_bound=, step_search=, iterate=), ModelSpec(outcome_by_arm=), CollaborativeTMLEMethod(patience=) and PointTreatment(treatment_adjustment=). A refusal is raised before any learner fits, unless the row says otherwise. A malformed value raises ValueError with no record. Every other row carries the code shown. tests/unit/test_stopping_rule.py, test_step_bound.py, test_best_iterate.py, test_outcome_by_arm.py, test_patience.py, test_treatment_covariates.py, test_treatment_covariates_collaborative.py and test_hazard_x22_refusal.py assert each text with pytest.raises(match=re.escape(...)).

id

refused

class and code

message

R-a1

iterate= other than "last" or "best"

ValueError

iterate must be 'last' or 'best'; got {v!r}

R-b1

stopping= other than "relative", "scaled_var" or "sample_size"

ValueError

stopping must be 'relative', 'scaled_var' or 'sample_size'; got {v!r}

R-c1

step_bound= that is not a positive finite number, a bool included

ValueError

step_bound must be a positive finite number; got {v!r}. step_bound=None takes the full Newton step

R-c1a

step_search= that is not a bool

ValueError

step_search must be True or False; got {v!r}

R-c2

step_bound= with algorithm="one_step"

MethodConfigurationError, api_redirection

algorithm='one_step' already walks the universal least-favorable submodel in steps of step_size. step_bound= bounds the steps of algorithm='iterative'.

R-c3

step_search=False without step_bound=

MethodConfigurationError, invalid_declaration

step_search= chooses how a bounded step is taken, so it needs step_bound=

R-abc1

iterate="best", a declared stopping= or step_bound= with fluctuation="linear"

MethodConfigurationError, invalid_declaration

fluctuation='linear' solves the score equation in one closed-form step, so it has no iterate, stopping rule or step bound to choose. Remove iterate=, stopping= and step_bound=, or use fluctuation='logistic'

R-abc2

a non-default stopping=, step_bound=, step_search= or iterate= on SDR, SDRMethod

MethodConfigurationError, invalid_declaration

the SDR estimator fits no fluctuation, so it has no logistic shrink, iteration limit or score tolerance. LTMLE takes stopping=, so pass the setting there. The text names the keyword that was declared

R-abc3

the same settings on IPSI, IPSIMethod

MethodConfigurationError, invalid_declaration

IPSI fits no fluctuation: Kennedy's estimator averages the influence function at the fitted nuisances, so it has no logistic shrink, iteration limit, score tolerance or submodel.

R-abc3a

the same settings on GComputationMethod, IPWMethod, SequentialGComputation, SequentialIPW

MethodConfigurationError, invalid_declaration

the sequential g-computation and IPW estimators fit no fluctuation, so they have no logistic shrink, iteration limit or score tolerance. LTMLE takes stopping=, so pass the setting there.

R-abc4

iterate=, stopping= or step_bound= with survival_model="hazard". LTMLE raises it when it validates its settings, after the malformed-value checks

CapabilityError, implementation_gap, roadmap X33

survival_model='hazard' walks its own path in steps of step_size, so iterate=, stopping= and step_bound= do not apply to it. X33 in docs/roadmap.md tracks this. survival_model='iterated_mean' with targeting='iterative' applies iterate=, stopping= and step_bound= at each node. The list names only the settings declared, in the condition and in the alternative

R-f1

outcome_by_arm=True with a continuous dose, including a dose MSM and a shift policy on a dose. The check runs before any learner fits

CapabilityError, invalid_declaration

outcome_by_arm=True fits one outcome regression per treatment arm, so it needs an arm-coded treatment. A continuous dose has no arms. Use outcome_by_arm=False, which fits one regression on the dose.

R-f2

an arm with no training row in a fold. It is raised at fit time

DataError

outcome_by_arm=True found no row with {treatment} = {arm!r} and an observed outcome in {where}, so that arm's outcome regression has nothing to fit. Use fewer folds, or outcome_by_arm=False. {where} reads “training fold {k}” or “the training sample”

R-f3

outcome_by_arm= on a longitudinal design

MethodConfigurationError, invalid_declaration

longitudinal TMLE cannot apply point-treatment option(s): ['outcome_by_arm']. Remove them or use a PointTreatment design.

R-g1

patience= that is not a positive integer, a bool included

ValueError

patience must be a positive integer; got {p!r}

R-g2

patience= with strategy="oat" or "derivative", which score no path

ValueError

patience= stops the cross-validated scoring of a candidate path, and strategy={s!r} scores no path. It applies to 'greedy', 'ordered', 'discrete', 'lasso' and 'learners'

R-h1

treatment_adjustment= that names a column outside adjustment=

DataError

treatment_adjustment names {cols} outside adjustment=. The treatment mechanism conditions on a subset of the adjustment set

R-h2

treatment_adjustment= with CTMLE(strategy="oat")

CapabilityError, invalid_declaration

strategy='oat' fits the treatment mechanism on the outcome predictions, so treatment_adjustment= has no covariates to restrict. Use another strategy, or omit treatment_adjustment=.

R-h3

treatment_adjustment= with a point incremental= estimand

CapabilityError, different_estimand

incremental= shifts the fitted propensity score, so a mechanism on {Wg} defines the shift on {Wg} and changes the estimand. Omit treatment_adjustment=, or treatment_covariates= in TMLE.fit.

R-h4

treatment_adjustment= with a known treatment_probabilities=

DataError

treatment_adjustment= restricts a fitted treatment mechanism, and treatment_probabilities= supplies a known one. Remove one of them

R-h5

a C-TMLE candidates= or ordering= that names a covariate outside the declared subset

ValueError

{arg} names {cols} outside treatment_adjustment=

R-h6

a repeated name in treatment_adjustment=

DataError

treatment_adjustment repeats {cols}

TMLE.fit(treatment_covariates=) raises R-h1, R-h4 and R-h6 with the argument named treatment_covariates in place of treatment_adjustment. The row R-abc4 names the iterated-mean survival fit as its alternative, and the roadmap item X33 tracks the gap. tests/unit/test_hazard_x22_refusal.py fits that alternative with each refused setting.

A declared stopping rule can leave a fit unconverged. That is a result and not a refusal. The fit warns, reports converged=False, and score_check() fails it. Stopping rules gives the text.

Experimental compositions#

A composition that no study covers, and whose code computes the requested quantity, runs. The fit returns its estimates under the experimental label. The old refusal text becomes the reason of the label, and the roadmap id becomes its evidence. result.summary() prints the reason, and estimate.support holds it as data.

The preflight collects each such condition and goes on. A later refusal therefore still fires, and no condition masks it. The fit reads no learner before the preflight ends.

tests/unit/test_group_a_experimental_fits.py checks each site below on four points.

  1. The preflight collects the condition before any learner.

  2. The fit carries the label, the reason, and the roadmap id.

  3. The point lies within three standard errors of the truth, where the complete-case answer does not.

  4. A deliberate mutation moves the point.

contract

settings that run as experimental

roadmap

arm-indexed, stacked

linear fluctuation, one-step update, weighted fluctuation

X60

arm-indexed, stacked

att and atc, observation weights, clusters

X60

arm-indexed, stacked

fold evaluation, split_plan=, repeats above 1, baseline strata

X53

arm-indexed, stacked

n_bootstrap > 0

F2

natural course, in sample

a linear fluctuation, one-step targeting, weighted targeting, n_bootstrap > 0

X60, X55

natural course, stacked

the same settings, and repeats above 1, split_plan=, weights, clusters, a continuous outcome

X53, X60, F2

DRTMLE with missing outcomes

weights= that tilt the target law

X62

The targeting and fitting options also run as experimental. The condition is collected in the same preflight, and tests/unit/test_option_support.py checks each label against the fit without the option.

option

runs as experimental

keeps the label of the fit

roadmap

stopping=

"scaled_var" and "sample_size", on a point fit and on LTMLE

"relative"

X76

iterate=, step_bound=

never on their own

under stopping="relative" the walk stops only at the solution of the score equation, to tol

none

outcome_by_arm=

True

False

X76

patience=

any value

None

X76

treatment_adjustment=

a declared subset

the whole adjustment set

X76

These requests stay refused, because the code returns a misleading number or has no path:

refused

why

a cross-fitted missing-outcome fit outside the two contracts (tmle.off_contract_axes)

the interval is misleading

targeting_scheme="fold" under either contract

the interval is misleading

a clustered CTMLE fit with missing outcomes

the interval is misleading

fold evaluation of the stacked natural-course mean

the fold-evaluated report has no natural-course mean to read

a continuous outcome without q_bounds, intermediate=, a selector-based CTMLE, DRTMLE on the natural-course target

an invalid declaration, or an estimator with no written result

Missing-outcome natural-course contracts#

NaturalCourseMean(), PopulationAttributableRisk() and PopulationAttributableFraction() with missingness= and at least one missing outcome have two TMLE contracts. A request that reads the natural course beside arm targets meets the same contracts. CrossFitting(enabled=...) selects the contract. The table gives each requirement, the compositions that each contract refuses, and the compositions that it runs with the experimental label.

requirement

ordinary TMLE, enabled=False

stacked CV-TMLE, enabled=True

target

ey_obs, par, paf, or ey_obs beside arm means, contrasts, ATT and ATC. estimands="all" keeps its arm-only list

the same. ATT and ATC run as experimental under the arm-indexed contract

estimator

TMLE. CTMLE and DRTMLE are refused. DRTMLE(guard=()) is the ordinary TMLE, and the message names TMLE

the same

treatment

two or more arms

two or more arms. With three or more, the scalar ey_obs also needs two respondents in each arm

outcome

binary, or continuous with declared q_bounds. A continuous outcome without q_bounds is refused

binary. A continuous outcome runs as experimental

outer folds

one fold

package-generated folds with stratify_by="none" and n_folds of at least 2. One fold and stratified folds are refused. split_plan= runs as experimental

repeats

repeats=1. A larger value fails when CrossFitting is constructed, because no split exists to repeat

repeats=1. A larger value runs as experimental (X53)

targeting

Targeting(fluctuation="logistic", algorithm="iterative", target_weights=False). A linear fluctuation, one-step targeting, and weighted targeting run as experimental (X60)

the same, with targeting_scheme="pooled" and fold_evaluation=False. Fold targeting and fold evaluation are refused

inference

influence-curve Wald interval. n_bootstrap > 0 runs as experimental (X55 part (a))

influence-curve Wald interval. n_bootstrap > 0 runs as experimental (F2 holds the bootstrap of the stacked fit)

rows

fixed weights=, id= and baseline strata are admitted. intermediate= is refused

unweighted iid rows. Weights and clusters run as experimental. intermediate= is refused. The scalar ey_obs admits baseline strata. PAR, PAF, and a joint fit meet the arm-indexed contract (X53 part (e))

response support

no added check

at least two respondents and two nonrespondents in the sample, and one of each in every training complement

Three checks enforce the two contracts. Each check runs at a different time and raises a different error. A composition that runs as experimental raises nothing. The preflight collects its condition, and the fit carries the condition as the reason of the label.

check

error

code

when it runs

a cross-fitting declaration that is invalid by itself

MethodConfigurationError. The engine raises ValueError with the same reason

invalid_declaration, and implementation_gap for a stratified fold policy

when CrossFitting or TMLEMethod is constructed

a composition this target does not support

CapabilityError

implementation_gap or invalid_declaration, by requirement

when the fit starts, before any learner is fitted

response support

DataError

invalid_declaration

after fold generation, before either learner receives data

The composition check refuses a CTMLE fit and a DRTMLE fit as an implementation_gap. The message names X51 for CTMLE and X50 for DRTMLE. On the stacked fit it also refuses fold targeting and fold evaluation (X53), and intermediate= (X60). It refuses a continuous outcome without q_bounds as an invalid_declaration. Fold targeting and the cross-fitted off-contract regimes stay refused because the code returns a misleading interval there. Fold evaluation stays refused because the fold-evaluated report has no natural-course mean to read.

Every other row of the table above runs. The condition text of the old refusal becomes the reason of the label, and the roadmap id becomes its evidence. The check collects the condition and continues, so a condition never hides a later refusal.

The declaration check is not specific to this target. One function runs its steps in a fixed order, and it returns the first reason it finds. The declaration and the engine therefore give one reason for one input. A reason that names the cross-fitting switch names enabled from the declaration and cross_fit from the engine. tests/unit/test_split_plan.py::TestOneInputEarnsOneReasonAtBothLayers checks that both layers give the same reason.

order

the declaration check refuses

code

1

repeats below 1

invalid_declaration

2

a split_plan that is not a SplitPlan

invalid_declaration

3

a plan that records no generator. See reusable outer split plans for the two plans that carry the record

invalid_declaration

4

a plan that the declared fold policy cannot use. A plan requires cross-fitting with at least two folds, and its fold and repeat counts must fit the declaration

invalid_declaration

5

a plan together with n_bootstrap above 0. TMLEMethod runs this step, because it holds both settings

invalid_declaration

6

repeats above 1 with cross-fitting disabled

invalid_declaration

7

cross-fitting declared with fewer than two folds

invalid_declaration

8

a fold-stratification policy this package draws no split under. stratify_by other than "none" is refused whenever the fit draws a split, including selector-based C-TMLE at every setting

implementation_gap

A split_plan can pass the declaration check and still fail the natural-course contract. Under enabled=True, a valid plan constructs, and the stacked contract refuses it when the fit starts. Under enabled=False, step 4 refuses any plan at construction.

response-support failure

what the message tells you to do

the sample holds fewer than two respondents or fewer than two nonrespondents

use the in-sample estimator. No fold count or random_state can succeed

one training complement holds no respondent or no nonrespondent

the message names the repeat and the fold. Use the in-sample estimator, or collect more observations at the rare level

Both messages name the in-sample estimator as CrossFitting(enabled=False), or cross_fit=False on the engine. One fold balances nothing, so no fold policy is part of the remedy. Neither message names a redraw, because the split reads neither the response indicator nor the outcome.

stratify_by="none" is the default, and it is the only policy any fit that draws a split accepts. It is not special to these two contracts. "treatment" and "treatment+outcome" are refused when the fit draws a split, and at every setting for selector-based C-TMLE, whose search draws selection folds without cross-fitting. An in-sample outcome-adaptive fit draws no split. It accepts the declaration and applies none of it. The fold and outcome-scale rules give the audit behind the refusal and the message each layer raises.

Missing-outcome arm-indexed contract#

The contract covers the arm-indexed mean group ey, ey0, ey1, ate, rr, or, att, and atc under cross-fitting with at least one missing outcome. It applies to an ordinary TMLE or C-TMLE fit under three conditions. The parameters are indexed by the arms of a discrete treatment. The design declares no intermediate. The request is not the scalar NaturalCourseMean.

A per-arm outcome-adaptive C-TMLE fit reports a point estimate only, so this contract does not govern it. The per-arm contract states its own preflight.

A request that reports ey_obs, PAR or PAF beside arm targets meets this contract and the natural-course contract, and its admitted list includes ey_obs, par and paf. Stacked CV-TMLE for arm-indexed targets defines the estimator, its preflight, and its evidence.

requirement

admitted

refused

estimator

TMLE or TMLEMethod

CTMLE or CollaborativeTMLEMethod, other than the per-arm oat design. DRTMLE raises its own refusal at every guard, including guard=()

targets

ey, ey0, ey1, and ate. rr and or for a binary outcome

att and atc run as experimental, also when the default list or "all" includes them. A continuous rr or or stays refused, as for every fit

treatment

two or more arms

none

outcome

binary, or continuous with a fixed q_bounds equal to the known support

continuous with q_bounds=None

outer folds

package-generated folds with stratify_by="none" and n_folds of at least 2

split_plan= runs as experimental. A stratified policy and one fold are refused earlier, by the declaration check

repeats and targeting

repeats=1 and targeting_scheme="pooled"

repeats above 1 runs as experimental. targeting_scheme="fold" is refused

fluctuation

Targeting(fluctuation="logistic", algorithm="iterative", target_weights=False) and fold_evaluation=False

a linear fluctuation, one-step targeting, weighted targeting, and fold evaluation run as experimental

rows

unweighted iid rows

observation weights, clusters, and baseline strata run as experimental

inference

pointwise influence-curve Wald intervals and the simultaneous band

n_bootstrap > 0 runs as experimental

content

at least the minimum in the preflight table

a sample or a training complement below that minimum. A sample below its minimum is refused with a remedy that does not repartition, because no partition can succeed

Three checks enforce the contract. The table gives the error of each check and the time at which it runs.

check

error

code

when it runs

a composition outside the contract that stays refused

CapabilityError

implementation_gap or invalid_declaration, by requirement

when the fit starts, before fold generation and before any learner is fitted

DRTMLE with delta= and cross_fit=True

CapabilityError

implementation_gap

when the fit starts, before any learner is fitted

minimum content

DataError

invalid_declaration

after fold generation, before any learner is fitted

A DRTMLE fit with missing outcomes and cross_fit=True raises the second CapabilityError in the table at every guard, including guard=(), and under every fold policy. tests/unit/test_drtmle_missing.py checks the guard=() case with zero learner calls.

The composition check runs its steps in a fixed order. A fit that breaks several rules meets the first refusal. Each message names the missing result and the setting that the contract admits. A setting message gives both the engine keyword and the public spelling. A step marked experimental does not stop the fit. It adds a condition to the label and the check goes on, so a later refusal still fires. tests/unit/test_arm_indexed_stacked_mar.py checks each step through the engine and through TMLEMethod, with zero learner calls.

order

the composition check meets

outcome

code

roadmap item

1

C-TMLE

refused

implementation_gap

X51

2

fluctuation="linear"

experimental

implementation_gap

X60

3

algorithm="one_step"

experimental

implementation_gap

X60

4

target_weights=True

experimental

implementation_gap

X60

5

fold_evaluation=True

experimental

implementation_gap

X53

6

att or atc

experimental

implementation_gap

X60

7

a continuous outcome with q_bounds=None

refused

invalid_declaration

none

8

split_plan=

experimental

implementation_gap

X53

9

repeats above 1

experimental

implementation_gap

X53

10

targeting_scheme="fold"

refused

implementation_gap

X53

11

observation weights

experimental

implementation_gap

X60

12

clusters

experimental

implementation_gap

X60

13

baseline strata

experimental

implementation_gap

X53

14

n_bootstrap > 0

experimental

inference_gap

F2

The list once held a stratified fold policy and a fold count below two. The declaration check refuses both, so neither step can run. A copied or modified estimator can still carry the refused policy. The fit then raises CapabilityError, a subclass of ValueError, with the declaration check’s own reason. A result that holds such an estimator reads its refit_nuisances replay slot false, with the code point_replay_refit_configuration.

A refusal that has an in-sample alternative names it as CrossFitting(enabled=False). The engine form is cross_fit=False. An in-sample fit that draws no split keeps whatever fold policy the declaration carries and applies none of it. Selector-based C-TMLE draws selection folds at every setting, so it refuses a stratified policy in sample as well.

Replay-only unavailability#

The longitudinal truncation grid can be scientifically supported while one result cannot replay it. result.replayability.unreconstructible carries the omission codes below as data. The capability row states the same codes inside its prose reason, so read the attribute when you want them as data. A refused replay carries the reason code invalid_declaration. cleverly exports no name for the omission code literals. Compare an omission code against the spelling in the table. The fitted-bound equality check is different: it can only run when the curve is invoked.

Replay accepts an outcome or pseudo-outcome learner only when every random_state parameter it exposes is an explicit integer. The check reads that learner, each nested learner, and each member of a Super Learner library. It reads the declared parameter and not the fitted behavior, so a declared random_state=None is refused even where the fit is deterministic.

the learner you pass

what replay does

LinearRegression()

accepts it, because the class declares no random_state

LogisticRegression(random_state=0)

accepts it, because the declared state is an explicit integer

LogisticRegression()

refuses it as longitudinal_replay_random_state_unseeded

LogisticRegression(random_state=rng), for a generator rng

refuses it as longitudinal_replay_random_state_non_integer

tests/unit/test_longitudinal_truncation_refit.py checks an unseeded learner, a non-integer state, an unseeded member of a nested Super Learner library, and a seeded stochastic learner that replays exactly.

omission code

refusal code

replay boundary

longitudinal_replay_learner_unclonable

invalid_declaration

an outcome or pseudo-outcome learner cannot be cloned

longitudinal_replay_random_state_unseeded

invalid_declaration

a replayed learner or nested learner declares an unspecified random state

longitudinal_replay_random_state_non_integer

invalid_declaration

a replayed learner or nested learner declares a non-integer random state

longitudinal_replay_fitted_bound_mismatch

invalid_declaration

invocation-time preflight found that a full replay at the fitted bound differs from the stored fitted artifacts

The static recipe omissions make the capability row unavailable. A fitted-bound mismatch instead raises a stable CapabilityError from that invocation; it is not known when the capability row is built. Missing bounds or the refit opt-in makes a combined report deferred, because the caller can supply either request.

A point-treatment result has two codes of its own. replayability() reads each one without a fit, and every row that needs the false slot reads unavailable.

omission code

refusal code

replay boundary

slots

point_replay_function_declaration

invalid_declaration

a stored regime or MSM function lacks an accepted known-function declaration

both slots read false

point_replay_refit_configuration

invalid_declaration

the package refuses the stored estimator configuration before a refit. The slot runs every check that refit() runs before a learner, including the design checks of an estimator subclass. A stratified fold policy, a cross-fitted continuous outcome with q_bounds=None, and a C-TMLE fit on clustered data are such configurations

refit_nuisances reads false. retarget_cached_nuisances stays true, because the cached nuisances still retarget

The second code reads the checks that refit() runs before its first learner. These are the fold policy and four data contracts. TestTheRefitSlotReadsTheRefitPreflight in tests/unit/test_capability_row_predicates.py checks each slot against the call it describes.

Hazard and isotonic survival refusals#

Hazard-based survival targeting and the isotonic projection refuse each composition below by name. Each refusal is raised before any learner. tests/unit/test_hazard_refusals.py pins the type and the message of each one, with learners that raise if they are fitted. tests/unit/test_hazard_x22_refusal.py pins H13.

id

trigger

when

type and code

message

H1

survival_model="hazard" on a design with a treatment at more than one node

fit

CapabilityError, implementation_gap

“survival_model=’hazard’ is built for one baseline treatment held over every node, and this design declares a treatment at {k} nodes. Hazard targeting for a treatment at several nodes is not built. X70 in docs/roadmap.md tracks this. LTMLE with survival_model=’iterated_mean’ estimates this curve, and isotonic=True makes it monotone. If one baseline treatment is held over every node, declare treatment=’A’.”

H2

survival_model="hazard" with a covariate measured after baseline (time_varying=)

fit

CapabilityError, implementation_gap

“survival_model=’hazard’ models the event and censoring hazards given the baseline treatment and the baseline covariates. This fit declares covariates measured after baseline ({names}), and hazard targeting with such covariates is not built: it needs a model and a fluctuation of each later covariate. X70 in docs/roadmap.md tracks this. LTMLE with survival_model=’iterated_mean’ adjusts for them, and isotonic=True makes its curve monotone.”

H3

survival_model="hazard" on an end-of-study outcome

fit

CapabilityError, invalid_declaration

“survival_model=’hazard’ needs an event indicator at every node, and this fit declares one end-of-study outcome. A hazard is the chance of the event at a node among the units still at risk, so an outcome measured once has none.”

H3

isotonic=True on an end-of-study outcome

fit

CapabilityError, invalid_declaration

“isotonic=True makes a curve over the horizons monotone, and an end-of-study fit reports one horizon.”

H4

targeting="one_step" with survival_model="iterated_mean"

construction

ValueError

“targeting=’one_step’ requires survival_model=’hazard’. The one-step walk moves one set of hazards toward the score equations of every horizon together (Cai and van der Laan 2020). A one-step update of the iterated-mean recursion is not built. Set survival_model=’hazard’ on a survival design with one baseline treatment, or keep targeting=’iterative’ and set isotonic=True for a monotone curve”

H5

msm= with survival_model="hazard" or isotonic=True

construction

CapabilityError, implementation_gap

“msm= with {option} is not built. A working model reports coefficients over regimen and horizon cells. X70 in docs/roadmap.md tracks this. Fit msm= with survival_model=’iterated_mean’ and isotonic=False, or report the curve and take contrasts.”

H6

pseudo_learner= with survival_model="hazard"

construction

ValueError

“pseudo_learner= is not used with survival_model=’hazard’. A hazard fit regresses the event indicator at each node and fits no pseudo-outcome. Pass the hazard learner as outcome_learner=”

H7

a modified treatment policy regimen with survival_model="hazard"

fit

CapabilityError, implementation_gap

“regimen {label!r} is a modified treatment policy, and survival_model=’hazard’ does not support one. Its plug-in evaluates the event and censoring hazards at the shifted dose, and that construction is not built. X70 in docs/roadmap.md tracks this. Use survival_model=’iterated_mean’, which supports a modified treatment policy at baseline.”

H8

an unknown targeting, survival_model or isotonic value, or a step_size that is not positive and finite

construction

ValueError

“targeting must be ‘iterative’ or ‘one_step’; got {v!r}”, “survival_model must be ‘iterated_mean’ or ‘hazard’; got {v!r}”, “isotonic must be True or False; got {v!r}”, and “step_size must be a positive finite number; got {v!r}”. A step_size under targeting="iterative" is accepted and unused

H9

q_bounds= with survival_model="hazard"

fit

ValueError

the binary-outcome refusal, “q_bounds does not apply to a binary outcome”

H10

survival_model="hazard" or isotonic=True on a point-treatment functional

method configuration

MethodConfigurationError

“survival_model= and isotonic= apply to a survival curve over time nodes, and a point-treatment functional has no curve. Remove them, or declare a longitudinal or TimeToEvent design”

H11

survival_model="hazard" with a continuous baseline treatment, or a vector with a dose

fit

CapabilityError, implementation_gap

“survival_model=’hazard’ needs a categorical baseline treatment, and treatment {name!r} is continuous. The hazard plug-in would need the treatment density at every dose, and that construction is not built. X70 in docs/roadmap.md tracks this. Use survival_model=’iterated_mean’.” A vector with a dose reads “is a vector with a dose” in place of “is continuous”

H12

a non-default survival_model, targeting, step_size or isotonic on SDRMethod, IPSIMethod, GComputationMethod or IPWMethod

method configuration

MethodConfigurationError

“survival_model=, a hazard targeting= algorithm, step_size= and isotonic= are settings of the targeted fit, method=’tmle’. The {method} estimator fits no hazard, and no registered study measures its projected curve. Fit method=’tmle’, or remove them”. The SDR, IPSI, SequentialGComputation and SequentialIPW engines do not take these keywords, and raise TypeError

H13

iterate=, stopping= or step_bound= with survival_model="hazard" (R-abc4)

construction

CapabilityError, implementation_gap, roadmap X33

“survival_model=’hazard’ walks its own path in steps of step_size, so iterate=, stopping= and step_bound= do not apply to it. X33 in docs/roadmap.md tracks this. survival_model=’iterated_mean’ with targeting=’iterative’ applies iterate=, stopping= and step_bound= at each node.” The list names the settings declared. tests/unit/test_hazard_x22_refusal.py pins it

The fit checks H3 first, then H1, H2, H11 and H7. So a continuous treatment meets H11 before a modified treatment policy meets H7.

H1 refuses an extension that qualifies and is not built. A static arm or a known rule at several nodes, without a covariate measured after baseline, reduces to an indicator. That indicator is the composite “uncensored and on the regimen through node \(k\)”. The hazard construction then applies to the composite. This package does not build that reduction.

H2 needs a model and a fluctuation of each later covariate. Stitelman, De Gruttola and van der Laan (2012) and Rytgaard, Gerds and van der Laan (2022) treat such covariates. Neither source was read for this contract.

X70 tracks H1, H2, H5, H7 and H11. Each message names the supported route. The roadmap carries no row for H4.

A different question#

These are well-posed parameters, but the selected estimator does not target them. No setting turns one into the other. The code column holds different_estimand for most rows. Another code marks a row where the raise site names a different remedy.

refused

code

the question it would answer instead

eliminate= on a competing-risks fit

different_estimand

the incidence of a cause if the competing events were removed. That intervenes on them rather than conditioning on the history. It needs a further factor per node in the denominator, and its own no-unmeasured-confounding and positivity assumptions for the competing event. What is reported instead is the incidence with the competing causes left alone

CTMLE on data that declares its treatment mechanism

api_redirection

C-TMLE selects or fits a treatment mechanism, and the declaration leaves nothing to select or fit. The question does not change. Fit TMLE or DRTMLE on the declared data

a declared known treatment mechanism beside treatment_delta=

invalid_declaration

a missing treatment needs \(P(A=a\mid\Delta_A=1,W)\), the mechanism among the recorded rows. It equals the design mechanism only when recording is independent of treatment given \(W\), which the fit cannot check

intermediate= on LTMLE

different_estimand

a controlled direct effect fixes a mediator at one time point. Over a sequence, with mediators that are themselves time-varying, that is a different identification rather than a further column

ey1 and ey_regime from one fit; msm= with interventions= or policies=; an item of another intervention kind in a typed estimand, such as an Incremental in RegimeContrast

implementation_gap for two axis keywords, for msm= beside another axis, and for a typed set that holds two kinds. The message names X57. api_redirection for an estimand of another axis and for a typed set that holds only the other kind

each keyword declares what “counterfactual” means for the fit, or how the counterfactuals are summarised. One fluctuation solves one set of score equations, so a fit reporting parameters from two axes would put two of them under one heading. CausalStudy.identify refuses the typed request with CapabilityError, and it names the typed estimand for the item

the per-arm propensity table on a continuous fit

different_estimand

a per-arm table has no rows when there are no arms. diagnostics.support() is not itself refused. On a fit that declared policies= it dispatches to the question that does apply, which is whether the density ratio stays bounded

res.sensitivity, res.diagnostics and res.validate() on an LTMLE result

implementation_gap for a sensitivity operation. api_redirection for the score equations of SDR and IPSI

each is part of the shared result contract. Stagewise support, scores, nuisance loss, and the descriptive full-recursion truncation grid are supported. Sensitivity operations without a longitudinal derivation report unavailable. res.save() is supported, and persistence and replayability states its contract

ratio(..., view="survival") on an end-of-study fit, on a repeated-outcome fit, or on a fit with two or more causes; rmst, rmtl and incidence_total() on an end-of-study, repeated-outcome or working-model fit; curve(scale="risk") or curve(scale="survival") on a repeated-outcome fit; ratio on a working-model fit

different_estimand

an end-of-study fit reports means and no survival curve. A repeated outcome can return to zero, so its mean at a horizon is not a cumulative risk. One minus one cause’s incidence is not all-cause survival. A coefficient is not a regimen mean. Functionals of a fitted result gives each message

a plan that changes the treatment after baseline on a held design (treatment="A" or TimeToEvent)

different_estimand

a time-varying treatment plan. Declare one treatment column per node on the wide layout. The held design raises CapabilityError before any learner, and a plan that repeats one label is read as that label

logistic_plugin on a fit that is not a selector CTMLE fit, or on an oat, multi-arm, cross-fitted, weighted, missing-outcome or intermediate= fit

different_estimand

R ctmle defines the logistic-estimation term for a binary treatment whose propensity is fitted once on all rows. Collaborative TMLE gives the term

logistic_plugin on a lasso or learners fit

different_estimand

the term is defined for one logistic model on a covariate set. A lasso fit fits an L1-penalized path. R ctmleGlmnet type 1 reports the plain \(D^*\) variance, and the square of plugin_std_error equals it. tests/unit/test_ctmle_sequence_witness.py checks the equality at a relative 1e-6. A learners fit fits declared candidates

logistic_plugin on a derivative fit

different_estimand

the fit adds derivative columns to the fluctuation. R ctmleGlmnet(ctmletype=3) reports the plain \(D^*\) variance, which std_error gives up to \(n/(n-1)\)

strategy="derivative" with targeting="one_step" or target_weights=True

api_redirection

C-TMLE0 is R’s single logistic fit of the \(2K\) clever covariates. The one-step path targets one parameter along another path. target_weights=True moves a row’s covariate into one weight, and each treated row loads two columns. C-TMLE0

strategy="derivative" with baseline strata

implementation_gap

No source defines a stratified derivative update. Fit the ordinary TMLE for stratum estimates. X62 part (e) tracks the gap

strategy="derivative" with a zero or collinear derivative column, or a \(2K\) solve that does not converge

none

the partner equals the mechanism after g_bounds on every row of an arm, or differs from it by a constant factor, so that arm’s derivative equation is empty or repeats \(h_a\). The message names the arm and the remedy

strategy="derivative" with screen_treatment=True

api_redirection

the lasso pair, the declared learners and the arrays each fit the mechanism and its partner on every covariate. A screen would apply to neither. Fit TMLE with a screened mechanism

a strategy="derivative" pair that is not two entries, a PropensityArrays entry, or an array entry with cross_fit=True, n_bootstrap above 0, or a refit

invalid_declaration

the pair is (mechanism, partner). An array holds one full-sample value for each analysis row, so an outer fold, a resample, or a changed covariate breaks it

a single propensity array in treatment_candidates=

api_redirection

an array fitted on every row would score a mechanism that saw the validation rows. Pass PropensityArrays(full=..., cv=...) with the split of cv as selection_split=. Candidate sequences

PropensityArrays without selection_split=, with cross_fit=True, or under a refit

invalid_declaration

cv was fitted under one declared split on the analysis sample. A random split, an outer fold, or an added covariate breaks that match. The message names the way out

a PropensityArrays row that does not sum to 1 within 1e-9

invalid_declaration

the row is not a distribution over the arms, so the clever covariate weights the arms wrongly. Pass predict_proba output in arm order, or divide each row by its sum

lambdas= with fewer than two penalties

invalid_declaration

one penalty gives one candidate, and the fit selects nothing. Pass two or more penalties, or lambdas=None for the default grid

selection_split= with repeats above 1, n_bootstrap above 0, a selection_folds= that differs from the split’s fold count, or strategy="oat"

invalid_declaration

a declared split is fixed on the analysis rows. A repeat redraws it, and a resample changes the rows. selection_folds= records the fold count, so it must equal the split’s count. oat draws no split

a non-empty assess(arguments=...) block for score_equations

api_redirection

the validation battery owns that name and runs it argument-free. The battery presents one row per name, and it presents the validation row. A caller’s tolerance would be computed and then hidden, so a check that failed at that tolerance would never reach attention. Call res.diagnostics.run_all(arguments=...), or the operation itself. The battery also owns support and nuisance_models, which accept no argument, so an argument for either is a TypeError from the signature

Wrong by construction#

These are worth reading even if you never reach for the keyword. Most are mistakes that are easy to make by hand, in any framework, and none announces itself. They can produce a number for the wrong target or an interval whose stated conditions are not met. The code column names the code of the raise site. A row marked none has no raise site. The package cannot detect the mistake, or its API does not offer the construction.

refused

code

what goes wrong if it is done anyway

ratio(..., kind="or") when the outcome family is not binary

api_redirection

the odds of a mean outside \((0, 1)\) is not defined. The refusal applies the rule of the registered or target, TARGETS["or"].requires_family, and names kind="rr"

a population-mechanism odds tilt supplied as a Stochastic density

implementation_gap for density_kind="estimated". invalid_declaration for None

the population-law target depends on P through g*. Its curve needs a derivative that the regime curve lacks. On one exact law, the reported standard error is 0.62 of the exact one at delta = 2 (tests/unit/test_stochastic_regime_densities.py). A realized learned density instead defines a data-adaptive target; its inference needs conditions this API does not check. Stochastic requires density_kind="known" and refuses None or "estimated" at construction and fit. A false "known" declaration still fits because code cannot inspect a closure. RM25 records the witness and both targets

a population-law incremental intervention built by hand as a Stochastic regime

implementation_gap for density_kind="estimated". invalid_declaration for None

the missing term has variance Var(delta * (Qbar(1,W) - Qbar(0,W)) / D^2 * (A - g)) on two levels and Var(h(A) * {Qbar(A,W) - m(W)}) with h(a) = delta_a / D on K levels. For that target, omitting the term understates variance wherever this variance is positive. Stochastic refuses the tilt when density_kind is None or "estimated". TMLE(incremental=) fits the population odds tilt and includes the term

a user-written Intervention class whose density depends on the population mechanism

implementation_gap for "estimated". invalid_declaration for None and for a missing attribute

it can omit the same derivative while reporting an interval for the population-law target. On the RM25 witness law, a class that computes the sample-mechanism odds tilt reports 0.62 of that target’s exact standard error (tests/unit/test_rule_and_intervention_declarations.py). A realized learned density has a different inference contract. The class must carry density_kind = "known". TMLE refuses None, a missing attribute, and "estimated" before any learner. RegimeSet.evaluate refuses them before density runs. A false "known" declaration still fits because code cannot inspect what density computes. RM28 records the witness

a Rule or DynamicRegimen learned from the analysis sample

implementation_gap for "estimated". The message names X11. invalid_declaration for None and for an inline callable

the fit treats its rule as fixed. A learned policy can instead target a realized policy value or a population-indexed policy. Those targets need distinct inference conditions; an optimal rule can also be nonregular at ties. For a threshold at the sample mean of a continuous covariate, the curve omits a derivative through that mean. On one exact law, the reported standard error is 0.73 of the exact one at one node (tests/unit/test_rule_and_intervention_declarations.py). It is 0.77 for a two-node regimen (tests/unit/test_regimen_rule_declarations.py). Rule and DynamicRegimen require rule_kind="known" and refuse None or "estimated" at construction and fit. A plan of labels alone needs no declaration. LTMLE refuses a callable written inline in regimens=, because it carries no declaration. A false "known" declaration still fits because code cannot inspect a closure. For a point treatment, LearnedRuleValue estimates the average value of rules learned inside each training fold (learned rules). A learned longitudinal regimen has no supported path (X11). RM28 records the witnesses

a shift’s inference taken from the regime inducing the same density

none

the means and the clever covariates agree entry for entry. The curves do not. The gap is `Var(Qbar(d(A,W),W) - E[Qbar(d(A,W),W)

a shift fit run on the complete cases when outcomes are missing

none

it is an ordinary shift fit on a different joint law of (A, W), so it converges to a different number, and nothing in its own output says so. Measured on the dose fixture at 0.17, four standard errors, with a mechanism whose slopes are mild. delta= is what corrects it

a missingness or intermediate mechanism read at the observed dose rather than the assigned one

none

the fluctuation updates Qbar as a function of the dose, so Qbar*(d(A,W),W) is the update evaluated where the policy sends the unit. Silent wherever the mechanism does not depend on the dose, and invisible to a Gateaux check on an exact law

a “stabilised” MSM weighting h by the estimated mechanism

implementation_gap for weights_kind="estimated". invalid_declaration for a callable with no declaration and for an array

the same argument once more. h becomes a functional of P, and a term goes missing from the influence curve. MSM refuses weights_kind="estimated" and a weights= callable with no declaration. MSM projections gives the declaration

an MSM design that reads a sample statistic, such as a covariate centred at its sample mean

implementation_gap for design_kind="estimated". invalid_declaration for a callable with no declaration

for the population-law target centred at \(E_P[W]\), the projection depends on \(P\) through the design. Its reported curve omits that derivative. On one exact law, the reported intercept standard error is 0.893 of the exact one (tests/unit/test_msm_design_declaration.py). An interval conditional on the centre learned from the same rows also needs separate validation. MSM refuses design_kind="estimated" and an undeclared callable. A design built by MSM.linear needs no declaration. Code cannot detect a false "known" declaration. MSM projections gives the declaration, and RM27 records the witness

g_bounds= or truncation_curve() on an incremental= fit

different_estimand

g is inside the estimand, so truncating it moves the parameter rather than regularising a denominator. The result is a number for a parameter nobody declared. truncation_curve() refuses the treatment axis with CapabilityError, and the omitted mechanism selects that axis. The module call and the facade call raise the same sentence. The capability row reads unavailable. On a fit with missing outcomes it reads deferred on mechanism, because mechanism=True runs

a cap= fitted from the data on a shift

none

the estimand becomes data-dependent. The interval conditions on an estimated boundary, and every bootstrap replicate targets a slightly different policy

CTMLE on an incremental= or policies= fit

implementation_gap

each candidate ghat defines a different estimand, so the cross-validated search selects between estimands rather than between estimators. The policy mean itself is well posed, and a plain TMLE fits it. F5 keeps the collaborative route

splitting a cluster across folds to buy more of them

invalid_declaration

the out-of-fold predictions stop being independent of the rows they are used on, and the standard error shrinks in exactly the direction id= was passed to prevent

a supplied SplitPlan whose labels no recorded draw produces

invalid_declaration

the labels could have been chosen by reading the outcome, the treatment, or a covariate, which is the leak cross-fitting exists to prevent. The fit draws each repeat again from the plan’s record and compares it label for label. Reusable outer split plans states what a plan records

reusing a supplied SplitPlan on rows it was not realized on

invalid_declaration

a fold label is a row position. A reordering, a replaced column, or an added covariate leaves every label pointing at a different unit, while the row count still agrees. The out-of-fold guarantee is gone, and nothing in the output says so. Reusable outer split plans states how a plan binds to its rows

joint covariance, post-fit contrasts, or simultaneous bands after repeats=

inference_gap for the covariance and the bands. api_redirection for contrast() and ratio()

coordinatewise medians do not preserve linear identities among estimates; a central-draw curve also does not represent the split-adjusted median estimator needed for a multiplier band

a cross-validated variance for the curve a repeated fit retains

none

at equal fold sizes it collapses to the pooled uncentred second moment for every partition, so the partition carries no information. That rule was rejected, and the split-adjusted median variance replaced it

a one-shot non-identity-link MSM

none

the derivative of the inverse link depends on the coefficient, so a single pass reports a standard error for an equation it did not solve. The link is supported. What is refused is skipping the alternation it needs

a censoring time between two grid times, read as censored at a grid time

invalid_declaration

the censoring cannot be ordered against the events of its interval, so either reading biases the risk. On one interval with exponential times the two readings give 0.280 and 0.236 against a truth of 0.259 (test_both_off_grid_censoring_conventions_are_biased). LongitudinalData.from_time_to_event raises DataError before any learner and names the visit grid as the remedy

an event value on a row whose censoring is 0, on a long panel or on the wide layout

invalid_declaration

the order at a node is \(A_t, C_t, Y_t\), so the event is unobserved once the unit is censored. Reading past it drops an event that was meant as “had it and left”. LongitudinalData.from_frame used to drop the value without a message. Both layouts raise DataError. Record an event as censoring 1 and event 1, and a loss after an event-free interval as censoring 0 with a missing event (test_refuses_an_event_on_a_censored_row, test_the_wide_path_refuses_an_event_on_a_censored_row)

a long-panel baseline, cluster or weights column that varies within a unit or is first recorded after the first row; an outcome that varies within a unit

invalid_declaration

a unit-level column is measured before the first decision. A later value read as W lets a rule at an earlier node read the future. LongitudinalData.from_long_panel raises DataError and names the unit

a long-panel covariate that equals the outcome, or the row’s event, on every row where it is recorded; one column in two roles

invalid_declaration

a rule or a history frame could read the outcome under another name. from_long_panel raises DataError and names both columns. A constant covariate is not mistaken for a copy of a constant outcome

a long panel whose time labels are not numbers, dates or durations, with no times=

invalid_declaration

the sort order of such labels need not be their time order, and the node order is the time order. from_long_panel raises DataError and asks for times= in time order

a long panel with a duplicated (unit, time) row, late entry, a gap in a unit’s rows, rows after a unit left, or rows that stop with no censoring 0 and no event

invalid_declaration

intermittent observation is not censoring, and a unit that left does not return. from_long_panel raises DataError and names the unit and the time. times= names every decision time and cannot coarsen a grid

a long-panel time-varying column with no value at a node after its first recorded node

invalid_declaration

leaving the column out there removes a confounder from that node’s adjustment set. A leading run of missing values is not refused. The column starts at its first recorded node, and PanelLayout.omitted records each omission

a long-panel treatment that never changes within a unit

invalid_declaration

one baseline decision held over every node, not a decision at each time. Build the wide frame and declare treatment="A"

frequency (count) weights

invalid_declaration

they assert a sample size the variance does not use. Expand the rows instead, which says the same thing where every part of the fit can see it

an LTMLE outcome missing for a reason other than censoring

api_redirection

its probability of being observed is silently taken to be one. Encode it as a final censoring column so that it is estimated and enters the cumulative product

a binary-only target on a multi-arm fit

invalid_declaration

it would report a contrast of arms 0 and 1 out of five, under the name of a parameter about all of them. Targets declare requires_binary_treatment for this

MSM.linear on non-numeric arm labels

invalid_declaration

a model linear in the arm reads it as a dose to interpolate between, and the fallback coding is the sort order. That is a dose scale nobody chose

The first three rows share one mechanism, and it generalises past this package. If an intervention’s density is a functional of P, the influence function carries its pathwise derivative. Omitting that term changes the variance. The direction depends on its covariance with the retained curve. The MSM rows on a “stabilised” weighting and on a sample-statistic design apply the same argument to a working-model function. The incremental and shift rows above give their own variance gaps. The rule row shares the mechanism when a population quantity indexes the rule, as in the threshold witness.

Two more share another. A nuisance that sits inside the estimand is not a knob. Truncating g on an incremental fit, or fitting a shift’s cap from the data, moves the target rather than regularising the estimator.

Three method entries carry their own detailed refusal tables, each with a code column: marginal structural models, longitudinal TMLE, and the simulated common-cause surface. The column holds the reason code of the raise site, as the code column of the tables above does.

The simulated common-cause surface refuses seven compositions for the whole fit. They are a longitudinal fit, multi-arm treatment, a missing outcome, a missing treatment, an intermediate variable, estimated observation weights, and a clustered fit. Each one is an implementation_gap (_FIT_WIDE_RULES in src/cleverly/sensitivity/_simulated_confounding_request.py). X47 and X48 track the laws a caller can declare. The future investigations grid tracks each part that waits on published theory, against the result a paper must supply.

The surface accepts fixed probability weights under ordinary TMLE, under binary complete-outcome collaborative TMLE, and under binary complete-outcome DR-TMLE. Read the surface’s own refusal table for every composition it refuses and the code of each one.

The simulated_confounding capability row resolves each request by the predicate that the call raises from. A request for a refused parameter reads unavailable with the call’s sentence. The natural-course mean and a zero-delta policy mean are such parameters. A categorical or a constant benchmark covariate reads unavailable in the same way. The bare row of a natural-course fit reads unavailable, because no reported parameter runs.

Where a multi-valued treatment is supported#

A multinomial treatment mechanism is the default construction here rather than a variant of a binary one. CausalData carries treatment_levels, Propensity holds an (n, K) simplex, and learners._fitting.predict_probabilities produces it. Two arms are the K = 2 case of that construction and not a separate path, which is what the bit-for-bit invariant is about.

So “does this estimator take more than two arms” usually has the answer “yes, through the same code as two”. The informative entries are those that do not. The status column holds supported, a reason code from How to read a refusal above, or waiting on published theory from the roadmap’s eligibility rules.

surface

status

why

TMLE: ey per arm, ate / att / atc per non-reference arm, regimes, MSMs over arms

supported

one counterfactual mean per arm and one contrast per non-reference arm. The oracle-law gate states that a target meant for more than two arms needs a branch on the three-armed law

DRTMLE: univariate and bivariate reductions

supported

armwise one-vs-rest, with each reduction and correction indexed by a free level. The cited theorem is binary, so this is an implementation-backed armwise extension rather than a claim about that theorem’s literal scope

DRTMLE with delta= in a randomized trial

supported

Díaz and van der Laan’s missing-outcome construction, applied to each arm indicator and stacked. Each arm’s treatment, observation and outcome tilts are its own, so no fluctuation parameter is shared across arms. See the multi-arm contract

TMLE and DRTMLE with an observational missing outcome or a missing treatment

supported

the composite indicator, applied to each arm and stacked. Each arm’s column of the composite mechanism is tilted alone, two arms included. See the composite contract

CTMLE: selectors, strategy="oat" and strategy="derivative"

supported

the selectors fit one shared n x K categorical mechanism, selected against one nonredundant vector. See the standing decision. strategy="oat" fits one binary mechanism per arm by default, and the "shared" design fits one categorical mechanism. See the per-arm design. strategy="derivative" fits one multinomial lasso and its partner, and one derivative column per arm

LTMLE: categorical nodes, static and dynamic regimens

supported

treatment over time. Each node owns its level set, and the clever covariate selects the assigned label’s probability

positivity, omitted-variable, E-value and MNAR sensitivity

supported

each is one parameter per contrast, and each reads its arms from the parameter’s structured index rather than assuming two

simulated common-cause sensitivity

supported for binary and continuous treatment; named theory stops cover the remaining fit-wide gaps

The binary surface accepts marginal and baseline-stratum arm means, ATE, and ratios under ordinary TMLE and C-TMLE. Ordinary TMLE also accepts ATT and ATC with perturbed group membership. Ordinary TMLE alone accepts marginal and baseline-stratum PAR, PAF, fixed-regime parameters, and identity-link MSM coefficients. It also accepts marginal incremental targets and nonlinear MSM coefficients. Complete-outcome DR-TMLE supports marginal arm means, ATE, and ratios only. The continuous surface accepts marginal and baseline-stratum modified-policy means and contrasts under ordinary TMLE. A risk-ratio tilt fit is refused, because the replay declares no reading of its rule on a perturbed draw (X66 part (d)). Continuous MSMs require marginal fits. Every listed row accepts fixed probability weights. Longitudinal, multi-arm, missing-outcome, intermediate, estimated-weight, and clustered fits report unavailable before work begins. Each of those six waits on published theory, and so does logical categorical calibration. NaturalCourseMean, the zero-delta policy mean, and the multiplier-one incremental mean remain refused. See the population contract and refusal table

ey1 / ey0 on a multi-arm fit

invalid_declaration

they name one of exactly two arms, so on five arms they would report a contrast of arms 0 and 1 under the name of a parameter about all of them. Declared by requires_binary_treatment. The multi-arm path reports per-arm ey instead. The incremental estimands declare no such requirement, because a categorical tilt names a factor for every level

a scalar Incremental(delta), as incremental= or in an IPSI plan, on a treatment or tilt node with other than two levels (R1)

invalid_declaration

a scalar odds multiplier names two arms. The refusal runs before any learner, names the treatment or node and its levels, and shows the mapping form: “a scalar odds multiplier names two arms; node ‘A1’ has 3 levels ([‘high’, ‘low’, ‘standard’]). Declare one factor per level, for example Incremental({‘high’: 2.0}), which multiplies the odds of ‘high’ against every unnamed level.” Declare Incremental({label: factor}) instead. A level that the mapping does not name keeps the factor 1. See categorical treatment

an Incremental mapping with a key that is not a level of the treatment or of a tilt node (R2)

invalid_declaration

the refusal runs before any learner and names the key, the treatment or node, and its levels. A key is a label as the data reports it, and a position is not a label. One Incremental in a plan meets each node in turn, so a key that is a level of node 1 and not of node 2 is refused at node 2

LTMLE: known stochastic categorical policies at a node

supported

known stochastic policies. A node holds a policy density fixed before the fit. The recursion carries the policy mean of the per-level predictions, and the clever covariate is the ratio of the policy to the bounded mechanism. Exact-law tests and the registered study cover it

LTMLE: modified treatment policies at a continuous, categorical or vector node

supported

modified treatment policies at a node. A plan node holds a Shift, Scale, Piecewise, ModifiedPolicy or RiskRatioTilt. A continuous node reads a pooled-hazard density or the classifier ratio. Exact-law tests cover it, and the registered study longitudinal-mtp validates it

LTMLE and SDR: modified treatment policies at a mixed vector node

supported

vector nodes with a continuous component. A mixed node holds a JointPolicy, one policy for each component. The ratio is a chain-rule density or the classifier ratio. Exact-law tests cover it, and the registered study vector-mtp validates it. Vector treatments lists the refusals

TMLE: modified treatment policies on a point vector treatment

supported

vector treatments. A vector of categorical columns is one categorical treatment over its tuples. A vector with a dose reports ey_policy and ate_policy. The registered study vector-mtp validates both

A source could close one entry as it stands. It is the multi-arm part of the simulated common-cause row.

The DRTMLE row needs no new source either. It needs the implementation that its roadmap row holds.

Vector treatments#

A vector treatment holds several treatment columns. A vector of categorical columns is one categorical treatment over the observed tuples. A vector with a continuous component has no arms. TMLE(policies=[JointPolicy(...)]) fits it at a point treatment, and LTMLE and SDR fit it at a node. The first table says what ships. The second table lists what a vector refuses. Each refusal arrives before the nuisance fit.

surface

status

where

point treatment, all columns categorical

supported. Every estimand of a categorical treatment runs on the tuple levels

vector treatments

point treatment, with a dose

supported for ey_policy and ate_policy, on ratio="density" and ratio="classifier". A dose component may have a data-tuned cap on either target=, with the curve terms of the longitudinal node

vector treatments, tests/unit/test_point_tuned_components.py

longitudinal node, all columns categorical

supported. The node is one categorical node over the tuples

modified treatment policies at a node

longitudinal node, with a dose

supported on both ratio routes, for LTMLE and SDR. A component may declare atoms, dose_atoms={name: (...)}, and each dose component may have a data-tuned cap, Shift(cap=DoseQuantile(...)) or Scale(cap=SupportBoundary(...)), on either target=. The curve adds one derivative term for each tuned component in each branch of a randomizer, weighted by the branch probability

vector nodes with a continuous component, tests/unit/test_mtp_component_atoms.py, tests/unit/test_mtp_tuned_components.py, tests/unit/test_mtp_joint_tuning.py

Each row of the next table gives the theoretical reason for its refusal.

refused

raised

reason

test

a RiskRatioTilt on a dose or on a categorical component with other than the levels 0 and 1

DataError

the tilt is the ipsi rule of lmtp. It keeps a treated unit treated with probability \(\delta\) and otherwise sets it to 0. A dose or a three-level component has no treated and untreated label, so the two-point randomizer is not defined. A binary component coded 0 and 1 is supported

tests/unit/test_vector_component_randomizer.py

an odds tilt, Incremental, at a mixed node, and IPSI at a mixed node

CapabilityError

an odds tilt multiplies the odds of one categorical node (Kennedy 2019, Equation (1)). The joint density of a node with a dose has no odds to multiply. IPSI estimates no density ratio, so it has no estimator for a policy node

tests/unit/test_longitudinal_mixed_node.py

a dose map that reads the natural value of a later dose

DataError

the Jacobian determinant of the joint map is the product of the declared derivatives only when each map reads earlier doses. Any acyclic read has an order that makes the map triangular. The message names it, and JointPolicy(..., order=...) declares it

tests/unit/test_longitudinal_mixed_node.py, tests/unit/test_vector_density_route.py

dose maps that read each other in a cycle, and any other joint map that is not triangular

DataError

Equation (3) of Díaz et al. (2023) needs a map that is one-to-one on each piece. A determinant checked on a grid does not establish that a multivariate map is one-to-one, so no declaration of such a map is checkable

tests/unit/test_vector_density_route.py

a policy of one column on a point vector with a dose, and a static label, a rule, a known policy density or a policy of one column at a mixed node

CapabilityError

a static or dynamic value on a dose is not pathwise differentiable (Díaz et al. 2023, Section 4). A policy of one column does not say which component it moves. Declare a JointPolicy

tests/unit/test_point_vector_treatment.py, tests/unit/test_longitudinal_mixed_node.py

a known treatment mechanism, treatment_probabilities=, on a vector with a dose

CapabilityError

a known mechanism is a probability for each level. A vector with a dose has a conditional density and no levels, and a known density is outside this package

tests/unit/test_point_vector_treatment.py, tests/unit/test_longitudinal_mixed_node.py

a piece that sends an interval to an atom, Piece(..., to_atom=), at a dose of a vector other than the last dose, on ratio="density"

CapabilityError

the mass that such a piece sends to the atom is an integral of the factors of the later components over the interval. Each later factor conditions on the collapsed dose, so the integral is not a finite sum over preimages. At the last dose, in the column order and in the policy’s order, the mass is the integral of one binned density, which is a finite sum, and the density route fits the piece. A categorical map that reads that dose is refused. ratio="classifier" fits the piece at any dose. Declared atoms, dose_atoms={name: (...)}, make the factor of that dose mixed on both routes. A declared support, treatment_support={name: Support}, is supported

tests/unit/test_mtp_component_atoms.py

a declared missing treatment, treatment_delta=, on a vector with a dose

DataError

the composite indicator divides by the probability that the treatment is recorded and that the arm is \(a\). A dose has no arm indicator

tests/unit/test_point_vector_treatment.py

DRTMLE and CTMLE on a vector with a dose, and DRTMLE with policies= at any treatment

CapabilityError

the CTMLE strategies need a discrete treatment, because strategy="oat" fits one prediction of \(\bar Q\) for each arm. The reduced-dimension regressions of DRTMLE are derived for static treatment, and no theorem read here gives the reductions of a policy

tests/unit/test_vector_study_spine.py

msm= on a point vector with a dose

CapabilityError

an MSM projects the dose-response curve of one dose. The clever covariate integrates over a grid on that dose by the trapezoid rule. A vector has no one dose axis, and the package defines no grid or cubature over a vector. A longitudinal msm= over joint-policy plans ships, because its cells are plans

tests/unit/test_vector_study_spine.py

an arm-indexed estimand, interventions=, incremental=, a learned rule, or a fit with no policies=, on a point vector with a dose

DataError for interventions=, incremental=, a learned rule and a fit with no policies=. CapabilityError for an arm-indexed estimands= and for a study estimand

such a treatment has no arms, and each of these parameters is indexed by an arm. Say which values to compare with policies=

tests/unit/test_point_vector_treatment.py

ratio="classifier" where no policy ratio is estimated, such as an ATE or a declared mechanism

MethodConfigurationError

the setting would have no effect, and a silent no-op reads as a validated choice

tests/unit/test_vector_study_spine.py

The refusal of a later-dose read is a scope limit and not a statement about the theory. A map that reads a later dose is valid after a reorder, and the message names the order that resolves the read. A cycle has no such order.