TransferFunctionLinearRegression#

class causalpy.pymc_models.TransferFunctionLinearRegression[source]#

Bayesian Transfer Function model for Graded Intervention Time Series.

This model jointly estimates transform parameters (adstock, saturation) and regression coefficients within a Bayesian framework using PyMC.

The model applies transforms to treatment variables using pymc-marketing functions, allowing full Bayesian inference on all parameters including the transform parameters themselves.

Parameters:
  • saturation_type (str or None) – Type of saturation transform. Options: “hill”, “logistic”, “michaelis_menten”, None. If None, no saturation is applied.

  • adstock_config (dict or None) – Configuration for adstock transform. Required keys: - “half_life_prior”: dict with prior specification (e.g., {“dist”: “Gamma”, “alpha”: 4, “beta”: 2}) - “l_max”: int, maximum lag - “normalize”: bool, whether to normalize weights If None, no adstock is applied.

  • saturation_config (dict or None) – Configuration for saturation transform. Structure depends on saturation_type: - For “hill”: {“slope_prior”: {…}, “kappa_prior”: {…}} - For “logistic”: {“lam_prior”: {…}} - For “michaelis_menten”: {“alpha_prior”: {…}, “lam_prior”: {…}}

  • coef_constraint (str, default="unconstrained") – Constraint on treatment coefficients: “nonnegative” or “unconstrained”.

  • sample_kwargs (dict, optional) – Additional kwargs passed to pm.sample().

Notes

The current implementation uses independent Normal errors.

Autocorrelation in Errors: Implementation Challenges

For time series with autocorrelated residuals, see TransferFunctionARRegression, which implements AR(1) errors via quasi-differencing. This note explains why AR errors require special handling and the design decisions made.

Why Standard PyMC AR Approaches Fail:

The fundamental issue is that observed data cannot depend on model parameters in PyMC’s computational graph. For AR errors, we want:

\[y[t] = \mu[t] + \epsilon[t] \epsilon[t] = \rho \cdot \epsilon[t-1] + \nu[t]\]

But \epsilon[t] = y[t] - \mu[t] depends on \mu (which depends on \beta, \theta, half_life, etc.), so this fails:

# This FAILS with TypeError
residuals = y - mu  # depends on parameters!
pm.AR("epsilon", rho, observed=residuals)  # ❌

Implemented Solution (TransferFunctionARRegression):

Uses quasi-differencing following Box & Tiao (1975):

\[y[t] - \rho \cdot y[t-1] = \mu[t] - \rho \cdot \mu[t-1] + \nu[t]\]

This transforms to independent innovations \nu[t], allowing manual log-likelihood via pm.Potential. This is the theoretically correct Box & Tiao intervention analysis model where AR(1) represents the noise structure itself.

Alternative: AR as Latent Component (Not Implemented):

An alternative that avoids the observed= issue would be:

\[y[t] = \mu_{baseline}[t] + ar[t] + \epsilon_{obs}[t] ar[t] = \rho \cdot ar[t-1] + \eta[t] \epsilon_{obs}[t] \sim N(0, \sigma^2_{obs})\]

This could use PyMC’s built-in pm.AR since ar[t] is latent (unobserved):

# This WOULD work (but changes model interpretation)
ar = pm.AR("ar", rho=[rho_param], sigma=sigma_ar, shape=n_obs)
mu_total = mu_baseline + ar
pm.Normal("y", mu=mu_total, sigma=sigma_obs, observed=y_data)  # ✅

Why We Don’t Use This Approach:

  1. Different model class: Represents AR + white noise, not pure AR errors. Has two variance parameters (\sigma_{ar}, \sigma_{obs}) vs. one (\sigma).

  2. Theoretical mismatch: Box & Tiao’s intervention analysis models autocorrelation in the noise process itself, not as an additional latent component. The AR process IS the residual structure, not a separate mean component.

  3. Identifiability concerns: With both AR and white noise, parameters may be poorly identified unless \sigma_{obs} \approx 0 (which defeats the purpose).

  4. Interpretation: The latent AR component would represent a time-varying offset rather than residual autocorrelation, changing the causal interpretation.

When to Use Each Model:

  • TransferFunctionLinearRegression (this class): When residuals show minimal autocorrelation, or computational efficiency is critical.

  • TransferFunctionARRegression: When residual diagnostics show significant autocorrelation (e.g., ACF plots, Durbin-Watson test), and you want to follow the classical Box & Tiao specification.

Prior Customization:

Priors are managed using the Prior class from pymc_extras and can be customized via the priors parameter:

from pymc_extras.prior import Prior

model = cp.pymc_models.TransferFunctionLinearRegression(
    saturation_type=None,
    adstock_config={...},
    priors={
        "beta": Prior(
            "Normal", mu=0, sigma=100, dims=["treated_units", "coeffs"]
        ),
        "sigma": Prior("HalfNormal", sigma=50, dims=["treated_units"]),
    },
)

By default, data-informed priors are set automatically via priors_from_data():

  • Baseline coefficients (beta): Normal(0, 5 * std(y))

  • Treatment coefficients (theta_treatment): Normal(0, 2 * std(y)) or HalfNormal(2 * std(y))

  • Error std (sigma): HalfNormal(2 * std(y))

This adaptive approach ensures priors are reasonable regardless of data scale.

Examples

Basic usage:

import causalpy as cp

model = cp.pymc_models.TransferFunctionLinearRegression(
    saturation_type=None,
    adstock_config={
        "half_life_prior": {"dist": "Gamma", "alpha": 4, "beta": 2},
        "l_max": 8,
        "normalize": True,
    },
    sample_kwargs={"chains": 4, "draws": 2000, "tune": 1000},
)

