Missingness

CausalTargeted owns Observable numerical policies for incomplete tables once Structural claims about response indicators (R) are stated (or deliberately omitted). CausalDynamics owns masks, certificates, and generative dropout; this page covers what estimators do with Julia missing.

Design rule

Same data gap; different object, different assumptions, different API.

Notation (aligned with CausalDynamics / the CDCS book): complete (V), response (R_V), recorded (V^{\mathrm{rec}}) (figure alias (V^)), fills (\tilde{V}) from a documented policy. Counterfactuals stay (V^{do(\cdot)}); do not overload (V^).

LayerOwnerObject
StructuralCausalDynamicsMissingnessSpec / MissingnessCertificate / identify(...; missingness=)
DynamicalCausalDynamics + this packageTime-indexed gaps; sequential / survival runners
Observablethis package / CausalMediationhandle_missing, impute_posterior, metadata

Do not coerce Missing to Float64 without a documented strategy. complete_numeric_column refuses silent promotion.

Strategies (handle_missing)

handle_missing_data implements:

StrategyOutcome missingnessCovariate missingness
:drop (default)Complete-case rowsDrop incomplete rows
:ipcwIPCW on $R_Y$, then analyse observed $Y$Drop incomplete covariates first
:imputeDrop incomplete $Y$Mean/mode impute + *_miss indicators
:ipcw_imputeIPCW after covariate imputationMean/mode impute + indicators

Results are MissingDataResult: cleaned frame, row weights, extra indicator columns, and meta (strategy, miss rates, optional PCH rung, time_indexed). Grid / NamedTuple runners attach the same meta via missingness_metadata / with_missingness.

For :ipcw and :ipcw_impute, fitted weights enter the influence-function summary in run_lmtp_grid, run_gcomp, sequential LMTP, discrete LMTP, repeated-outcome MSM, and survival LMTP, so :drop and :ipcw need not coincide under MAR $Y$.

using CausalTargeted, StableRNGs

df, _ = simulate_missing_outcome_mtp(200; rng = StableRNG(1))
grid = run_lmtp_grid(
    df, :A, :Y; baseline = [:W], deltas = [0.5],
    folds = 2, learners_outcome = (:glm, :mean), learners_trt = (:glm, :mean),
    parallel = false, handle_missing = :ipcw, rng = StableRNG(1),
)
missingness_metadata(grid).strategy  # :ipcw

Estimand families

FamilyAPINotes
Point LMTP / MTPrun_lmtp_grid(...; handle_missing=)Default :drop
G-computationrun_gcomp(...; handle_missing=)Same four strategies
Sequential LMTPrun_sequential_lmtp(...; handle_missing=)time_indexed=true in meta
Discrete LMTPrun_discrete_lmtp(...; handle_missing=)Factor $A$
Repeated-outcome MSMrun_repeated_outcome_msm(...; handle_missing=)Complete-profile $R$ across all $Y_t$; outcomes are not imputed
Survival LMTPrun_survival_lmtp(...; handle_missing=)MAR missing terminal $S_T$ ≠ censoring IPCW
MediationCausalMediation handle_missingForwards to handle_missing_data

Survival. Censoring weights for discrete-time survival are a Dynamical object. Do not reuse outcome-missingness IPCW as if it were right-censoring, and do not treat missing terminal $S_T$ as a censoring covariate when weighting MAR event indicators.

Certificates and MAR sets

When identification attaches a missingness certificate,

using CausalDynamics, Graphs

g = DiGraph(3)
add_edge!(g, 1, 2); add_edge!(g, 1, 3); add_edge!(g, 2, 3)
id = identify(g, TotalEffectQuery(:A, :Y);
    node_names = Dict(1 => :W, 2 => :A, 3 => :Y),
    missingness = MissingnessSpec(:Y; regime = :mar, conditioning_set = [:W]),
)
mar_set(id)  # [:W]

mar_set returns Symbol[] when no certificate is present.

Opt-in posterior imputation

impute_posterior draws completed continuous outcomes under a Gaussian MAR model given predictors (or the certificate's mar_set). Observed $Y$ are preserved. Each completed table draws residual variance and regression coefficients under the Gaussian linear-model posterior with prior $p(\beta,\sigma^2)\propto1/\sigma^2$, then draws missing outcomes conditionally. The design must have full column rank and positive residual degrees of freedom. Residual-only draws with fitted parameters held constant would omit imputation-model estimation uncertainty. The categorical encoding is fitted once on outcome-observed rows and reused for missing rows, so category columns keep the same meaning; a level absent from the fitted schema is reported as an unseen-level error. Unidentified MNAR certificates throw.

run_lmtp_grid(...; imputation=draws) runs the grid on each draw (handle_missing=:drop per draw) and pools with Rubin's rule via pool_lmtp_grids, using finite-imputation t limits. Rubin pooling requires at least two draws and identical rowwise estimand, delta and stratum keys; a permuted or mismatched grid is refused. This pooling requires appropriate complete-data uncertainty and a suitable imputation model; it does not validate a clamped-policy score. Simultaneous limits are NaN, because pointwise Rubin pooling does not supply joint bands. This path is opt-in; defaults remain :drop / :ipcw. Turing / RxInfer backends are deferred.

Stress notebooks

NotebookContent
Missingness gridStructural / Dynamical / Observable ledger
PosteriorGaussian MAR → pooled LMTP
Deep SCM estimationRaw assay missing → drop → encode → LMTP

CDCS harness entry (book repo):

julia --project=. --threads=auto scripts/stress_harness/run_missingness_validation.jl