Field note · April 16, 2025
The overall average hides 5 different numbers
Real group means for one column across 5 segments, and what the pooled average conceals.
seniority splits data-job-postings into 5 groups. Here is what salary_min_usd looks like inside each.
lead— mean 128,586, median 142,500 (243 rows)staff— mean 114,044, median 128,000 (339 rows)senior— mean 95,482, median 108,000 (984 rows)mid— mean 71,572, median 80,500 (1,170 rows)junior— mean 49,008, median 55,500 (764 rows)
Top to bottom that is 128,586 against 49,008, a spread of 162.4%. The pooled average is 104,106.
select seniority,
count(*) as rows,
round(avg(salary_min_usd)::numeric, 2) as mean,
percentile_cont(0.5) within group (order by salary_min_usd) as median
from data_job_postings
group by 1
order by mean desc;A spread that wide means the pooled number is not a summary, it is an artefact of the mix. Change the proportion of lead rows and the overall average moves without any individual group changing at all — which is how a metric goes up while every segment goes down.
Notice the mean and median columns disagree only slightly here. 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.