Skip to main content

modeling.PathModel

A compiled Bayesian path model.

Usage

modeling.PathModel(
    spec,
    graph_info,
    data,
    families=None,
    panel_info=None,
    pooling=None,
    latent=None,
    priors=None,
    scaling_factors=None,
    *,
    initial_internal=None,
    initial_explicit=False
)

Created by pathmc.model(). Holds the parsed specification, graph structure, design matrices, and the compiled PyMC model.

When data is None, the model operates in data-free mode: introspection (graph(), equations(), priors()) and identification helpers work, but methods requiring data or a compiled PyMC model raise RuntimeError.

Parameters

spec: Spec

Parsed model specification.

graph_info: GraphInfo

DAG with topological order and node classification.

data: nw.DataFrame | None

Observed data used to build design matrices. None for data-free DAG exploration.

families: dict[str, str] | None = None

Per-variable distribution families.

panel_info: PanelInfo | None = None

Panel metadata (unit/time structure).

pooling: str | dict | None = None

Pooling specification for hierarchical panel models.

latent: set[str] | None = None

Endogenous variables with no observed data column.

priors: dict | None = None

Custom prior configuration mapping parameter names to Prior objects from pymc_extras. Overrides are merged with defaults.

scaling_factors: ScalingFactors | None = None
Fitted scale factors applied to the data before compilation (see pathmc.Scaling). Exposed via fitted_scaling.

Attributes

Name Description
fitted_scaling Fitted scale factors when the model was built with scaling=, else None.
initial_baseline Pre-sample lag states in model units, or None without a lag scan.
pymc_model The compiled PyMC model.

fitted_scaling

Fitted scale factors when the model was built with scaling=, else None.

fitted_scaling: ScalingFactors | None

Pass this object back as scaling= to pathmc.simulate() to reuse the exact estimation-time scales.


initial_baseline

Pre-sample lag states in model units, or None without a lag scan.

initial_baseline: InitialBaseline | None

The first fitted time uses these values as the lag() input. They are not an extra period in do() or predict() summaries. explicit is False when the values were inferred. An endogenous base then uses that unit’s first observed value. An exogenous base uses a matching <variable>_lag1 column when present, otherwise the first observed row, otherwise zeros. A do(initial=) override does not change this record. The returned arrays are a snapshot; editing them does not alter model execution.


pymc_model

The compiled PyMC model.

pymc_model: pm.Model

Raises
RuntimeError
If the model was created without data.

Methods

Name Description
adjustment_model() Build a backdoor-adjusted single-equation outcome model.
adjustment_sets() Find valid backdoor adjustment sets for the causal effect
ate() Compute the average treatment effect of treatment on outcome.
att() Compute the average treatment effect on the treated (ATT).
atu() Compute the average treatment effect on the untreated (ATU).
cate() Compute the conditional average treatment effect.
collider_warnings() Warn when a proposed adjustment variable is a collider on a
comparisons() Interventional contrasts between two values of a variable.
counterfactual() Compute unit-level counterfactual outcomes.
datagrid() Build a business-unit covariate grid from the fitted data frame.
design() Return the design matrix for an endogenous variable’s equation.
do() Simulate an intervention using the do-operator.
effect() Compute the effect along a causal path in the DAG.
effects_summary() Return a posterior summary of labeled coefficients and defined parameters.
equations() Return model equations and/or prior specifications.
falsify() Falsify the whole DAG against the data via a permutation test.
fit() Run MCMC sampling and store the resulting posterior DataTree.
frontdoor_identifiable() Check whether the front-door criterion identifies the causal
graph() Return a graphviz DAG of the structural model.
implied_independences() List all conditional independences implied by the DAG.
is_identifiable() Check if the causal effect of treatment on outcome is
latent_trajectory() Return the posterior latent state of a panel variable over time.
predict() Run posterior predictive sampling.
predictions() Interventional or associational response-mean predictions.
priors() Return a summary of prior distributions for all parameters.
prob() Compute the probability of an expression under an intervention.
refute_placebo() Refute the estimated effect with a Bayesian placebo treatment.
sample_prior_predictive() Draw samples from the prior predictive distribution.
sensitivity() Assess robustness of a causal effect to unmeasured confounding.
set_priors() Update prior distributions and recompile the model.
slopes() Finite-difference interventional slopes.
standardized() Return stdyx-standardized coefficients for labeled effects.
summary() Return a posterior summary table.
test_implications() Test all DAG-implied conditional independences against the data.
to_graphviz() Render the underlying PyMC model as a graphviz plate diagram.

adjustment_model()

Build a backdoor-adjusted single-equation outcome model.

Usage

adjustment_model(
    query=None,
    *,
    treatment=None,
    outcome=None,
    adjustment_set=None,
    formula=None,
    data=None,
    families=None,
    priors=None
)

