In short
- A cohort table measured up to today always declines down the page, because older groups have had longer to spend.
- On a business that never changes, the newest groups can look 67–85% worse; a real improvement can still read as a fall.
- Measure every group over the same stretch from each person's first visit, such as 30 days.
- A group is finished when its last arrival has had the full stretch; mark the rest and leave them out of comparisons.
- In GA4, read down one column of a cohort exploration; in BigQuery, count only each visitor's first 30 days.
Group your customers by the month they first visited, put what each group has spent beside it, and the table nearly always tells the same story: the further down you read, the less each person is worth. January's people look like your best ever and last month's look like a disaster. It is tempting to conclude that the customers you are winning now are worse.
Usually they are not. January's group has had nine months to spend and last month's has had a few weeks. The table is measuring how long each group has had, not how good it is, and it will show a decline on a business where nothing has changed at all.
A business that never changes
An example with invented numbers. A shop gets 1,000 new visitors every month, spread evenly over the month. Every one of them, whenever they arrive, spends €3 on average in their first 30 days and €1 every 30 days after that. The customers are identical from month to month. Here is the cohort table for 1 January to 30 September 2026, measured the usual way, up to the end of the period:
| First visited | Days to spend | Spent so far, per person | Spent in their first 30 days, per person |
|---|---|---|---|
| January | 273 | €10.60 | €3.00 |
| March | 214 | €8.63 | €3.00 |
| May | 153 | €6.60 | €3.00 |
| July | 92 | €4.57 | €3.00 |
| August | 61 | €3.53 | €3.00 |
| September | 30 | €1.55 | not finished |
Read the third column and August's customers are worth 67% less than January's, September's 85% less. Read the fourth and every group is worth exactly the same, which is the truth.
The decline is built into the table. It appears on every site that has customers who come back and spend again, whatever the business is doing.
Why it happens
Spending piles up. The longer someone has been a customer, the more they have had the chance to buy, so a total measured up to a fixed date always favours the oldest group. Each row in the third column is a different length of time, and the table puts them side by side as if they were the same.
There is a second, smaller trap inside each row. A month that has ended is not a group that has finished. On 30 September, the September row includes people who arrived that morning. Even August's row, a month later, holds people who arrived on the 31st and have had only a month, while those from the 1st have had two.
Where it bites
- Customer value reports. "Revenue per customer by first month" built from GA4's First session date and a revenue metric over a long range is exactly the table above.
- Judging a change. A new campaign, a price change or a new market launched in the summer will look like it brought worse customers, because they are younger.
- Hiding a real improvement. Suppose August's customers really were 20% better, spending €3.60 in their first 30 days instead of €3. Measured to date they still show €4.24 each against January's €10.60: a 60% fall for a group that improved.
How to compare fairly
The rule: measure every group over the same stretch of time from each person's own first visit, and only compare groups that have had all of it.
- Pick the stretch first. 30 days covers a month of paydays and weekends and tells you something a month after a group arrives. Use 90 days if your customers take longer to buy, and accept that you will wait longer for an answer.
- A group is finished when its last arrival has had the full stretch, not its first. For 30 days, August's group is complete only once 29 September is in your data. Keep unfinished rows on the table, but mark them and leave them out of any comparison.
- In GA4, use a cohort exploration (Explore > Cohort exploration) with cohort inclusion set to First touch (acquisition date) and monthly granularity. Each column is a period after the group started, so reading down one column compares groups at the same age. The Cumulative calculation shows a running total. Cohort explorations read GA4's detailed data, so they only reach back as far as your data retention setting allows.
- In BigQuery, if you export GA4 there, work out each visitor's first day and add up only what they spent in the 30 days from it:
WITH first_seen AS (
SELECT user_pseudo_id, MIN(PARSE_DATE('%Y%m%d', event_date)) AS first_day
FROM `your-project.analytics_123456789.events_*`
WHERE _TABLE_SUFFIX BETWEEN '20250101' AND '20260930'
GROUP BY user_pseudo_id
),
purchases AS (
SELECT user_pseudo_id, PARSE_DATE('%Y%m%d', event_date) AS day,
ecommerce.purchase_revenue AS revenue
FROM `your-project.analytics_123456789.events_*`
WHERE _TABLE_SUFFIX BETWEEN '20260101' AND '20260930'
AND event_name = 'purchase'
)
SELECT DATE_TRUNC(f.first_day, MONTH) AS first_month,
COUNT(DISTINCT f.user_pseudo_id) AS people,
ROUND(IFNULL(SUM(p.revenue), 0) / COUNT(DISTINCT f.user_pseudo_id), 2)
AS per_person_first_30_days
FROM first_seen f
LEFT JOIN purchases p
ON p.user_pseudo_id = f.user_pseudo_id
AND p.day < DATE_ADD(f.first_day, INTERVAL 30 DAY)
WHERE f.first_day BETWEEN DATE '2026-01-01' AND DATE '2026-08-31'
GROUP BY first_month
ORDER BY first_month
The first part reads further back than the table on purpose: someone who first visited last year and came back in January is not a January newcomer. The last line stops at August, the newest group to have had its full 30 days by the end of September. user_pseudo_id identifies a browser, so a person on a phone and a laptop counts twice; the figures are per browser, not per human.
How Glancely handles it
Glancely's Revenue page shows its cohort table with a Time to earn column, so each row says how long its group has had, marks the youngest groups "still early", and does not compare the rows with each other, because measured to the end of the period they are not comparable. With a BigQuery export connected, the Cohorts tab on the Behaviour page measures each person over the day of their first visit and the 30 days after it, and treats a group as finished only when its last arrival has had all of them. Its headline sentence compares finished groups only.
Kosmas Botsis · Founder of Glancely
Kosmas Botsis is the founder and lead data engineer of KB Analytics, an analytics engineering practice in Athens working with clients across Europe and the US: more than 250 projects for over 194 clients, and in Toptal's top 3% of talent. Glancely is what came out of that work.
About Kosmas at KB AnalyticsMore from the blog
- Why your return on ad spend counts sales your ads didn't make
Divide all of your revenue by your ad spend and the ads take credit for every organic, direct and email sale. Here is how to work out the return on the channels you actually pay for, and why it can be five times lower.
- Why your Google Ads campaigns don't line up with GA4's
Put Google Ads spend beside GA4 revenue by campaign and half the rows often come back empty, because the join is on a name and the names don't agree. Here is where GA4's campaign name comes from, and how to match them without hiding your best campaign.
- Where Performance Max shows up in GA4
GA4 files Performance Max visits under a channel called Cross-network, not Paid Search or Paid Shopping. Put its spend anywhere else and every channel's return is wrong; here is where it belongs and how to read it.