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.
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 + saturationSee 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.