Returns an ~pathmc.adjustment.AdjustmentModel facade around a reduced Y ~ treatment + Z regression for the designated treatment–outcome query on this structural DAG.

Parameters
query: str | None = None

Treatment–outcome query, e.g. "X -> Y". Exactly one of query or both treatment= and outcome= must be passed.

treatment: str | None = None

Treatment variable name.

outcome: str | None = None

Outcome variable name.

adjustment_set: set[str] | None = None

Explicit backdoor adjustment set. Required when several minimal sets exist; otherwise the unique minimal set is auto-selected.

formula: str | None = None

Custom reduced regression formula. Must include the treatment and every member of the adjustment set.

data: IntoFrame | None = None

Observed data. Required when this model is data-free; overrides the parent’s data when both are present.

families: dict[str, str] | None = None

Per-variable families for the reduced model only.

priors: dict | None = None
Prior overrides for the reduced equation. A shared key is inherited when its shape does not follow the predictor set or treatment coding: outcome dispersion priors, transform-parameter priors used by the reduced formula, and scalar hierarchical categorical hyperpriors (mu_beta_*, sigma_beta_*). Otherwise a custom parent value must be passed here or construction raises. That includes coefficient priors and non-scalar hyperpriors. Unknown shared keys fail closed the same way. An explicit value here still wins.
Returns
AdjustmentModel
Facade with fit() and ate() / att() / atu() / cate() delegating to the inner outcome model.
Raises
ValueError
If this model has a custom prior on a shared key whose shape follows the predictor set or treatment coding, or on an unknown shared key, and priors does not override that key.
See Also
AdjustmentModel

The returned facade.

datagrid()

Build a covariate grid for interpret queries.


adjustment_sets()

Find valid backdoor adjustment sets for the causal effect

Usage

adjustment_sets(treatment, outcome)

of treatment on outcome.

Note

This function reasons about the DAG structure declared in the model specification. It cannot detect omitted variables, missing edges, or other forms of misspecification. Use test_implications() to check whether the DAG’s structural assumptions are consistent with observed data.

A ~~ residual covariance block is a declaration of an unobserved common cause of its members, so it is expanded into a latent confounder node before the criterion is applied: no adjustment set can block it.

Parameters
treatment: str

Treatment variable name.

outcome: str
Outcome variable name.
Returns
list[set[str]]
All valid minimal adjustment sets, sorted by size.

ate()

Compute the average treatment effect of treatment on outcome.

Usage

ate(outcome, treatment, values=(0.0, 1.0), **do_kwargs)

Shorthand for do(set={treatment: hi}) - do(set={treatment: lo}).

Parameters
outcome: str

Outcome variable name; the default target of the result’s accessors (.mean(), .hdi(), .prob()).

treatment: str

Treatment variable to intervene on.

values: tuple[float, float] = (0.0, 1.0)

(lo, hi) intervention values. Default (0.0, 1.0).

**do_kwargs: Any
Passed to do() (e.g. kind, simulate_over).
Returns
EstimandResult
Contrast do(treatment=hi) - do(treatment=lo), focused on outcome.

att()

Compute the average treatment effect on the treated (ATT).

Usage

att(outcome, treatment, values=(0.0, 1.0), treated_value=1.0, kind="mean")

Estimates E[Y(hi) - Y(lo) | T = treated_value] using subgroup-aware empirical integration over the covariate distribution of the treated subgroup.

Unlike ate(), which averages over the full covariate distribution, att() restricts to rows where the treatment variable equals treated_value. In linear models without interactions, ATT equals ATE; they diverge with effect modification or nonlinear link functions.

Parameters
outcome: str

Outcome variable name (used for documentation; the contrast is computed over all endogenous variables).

treatment: str

Treatment variable to intervene on.

values: tuple[float, float] = (0.0, 1.0)

(lo, hi) intervention values. Default (0.0, 1.0).

treated_value: float = 1.0

Value of the treatment variable identifying the treated subgroup (default 1.0).

kind: str = "mean"
"mean" for deterministic propagation, "predictive" to include residual noise.
Returns
EstimandResult
Contrast do(treatment=hi) - do(treatment=lo) integrated over the treated subgroup’s covariate distribution, focused on outcome.
Raises
RuntimeError

If called before .fit().

NotImplementedError

If the model is a panel model (not yet supported).

ValueError
If no observations match treated_value.
Examples
>>> model = pathmc.model("Y ~ T + X", data=df)
>>> model.fit(draws=1000, tune=1000)
>>> att = model.att("Y", "T")
>>> att.mean()  # E[Y(1) - Y(0) | T=1]

atu()

Compute the average treatment effect on the untreated (ATU).

Usage

atu(outcome, treatment, values=(0.0, 1.0), untreated_value=0.0, kind="mean")

Estimates E[Y(hi) - Y(lo) | T = untreated_value] using subgroup-aware empirical integration over the covariate distribution of the untreated subgroup.

