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^).
| Layer | Owner | Object |
|---|---|---|
| Structural | CausalDynamics | MissingnessSpec / MissingnessCertificate / identify(...; missingness=) |
| Dynamical | CausalDynamics + this package | Time-indexed gaps; sequential / survival runners |
| Observable | this package / CausalMediation | handle_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:
| Strategy | Outcome missingness | Covariate missingness |
|---|---|---|
:drop (default) | Complete-case rows | Drop incomplete rows |
:ipcw | IPCW on $R_Y$, then analyse observed $Y$ | Drop incomplete covariates first |
:impute | Drop incomplete $Y$ | Mean/mode impute + *_miss indicators |
:ipcw_impute | IPCW after covariate imputation | Mean/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 # :ipcwEstimand families
| Family | API | Notes |
|---|---|---|
| Point LMTP / MTP | run_lmtp_grid(...; handle_missing=) | Default :drop |
| G-computation | run_gcomp(...; handle_missing=) | Same four strategies |
| Sequential LMTP | run_sequential_lmtp(...; handle_missing=) | time_indexed=true in meta |
| Discrete LMTP | run_discrete_lmtp(...; handle_missing=) | Factor $A$ |
| Repeated-outcome MSM | run_repeated_outcome_msm(...; handle_missing=) | Complete-profile $R$ across all $Y_t$; outcomes are not imputed |
| Survival LMTP | run_survival_lmtp(...; handle_missing=) | MAR missing terminal $S_T$ ≠ censoring IPCW |
| Mediation | CausalMediation handle_missing | Forwards 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
| Notebook | Content |
|---|---|
| Missingness grid | Structural / Dynamical / Observable ledger |
| Posterior | Gaussian MAR → pooled LMTP |
| Deep SCM estimation | Raw assay missing → drop → encode → LMTP |
CDCS harness entry (book repo):
julia --project=. --threads=auto scripts/stress_harness/run_missingness_validation.jl