If you spend $100 on Google Search, you might get 10 clicks. If you spend $10,000, you will not get 1,000 clicks. You might get 600.
Why? because there are only so many people searching for "Best Running Shoes" in a given week. Eventually, you exhaust the audience. The next dollar you spend becomes less efficient than the first dollar.
If we use a standard Linear Regression (y = mx + c), the model assumes a straight line that goes up forever. This is dangerous. It tells the CFO: "If we spend $1 Billion, we will make $5 Billion." We need to bend that line.
While there are many saturation curves (Logistic, Power, Negative Exponential), the industry standard for modern MMM (used by Facebook's Robyn and Google's LightweightMMM) is the Hill Function. It is flexible enough to model S-curves and C-curves.
This looks intimidating, but it only has two levers you need to understand:
Controls the shape of the curve.
S > 1: It's an "S-Curve". Spend doesn't work at first, then accelerates, then flattens.
S < 1: It's a "C-Curve". Returns diminish immediately (typical for Search).
This is the inflection point. K is the amount of spend where you achieve exactly 50% of your maximum possible return.
We do not need to invent this math. We can define a simple Python function to transform our Adstocked data into Saturated data.
import numpy as np def hill_saturation(x_adstocked, alpha, gamma): """ x_adstocked: The media data AFTER adstock transformation alpha: The Slope parameter (S) gamma: The Half-Saturation parameter (K) """ # 1. Calculate the inflection point (K) based on gamma range # Gamma is usually a % of the max spend range (e.g., 0.3 to 0.8) inflection = np.max(x_adstocked) * gamma # 2. Apply the Hill Equation return (x_adstocked**alpha) / (x_adstocked**alpha + inflection**alpha)
When you run this transformation, your linear spend data (which goes from 0 to infinity) is squashed into a range between 0 and 1.
- 0 means $0 Spend.
- 1 means "Total Saturation" (spending more yields 0 extra results).
It is critical to remember the Order of Operations in MMM. You cannot saturate first, then adstock. It must represent the physical reality:
# The Full Pipeline for one channel step_1_adstock = geometric_adstock(df['fb_spend'], alpha=0.5) step_2_final_feature = hill_saturation(step_1_adstock, alpha=1.2, gamma=0.6)
Just like Adstock, we do not guess the Alpha and Gamma values. We will let the machine learn them during the Modeling phase.