Unlike ate(), which averages over the full covariate distribution, atu() restricts to rows where the treatment variable equals untreated_value. In linear models without interactions, ATU equals ATE; they diverge with effect modification or nonlinear link functions.

Parameters
outcome: str

Outcome variable name (used for documentation; the contrast is computed over all endogenous variables).

treatment: str

Treatment variable to intervene on.

values: tuple[float, float] = (0.0, 1.0)

(lo, hi) intervention values. Default (0.0, 1.0).

untreated_value: float = 0.0

Value of the treatment variable identifying the untreated subgroup (default 0.0).

kind: str = "mean"
"mean" for deterministic propagation, "predictive" to include residual noise.
Returns
EstimandResult
Contrast do(treatment=hi) - do(treatment=lo) integrated over the untreated subgroup’s covariate distribution, focused on outcome.
Raises
RuntimeError

If called before .fit().

NotImplementedError

If the model is a panel model (not yet supported).

ValueError
If no observations match untreated_value.
Examples
>>> model = pathmc.model("Y ~ T + X", data=df)
>>> model.fit(draws=1000, tune=1000)
>>> atu = model.atu("Y", "T")
>>> atu.mean()  # E[Y(1) - Y(0) | T=0]

cate()

Compute the conditional average treatment effect.

Usage

cate(outcome, treatment, values=(0.0, 1.0), condition=None, **do_kwargs)

Like ate() but with additional variables fixed in both scenarios, enabling effect modification analysis.

Parameters
outcome: str

Outcome variable name; the default target of the result’s accessors.

treatment: str

Treatment variable to intervene on.

values: tuple[float, float] = (0.0, 1.0)

(lo, hi) intervention values.

condition: dict[str, float] | None = None

Variables to fix at specific values in both scenarios.

**do_kwargs: Any
Passed to do().
Returns
EstimandResult
Contrast with conditioning variables held fixed, focused on outcome.

collider_warnings()

Warn when a proposed adjustment variable is a collider on a

Usage

collider_warnings(adjustment_vars, treatment, outcome)

path between treatment and outcome.

This is a structural heuristic. It flags the collider even when the rest of the set keeps that path blocked. is_valid_adjustment_set() decides whether the set satisfies the backdoor criterion.

Note

This function reasons about the DAG structure declared in the model specification. It cannot detect omitted variables, missing edges, or other forms of misspecification. Use test_implications() to check whether the DAG’s structural assumptions are consistent with observed data.

A ~~ residual covariance block is a declaration of an unobserved common cause of its members, so it is expanded into a latent confounder node before colliders are searched for: a variable with only one declared parent can still become a collider once that synthetic confounder is added.

Parameters
adjustment_vars: set[str]

Proposed adjustment set.

treatment: str

Treatment variable name.

outcome: str
Outcome variable name.
Returns
list[str]
Warning strings for problematic variables.

comparisons()

Interventional contrasts between two values of a variable.

Usage

comparisons(
    outcome,
    variable,
    *,
    contrast=(0.0, 1.0),
    comparison="diff",
    conditional=None,
    average_by="all"
)

With comparison="diff" and average_by="all", matches ate() on the same contrast.

Parameters
outcome: str

Outcome variable name.

variable: str

Variable to vary under do().

contrast: tuple[float, float] = (0.0, 1.0)

(lo, hi) intervention values.

comparison: str = "diff"

"diff", "ratio", or "lift".

conditional: dict[str, float | str] or None = None

Additional variables held fixed at scalar values. A categorical predictor takes a fitted level label.

average_by: str or None = "all"
"all" collapses to EstimandResult; None keeps unit-level InterpretResult draws.
Returns
EstimandResult or InterpretResult

counterfactual()

Compute unit-level counterfactual outcomes.

Usage

counterfactual(evidence, do, allow_partial_evidence=False)

Implements Pearl’s three-step procedure (abduction → action → prediction) using posterior draws. Unlike do(), which answers population-level questions with exogenous terms at their means, counterfactuals infer individual-specific exogenous values from evidence before simulating the intervention in do.

Parameters
evidence: dict[str, Any]

Observed values for a specific individual. Used in the abduction step to recover their exogenous (U) terms. A categorical predictor is given as one of its fitted level labels and must always be supplied, since a label has no population mean to fall back on.

do: dict[str, Any]

Intervention values (same format as do(set=...)); a categorical predictor takes a level label.

allow_partial_evidence: bool = False
If True, missing numeric evidence is assigned its population mean (U = 0) with a warning. By default, all model variables must be supplied so a counterfactual cannot silently mix individual and population information.
Returns
DoResult
Posterior over counterfactual outcomes with .mean(var), .hdi(var), and contrast arithmetic.
Raises
RuntimeError

If called before .fit().

ValueError

If evidence or do keys are invalid, an intervention on an hsgp() input falls outside the basis boundary frozen from the fitted data, or the model uses non-Gaussian families.

