Ridge regression is linear regression with a penalty on the squared size of the coefficients, so the model has to balance fitting the data against keeping its estimates small. In a marketing mix model that penalty stops correlated channels from getting wildly large positive and negative coefficients, trading a little bias for a big reduction in variance, which is why Meta’s open-source Robyn fits every candidate model with ridge regression.
When I taught Robyn in the earlier Vexpower MMM path, ridge was the part people were most likely to treat as a black box: it was “the regression” inside a lot of hyperparameter machinery. That’s a shame, because it’s a simple idea with a clear interpretation, and knowing what it does tells you a lot about how to read Robyn’s output. It also tells you what it can’t do, which matters more for budget decisions. I’ll start with the objective itself and finish with how Robyn chooses the penalty.
The ridge regression objective
Ordinary least squares (OLS) picks the coefficients that minimize the sum of squared errors. Ridge adds a second term that charges for every coefficient’s squared size, weighted by a penalty . In the form used by glmnet, the R package Robyn relies on, with weeks of data and predictors, the ridge objective is:
The intercept isn’t penalized, and with you’re back to OLS. As grows, the cheapest way to lower the objective is to shrink the coefficients, so they’re pulled towards zero, and correlated coefficients are pulled towards each other, because splitting the credit evenly between two linked channels costs less penalty than giving it all to one. The solution even has a closed form, which shows where the name comes from:
Adding a constant to the diagonal of is exactly what Arthur Hoerl and Robert Kennard proposed in their 1970 Technometrics paper, written for “nonorthogonal” problems, which is the statisticians’ term for correlated predictors. When two channels move together, is close to singular and inverting it amplifies noise enormously; the added diagonal makes it well conditioned again. They also introduced the “ridge trace”, a plot of every coefficient across a range of penalties, which is still the best way to see what ridge is doing.
Why standardization matters before you penalize
The penalty treats every coefficient the same, but coefficients depend on units. A channel measured in dollars of weekly spend has a coefficient around 1 or 2, a 0/1 promotion flag might have a coefficient of 5,000, and impressions measured in raw counts might have a coefficient of 0.0001. Penalise those on their raw scales and the promotion is crushed while the impressions channel barely notices. The fix is to standardize each predictor (subtract its mean, divide by its standard deviation) before fitting, so the penalty is applied on a common scale, then convert the coefficients back. glmnet does this by default (standardize = TRUE) and, as its vignette notes, always returns the coefficients on the original scale, so you never see the standardized versions unless you go looking.
It’s worth keeping in mind that standardization is itself a modeling choice. A channel with very little variation (an always-on display line, say) gets a small standard deviation, so on the standardized scale its coefficient is large relative to how much it moved, and the penalty bites it harder. That’s not a bug, since a channel that barely varied really is weakly identified, but it’s one reason small, steady channels often come out of ridge-based models looking unremarkable.
The bias-variance trade-off in one slider
The point of the penalty is the bias-variance trade-off. OLS is unbiased (right on average over many hypothetical datasets) but with correlated channels and only a year or two of weekly data, the estimate from your one dataset can be far from the truth, as the multicollinearity guide shows. Ridge accepts some bias, pulling every estimate towards zero and towards its correlated neighbors, in exchange for estimates that move much less from one dataset to the next. If the variance you remove is larger than the bias you add, the model predicts new weeks better.
The explorer below fits three correlated channels on one simulated year. The truth is fixed: search returns $3.00 per $1, social $0.50 and video $1.50. Start at a small penalty, then slide λ to the right and watch the ridge trace on the left and the error curves on the right.
Three things are worth noticing. First, the training error only ever goes up as λ grows, because the penalty stops the model fitting the training weeks as closely as it could, while the error on new weeks usually falls a little before rising, and the bottom of that curve is the λ you’d want. Second, the lines in the ridge trace converge before they fall: the penalty first pushes the three channels towards each other, so the large search effect gets pulled down and the small social effect gets pulled up. Third, at the best λ the estimates are more stable, but they aren’t the truth. Social is usually overstated and search understated, because ridge’s idea of a plausible answer is “correlated channels have similar effects”, and that’s an assumption, not something the data told it. Push the correlation down towards zero and the best λ usually falls towards zero too, since there’s little variance left to remove.
It’s also worth comparing the last two stats. In the default year, OLS gives social a negative return, and the best λ brings the average distance from the true returns down by more than half while improving out-of-sample error by only a few percent. That gap is typical: noise in weekly sales dominates prediction error, so a model can predict almost equally well with very different channel splits, which is exactly why fit statistics are a poor guide to whether the coefficients are right. Across 40 simulated training years at these settings, the best-λ ridge model understated search on average (around 2.8 against 3.00) and overstated social (around 0.7 against 0.50), while cutting the typical coefficient error by roughly a fifth to a quarter.
How Robyn uses ridge regression
Robyn’s documentation says it uses “the L2-form of the regularisation, also known as Ridge regression”, fitted with glmnet, and explains why the team chose it over lasso: lasso tends to set some channels’ coefficients to exactly zero, and a model that concludes several media channels have no effect at all was considered unrealistic. In the source code (R/R/model.R), each candidate model is a call to glmnet with alpha = 0, which is glmnet’s setting for pure ridge. Here’s how the pieces fit together, as of the version on CRAN in October 2026 (3.12.1):
- λ is a hyperparameter, not a separate cross-validation step. Alongside the adstock and saturation hyperparameters for every channel, Robyn samples a value of
lambdabetween 0 and 1, and Nevergrad’s evolutionary optimizer (TwoPointsDE by default) searches over all of them together. - That 0-1 value is rescaled from the data. Robyn computes a glmnet-style maximum penalty from the data, uses 10 percent of it as
lambda_max, setslambda_mintolambda_max× 0.0001 (glmnet’s default ratio), and maps the sampled value linearly between the two. - Models are scored on more than fit. Each candidate is evaluated on NRMSE (prediction error), DECOMP.RSSD (how far each channel’s share of effect strays from its share of spend) and, if you calibrate with experiments, MAPE.LIFT. Robyn keeps the Pareto-optimal models across these objectives and clusters them to suggest a few to choose from.
- Signs are constrained. By default (
paid_media_signs = "positive") paid media coefficients get a lower limit of zero through glmnet’slower.limits, so a channel can be shrunk to nothing but not made negative. If the intercept comes out negative, Robyn by default drops it and refits.
The practical upshot is that the λ in a Robyn model wasn’t chosen to minimize out-of-sample error alone. It was chosen together with the adstock and saturation shapes, partly on how well the decomposition matches spend shares. That’s a reasonable design for a tool that has to produce believable budgets, but it means the penalty is doing double duty as a business-plausibility prior, which is worth knowing when you compare a Robyn model with one fitted another way. The Robyn guide covers the rest of the workflow, including picking a model from the Pareto front.
Choosing λ outside Robyn
If you’re fitting ridge yourself, in Python or R, the standard approach is cross-validation: fit the model on part of the data for a grid of λ values, score each on the held-out part, and pick the λ with the lowest error. For weekly MMM data, the folds must respect time. Randomly shuffled folds let the model train on next week to predict last week, which flatters it, so use an expanding window instead (train on weeks 1 to 52, test on 53 to 60, then train on 1 to 60, test on 61 to 68, and so on). Many practitioners then take the “one standard error” choice, the largest λ whose error is within one standard error of the best, which gives a slightly more conservative model for very little loss of accuracy.
In R the core call is short. This is the glmnet interface Robyn wraps, shown on its own for a matrix x of transformed media and controls and a revenue vector y:
library(glmnet)
fit <- glmnet(x, y, alpha = 0) # ridge path over glmnet's default lambda grid
cv <- cv.glmnet(x, y, alpha = 0, foldid = fold_ids) # pass time-ordered folds, don't use random ones
coef(cv, s = "lambda.1se") # coefficients at the one-standard-error lambda
The notebook below does the same from scratch in numpy on the practice dataset, with fixed adstock, a time-series cross-validation loop and a comparison of OLS and ridge contributions, so you can see every step rather than trusting a library. Be clear about what the practice data can show: it was generated with no noise, no carryover and almost uncorrelated channels (the true returns are 1.4, 0.8 and 0.6 per $1), so ridge changes the coefficients only slightly, and the notebook’s fixed adstock rates are illustrative rather than discovered. Swap in your own data, where the channels move together and sales are noisy, and the difference is usually much larger.
Why ridge regression isn’t a causal fix
The most common misunderstanding I see is that regularization makes a model “more accurate” in the sense that matters for budget decisions. It makes the estimates more stable and often improves prediction, but it doesn’t address any of the reasons a channel’s coefficient might be causally wrong. If a confounder such as seasonality or promotions is missing, ridge shrinks the biased coefficient a little; it’s still biased. If brand search is in the model as a control, ridge doesn’t know it’s a mediator. The confounding variables guide covers those problems, and they need to be solved before the penalty is worth discussing.
Ridge also has a direction. It shrinks towards zero, which is a statement that, absent strong evidence, channels probably don’t work. For a small channel that’s often a sensible default, but for a big channel with a genuinely large effect it means the model systematically understates it, and the larger the penalty, the more so. In Bayesian terms, ridge is equivalent to putting a normal prior centered at zero on every standardized coefficient, with the same width for all of them. Seen that way, the alternative is obvious: a Bayesian MMM can center each channel’s prior on something informed, such as an experiment result or a benchmark ROI, rather than on zero, and can make the prior tighter for channels you know more about. That’s more work to justify, but it’s an assumption you chose for a reason rather than one built into the method.
None of this is an argument against ridge. With 104 weeks and a dozen correlated channels, OLS estimates are often so noisy that they’re useless, and some form of regularization, whether a ridge penalty or a prior, is unavoidable. The point is to know which assumption is settling the answer, say so when you present the results, and use experiments to calibrate the channels where the assumption matters most. The model validation guide covers how to check the out-of-sample side properly.
Ridge, lasso and elastic net compared
Since the question comes up whenever someone opens the glmnet documentation, here’s how the three penalties differ in practice for MMM:
- Ridge (L2, glmnet
alpha = 0): shrinks all coefficients smoothly, never to exactly zero; spreads credit across correlated channels. Robyn’s choice. - Lasso (L1,
alpha = 1): can set coefficients exactly to zero, which selects variables; among correlated channels it tends to keep one and drop the others somewhat arbitrarily, which is a poor fit for media you know you spent money on. - Elastic net (0 <
alpha< 1): a mix of both; useful for large sets of control variables, less often for media.
For most marketing mix models I’d use ridge or priors on the media channels and be cautious with anything that can zero out a channel the business is actively buying. If you want to see where all of this sits in the bigger picture, the pillar guide to marketing mix modeling is the place to start, and the Vexpower MMM course works through linked channels, priors and calibration on the invented Northstar case.
