← Back to Insights
Lester Leong

Lester Leong

·9 min read

Cohort Analysis for Product Teams: A Practical Guide With Python

Blended Averages Are Lying to You

Every product metric you report as a single number is hiding something. Average revenue per user, conversion rate, time to activation, feature adoption: the blended versions of these numbers describe a fictional user who does not exist. Cohort analysis is how you stop lying to yourself and start seeing what is actually happening inside your product.

I have built cohort analysis systems in three very different environments. As a consultant across 20+ SMBs and startups, where the data was messy but the insights were immediate. At a financial social media startup before its acquisition, where cohort-level revenue analysis became the backbone of the diligence narrative. And now on a GenAI squad at a major finance technology company, where behavioral cohorts drive feature prioritization for millions of users.

The technique is the same everywhere. The types of cohorts you build, and the questions you ask of them, change depending on what problem you are solving.

What Cohort Analysis Actually Is

Cohort analysis groups users by a shared characteristic (usually when they signed up) and tracks their behavior over time as separate groups rather than a single aggregate. That is the entire concept.

The power comes from what this simple grouping reveals. When you look at revenue as a single number, a 5% month-over-month increase looks like growth. When you break it into cohorts, you might discover that your January cohort is spending 30% less per month than your October cohort, and the "growth" is entirely driven by a larger March cohort masking the decay. Without cohorts, you celebrate. With cohorts, you investigate.

There are three types of cohort analysis that matter for product teams. Most teams only build the first one and miss the other two entirely.

Type 1: Retention Cohorts

This is the standard version. Group users by signup date, track what percentage come back over time. I covered this in depth in the [retention curve analysis guide](/insights/retention-curve-analysis-guide), including the Python implementation and how to read the flattening point.

Retention cohorts answer: "Are we keeping users?" That is necessary but not sufficient. A user who logs in once a month but never pays you anything is "retained" in a way that does not matter to the business. Which brings us to the cohort type that most teams neglect.

Type 2: Revenue Cohorts

Revenue cohort analysis groups users by signup period and tracks their cumulative (or per-period) spending over time. This answers a fundamentally different question than retention: "Are our customers becoming more valuable, or less?"

The distinction matters because revenue behavior and retention behavior often diverge. At the startup I worked at before its acquisition, we had a cohort from Q2 that retained at nearly the same rate as Q1. But when we ran revenue cohorts, Q2 users were spending 22% less per month by their sixth month. Same retention, worse monetization. The blended ARPU hid this completely because the user count was growing fast enough to offset the per-user revenue decline.

Revenue cohorts surface three signals that no other analysis will give you:

Expansion vs. contraction by vintage. Are newer cohorts spending more over time (expansion) or less (contraction)? If your Q1 cohort averages $45/month at month 3 and $62/month at month 9, that is healthy expansion. If the trajectory reverses for Q3 cohorts, something changed: pricing, onboarding, product, or customer mix.

Cohort payback period. How many months does it take for each cohort to generate enough cumulative revenue to cover its acquisition cost? If your January cohort pays back in 4 months but your April cohort takes 7 months, your unit economics are deteriorating even if top-line revenue looks fine. This feeds directly into [LTV calculations](/insights/customer-ltv-calculation-startups).

Revenue concentration risk. If 60% of your revenue comes from cohorts that are 12+ months old and recent cohorts are trending lower, your growth is dependent on a shrinking base of early adopters. That is a structural problem, not a marketing problem.

Type 3: Behavioral Cohorts

Behavioral cohorts group users not by when they signed up, but by what they did. This is where the analysis gets genuinely useful for product decisions.

Instead of asking "how do January users behave differently from March users," you ask "how do users who completed onboarding in their first session behave differently from users who took three days?" The grouping variable is an action, not a date.

The behavioral cohorts I have found most useful across consulting engagements and my own roles:

- Activation cohorts. Users who hit a key activation milestone within N days versus those who did not. At one B2B SaaS client, users who connected a data source within 48 hours had 3.1x the 6-month revenue of users who waited longer than a week. That finding justified a complete onboarding redesign. - Feature adoption cohorts. Users who adopted a specific feature versus those who did not. On the GenAI squad where I work now, users who engaged with a particular feature cluster within their first two weeks showed meaningfully higher long-term engagement. That signal prioritized the feature in the onboarding flow. - Engagement intensity cohorts. Users grouped by sessions per week (light: 1-2, moderate: 3-5, heavy: 6+). The revenue differences between these groups are almost always larger than the differences between time-based cohorts. At the startup before its acquisition, heavy users generated 4.7x the revenue of light users. The product roadmap shifted to focus on converting moderate users to heavy, because that transition had the highest revenue leverage.

