Skip to main content

Transforms

pathmc supports named transforms with estimable parameters directly in the DSL. Outcome distribution families are a separate choice: a transform changes a predictor, and a family changes the likelihood of an outcome.

Built-in transforms

Transforms are applied to predictor columns before entering the regression, with their parameters learned from data alongside the structural coefficients.

Transform Syntax Behaviour
adstock adstock(x, decay=theta) Panel scan: y_t = x_t + decay * y_{t-1}. Without panel=, a finite convolution that warns.
logistic_saturation logistic_saturation(x, lam=lam) Diminishing returns: y = (1 - exp(-lam * x)) / (1 + exp(-lam * x))

Transform parameters get appropriate priors automatically: decay receives a Beta(2, 2) prior (constrained to (0, 1)), and lam receives a HalfNormal(1) prior (positive support).

Without panel=, adstock(), delayed_adstock(), and weibull_adstock() convolve down row order and treat those rows as one series. pathmc warns: a stacked-unit frame would carry one unit’s tail into the next, and the compiler cannot see the unit change. Pass panel={"unit": ..., "time": ...} to keep each unit on its own time axis. For geometric adstock(), that warning also says the non-panel path is a 12-lag convolution, not the full recursive stock y_t = x_t + decay * y_{t-1} used when panel= compiles the scan. Delayed and Weibull adstock are finite kernels either way; their warning is only about the row-order assumption.

Composability

Transforms nest naturally — inner transforms are evaluated first:

spec = """
sales ~ b_tv*logistic_saturation(adstock(tv, decay=theta_tv), lam=lam_tv)
"""

The nesting order defines a pipeline: raw spend flows through adstock (accumulating carry-over), then through saturation (applying diminishing returns), then enters the linear predictor scaled by its coefficient.

tv tv (raw spend) adstock adstock (carry-over) tv->adstock decay=θ sat logistic_saturation (diminishing returns) adstock->sat lam=λ coef × b_tv (coefficient) sat->coef mu μ_sales (linear predictor) coef->mu
Figure 1: Transform composition pipeline for a single channel. Raw spend enters the inner transform (adstock) first, producing an accumulated stock. The stock then passes through the outer transform (logistic saturation), producing a 0–1 response. The response is multiplied by the channel coefficient and added to the linear predictor.

Transforms and do()

When you intervene on a variable that feeds into a transform, the transform is recomputed using the intervened value and posterior draws of the transform parameters. For panel models with simulate_over="time", adstock accumulates correctly across time steps under the intervention.

r0 = model.do(set={"tv": 10}, simulate_over="time", kind="mean")
r1 = model.do(set={"tv": 50}, simulate_over="time", kind="mean")
contrast = r1 - r0  # effect of increasing TV spend, accounting for adstock + saturation

See Time-Forward Panel Simulation for why simulate_over="time" is necessary when models have adstock or lagged variables.

For a non-Gaussian outcome, see Distribution Families. The Media Mix Models example uses both transforms and a posterior predictive check.