
The S-Curve is the core concept that explains the non-linear relationship between advertising spend and performance outcomes (such as revenue or conversions). Unlike the simplistic assumption that “doubling ad spend will double sales,” the S-Curve reflects the Law of Diminishing Returns commonly observed in marketing. In other words, once advertising spend passes a certain point, efficiency gradually declines, and each additional dollar yields less impact.
In Marketing Mix Modeling (MMM), S-Curves are typically modeled using mathematical functions such as the logistic function or the Hill function, which allow analysts to statistically estimate the curve and calculate channel-specific parameters that define its shape.
Key Phases of the S-Curve
1. Ramp-up Phase (Gradual Growth)
- Position on curve: Lower section of the S-shape.
- Characteristics: This is the early stage of ad spending, where brand or message exposure is still limited. As a result, performance does not increase proportionally with spend, since consumers need repeated exposure and time before reacting.
- ROI: Relatively low. Spend may appear inefficient in this phase.
- Implication: There is a threshold effect—too little budget produces no meaningful impact and risks being wasted.
2. Sweet Spot / Steep Growth Phase
- Position on curve: Middle, steepest section of the S-shape.
- Characteristics: The “optimal efficiency zone,” where ad effects accelerate rapidly. Once exposure surpasses the threshold, even modest increases in spend drive disproportionately large performance gains. Brand awareness surges and conversions grow quickly.
- ROI: Highest in this phase. This is the ideal target range for budget allocation.
- Implication: A key objective in MMM is to identify this “sweet spot” for each channel and concentrate spend there.
3. Saturation / Plateau Phase
- Position on curve: Upper, flattening section of the S-shape.
- Characteristics: At high levels of spend, the market becomes saturated. Most target customers have already been reached, or ad fatigue sets in. Additional spend produces minimal incremental impact.
- ROI: Declines sharply. Further investment is inefficient and risks waste.
- Implication: When a channel reaches this phase, budgets should be cut back and reallocated to other channels that are still in the growth phase.
S-Curve Code Implementation Example
- A is the maximum possible sales (plateau).
- B controls the growth rate.
- C is the inflection point (where sales increase quickly).
import numpy as npimport pandas as pdimport matplotlib.pyplot as pltfrom scipy.optimize import curve_fit
# create datasetdata = { 'Day' : ['Mon', 'Tue', 'Wed', 'Thu', 'Fri'], 'Spend' : [10, 20, 30, 40, 50], 'Sales' : [200, 250, 400, 450, 460]}df = pd.DataFrame(data)
# define s-curve functionx_data = df['Spend']y_data = df['Sales']def s_curve(x, A, B, C): return A / (1 + np.exp(-B * (x - C)))
optimal_params, covariance = curve_fit( s_curve, x_data, y_data, p0=[max(y_data), 0.1, np.median(x_data)] # initial point (A, B, C))
A, B, C = optimal_paramsprint(f"Optimal parameters: A={A}, B={B}, C={C}")# Optimal parameters: A=501.0309931841163, B=0.0844223122026954, C=16.67591505186686
# plot resultsx_fit = np.linspace(min(x_data), max(x_data), 100)y_fit = s_curve(x_fit, *optimal_params)plt.scatter(x_data, y_data, label='Data')plt.plot(x_fit, y_fit, color='red', label='Fitted S-Curve')plt.xlabel('Spend')plt.ylabel('Sales')plt.legend()
Generalized S-Curve (Richards’ Curve)
- : Plateau; maximum sales
- : Growth rate
- : Inflection point
- : Asymmetry parameter
if , same as basic logistic function.
if , slow early growth, rapid later growth.
if , rapid early growth, slow later growth.
code implementation example:
import numpy as npimport pandas as pdimport matplotlib.pyplot as pltfrom scipy.optimize import curve_fit
# create datasetdata = { 'Day' : ['Mon', 'Tue', 'Wed', 'Thu', 'Fri'], 'Spend' : [10, 20, 30, 40, 50], 'Sales' : [200, 250, 400, 450, 460]}df = pd.DataFrame(data)
# define s-curve functionx_data = df['Spend']y_data = df['Sales']def s_curve(x, A, B, C, nu=1): return A * (1 + nu*np.exp(-B * (x - C)))**(-1/nu)
optimal_params, covariance = curve_fit( s_curve, x_data, y_data, p0=[max(y_data), 0.1, np.median(x_data), 0.9] # initial point (A, B, C, nu))
A, B, C, nu = optimal_paramsprint(f"Optimal parameters: A={A}, B={B}, C={C}, nu={nu}")# Optimal parameters: A=455.0000003735234, B=4.078058186448524, C=32.74072510361706, nu=108.54222404122513
# plot resultsx_fit = np.linspace(min(x_data), max(x_data), 100)y_fit = s_curve(x_fit, *optimal_params)plt.scatter(x_data, y_data, label='Data')plt.plot(x_fit, y_fit, color='red', label='Fitted S-Curve')plt.xlabel('Spend')plt.ylabel('Sales')plt.legend()