Chapter 1 — Anatomy of Metrics

The cohort table: how to read it

15 min
O que você vai aprender
  • read a table: rows are cohorts, columns are age (D0/D1/D7/D30)
  • tell absolute time (the calendar) from cohort age — and see where the diagonal lives in the table
  • understand why the table is triangular and what the empty cells mean
  • compare generations: reading a column down versus a row across
  • do a first heatmap read in under a minute

One cohort is a row. Many cohorts make a table

In the previous lesson we computed for one . One cohort is one row of numbers. Stack every cohort of the product one under another and you get a cohort table — the main instrument through which an analyst looks at retention.

The layout is simple and always the same:

  • a row = a cohort, everyone who arrived for the first time on the same day (or in the same week);
  • a column = the cohort's age: D0, D1, D7, D14, D30;
  • a cell = how many of that cohort were active at that age — in absolutes or as a percentage of the cohort size.

Here is Kotomarket's black box, weekly cohorts, today is March 14. First in absolutes:

cohortsize (D0)D1D7D14D30
Feb 11,20045616812084
Feb 81,40051818212684
Feb 151,300468156104
Feb 221,500525165105
Mar 11,600528176
Mar 81,500480

Rows of absolutes cannot be compared: the cohorts differ in size. So the table is almost always converted to percentages — each cell divided by the size of ITS OWN cohort (the fixed denominator from the previous lesson):

cohortsizeD1D7D14D30
Feb 11,20038%14%10%7%
Feb 81,40037%13%9%6%
Feb 151,30036%12%8%
Feb 221,50035%11%7%
Mar 11,60033%11%
Mar 81,50032%

The size column always stays: without it the percentages hang in the air — exactly as in the lesson about denominators.

Retention cohort tablerow = signup cohort, column = cohort agecohortsizeD0D1D7D30Mar 11000100%38%19%9%Mar 2920100%36%18%Mar 31100100%31%not reached yet ≠ zerogrey cells must not be averaged together with the rest
Rows are cohorts, columns are age. The upper-right corner is empty: young cohorts have not lived to the older marks yet.

Absolute time versus cohort age

The most important idea of this lesson: a table contains two different times, and they are constantly confused.

Cohort age lives in the columns. Moving right along a row is moving through one group's life: day one, day seven, day thirty.

Absolute time (the calendar) lives in the rows: the lower the row, the later the cohort arrived. But a calendar date inside the table lives in neither a column nor a row — it runs along a diagonal.

Check it on March 1. For the March 1 cohort that is D0. For the February 22 cohort it is D7 (Feb 22 + 7 days). For February 15 it is D14. For February 1 it is D28. One calendar date has cut diagonally across the whole table:

            D0    D1    D7   D14   D30
Feb 1        ·     ·     ·     ·     ·        ↖
Feb 8        ·     ·     ·     ·     ·         ↖  a diagonal =
Feb 15       ·     ·     ·   [Mar 1] ·          ↖  one calendar
Feb 22       ·     ·  [Mar 1] ·      ·           ↖  date
Mar 1     [Mar 1]  ·     ·     ·     ·            ↖

Why this matters. Events of the outside world — a release, a tracking outage, a holiday, an ad launch — happen on a specific calendar date and therefore leave a trace along a diagonal, not down a column. If event collection broke on March 1, the D0 cell of the March cohort sags, the D7 cell of the February 22 cohort sags, the D14 cell of an older one sags — and in the D7 column it will look like "one weird cohort", although the problem was shared and lasted a day.

Changes to the product itself — a new onboarding, a different price — affect entire fresh cohorts, that is, they show up in the lower rows. Telling those two patterns apart is half the craft of reading cohort tables.

Why the table is triangular

Look at the shape: the filled part of the table is a triangle, and the lower-right corner is empty. That is not lost data or a broken export.

The reason is exactly the one from the previous lesson: young have not lived the older ages yet. Today is March 14, therefore:

  • the March 8 cohort only has D1 (March 9). Its D7 arrives on March 15 — tomorrow;
  • the March 1 cohort has D1 and D7, while its D14 falls on March 15;
  • the February 15 cohort will reach D30 only on March 17.

