34 — Data Analytics

Funnel & Conversion Analysis#

AnalyticsStatistical 1 min read
◆ The PatternWhere do users leak out? Stage conversions, drop-offs, and fixing the right bottleneck

A funnel is an ordered sequence of steps — visit → view product → add to cart → checkout → purchase. Because overall conversion is the product of stage conversions, small stage improvements compound, and one bad stage caps everything downstream. The job of funnel analysis is finding the stage where fixing the leak buys the most.

overall = ∏ stageᵢ     e.g. 0.40 × 0.30 × 0.60 × 0.75 = 5.4%
Overall conversion = product of per-stage rates. A 10% relative lift at any stage lifts the whole funnel 10%.
// Interactive funnel — tune two stages, watch the overall conversion and the biggest leak
View → Cart30%
Checkout → Purchase60%
Overall—
MetricDefinitionWatch for
Stage conversionusers(stage) / users(previous)The bottleneck — lowest rate first
Overall conversionusers(last) / users(first)Topline; hides where the leak is
Drop-off1 − stage conversionMultiply by traffic to rank by impact
Time-to-convertMedian time between stagesSlow stages often precede abandonment
Fix by impact, not by rate: the lowest-converting stage isn't automatically the best fix — weight each leak by the users flowing into it and by how movable it plausibly is. A 2-point lift on a high-traffic early stage often beats 10 points at the bottom.
Pattern bridge: Found the bottleneck? Verify the fix with Bayesian A/B testing, and size the experiment with power analysis — conversion deltas of a few percent need surprisingly many users.
When to use this
✓ Use when: Any multi-step flow — signup, onboarding, checkout, sales pipelines, even ML pipeline stage attrition (candidates → labeled → trained → deployed).
✗ Skip when: Steps aren't genuinely ordered, or users routinely loop and skip — session-path analysis fits exploratory browsing better than a strict funnel.
Try it on real data
Kaggle: eCommerce Events — Cosmetics Shop (20M view/cart/purchase events)
Real view → cart → purchase events with timestamps — build the funnel, then measure time-to-convert between stages.
Quick start — copy to notebook
pip install pandas numpy
# ────────────────────────────────────────
import numpy as np
import pandas as pd

rng = np.random.default_rng(11)
stages = ['visit', 'view_product', 'add_to_cart', 'checkout', 'purchase']
rates  = [1.0, .55, .30, .70, .60]

events = []
for uid in range(10_000):
    for stage, rate in zip(stages, rates):
        if rng.random() > rate:
            break
        events.append((uid, stage))

ev = pd.DataFrame(events, columns=['user_id', 'event'])
funnel = ev.groupby('event')['user_id'].nunique().reindex(stages)
print((funnel / funnel.iloc[0]).round(3))
print("fix first:", (funnel / funnel.shift(1)).idxmin())
Worked example

A funnel converting 40%, 50% and 20% at its three steps: 4.0% end to end. Ten more points on the weakest step (20 → 30%) lift it to 6.0%; ten more points on the 50% step only to 4.8%. Steps multiply, so the same absolute gain is worth most where the rate is lowest — it is the biggest relative change.

Where it misleads
  • Steps are not independent: pushing more people through one step often lowers the quality of those reaching the next.
  • Funnels built from sessions double-count users who return; decide whether you are counting people or visits.
  • A funnel shows where people leave, not why; pair it with experiments.
In code
stages = np.array([0.40, 0.50, 0.20])               # visit -> signup -> trial -> paid
overall = stages.prod()
fix_small = np.array([0.40, 0.50, 0.30]).prod()      # +10 points on the weakest step
fix_large = np.array([0.40, 0.60, 0.20]).prod()      # +10 points on a stronger one
Python, in your browser — edit the code and run it again

Assumes import numpy as np and import pandas as pd (and SciPy where imported). Each snippet simulates or makes up its own data, as the comments say.

The same idea with a library
# Python — funnel from an event log
import pandas as pd

stages = ['visit', 'view_product', 'add_to_cart', 'checkout', 'purchase']

# events: user_id, event — count users reaching each stage
reached = {s: events.loc[events.event == s, 'user_id'].nunique()
           for s in stages}

funnel = pd.DataFrame({'users': reached.values()}, index=stages)
funnel['stage_conv'] = funnel['users'] / funnel['users'].shift(1)
funnel['overall'] = funnel['users'] / funnel['users'].iloc[0]
print(funnel.round(3))

# Biggest leak = stage with lowest stage_conv
print("Fix first:", funnel['stage_conv'].idxmin())
Sources
  • Trustworthy Online Controlled Experiments, R. Kohavi, D. Tang & Y. Xu, Cambridge University Press, 2020
  • Lean Analytics, A. Croll & B. Yoskovitz, O’Reilly, 2013

Last reviewed October 2026 · How this site checks its content · Report a mistake

Linked from 1
← Previous
Cohort & Retention Analysis
Open in the full reader, with the topic sidebar