Field note · January 3, 2025
salary_min_usd by country: a 379% spread
Real group means for one column across 7 segments, and what the pooled average conceals.
Breaking salary_min_usd down by country on data-job-postings, because the headline average is 104,106 and no segment is actually there.
US— mean 120,039, median 118,500 (1,579 rows)GB— mean 64,365, median 69,000 (436 rows)NL— mean 62,590, median 69,500 (245 rows)CA— mean 59,213, median 67,000 (247 rows)DE— mean 58,829, median 65,500 (333 rows)BR— mean 30,650, median 34,000 (243 rows)
Top to bottom that is 120,039 against 25,038, a spread of 379.4%. The pooled average is 104,106.
select country,
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 US 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.