NotImplementedError
If the model is a panel model.

datagrid()

Build a business-unit covariate grid from the fitted data frame.

Usage

datagrid(**cols)

On a model fitted with scaling=, user-supplied values are already business units. Columns held at their fitted mean or mode are converted from the model’s internal scale before the grid is returned. See pathmc.datagrid() for details.

Parameters
**cols: list[float] | list[int]
Column names mapped to lists of values to cross.
Returns
pd.DataFrame
Cartesian product grid with unspecified columns held constant.

design()

Return the design matrix for an endogenous variable’s equation.

Usage

design(var)
Parameters
var: str
Name of the endogenous (LHS) variable.
Returns
IntoFrame
Design matrix with named columns, backed by the same DataFrame library as the data passed to model().
Raises
RuntimeError

If the model was created without data.

KeyError
If var is not an endogenous variable in the model.

do()

Simulate an intervention using the do-operator.

Usage

do(self, set: dict[str, Any] | None = None, shift: dict[str, float] | None = None, kind: str = 'mean', simulate_over: str | None = None, random_seed: int | None = None, average_units: bool = True, initial: Mapping[str, Any] | None = None, policy: None = None, horizon: None = None, n_simulations: None = None, keep_trajectories: None = None) -> DoResult
do(self, set: dict[str, Any] | None = None, shift: dict[str, float] | None = None, kind: str = 'mean', simulate_over: str | None = None, random_seed: int | None = None, average_units: bool = True, initial: Mapping[str, Any] | None = None, policy: TreatmentPolicy, horizon: int | None = None, n_simulations: int | None = None, keep_trajectories: bool | None = None) -> PolicyRun

Uses PyMC-native graph surgery: pm.do() on the generative model for interventions, then pm.sample_posterior_predictive() (kind=“predictive”) or compute_deterministics (kind=“mean”) for propagation.

Parameters
set: dict[str, float | np.ndarray] | None = None

Variables to fix at specific values (hard intervention). For panel models with simulate_over="time", values can be arrays of shape (n_times,) for time-varying interventions (e.g., a temporary spend increase). A NaN in that array releases the step of an endogenous variable: the scan runs the variable’s own equation there, and the reported path keeps the finite held values elsewhere. NaN does not release an exogenous input, which has no equation to fall back on. On a model compiled with scaling=, values are in business units: pathmc divides by the fitted factor before graph surgery so do(set={"tv": 500}) means 500 of the original column, not 500 scaled units.

shift: dict[str, float] | None = None

Not implemented; raises NotImplementedError if given.

kind: str = "mean"

"mean" for deterministic propagation via mu Deterministics, "predictive" to include residual noise. Residual-covariance blocks are sampled from a generative sibling so that noise reaches descendants; the estimation likelihood is unchanged.

simulate_over: str | None = None

"time" to activate time-forward panel simulation. Required for models with lag() or adstock(), which also require the model to have been fitted with panel=.

average_units: bool = True

When True (default), kind="mean" averages response means over observation rows. prob sets this to False when the expression names both an endogenous variable and a per-unit intervention, so the comparison uses unit-level means.

random_seed: int | None = None

Seed for reproducible predictive process noise. Does not change stored posterior draws. Ignored for deterministic mean queries.

initial: Mapping[str, Any] | None = None

Pre-sample state for every observed lag() base, in business units. A scalar broadcasts to every fitted unit; a mapping must contain exactly the fitted unit labels. None reuses initial_baseline. An override is local to this call: it governs both mean and predictive recursion and does not refit the model or change the stored baseline. The values are the state before the first fitted time, not an extra outcome period.

policy: TreatmentPolicy | None = None

History-dependent treatment assigned inside the panel scan. Requires an explicit pre-sample baseline, simulate_over='time', and a Gaussian lag model. kind='predictive' is rejected: the policy path always integrates process noise.

horizon: int | None = None

Required. Must equal the fitted time count. Do not omit it.

n_simulations: int | None = None

Stochastic histories per posterior draw. Default 256. Minimum 2.

keep_trajectories: bool | None = None
Retain unit-level paths. Default False for a policy query.
Returns
DoResult | PolicyRun
A set= or shift= query returns a DoResult. A policy= query returns a PolicyRun. The two are not one type and cannot be subtracted.
Raises
RuntimeError

If called before .fit().

ValueError
If kind is not "mean" or "predictive". Also if simulate_over="time" without panel, or if an intervention on an hsgp() input falls outside the basis boundary [mid - L, mid + L] frozen from the fitted data (beyond it the basis aliases instead of extrapolating), or if a temporal scan model is simulated without simulate_over="time".

effect()

Compute the effect along a causal path in the DAG.

Usage

effect(path)

Uses the product-of-coefficients method, which assumes linear structural equations (identity link). For multi-edge paths that cross non-Gaussian link scales, use simulate() with do() interventions instead.

