Data and study design#
Accepted table backends#
CausalStudy accepts pandas, polars, Arrow-backed pandas, and pyarrow.Table inputs through
narwhals. Table-returning output comes back in the backend the study was built from, so
result.to_frame() on a pyarrow.Table study returns a pyarrow.Table. Column-role and dtype
validation happens before nuisance fitting.
The input must have one row per independent observational unit unless cluster= declares the unit
at which inference is independent. A cross-fitted fit needs each treatment arm in at least two of
those units, because a split moves whole units. It also needs the outcome’s known support when the
outcome is continuous.
Fold and outcome-scale rules
gives both requirements and their refusals. Missing values are supported only in roles whose design
explicitly models missingness; a missing adjustment or treatment value is not silently imputed.
Point treatment#
from cleverly import CausalStudy, PointTreatment
study = CausalStudy(
frame,
design=PointTreatment(
outcome="Y",
treatment="A",
adjustment=("W1", "W2", "W3", "region"),
weights="sampling_weight",
cluster="household",
strata=("region",),
),
)
adjustmentis the measured pre-treatment set used by the identification argument.weightsdefines the target population and flows through nuisance losses, targeting, influence curves, and covariance.clusterselects cluster-robust inference, and it selects a grouped fold draw that keeps each cluster whole. It is not another adjustment variable. A cluster is then the independent unit a cross-fitted fit counts, so each treatment arm must appear in at least two clusters. C-TMLE refusescluster=at every setting. Longitudinal TMLE cross-fits with whole-cluster folds.Clusters may differ in size. A TMLE, DR-TMLE, or longitudinal TMLE fit with fewer than 40 clusters with positive weight mass reports a Student t interval with the cluster count minus 2 degrees of freedom. Below 10 such clusters, or 20 for longitudinal TMLE, it reports no interval. For TMLE and DR-TMLE, both rules also apply to one reported stratum (clusters).
stratarequests subgroup parameters and preserves the stratum in structured parameter keys. A stratum variable must also appear inadjustment: it conditions the reported parameter, so a design that stratified on a variable it did not adjust for is refused rather than fitted. Arm, regime, shift, incremental, MSM and natural-course targets fit strata, and so does DR-TMLE at everyguard. Fold targeting and fold evaluation refuse them (weights, strata, and clusters).intermediateand explicit missingness roles activate supported controlled-direct-effect and missing-outcome compositions. The next section gives the two missingness roles.
For a randomized study with no adjustment variables, declare the design:
randomized = CausalStudy(
frame[["Y", "A"]],
design=PointTreatment(outcome="Y", treatment="A", randomized=True),
)
A trial that records the probability with which each unit was assigned each arm declares that known mechanism. Name the frame column of each arm’s probability. In this example, every unit had probability one half:
trial = CausalStudy(
frame.assign(p_control=0.5, p_treated=0.5),
design=PointTreatment(
outcome="Y",
treatment="A",
adjustment=("W1", "W2"),
treatment_probabilities={0: "p_control", 1: "p_treated"},
),
)
Every method divides by the declared probabilities and fits no treatment learner. On complete data, the estimate is then consistent for any outcome learner, and the interval is not conservative. With missing outcomes, the fit still estimates the missingness mechanism, and the outcome learner must also be consistent. Known treatment mechanism gives each case.
The probability columns are not adjustment variables, and they can depend on the adjustment set, as a stratified allocation does.
CollaborativeTMLEMethodis unavailable on such a design, because there is no mechanism to select.A fit refuses a bound pair that would move a declared value, before any learner. Pass
g_bounds=wide enough to hold every declared probability.
Known treatment mechanism
gives the forms, the curves and the refusals. LongitudinalTreatment takes the same declaration
per node (known node mechanisms).
Missing outcomes and missing treatments#
Declare each missingness role with a 0/1 indicator column. The column is 1 where the value is recorded. The package does not infer missingness from a missing value, so an accidental gap is refused rather than analyzed.
role |
|
|
|
|---|---|---|---|
outcome recorded |
|
|
|
treatment recorded |
|
|
|
A declared missing treatment supports the arm means and their contrasts on in-sample TMLE and
DRTMLE fits. In-sample TMLE fits also support regime means and arm-indexed msm= coefficients. Each arm uses the composite indicator: the treatment and the outcome are recorded,
and the treatment is that arm. The recording of the treatment may depend on the treatment, so the
ATT, the ATC and the population-intervention targets are not identified and are refused.
import numpy as np
from sklearn.linear_model import LinearRegression, LogisticRegression
from cleverly import ATE, CausalStudy, DRTMLEMethod, PointTreatment
from cleverly.datasets import make_missing_outcome
observed, _ = make_missing_outcome(n=600, seed=1)
rng = np.random.default_rng(1)
observed["A_recorded"] = (rng.random(len(observed)) < 0.85).astype(float)
observed.loc[observed["A_recorded"] == 0.0, "A"] = np.nan
missing_study = CausalStudy(
observed,
design=PointTreatment(
outcome="Y",
treatment="A",
adjustment=("W1", "W2", "W3"),
missingness="Delta",
treatment_missingness="A_recorded",
),
)
effect = missing_study.estimate(
ATE(),
method=DRTMLEMethod(
reduced_outcome_learner=LinearRegression(),
reduced_treatment_learner=LogisticRegression(max_iter=1000),
),
outcome_learner=LinearRegression(),
treatment_learner=LogisticRegression(max_iter=1000),
missingness_learner=LogisticRegression(max_iter=1000),
cross_fit=False,
random_state=0,
)
print(effect.extra["missing_data"])
The fit records the construction it ran as composite. The
composite contract
states the conditions, and scope and refusals
lists the refused compositions.
Continuous treatment#
Set treatment_kind="continuous" when the target is a modified treatment policy. Arm-indexed
estimands are refused on this design.
dose_study = CausalStudy(
frame,
design=PointTreatment(
outcome="Y",
treatment="dose",
adjustment=("W1", "W2"),
treatment_kind="continuous",
),
)
Longitudinal treatment#
A longitudinal design records one ordered treatment role per node, the history available at each node, censoring, and either an end-of-study outcome or event processes. Continue with the longitudinal guide.
Observation weights are not estimand weights#
Sampling weights change the population represented by the empirical distribution. MSM projection weights change the definition of the projection. Clever covariates weight observations inside an estimating equation. These three roles are deliberately separate.