Cohort Analysis
When to use this skill
Use when the user wants to understand how behavior changes over the lifecycle of users, segmented by when they entered. Triggers:
- "Show retention by signup cohort…"
- "Build a cohort table…"
- "N-day retention for…"
- "Compare cohorts by acquisition channel"
- "Is retention improving?"
If the user wants step-by-step conversion within a single session/flow, use funnel-analysis instead.
Required inputs
| Input |
Why it matters |
| Cohort anchor event |
Defines when a user enters the cohort (signup, first purchase, install) |
| Cohort grain |
Day, week, or month |
| Retention event |
What action defines "retained" (any session, key feature use, purchase) |
| Time window per period |
Daily / weekly / monthly columns |
| Lookback window |
How many periods to display |
Workflow
Confirm the anchor event. A user belongs to exactly one cohort. If they could be re-anchored (e.g., resubscribe), pre-specify the rule (first only, latest, both as separate cohorts).
Pick the cohort grain.
- Daily: short time-scale features, fast-moving products
- Weekly: most product analytics defaults — smooths weekday/weekend variance
- Monthly: long sales/subscription cycles
Define "retained" precisely. Most common definitions:
- Any-activity: at least one logged event in period
- Key-action: specific event (e.g., completed_order)
- N-day: active on day N exactly (rolling vs absolute matters)
- N-day-bracket: active anywhere in days [N, N+window)
Pick one. Document it in the readout.
Pick the denominator policy. This is the most common source of misleading cohort charts:
- Cohort-fixed: divide by original cohort size every period. Curves only go down.
- Active-base: divide by users active in previous period. Shows period-over-period stickiness, but can hide leaky retention.
- Eligible-base: divide by users who had the opportunity to retain (e.g., still on the platform). Use when accounts can churn permanently.
Default to cohort-fixed. Only switch if there's a specific question about period-over-period stickiness.
Generate cohort matrix using scripts/cohort_table.sql (Snowflake) or scripts/cohort_table.py (pandas).
Compute key summary stats:
- D1, D7, D30 retention by cohort
- "Smile curve" check (does retention stabilize after some N?)
- Cohort-over-cohort delta (is the latest cohort retaining better than 4 cohorts ago?)
Write the readout with the matrix, summary stats, and interpretation.
Output format
# Cohort Analysis: <product/segment>
## Definitions
- Cohort anchor: <event> on <grain>
- Retention event: <event>
- Denominator: cohort-fixed (original cohort size)
- Lookback: <N> periods
## Summary
- Latest cohort D7 retention: <X%> (vs <Y%> 4 weeks ago, <delta>pp)
- D30 retention asymptote: <Z%>
- Retention "stabilizes" around day <N>
## Cohort table
| Cohort | Size | D1 | D7 | D14 | D30 | D60 | D90 |
|---|---|---|---|---|---|---|---|
| 2026-01 | 12,400 | 48.2% | 22.1% | 16.4% | 12.0% | 9.8% | 8.5% |
| 2026-02 | 14,100 | 51.0% | 24.8% | 18.2% | 13.5% | 10.4% | — |
| ... | ... | ... | ... | ... | ... | ... | ... |
## Interpretation
- <Are recent cohorts improving, flat, or declining?>
- <What's the asymptote? Is the long-term retained base stable?>
- <Any anomalies — single bad cohort, missing data?>
## Caveats
- Recent cohorts have incomplete data through the latest periods (mark provisional)
- Cohort assignment uses <rule>; users who re-anchored are <treated as>
- <late-arriving events caveat if applicable>
## Next steps
- <e.g., investigate the drop in 2026-03 cohort>
- <e.g., segment by acquisition channel to find a driver>
Validation checks
Edge cases & failure modes
- Mid-week cohort start: if your cohort grain is "week" but anchor events occur all days, define week as Mon-Sun and align. Don't let cohort #1 be 3 days long.
- Re-engaging users: if a user goes dormant and comes back, are they in their original cohort or a new "reactivation" cohort? Pre-specify.
- Sparse recent cohorts: the last 1-2 periods of the matrix have <100% observation. Mark cells as "—" (no data) rather than 0% (no retention).
- Cohort size differences: if cohort sizes vary 10x, displaying raw percentages can mislead. Add absolute counts or use rolling averages.
- Survivorship in subscription products: if users can't churn within a billing cycle, "retention" before cycle end is meaningless. Use eligible-base denominator.
Scripts
scripts/cohort_table.sql — Snowflake template producing a cohort matrix
scripts/cohort_table.py — pandas implementation for in-memory data
python scripts/cohort_table.py \
--input events.csv \
--user-col user_id \
--event-time-col event_at \
--cohort-grain week \
--periods 12
Related skills
funnel-analysis — for within-session/within-flow conversion
survival-analysis — when you need time-to-churn distribution with censoring
metric-definition — write a clean retention metric spec
stakeholder-readout — for packaging cohort findings
1---2name: cohort-analysis3description: Builds cohort retention tables and retention curves with a single consistent denominator policy. Use when the user mentions cohort, retention, retention curve, "by signup month", "by acquisition channel", N-day retention, churn over time, or lifecycle analysis.4---56# Cohort Analysis78## When to use this skill910Use when the user wants to understand how behavior changes over the **lifecycle of users**, segmented by when they entered. Triggers:1112- "Show retention by signup cohort…"13- "Build a cohort table…"14- "N-day retention for…"15- "Compare cohorts by acquisition channel"16- "Is retention improving?"1718If the user wants step-by-step conversion within a single session/flow, use `funnel-analysis` instead.1920## Required inputs2122| Input | Why it matters |23|---|---|24| Cohort anchor event | Defines when a user enters the cohort (signup, first purchase, install) |25| Cohort grain | Day, week, or month |26| Retention event | What action defines "retained" (any session, key feature use, purchase) |27| Time window per period | Daily / weekly / monthly columns |28| Lookback window | How many periods to display |2930## Workflow31321. **Confirm the anchor event.** A user belongs to exactly one cohort. If they could be re-anchored (e.g., resubscribe), pre-specify the rule (first only, latest, both as separate cohorts).33342. **Pick the cohort grain.**35 - Daily: short time-scale features, fast-moving products36 - Weekly: most product analytics defaults — smooths weekday/weekend variance37 - Monthly: long sales/subscription cycles38393. **Define "retained" precisely.** Most common definitions:40 - **Any-activity**: at least one logged event in period41 - **Key-action**: specific event (e.g., completed_order)42 - **N-day**: active on day N exactly (rolling vs absolute matters)43 - **N-day-bracket**: active anywhere in days [N, N+window)4445 Pick one. Document it in the readout.46474. **Pick the denominator policy.** This is the most common source of misleading cohort charts:48 - **Cohort-fixed**: divide by original cohort size every period. Curves only go down.49 - **Active-base**: divide by users active in previous period. Shows period-over-period stickiness, but can hide leaky retention.50 - **Eligible-base**: divide by users who had the *opportunity* to retain (e.g., still on the platform). Use when accounts can churn permanently.5152 **Default to cohort-fixed.** Only switch if there's a specific question about period-over-period stickiness.53545. **Generate cohort matrix** using `scripts/cohort_table.sql` (Snowflake) or `scripts/cohort_table.py` (pandas).55566. **Compute key summary stats:**57 - D1, D7, D30 retention by cohort58 - "Smile curve" check (does retention stabilize after some N?)59 - Cohort-over-cohort delta (is the latest cohort retaining better than 4 cohorts ago?)60617. **Write the readout** with the matrix, summary stats, and interpretation.6263## Output format6465```markdown66# Cohort Analysis: <product/segment>6768## Definitions69- Cohort anchor: <event> on <grain>70- Retention event: <event>71- Denominator: cohort-fixed (original cohort size)72- Lookback: <N> periods7374## Summary75- Latest cohort D7 retention: <X%> (vs <Y%> 4 weeks ago, <delta>pp)76- D30 retention asymptote: <Z%>77- Retention "stabilizes" around day <N>7879## Cohort table80| Cohort | Size | D1 | D7 | D14 | D30 | D60 | D90 |81|---|---|---|---|---|---|---|---|82| 2026-01 | 12,400 | 48.2% | 22.1% | 16.4% | 12.0% | 9.8% | 8.5% |83| 2026-02 | 14,100 | 51.0% | 24.8% | 18.2% | 13.5% | 10.4% | — |84| ... | ... | ... | ... | ... | ... | ... | ... |8586## Interpretation87- <Are recent cohorts improving, flat, or declining?>88- <What's the asymptote? Is the long-term retained base stable?>89- <Any anomalies — single bad cohort, missing data?>9091## Caveats92- Recent cohorts have incomplete data through the latest periods (mark provisional)93- Cohort assignment uses <rule>; users who re-anchored are <treated as>94- <late-arriving events caveat if applicable>9596## Next steps97- <e.g., investigate the drop in 2026-03 cohort>98- <e.g., segment by acquisition channel to find a driver>99```100101## Validation checks102103- [ ] Each user appears in exactly one cohort104- [ ] Denominator policy stated and consistent across all cells105- [ ] Recent incomplete cohorts flagged as provisional106- [ ] Period boundaries aligned (Mon-Sun or 1st-end-of-month, not mid-week)107- [ ] Late-arriving events caveat if data has > 24h pipeline lag108109## Edge cases & failure modes110111- **Mid-week cohort start**: if your cohort grain is "week" but anchor events occur all days, define week as Mon-Sun and align. Don't let cohort #1 be 3 days long.112- **Re-engaging users**: if a user goes dormant and comes back, are they in their original cohort or a new "reactivation" cohort? Pre-specify.113- **Sparse recent cohorts**: the last 1-2 periods of the matrix have <100% observation. Mark cells as "—" (no data) rather than 0% (no retention).114- **Cohort size differences**: if cohort sizes vary 10x, displaying raw percentages can mislead. Add absolute counts or use rolling averages.115- **Survivorship in subscription products**: if users *can't* churn within a billing cycle, "retention" before cycle end is meaningless. Use eligible-base denominator.116117## Scripts118119- `scripts/cohort_table.sql` — Snowflake template producing a cohort matrix120- `scripts/cohort_table.py` — pandas implementation for in-memory data121122```bash123python scripts/cohort_table.py \124 --input events.csv \125 --user-col user_id \126 --event-time-col event_at \127 --cohort-grain week \128 --periods 12129```130131## Related skills132133- `funnel-analysis` — for within-session/within-flow conversion134- `survival-analysis` — when you need time-to-churn distribution with censoring135- `metric-definition` — write a clean retention metric spec136- `stakeholder-readout` — for packaging cohort findings