Parameters
path: str
A path string like "X -> M -> Y" specifying the causal pathway. Each edge must correspond to a regression term.
Returns
EffectResult
Posterior draws for the path-specific effect.
Raises
RuntimeError

If the model was created without data, or called before .fit().

ValueError

If a node is not endogenous, an edge does not exist, or a drawn edge exists only as an interaction constituent. In that last case the error names the interaction term (for example X:Z) instead of claiming the edge is absent.

NotImplementedError
If a multi-edge path includes a non-Gaussian edge target whose coefficient would be multiplied across link scales.

effects_summary()

Return a posterior summary of labeled coefficients and defined parameters.

Usage

effects_summary()
Returns
pd.DataFrame
Summary with mean, sd, and HDI for each labeled coefficient and := defined parameter. On a scaled model, labeled coefficients are mapped to business units according to the term (linear/adstock: f_out / f_pred; saturation/HSGP: f_out; interaction: f_out / prod(f_pred_i)), and defined parameters are evaluated from those rescaled draws so a product such as indirect := a*b agrees with the rows above it. Per-unit factors are reduced with a data-weighted mean — a ratio of means, not the mean of ratios.
Raises
RuntimeError

If the model was created without data, or called before .fit().

DuplicateEquationError
If a defined parameter uses a coefficient label’s name. The summary reports each labeled coefficient and each defined parameter, so the defined parameter must not occupy the label.

equations()

Return model equations and/or prior specifications.

Usage

equations(show="all")

Works before sampling. By default renders both structural equations and priors as combined LaTeX in Jupyter / Quarto.

Parameters
show: ("all", "structural", "priors") = "all"

What to display:

  • "all" — structural equations and priors (default).
  • "structural" — only the structural (regression) equations.
  • "priors" — only the prior distributions.
Returns
ModelEquations | EquationList | PriorTable
ModelEquations when show=“all”, EquationList when show=“structural”, PriorTable when show=“priors”.

falsify()

Falsify the whole DAG against the data via a permutation test.

Usage

falsify(
    n_permutations=None,
    significance_level=0.05,
    significance_ci=0.05,
    include_unconditional=True,
    random_seed=None
)

Grades the entire DAG at once, rather than one missing edge at a time as test_implications() does. It counts how many Local Markov Conditions (implied parental conditional independences) the data violate, then compares that count to a baseline of randomly relabeled competitor graphs.

The DAG is reported as informative (falsifiable) when few node permutations share its Markov equivalence class. An informative DAG that violates fewer conditions than the permuted baseline is the positive case: not contradicted and testable. A non-informative DAG is also not rejected, but that verdict is vacuous — interpret it as not falsifiable rather than as evidence for the graph. This ports dowhy’s gcm.falsify_graph (Eulig et al., 2023) using the same partial-correlation conditional independence methodology as test_implications(). Because that test is linear, purely nonlinear dependencies are not detected, so a “not rejected” verdict is only as strong as the linear-Gaussian assumption. local_violations includes requested_k, effective_k, rank_collapsed, and df for each test on this DAG. A rank-deficient parent set (a constant, duplicate, or collinear parent) sets rank_collapsed and warns. That row stays in the frame, but given_lmc_violations, n_lmc_tests, violations, and the permutation fraction all exclude it. A permuted DAG that produced no written parental test is omitted from that fraction, not scored as zero violations. If every drawn permutation is omitted, falsified is unset and the summary, compact repr, and HTML say the LMC comparison was not run. The summary names the collapsed count separately. If every requested test collapses, the DAG cannot be evaluated.

Uses the observed data, not the posterior, and works before sampling. Models with residual covariances (~~) are not supported and raise ValueError — falsify the directed structure without the ~~ terms instead.

Parameters
n_permutations: int | None = None

Number of permuted DAGs in the baseline. Defaults to round(1 / significance_level). For small graphs (at most 7 nodes), if it meets or exceeds the number of distinct node relabelings (n!), all are enumerated exactly; otherwise random relabelings are sampled.

significance_level: float = 0.05

Significance level for the permutation-based verdict (default 0.05).

significance_ci: float = 0.05

Significance level for each conditional independence test (default 0.05).

include_unconditional: bool = True

Whether to also test unconditional independences implied by root nodes (default True).

random_seed: int | None = None
Seed for the permutation sampler, for reproducible results.
Returns
FalsificationResult
Verdict (.falsified, .falsifiable), permutation p-values, per-test local violations, and a .plot() helper.

fit()

Run MCMC sampling and store the resulting posterior DataTree.

Usage

fit(compute_log_likelihood=True, **kwargs)

All keyword arguments are forwarded to pm.sample().

Parameters
compute_log_likelihood: bool = True

Whether to compute the pointwise log-likelihood after sampling (default True). Set False to skip the extra pass when you do not need az.loo/az.waic — e.g. internal re-fits during refutation.

