Saturation curve — 광고비를 더 부어도 안 늘어나는 지점 찾기
같은 채널에 광고비를 두 배로 써도 매출은 두 배가 안 됩니다. Hill·Logistic·Michaelis-Menten 같은 saturation 곡선이 그 한계를 모델링하는 법, 그리고 절반 포화 지점 L과 한계 ROAS의 관계를 마케터 시선에서 풀어쓴 글.
들어가며 — saturation 곡선이 예산 재배분으로 이어지는 흐름
광고비를 두 배로 쓴다고 매출이 두 배가 되지 않습니다. 이 한계를 수식으로 잡는 게 saturation 함수이고, 그 함수의 기울기(미분)가 한계 ROAS이며, 채널 간 한계 ROAS 비교가 예산 재배분의 1순위 신호입니다. saturation 곡선 → 한계 ROAS → 예산 재배분, 이 세 단계가 MMM 결과를 의사결정으로 번역하는 핵심 흐름입니다.
마케터가 이 글을 읽어야 하는 이유는 하나입니다. 채널 평균 ROAS만 보고 “이 채널이 효율이 좋으니 더 넣자”는 의사결정은 saturation 영역을 모르면 틀릴 수 있습니다. 현재 지출이 절반 포화 지점 의 어느 쪽에 있는지를 알아야 “더 넣을지 줄일지”가 명확해집니다.
이 글은 Hill·Michaelis-Menten·Logistic 세 형태의 수식과 직관, scipy로 Hill 곡선 fit하는 코드, 한계 ROAS 계산 코드, PyMC로 posterior 신용구간을 시각화하는 코드까지 정리합니다. mmm-adstock-deep-dive에서 adstock을 잡고 나면 이 글이 다음 단계입니다.
왜 한계가 있는가 — 직관 먼저
세 가지 메커니즘
광고비를 늘릴수록 효율이 떨어지는 데는 세 가지 독립적인 이유가 있습니다.
타깃 풀의 한계 — 우리 제품을 살 만한 사람의 풀이 한정되어 있고, 광고비를 늘리면 풀 안 깊은 곳으로 들어갑니다. 깊을수록 구매 가능성이 낮은 사람들이라 효율이 떨어집니다.
빈도(frequency) 피로 — 같은 사람에게 같은 광고를 자주 보이면 처음 몇 번까지는 효과가 늘지만, 그 뒤에는 평평해지거나 오히려 떨어집니다. 광고비를 늘리면 새 사람보다 같은 사람의 빈도가 더 빨리 올라가니까 한계가 더 빨리 옵니다.
경매 동학 — 광고비를 늘리려면 더 비싼 슬롯·더 비싼 키워드에 입찰해야 합니다. 같은 노출을 사기 위한 단가가 점점 오릅니다.
세 가지 메커니즘이 합쳐져 saturation 곡선이라는 한 함수로 표현됩니다.
Hill 곡선 — MMM 표준 형태
수식과 파라미터
은 절반 포화 지점, 는 곡선의 가파름입니다. 일 때 , 일 때 입니다.
| 파라미터 | 의미 | 의사결정 함의 |
|---|---|---|
| 절반 포화 지출 | 이 지출 이상부터 한계 효율 감소 본격화 | |
| 곡선 기울기 | 클수록 임계 지점이 뚜렷, 작을수록 완만 |
이 1억 원으로 추정된 채널은 1억 원 부근에서 효율이 절반으로 떨어지기 시작합니다. 현재 5천만 원을 쓰고 있다면 더 부어도 효율이 좋고, 1.5억 원을 쓰고 있다면 줄여서 다른 채널로 옮기는 게 낫습니다.
scipy로 Hill 곡선 fit하기
import numpy as npfrom scipy.optimize import curve_fitimport matplotlib.pyplot as plt
def hill(x, L, k): """Hill saturation 함수""" return x**k / (x**k + L**k)
# 채널별 주간 지출과 관측 기여도 (MMM 회귀 후 채널 기여도 시계열)spend_meta = np.array([20e6, 40e6, 60e6, 80e6, 100e6, 120e6, 150e6])contrib_meta = np.array([0.18, 0.32, 0.43, 0.51, 0.57, 0.61, 0.65])
# 초기값과 bounds 설정p0 = [80e6, 1.5] # L 초기값, k 초기값bounds = ([10e6, 0.5], [500e6, 5.0]) # (L_min, k_min), (L_max, k_max)
popt, pcov = curve_fit(hill, spend_meta, contrib_meta, p0=p0, bounds=bounds, maxfev=5000)L_fit, k_fit = poptL_std, k_std = np.sqrt(np.diag(pcov))
print(f"절반 포화 지점 L = {L_fit/1e6:.1f}M ± {L_std/1e6:.1f}M")print(f"곡선 가파름 k = {k_fit:.2f} ± {k_std:.2f}")
x_plot = np.linspace(5e6, 200e6, 300)plt.plot(x_plot / 1e6, hill(x_plot, L_fit, k_fit), label="Hill fit")plt.scatter(spend_meta / 1e6, contrib_meta, color='red', zorder=5, label="관측값")plt.axvline(L_fit / 1e6, color='gray', ls='--', label=f"L = {L_fit/1e6:.0f}M")plt.xlabel("주간 지출 (백만 원)"); plt.ylabel("기여도 (정규화)")plt.title("Meta 채널 Hill Saturation Fit"); plt.legend(); plt.show()curve_fit은 최소제곱으로 L과 k를 추정합니다. pcov의 대각선이 분산이므로 제곱근이 표준오차입니다. L 추정치의 신뢰구간이 넓으면(stddev > 평균의 50%) 그 채널은 데이터 부족 신호이고, 의사결정의 신뢰도를 낮춰서 보고해야 합니다.
한계 ROAS — saturation 곡선의 미분
수식
한 채널의 saturation 곡선 위에서 현재 지출 위치의 미분(접선 기울기)이 한계 ROAS입니다.
는 채널의 회귀 계수, 미분이 곡선의 기울기입니다.
한계 ROAS 계산 코드
def hill_deriv(x, L, k): """Hill 함수의 도함수 = 한계 saturation 기울기""" return k * L**k * x**(k - 1) / (x**k + L**k)**2
# 채널별 파라미터 (posterior 평균 또는 curve_fit 결과)channels = ['Meta', 'Naver', 'TV']beta = np.array([2.5, 1.8, 3.2]) # 회귀 계수L_arr = np.array([80e6, 40e6, 150e6]) # 절반 포화 지점k_arr = np.array([1.5, 1.2, 2.0]) # 곡선 가파름
# 현재 분기 실제 지출current_spend = np.array([95e6, 35e6, 140e6])
mroas = beta * hill_deriv(current_spend, L_arr, k_arr)avg_roas_proxy = beta * hill(current_spend, L_arr, k_arr) / (current_spend / 1e8)
print(f"{'채널':<8} {'현재지출':>10} {'평균ROAS':>10} {'한계ROAS':>10} {'판단':>12}")for i, ch in enumerate(channels): pos = "L 우측(줄여야)" if current_spend[i] > L_arr[i] else "L 좌측(더 가능)" print(f"{ch:<8} {current_spend[i]/1e6:>8.0f}M {avg_roas_proxy[i]:>10.2f}x " f"{mroas[i]:>10.2f}x {pos:>14}")출력 예시:
채널 현재지출 평균ROAS 한계ROAS 판단Meta 95M 3.45x 1.28x L 우측(줄여야)Naver 35M 2.91x 3.47x L 좌측(더 가능)TV 140M 2.18x 0.91x L 우측(줄여야)Meta와 TV의 한계 ROAS가 낮고 Naver가 높습니다. 이 숫자가 분기 예산 재배분의 1순위 신호입니다. 재배분 알고리즘은 mmm-budget-optimization에서 scipy로 구현합니다.
평균 ROAS와 한계 ROAS의 갭
| 채널 위치 | 평균 ROAS | 한계 ROAS | 의사결정 |
|---|---|---|---|
| 5x | 7x | 더 부으면 효율 더 좋음 | |
| 4x | 4x | 균형점 | |
| 3x | 1.5x | 줄여서 다른 채널로 |
평균만 보면 줄여야 할지 알기 어렵고, 한계를 보면 채널 간 비교가 명확해집니다.
Michaelis-Menten과 Logistic — 나머지 두 형태
Michaelis-Menten
Hill 곡선의 특수형태입니다. 추정이 가장 쉽고 직관도 명료해 MMM 입문에 적합합니다. 다만 곡선이 너무 매끄러워 실제 시장의 sharp threshold를 표현하지 못할 수 있습니다.
Logistic — S자 곡선
처음에는 효율이 늘다가 어느 지점( 근처)부터 떨어지는 S자 곡선입니다. 신규 채널 진입 초기에 자주 보이는 패턴입니다. 처음 몇 주는 학습이 필요해 한계 ROAS가 낮고, 어느 정도 데이터가 쌓이면 ROAS가 가장 높아진 뒤 saturation 영역으로 들어갑니다.
PyMC로 posterior 신용구간 시각화
L·k posterior 분포와 90% CI
L과 k를 점추정(curve_fit)으로 잡으면 추정의 불확실성이 사라집니다. PyMC로 베이지안 추정하면 L과 k가 분포로 나오고, 그 분포를 saturation 곡선에 투영하면 90% 신용구간이 생깁니다.
import pymc as pmimport arviz as az
# spend_meta, contrib_meta: 위와 동일
with pm.Model() as saturation_model: # L prior: 현재 지출 평균의 1~5배 사이 L = pm.HalfNormal("L", sigma=150e6) # k prior: 곡선 가파름 (0.5~4 사이) k = pm.Gamma("k", alpha=2, beta=1) # 평균=2, 분산=2
# Hill saturation mu = (spend_meta**k) / (spend_meta**k + L**k)
# 관측 오차 sigma_obs = pm.HalfNormal("sigma_obs", sigma=0.05) obs = pm.Normal("obs", mu=mu, sigma=sigma_obs, observed=contrib_meta)
idata_sat = pm.sample(1000, tune=500, target_accept=0.9, random_seed=42, return_inferencedata=True)
# 수렴 진단summary = az.summary(idata_sat, var_names=["L", "k"])print(summary[["mean", "sd", "hdi_3%", "hdi_97%", "r_hat"]])
# posterior 곡선 시각화x_plot = np.linspace(5e6, 200e6, 300)posterior_L = idata_sat.posterior["L"].values.flatten()posterior_k = idata_sat.posterior["k"].values.flatten()
curves = np.array([ hill(x_plot, l, kk) for l, kk in zip(posterior_L[:500], posterior_k[:500])])
plt.figure(figsize=(9, 5))plt.fill_between(x_plot / 1e6, np.percentile(curves, 5, axis=0), np.percentile(curves, 95, axis=0), alpha=0.25, color='steelblue', label='90% 신용구간')plt.plot(x_plot / 1e6, np.median(curves, axis=0), color='steelblue', lw=2, label='posterior 중앙값')plt.scatter(spend_meta / 1e6, contrib_meta, color='red', zorder=5, label='관측값')plt.xlabel("주간 지출 (백만 원)"); plt.ylabel("기여도 (정규화)")plt.title("Hill Saturation — PyMC posterior 90% 신용구간")plt.legend(); plt.tight_layout(); plt.show()# → 신용구간이 넓은 지출 구간 = 데이터 부족, 의사결정 신중히출력 해석: r_hat이 1.01 이하여야 수렴 성공입니다. hdi_3%~hdi_97%가 L의 94% 신용구간(HDI)이고, 이 구간이 현재 지출을 감싸고 있으면 “지금 L 근처에 있다”고 보고할 수 있습니다. prior 민감도 점검은 mmm-prior-elicitation을 참고하세요.
실무 — saturation 결과를 의사결정으로
분기 회의 슬라이드 한 장
각 채널의 saturation 곡선과 현재 지출 위치, 한계 ROAS를 한 plot에 그려 회의에 띄웁니다. 마케팅팀이 보면 자동으로 다음 질문이 나옵니다:
- 어느 채널이 좌측에 있는가? (더 부어도 효율 좋음)
- 어느 채널이 우측에 있는가? (줄여서 옮길 후보)
- 곡선의 90% 신뢰구간이 가장 넓은 채널은? (데이터 부족, 추가 검증 필요)
채널별 saturation 파라미터 사례
아래는 성숙 이커머스 브랜드의 전형적 추정치 범위입니다(실제 fit 결과는 회사·시장마다 다름).
| 채널 | 범위 | 범위 | 현재 위치 | 판단 |
|---|---|---|---|---|
| Meta (퍼포먼스) | 60M~120M | 1.2~1.8 | L 우측 | 줄이고 다른 채널로 |
| Naver 검색 | 30M~60M | 1.0~1.5 | L 좌측 | 더 부을 여지 |
| TV 브랜드 | 100M~250M | 1.5~2.5 | L 우측 | 유지 (브랜드 목적) |
| YouTube | 40M~90M | 1.0~2.0 | L 근처 | 소폭 증액 후 재관측 |
이 숫자를 분기 회의 슬라이드에 표로 올리고, 채널별 90% CI와 함께 제시하면 “왜 줄여야 하나” 질문에 데이터로 답할 수 있습니다.
함정 모음
- 외삽 — 학습 구간 밖에서 곡선을 그대로 외삽하면 위험. 시뮬레이션 범위 제한 필수
- 채널 간 spillover — TV가 search lift를 만들면 검색 곡선이 평탄해 보임. spillover 변수 추가
- cold start — 신규 채널 첫 8~12주는 곡선 추정이 안 됨
- 외부 충격 미반영 — 큰 캠페인·경쟁사 진입을 외생 변수로 모델에 안 넣으면 곡선이 휘어짐
- adstock 오설정 영향 — adstock을 잘못 잡으면 saturation 파라미터도 함께 틀어집니다. mmm-adstock-deep-dive에서 adstock 먼저 검증하세요.
마치며
saturation 곡선은 MMM 결과를 의사결정으로 번역하는 가장 직접적인 통로입니다. Hill 함수를 scipy로 fit해 L·k를 추정하고, 도함수로 한계 ROAS를 계산하고, PyMC posterior로 신용구간을 표현하는 세 단계가 있어야 “왜 이 채널 줄여야 하나” 질문에 데이터로 답할 수 있습니다.
saturation 곡선이 준비되면 한계 ROAS 균등화 최적화로 자연스럽게 넘어갑니다.
다음에 읽을 글- MMM 예산 재배분 — saturation 곡선 → 한계 ROAS → 배분 최적화
- MMM prior 설정 — L·k prior 잡는 방법
- MMM adstock 심화 — saturation 전에 adstock을 먼저 잡는 이유
참고
- Jin et al., “Bayesian Methods for Media Mix Modeling”: https://research.google/pubs/pub46001/
- Robyn, “Saturation transformations”: https://facebookexperimental.github.io/Robyn/
- PyMC-Marketing, “Saturation functions”: https://www.pymc-marketing.io/
- Tellis, “Effective frequency”: https://psycnet.apa.org/record/2009-04609-001
- Recast, “Diminishing returns in marketing”: https://getrecast.com/diminishing-returns/
퍼포먼스 마케팅 카테고리의 다른 글
전체 보기 →-
2026·06·05
ROAS 보고서가 늘 거짓말하는 이유 — incrementality 3대장
Meta 대시보드 ROAS 5가 실제로는 1.x인 이유. last-click·view-through·incremental 세 가지 ROAS의 차이와, holdout·geo-lift·ghost ads·conversion lift로 진짜 증분을 측정하는 법을 마케터 시선으로 정리합니다.
-
2026·05·16
DSP·SSP·DMP 인프라 해부 — 매체 영업 미팅에서 듣는 약자들의 정체
매체 영업 미팅에서 DSP, SSP, DMP, CDP, ad exchange, 헤더비딩 같은 약자들이 쏟아집니다. 각각이 어느 회사이고, 광고비가 어디로 흘러가며, 마케터가 의사결정할 때 어떤 의미를 갖는지 한 글에 정리합니다.
-
2026·05·16
Lookback window가 ROAS를 바꾸는 법 — click 7d, view 1d, 28d, 90d의 차이
같은 캠페인이라도 attribution lookback window를 click 7d / view 1d / 28d / 90d 중 어느 걸로 보느냐에 따라 ROAS가 두 배까지 차이납니다. 매체별 default와 그것을 마케터가 어떻게 의사결정에 써야 하는지를 정리합니다.
-
2026·05·09
Brand lift study 설계 — 광고가 인지·호감도를 끌어올렸나
브랜드 광고는 ROAS로 잡히지 않고 인지·호감도·구매의향으로만 측정됩니다. 노출 그룹과 비노출 그룹을 비교하는 brand lift study의 설계, 표본 계산, 실무 함정을 마케터 시선에서 정리.