Methods

TransferFunctionLinearRegression.__init__([...])

Initialize TransferFunctionLinearRegression model.

TransferFunctionLinearRegression.add_coord(name)

Register a dimension coordinate with the model.

TransferFunctionLinearRegression.add_coords(...)

Vectorized version of Model.add_coord.

TransferFunctionLinearRegression.add_named_variable(var)

Add a random graph variable to the named variables of the model.

TransferFunctionLinearRegression.build_model(X, ...)

Build the PyMC model with transforms.

TransferFunctionLinearRegression.calculate_cumulative_impact(impact)

TransferFunctionLinearRegression.calculate_impact(...)

Calculate the causal impact as the difference between observed and predicted values.

TransferFunctionLinearRegression.check_start_vals(...)

Check that the logp is defined and finite at the starting point.

TransferFunctionLinearRegression.compile_d2logp([...])

Compiled log probability density hessian function.

TransferFunctionLinearRegression.compile_dlogp([...])

Compiled log probability density gradient function.

TransferFunctionLinearRegression.compile_fn(outs, *)

Compiles a PyTensor function.

TransferFunctionLinearRegression.compile_logp([...])

Compiled log probability density function.

TransferFunctionLinearRegression.copy()

Clone the model.

TransferFunctionLinearRegression.create_value_var(...)

Create a TensorVariable that will be used as the random variable's "value" in log-likelihood graphs.

TransferFunctionLinearRegression.d2logp([...])

Hessian of the models log-probability w.r.t.

TransferFunctionLinearRegression.debug([...])

Debug model function at point.

TransferFunctionLinearRegression.dlogp([...])

Gradient of the models log-probability w.r.t.

TransferFunctionLinearRegression.eval_rv_shapes()

Evaluate shapes of untransformed AND transformed free variables.

TransferFunctionLinearRegression.fit(X, y, ...)

Fit the Transfer Function model.

TransferFunctionLinearRegression.get_context([...])

TransferFunctionLinearRegression.initial_point([...])

Compute the initial point of the model.

TransferFunctionLinearRegression.logp([...])

Elemwise log-probability of the model.

TransferFunctionLinearRegression.logp_dlogp_function([...])

Compile a PyTensor function that computes logp and gradient.

TransferFunctionLinearRegression.make_obs_var(...)

Create a TensorVariable for an observed random variable.

TransferFunctionLinearRegression.name_for(name)

Check if name has prefix and adds if needed.

TransferFunctionLinearRegression.name_of(name)

Check if name has prefix and deletes if needed.

TransferFunctionLinearRegression.point_logps([...])

Compute the log probability of point for all random variables in the model.

TransferFunctionLinearRegression.predict(X)

Predict data given input data X

TransferFunctionLinearRegression.print_coefficients(labels)

TransferFunctionLinearRegression.priors_from_data(X, y)

Generate data-informed priors that scale with outcome variable.

TransferFunctionLinearRegression.profile(outs, *)

Compile and profile a PyTensor function which returns outs and takes values of model vars as a dict as an argument.

TransferFunctionLinearRegression.register_data_var(data)

Register a data variable with the model.

TransferFunctionLinearRegression.register_rv(...)

Register an (un)observed random variable with the model.

TransferFunctionLinearRegression.replace_rvs_by_values(...)

Clone and replace random variables in graphs with their value variables.

TransferFunctionLinearRegression.score(X, y)

Score the Bayesian \(R^2\) given inputs X and outputs y.

TransferFunctionLinearRegression.set_data(...)

Change the values of a data variable in the model.

TransferFunctionLinearRegression.set_dim(...)

Update a mutable dimension.

TransferFunctionLinearRegression.set_initval(...)

Set an initial value (strategy) for a random variable.

TransferFunctionLinearRegression.shape_from_dims(dims)

TransferFunctionLinearRegression.to_graphviz(*)

Produce a graphviz Digraph from a PyMC model.

Attributes

basic_RVs

List of random variables the model is defined in terms of.

continuous_value_vars

All the continuous value variables in the model.

coords

Coordinate values for model dimensions.

datalogp

PyTensor scalar of log-probability of the observed variables and potential terms.

default_priors

dim_lengths

The symbolic lengths of dimensions in the model.

discrete_value_vars

All the discrete value variables in the model.

isroot

observedlogp

PyTensor scalar of log-probability of the observed variables.

parent

potentiallogp

PyTensor scalar of log-probability of the Potential terms.

prefix

root

unobserved_RVs

List of all random variables, including deterministic ones.

unobserved_value_vars

List of all random variables (including untransformed projections), as well as deterministics used as inputs and outputs of the model's log-likelihood graph.

value_vars

List of unobserved random variables used as inputs to the model's log-likelihood (which excludes deterministics).

varlogp

PyTensor scalar of log-probability of the unobserved random variables (excluding deterministic).

varlogp_nojac

PyTensor scalar of log-probability of the unobserved random variables (excluding deterministic) without jacobian term.

__init__(saturation_type=None, adstock_config=None, saturation_config=None, coef_constraint='unconstrained', sample_kwargs=None, priors=None)[source]#

Initialize TransferFunctionLinearRegression model.

Parameters:
  • saturation_type (str | None)

  • adstock_config (Dict | None)

  • saturation_config (Dict | None)

  • coef_constraint (str)

  • sample_kwargs (Dict[str, Any] | None)

  • priors (dict[str, Any] | None)

classmethod __new__(*args, **kwargs)#