**kwargs: Any
Keyword arguments forwarded to pm.sample(). Common options include draws, tune, chains, random_seed, target_accept, and nuts_sampler.
Returns
xarray.DataTree
Posterior samples.

frontdoor_identifiable()

Check whether the front-door criterion identifies the causal

Usage

frontdoor_identifiable(treatment, mediator, outcome)

effect of treatment on outcome through mediator.

Note

This function reasons about the DAG structure declared in the model specification. It cannot detect omitted variables, missing edges, or other forms of misspecification. Use test_implications() to check whether the DAG’s structural assumptions are consistent with observed data. If the spec includes adjustment variables that add edges absent from the true causal DAG, the check may report false negatives. Build a separate GraphInfo from the causal structure for an accurate check in that case.

Parameters
treatment: str

Treatment variable name.

mediator: str

Mediator variable name.

outcome: str
Outcome variable name.
Returns
tuple[bool, str]
(identifiable, message) where message explains the result or describes which condition fails.

graph()

Return a graphviz DAG of the structural model.

Usage

graph()

Works before sampling. Exogenous nodes are drawn as boxes, endogenous nodes as ellipses. Latent nodes get dashed borders (bold for stochastic latent). Labeled coefficients appear on edges.


implied_independences()

List all conditional independences implied by the DAG.

Usage

implied_independences()

For each pair of non-adjacent nodes, returns the independence statement with the conditioning set derived from the basis set method (Shipley, 2000). Works before sampling — only the graph structure is needed.

A ~~ residual covariance block declares an unobserved common cause of its members, so d-separation is checked with that confounder made explicit: two variables joined only by a declared ~~ edge are never reported as independent.

Returns
list[ConditionalIndependence]
Implied independence statements, sorted alphabetically.

is_identifiable()

Check if the causal effect of treatment on outcome is

Usage

is_identifiable(treatment, outcome)

identifiable via the backdoor criterion.

Note

This function reasons about the DAG structure declared in the model specification. It cannot detect omitted variables, missing edges, or other forms of misspecification. Use test_implications() to check whether the DAG’s structural assumptions are consistent with observed data.

A ~~ residual covariance block is a declaration of an unobserved common cause of its members, so it is expanded into a latent confounder node before the criterion is applied: no adjustment set can block it.

Parameters
treatment: str

Treatment variable name.

outcome: str
Outcome variable name.
Returns
bool
True if at least one valid adjustment set exists.

latent_trajectory()

Return the posterior latent state of a panel variable over time.

Usage

latent_trajectory(var)

For scan-compiled panel models with latent variables (latent=[...]), extracts the inferred latent trajectory from the posterior.

Shape Semantics

The returned DataArray has dimensions (chain, draw, time, unit):

  • chain / draw: posterior sampling dimensions.
  • time: sorted unique time values from the model’s panel structure (the time column values).
  • unit: unit labels (the unit column values).

The underlying posterior variable is the Deterministic registered at compile time: for stochastic latents (latent_normal family) the scan emits the realized state under the variable’s own name (var); for deterministic latents it is stored as mu_{var}. Each posterior slice is an (n_times, n_units) matrix in time-major sorted order; this method attaches the matching time/unit coordinates.

The default init_{var} prior is Normal(0, 1) and is not scale-free: override it when the latent is not O(1), otherwise t=0 is pinned near zero and early-period trajectories are biased.

Parameters
var: str
Name of a latent endogenous variable.
Returns
xarray.DataArray
Posterior latent states with dims (chain, draw, time, unit) and time/unit coordinates.
Raises
RuntimeError

If the model has no data or has not been fitted yet.

ValueError
If var is not a latent variable, or the model is not a scan-compiled panel model (no temporal terms).
Examples
>>> traj = m.latent_trajectory("awareness")  # doctest: +SKIP
>>> traj.mean(dim=("chain", "draw"))  # doctest: +SKIP

predict()

Run posterior predictive sampling.

Usage

predict(data=None, *, one_step_ahead=True, **kwargs)

Wraps pm.sample_posterior_predictive() and extends the stored DataTree with a posterior_predictive group.

Parameters
data: IntoFrame | None = None

Optional predictor data for out-of-sample prediction. It must have the same number of rows as the fitted cross-sectional model. Categorical columns are encoded with the fitted level order and reference; unseen levels raise a clear error.

one_step_ahead: bool = True

Only meaningful for panel models with a lagged endogenous term such as y ~ lag(y). When True (default) each time step conditions on the observed previous value, so the predictions are one-step-ahead forecasts — the posterior predictive distribution that matches the likelihood the model was fitted with, and the right choice for a posterior predictive check. When False the recursion is free-running: step t conditions on the model’s own simulated value at t-1, giving a multi-step trajectory whose uncertainty compounds over time. Ignored for models without a lagged endogenous term, where the two are identical. Scan residual blocks jointly sample their original outcome names. One-step means use observed current parents and lag history; free trajectories propagate the same realized correlated states to descendants and later lags. Scan + ~~ requires complete, observed Gaussian equations throughout the model.