Behavioral cohorts are harder to build because they require defining the grouping criteria upfront. But they answer the questions product teams actually care about: which actions predict long-term value, and what should we optimize the experience around?

Python: Building Revenue and Behavioral Cohort Analysis

This implementation handles both revenue cohorts (grouped by signup month) and behavioral cohorts (grouped by a user action). It is designed to work with raw event data and a separate revenue table.

```python import pandas as pd import numpy as np

def build_revenue_cohorts( users: pd.DataFrame, revenue: pd.DataFrame, user_col: str = "user_id", signup_date_col: str = "signup_date", revenue_date_col: str = "payment_date", amount_col: str = "amount", ) -> pd.DataFrame: """ Build a revenue cohort table from user signups and payment data.

Args: users: DataFrame with user_id and signup_date. revenue: DataFrame with user_id, payment_date, and amount.

Returns: DataFrame where rows are signup cohorts (months) and columns are months since signup, with values as avg revenue per user. """ users = users.copy() revenue = revenue.copy()

users[signup_date_col] = pd.to_datetime(users[signup_date_col]) revenue[revenue_date_col] = pd.to_datetime(revenue[revenue_date_col])

# Assign users to signup cohort users["cohort"] = users[signup_date_col].dt.to_period("M") cohort_sizes = users.groupby("cohort")[user_col].nunique()

# Merge cohort onto revenue merged = revenue.merge( users[[user_col, "cohort"]], on=user_col, how="inner" ) merged["revenue_period"] = merged[revenue_date_col].dt.to_period("M") merged["period_offset"] = ( merged["revenue_period"].astype(int) - merged["cohort"].astype(int) )

# Total revenue per cohort per period cohort_revenue = ( merged.groupby(["cohort", "period_offset"])[amount_col] .sum() .reset_index() )

# Pivot to matrix matrix = cohort_revenue.pivot( index="cohort", columns="period_offset", values=amount_col )

# Normalize by cohort size to get avg revenue per user for cohort in matrix.index: if cohort in cohort_sizes.index: matrix.loc[cohort] = matrix.loc[cohort] / cohort_sizes[cohort]

return matrix.round(2)

def build_behavioral_cohorts( events: pd.DataFrame, users: pd.DataFrame, behavior_col: str = "activated_first_session", metric_col: str = "amount", user_col: str = "user_id", date_col: str = "event_date", signup_date_col: str = "signup_date", agg: str = "sum", ) -> pd.DataFrame: """ Build behavioral cohort analysis: group users by a boolean behavior flag and compare their metric trajectory over time.

Args: events: DataFrame with user_id, event_date, and a metric column. users: DataFrame with user_id, signup_date, and behavior_col (bool). behavior_col: Column on users table indicating the behavior group. metric_col: Column on events table to aggregate. agg: Aggregation method ("sum", "mean", "count").

Returns: DataFrame with multi-index (behavior_group, period_offset) and the aggregated metric value per user. """ users = users.copy() events = events.copy()

users[signup_date_col] = pd.to_datetime(users[signup_date_col]) events[date_col] = pd.to_datetime(events[date_col])

users["cohort"] = users[signup_date_col].dt.to_period("M") users["behavior_group"] = users[behavior_col].map( {True: "yes", False: "no"} )

merged = events.merge( users[[user_col, "cohort", "behavior_group"]], on=user_col ) merged["event_period"] = merged[date_col].dt.to_period("M") merged["period_offset"] = ( merged["event_period"].astype(int) - merged["cohort"].astype(int) )

# Aggregate metric per behavior group per period grouped = ( merged.groupby(["behavior_group", "period_offset"]) .agg( metric_total=(metric_col, agg), user_count=(user_col, "nunique"), ) .reset_index() ) grouped["metric_per_user"] = ( grouped["metric_total"] / grouped["user_count"] )

# Pivot for easy comparison result = grouped.pivot( index="behavior_group", columns="period_offset", values="metric_per_user", )

return result.round(2)

# Example: revenue cohort analysis np.random.seed(42) n_users = 2000

users_df = pd.DataFrame({ "user_id": range(1, n_users + 1), "signup_date": pd.date_range("2025-01-01", periods=n_users, freq="4h"), "activated_first_session": np.random.choice( [True, False], n_users, p=[0.35, 0.65] ), })

# Simulate payment events payments = [] for _, user in users_df.iterrows(): n_payments = np.random.poisson(6) for m in range(n_payments): payments.append({ "user_id": user["user_id"], "payment_date": user["signup_date"] + pd.DateOffset(months=m), "amount": round(np.random.lognormal(3.5, 0.4), 2), })

revenue_df = pd.DataFrame(payments)

# Revenue cohorts rev_cohorts = build_revenue_cohorts(users_df, revenue_df) print("=== Revenue per User by Cohort ===") print(rev_cohorts.head())

# Behavioral cohorts: activated vs not, revenue trajectory behavioral = build_behavioral_cohorts( events=revenue_df.rename(columns={"payment_date": "event_date"}), users=users_df, behavior_col="activated_first_session", metric_col="amount", date_col="event_date", ) print("\n=== Revenue per User: Activated vs Not ===") print(behavioral) ```