The lower the row, the shorter its filled tail. Hence the triangle — and hence the rule:

A column is compared only across rows where the cell is filled. An empty cell means "not known yet", not "zero".

The mistake nearly everyone makes when first building such a table: computing "average D30 across all cohorts". The empty cells either turn into zeros (the average collapses and a nonexistent drop appears) or silently drop out — and then the average is computed over old cohorts only and presented as the overall figure.

There is an opposite bias too: looking only at mature cohorts hides fresh data entirely, and a problem gets noticed a month later than it could have been. So in practice: read the early marks (D1, D7) across all cohorts including the fresh ones, and the late ones (D30) only across mature cohorts. D1 and D7 mature quickly and are the first to show that new generations have got worse.

How to read it: a row across, a column down

A table has exactly two reading directions, and they answer different questions.

A row across — the life of one cohort. 38% → 14% → 10% → 7%. You see the shape of the curve: a sharp fall between D1 and D7, then flattening. Nearly every product behaves this way: most newcomers drop off in the first days, while those who survive to the second week leave far more slowly. If the curve reaches a plateau, the product has a core that stays. If it keeps sliding towards zero, there is no core.

A column down — comparing generations at the same age. This is the most valuable movement, because it compares like with like: everyone at exactly seven days old.

column D1:  38% → 37% → 36% → 35% → 33% → 32%
column D7:  14% → 13% → 12% → 11% → 11%

Six consecutive generations — and both columns creep downwards. That is no longer noise: each new generation retains worse than the one before. Meanwhile the size of the last cohort is larger than that of the first (1,200 → 1,500): more people arrive and a smaller share of them stays.

Entry #7 in L.'s journal: "Marketing celebrates DAU growth. I look at the D7 column of the new cohorts: 14, 13, 12, 11. We are pouring water into a leaky bucket and praising the tap."

A first heatmap read in one minute

Cohort tables are often coloured — darker means more, lighter means less. Such colouring is called a heatmap, and the eye reads it faster than digits. The order of a first read:

  1. Check the denominators — the first column with cohort sizes. A row of 40 people will jump by tens of percent because of one cat, and it cannot be compared with a row of a thousand.
  2. Look at the shape of a row — where the cliff is and where the plateau begins.
  3. Walk the columns down — are generations improving or degrading?
  4. Find anomalies. One odd row means something happened to that particular set of people (a traffic source, a promo). An odd diagonal means something happened on a specific calendar day (a release, an outage). An odd column usually points at the metric itself, or at that age landing on a weekend.
  5. Ignore the lower-right corner — it is empty not because things are bad there.
One row of the cohort table by hand: converting the February 1 cohort's absolutes into percentages. Every formula shares one denominator — the cohort size.
python · pandas
Practice: write the code
Convert the February 8 cohort row into percentages: size 1,400; 518 active on D1, 182 on D7, 84 on D30. Fill in d1_pct, d7_pct and d30_pct.
python · pandas
Practice: write the code
Compare two generations down the D7 column. February 1 cohort: 168 of 1,200. March 1 cohort: 176 of 1,600. Compute d7_feb and d7_mar as percentages, the difference in percentage points diff_pp, and the relative change diff_rel in percent. Note: in absolutes March looks better.
python · pandas
Check yourself
One diagonal sags in the cohort table: a young cohort has a bad D0, an older one a bad D7, the oldest a bad D14. What is this most likely?
Check yourself
The D1 column across six consecutive cohorts: 38%, 37%, 36%, 35%, 33%, 32%. Meanwhile the size of the last cohort is higher than the first: 1,500 versus 1,200. What does that mean?
Principais pontos
  • table rows are cohorts, columns are age (D0/D1/D7/D30), a cell is an active count or a share of its own cohort size
  • the table holds two times: age runs along columns, and in a full table with a uniform step a calendar date runs along a diagonal
  • the table is triangular because young cohorts have not lived the older ages; empty ≠ zero
  • a row across shows the shape of the curve; a column down compares generations at the same age
  • anomalies: a row means something about that group of people, a diagonal means an event on one date, a column raises a question about the metric itself

Next — Case #0. You run the investigation: a six-step template, your own computations, your own verdict. L.'s debrief opens only afterwards.