**kwargs: Any
Passed directly to pm.sample_posterior_predictive(). Pass extend_inferencedata=False to leave the stored DataTree untouched and get the standalone posterior predictive samples back instead.
Returns
xarray.DataTree
The stored DataTree with a posterior_predictive group added, or the standalone posterior predictive samples when extend_inferencedata=False is passed.
Raises
RuntimeError

If the model was created without data, or called before .fit().

ValueError
For panel models with a temporal dependency (lag() or adstock()), if pm.set_data() was used to swap in a differently-shaped panel first. n_units/n_times are baked into the scan graph at model-compile time, so out-of-sample prediction on a new panel shape is not supported here: build a new model with pathmc.model(..., data=new_data, panel=...) on the new data instead.

predictions()

Interventional or associational response-mean predictions.

Usage

predictions(outcome, *, set=None, newdata=None)
Parameters
outcome: str

Outcome variable name.

set: dict[str, float | str] or None = None

Intervention values. A categorical predictor takes a fitted level label. When omitted, predictions are associational.

newdata: IntoFrame or None = None
Covariate grid or frame to predict on, in business units. Defaults to the fitted data. On a scaled model the grid is divided by the fitted factors before compilation, matching do(set=).
Returns
InterpretResult
Unit-level posterior draws for the outcome.

priors()

Return a summary of prior distributions for all parameters.

Usage

priors()

Works before sampling. The table reflects any custom priors set via set_priors() or the priors argument to pathmc.model().

Tip

Use equations() for a unified view of both structural equations and priors.


prob()

Compute the probability of an expression under an intervention.

Usage

prob(expr, set=None, kind="predictive", **do_kwargs)

Evaluates P(expr | do(set)) using posterior predictive draws.

Parameters
expr: str

Boolean expression over variable names, e.g. "Y > 0".

set: dict[str, float] | None = None

Intervention values for the do-operator.

kind: str = "predictive"

Propagation kind (default "predictive" to include residual noise, which is needed for meaningful probabilities).

**do_kwargs: Any
Passed to do().
Returns
float
Fraction of aligned comparisons that satisfy expr. For kind="mean", a per-unit intervention is compared to unit-level means, not to the outcome averaged over units.

refute_placebo()

Refute the estimated effect with a Bayesian placebo treatment.

Usage

refute_placebo(
    outcome,
    treatment,
    *,
    values=(0.0, 1.0),
    n_permutations=4,
    significance_level=0.05,
    sample_kwargs=None,
    random_seed=None
)

Permutes the treatment column n_permutations times, re-fitting and re-estimating the ATE each time. A sound pipeline reports no effect for a permuted treatment. The per-permutation posterior summaries are pooled through a hierarchical normal-normal random-effects model that separates the systematic placebo bias mu_null (which should straddle zero) from the structural volatility tau_het; the real effect is then calibrated against the resulting null predictive distribution.

Each permutation triggers a full MCMC re-fit, so cost scales linearly with n_permutations.

Parameters
outcome: str

Outcome variable name.

treatment: str

Treatment variable to replace with a placebo. Must be an observed (non-latent) data column.

values: tuple[float, float] = (0.0, 1.0)

(lo, hi) intervention values for the ATE contrast (default (0.0, 1.0)), matching ate().

n_permutations: int = 4

Number of placebo permutations / folds (default 4). Four is a floor — the between-fold volatility tau_het is prior-dominated at small J; use 8+ for a data-driven null spread (each fold is a full MCMC re-fit, so cost is linear).

significance_level: float = 0.05

Threshold for the effect_survives verdict (default 0.05).

sample_kwargs: dict | None = None

Keyword arguments forwarded to .fit() for each placebo re-fit and to the hierarchical null model’s sampler.

random_seed: int | None = None
Seed for reproducible permutations and hierarchical fit.
Returns
PlaceboRefutationResult
Placebo null summary, verdicts (.passes_placebo, .effect_survives), calibration (.z_cal, .p_tail), and a .plot() helper.
Raises
RuntimeError

If the model was created without data, called before .fit(), or not created via pathmc.model().

ValueError

If treatment/outcome are invalid or n_permutations < 2.

NotImplementedError
If the model is a panel model (not yet supported).

sample_prior_predictive()

Draw samples from the prior predictive distribution.

Usage

sample_prior_predictive(**kwargs)

Useful for checking whether default or custom priors generate plausible data ranges before running MCMC.

Parameters
**kwargs: Any
Forwarded to pm.sample_prior_predictive().
Returns
xarray.DataTree
Prior predictive samples.

sensitivity()

Assess robustness of a causal effect to unmeasured confounding.

