Prediction contract for causal impact#
CausalPy calculates Bayesian causal impact as the difference between observed outcomes and a counterfactual expected outcome in observed outcome units. Experiments call the model adapter’s predict(), which extracts posterior_predictive["mu"], and subtract that from the observed y. That subtraction only has a causal interpretation when mu is the conditional expectation :math:E[Y \mid \text{parameters}, \text{covariates}] on the response scale, excluding observation-level noise.
Gaussian models with an identity link make the linear predictor, conditional expectation, and outcome scale look interchangeable. For generalized linear models they are not. With a Poisson log link, the linear predictor :math:\eta is on the log-mean scale while counts live on the outcome scale; CausalPy needs :math:\exp(\eta) = E[Y \mid \cdot] in mu, not :math:\eta itself. The same principle applies to Bernoulli/logit models, where mu must be a probability in :math:(0, 1).
Three prediction quantities#
Quantity |
Typical name in CausalPy |
Scale |
Includes observation noise? |
Used for impact? |
|---|---|---|---|---|
Linear predictor / latent state |
model-specific (e.g. |
Link scale |
No |
No |
Conditional expected outcome |
|
Outcome / response scale |
No |
Yes |
Posterior predictive draw |
|
Outcome scale |
Yes |
No (by default) |
Impact uses the middle row: draw-level contrasts y - mu are computed in outcome units, then summarized. Plots and effect summaries inherit this convention unless a method documents a different estimand (for example link-scale regression coefficients).
Posterior transformations for nonlinear models must be applied draw by draw before contrasts or averages. In general, inverse_link(mean(eta)) differs from mean(inverse_link(eta)); g-computation averages unit-level potential outcomes on the response scale. See Estimands in CausalPy for how this relates to empirical estimands.
Backend obligations#
Every Bayesian backend that participates in impact calculation must expose outcome-scale conditional expectations through mu in the object returned by predict():
Backend |
|
Notes |
|---|---|---|
Native :class: |
|
Identity-link Gaussian models satisfy the contract by construction. Custom subclasses must inverse-link before naming the node |
:class: |
|
Built-in curated families: Gaussian/identity, Poisson/log, Negative Binomial/log, and Bernoulli/logit. Non-Gaussian families skip Bayesian :math: |
:class: |
|
Gaussian observation model; |
:class: |
Upstream |
CausalPy treats these as outcome-scale expectations. For linked GLMs, pass the inverse-linked expectation as the latent until upstream exposes a dedicated expected-observation output ( |
scikit-learn adapters |
Point predictions on the outcome scale |
OLS backends return fitted values in |
sklearn backends compute impact as y_true - y_pred with the same outcome-scale requirement.
Public name and compatibility#
The public posterior predictive name remains mu for backward compatibility. The contract is semantic, not lexical: mu must denote the conditional expected outcome in observed units. A future explicit name such as expected_outcome may be added in a minor release once all built-in backends are audited; until then, custom model authors should treat mu as that quantity.
Built-in generalized linear regression#
:class:~causalpy.pymc_models.GeneralizedLinearRegression is the supported built-in GLM for count and binary outcomes. Choose family only at construction time; the canonical inverse link is exposed read-only via :attr:~causalpy.pymc_models.GeneralizedLinearRegression.link. It shares the eta → mu → y_hat graph with :class:~causalpy.pymc_models.LinearRegression, but applies the canonical inverse link before exposing mu:
import causalpy as cp
model = cp.GeneralizedLinearRegression(
family="poisson", # or "negative_binomial", "bernoulli", "gaussian"
sample_kwargs={"draws": 1000, "random_seed": 42},
)
Family-specific default priors include a shared Normal prior on beta (sigma=50 for Gaussian models, sigma=1 for Poisson and Negative Binomial, sigma=2.5 for Bernoulli). Gaussian models retain a HalfNormal sigma through the y_hat prior; custom priors["y_hat"] overrides are supported only for the Gaussian family. Negative Binomial models expose optional alpha dispersion through priors["alpha"]. Use :meth:~causalpy.pymc_models.GeneralizedLinearRegression.score only for Gaussian/identity models; Poisson, Negative Binomial, and Bernoulli backends return None rather than a misleading :math:R^2.