Field note · September 18, 2023

The overall average hides 4 different numbers

Real group means for one column across 4 segments, and what the pooled average conceals.

1 min read ·Analytics practice ·statistics practice

channel splits retail-orders into 4 groups. Here is what revenue_usd looks like inside each.

  • ios — mean 116, median 65 (1,729 rows)
  • android — mean 114, median 63 (1,386 rows)
  • web — mean 111, median 59 (2,734 rows)
  • marketplace — mean 95, median 51 (1,151 rows)

Top to bottom that is 116 against 95, a spread of 22.4%. The pooled average is 111.

sql
select channel,
  count(*)                                                 as rows,
  round(avg(revenue_usd)::numeric, 2)                      as mean,
  percentile_cont(0.5) within group (order by revenue_usd) as median
from retail_orders
group by 1
order by mean desc;

The groups are close enough that the pooled average is a fair summary. That is worth confirming rather than assuming: the check costs one query and the failure mode is invisible.

Notice the mean and median columns disagree most in ios, where the mean sits well above the median. Always compute both in the group-by. The comparison between them per segment is free and tells you whether you are looking at a level difference or a tail difference.

This is the setup for Simpson's paradox — the case where every segment moves one way and the total moves the other.