Usage

sensitivity(
    outcome,
    treatment,
    gamma_range=(0.0, 1.0),
    delta_range=(0.0, 1.0),
    n_grid=20,
    values=(0.0, 1.0),
    **do_kwargs
)

Hypothesizes an unmeasured confounder U with effect γ on the treatment and δ on the outcome. The confounding bias is γ × δ, so the adjusted ATE at each grid point is::

adjusted ATE = observed ATE − γ × δ

The result includes a contour plot showing which (γ, δ) combinations would overturn the causal conclusion.

Parameters
outcome: str

Outcome variable name.

treatment: str

Treatment variable name.

gamma_range: tuple[float, float] = (0.0, 1.0)

(min, max) range for γ (confounder → treatment effect).

delta_range: tuple[float, float] = (0.0, 1.0)

(min, max) range for δ (confounder → outcome effect).

n_grid: int = 20

Number of grid points per dimension (default 20).

values: tuple[float, float] = (0.0, 1.0)

(lo, hi) intervention values for computing the ATE (default (0.0, 1.0)).

**do_kwargs: Any
Passed to ate() and thence to do() (e.g. kind, simulate_over).
Returns
SensitivityResult
Sensitivity analysis results with .plot() method.
Raises
RuntimeError

If called before .fit().

ValueError
If the ranges are invalid or n_grid < 2.

set_priors()

Update prior distributions and recompile the model.

Usage

set_priors(overrides)

Only the specified priors are changed; all others keep their current values. Any existing posterior samples are invalidated.

On a data-free model, priors are updated without recompilation.

Parameters
overrides: dict[str, Prior]
Mapping from parameter name to Prior object. Use .equations() to see available parameter names.
Raises
ValueError
If a key does not match any model parameter.

slopes()

Finite-difference interventional slopes.

Usage

slopes(
    outcome,
    wrt,
    *,
    slope="dydx",
    eps=0.0001,
    conditional=None,
    average_by="all"
)
Parameters
outcome: str

Outcome variable name.

wrt: str

Variable to differentiate with respect to.

slope: str = "dydx"

Slope type: "dydx", "eyex", "eydx", or "dyex".

eps: float = 0.0001

Finite-difference step size.

conditional: dict[str, float | str] or None = None

Additional variables held fixed at scalar values. A categorical predictor takes a fitted level label.

average_by: str or None = "all"
"all" collapses to EstimandResult; None keeps unit-level draws.
Returns
EstimandResult or InterpretResult

standardized()

Return stdyx-standardized coefficients for labeled effects.

Usage

standardized()

Each coefficient is standardized as coef * sd(X) / sd(Y), giving the expected change in Y (in SD units) per SD change in X. coef is the business-unit coefficient reported by effects_summary(). sd(X) and sd(Y) are the observed marginal SDs of the data passed to model(), including on a scaled model whose internal columns were divided by per-unit factors. Binary 0/1 predictors are omitted: their SD depends on the base rate.

Returns
pd.DataFrame
Summary with mean, sd, and HDI for each standardized coefficient.
Raises
RuntimeError
If the model was created without data, or called before .fit().

summary()

Return a posterior summary table.

Usage

summary()
Returns
pd.DataFrame
ArviZ summary of all model parameters. Columns are full-precision floats; apply .round() downstream as needed.
Raises
RuntimeError
If the model was created without data, or called before .fit().

test_implications()

Test all DAG-implied conditional independences against the data.

Usage

test_implications(alpha=0.05)

For each implied independence X ⊥⊥ Y | Z, computes the partial correlation between X and Y controlling for Z and tests whether it is significantly different from zero.

A significant result flags a violation: the data show an association that the DAG says should not exist, suggesting a missing edge or incorrect structure. Each row also reports requested_k, effective_k, rank_collapsed, and df. When a conditioner is constant, duplicated, collinear with the others, or a single-level categorical that contributed no columns, rank_collapsed is set, significant is false, and a warning is emitted. The p-value still refers to the columns that entered, not the independence implied by the DAG. n_violations equals results["significant"].sum(). The display does not mark that row pass or fail. Drop the redundant variable and re-test.

Uses the observed data, not the posterior. Statements that name a latent or any other column absent from the data are skipped; implied_independences() may still list them. Only observable implications are returned.

Parameters
alpha: float = 0.05
Significance level for flagging violations (default 0.05).
Returns
ImplicationTestResult
Test results with .violations, .to_dataframe(), and rich display in Jupyter via _repr_html_().

to_graphviz()

Render the underlying PyMC model as a graphviz plate diagram.

Usage

to_graphviz()

Delegates to pm.Model.to_graphviz() on the compiled model, showing random variables, deterministics, observed data, and plate structure.

Returns
graphviz.Digraph
Graphviz diagram of the PyMC model graph.
Raises
RuntimeError
If the model was created without data.