The revenue cohort table is a heatmap waiting to happen. Each row is a signup month, each column is months since signup, and each cell is average revenue per user. When you plot it, degradation across cohorts is immediately visible. The behavioral cohort output puts the two groups side by side so you can quantify the revenue impact of a specific user action.

How to Read a Cohort Table

Three patterns to look for, in order of importance:

Diagonal consistency. In a revenue cohort table, the diagonal (each cohort at the same age) should be roughly stable or improving. If the diagonal trends downward, newer cohorts are generating less revenue at the same maturity point. That is the clearest signal of product or market deterioration.

Row-level expansion. Within a single row, values should be flat or increasing as you move right (later periods). Flat means customers pay a consistent amount. Increasing means expansion revenue: upsells, upgrades, increased usage. If values decline within a row, customers are downgrading or reducing usage over time, which is a churn precursor that shows up in revenue before it shows up in retention.

Column-level improvement. Looking down a single column (e.g., "month 3 revenue"), values should improve over time if your product and onboarding are getting better. If month-3 revenue is declining for more recent cohorts, your product changes are not translating into better monetization.

Mistakes That Waste the Analysis

Building cohorts but never acting on them. The most common failure mode. The team builds a beautiful cohort heatmap, presents it at the monthly review, and then makes the same decisions they would have made without it. Cohort analysis is a diagnostic tool, not a reporting tool. Every cohort table should produce at least one specific question that gets investigated.

Cohort windows that are too narrow. Weekly cohorts with 50 users each produce noise, not signal. If your monthly signups are below 500, use monthly cohorts. Below 200, use quarterly. The statistical reliability of the cohort matters more than the granularity.

Ignoring the behavioral dimension. Time-based cohorts tell you whether things are getting better or worse. Behavioral cohorts tell you why. Most teams build the first and skip the second. If your January cohort has higher revenue than your March cohort, the time-based view tells you there is a problem but not what to fix. The behavioral view (activated vs. not, feature X adopted vs. not) tells you exactly which levers to pull.

Confusing correlation with causation in behavioral cohorts. Users who activate quickly might generate more revenue because activation causes engagement. Or they might be higher-intent users who would have generated more revenue regardless. Behavioral cohorts identify the correlation. Proving causation requires an experiment. Do not skip the experiment.

When to Use Which Type

Revenue cohorts when you need to answer: Are our unit economics improving? Is expansion revenue working? Which vintage of customers is most profitable?

Behavioral cohorts when you need to answer: Which onboarding actions predict long-term value? Should we gate a feature or make it prominent? What separates power users from casual ones?

Retention cohorts (covered in [the retention guide](/insights/retention-curve-analysis-guide)) when you need to answer: Is the product getting stickier? Where is the flattening point? Which acquisition channels produce durable users?

In practice, the three types reinforce each other. A behavioral cohort tells you that users who complete action X generate 3x the revenue. A revenue cohort confirms whether that pattern holds across signup vintages. A retention cohort tells you whether those high-revenue users also stick around longer or simply spend more before churning. The complete picture requires all three.

The Compound Effect of Weekly Cohort Reviews

The teams I have worked with that get the most value from cohort analysis are not the ones with the most sophisticated tooling. They are the ones that review cohort tables every week and treat degradation as a trigger for investigation, not a data point to note.

At the startup before its acquisition, the weekly cohort review became the single most important meeting on the calendar. Every product decision, every pricing change, every marketing reallocation was visible in the cohort data within 4 to 8 weeks. The discipline of looking at cohorts weekly, rather than quarterly, compressed the feedback loop from "we might have a problem" to "we know exactly where the problem is and we are already running an experiment to fix it."

That feedback loop is the real value of cohort analysis. Not the chart. Not the Python code. The organizational habit of refusing to accept blended averages as the final word.

---

I help teams build cohort analysis systems that surface the signals hiding in blended averages. [lester@gradientgrowth.com](mailto:lester@gradientgrowth.com)

Want frameworks like this for your company?

I work with 3 to 4 AI-era companies at a time, building the analytics systems that turn data into decisions. If that sounds like what you need, let’s talk.

Get Your Free Diagnosis

Keep Reading