Field note · September 3, 2026
The overall average hides 10 different numbers
Real group means for one column across 10 segments, and what the pooled average conceals.
city splits data-job-postings into 10 groups. Here is what salary_min_usd looks like inside each.
San Francisco— mean 132,017, median 130,500 (532 rows)New York— mean 126,626, median 129,500 (495 rows)Austin— mean 104,188, median 105,500 (253 rows)Chicago— mean 101,232, median 102,500 (299 rows)London— mean 64,365, median 69,000 (436 rows)Amsterdam— mean 62,590, median 69,500 (245 rows)
Top to bottom that is 132,017 against 25,038, a spread of 427.3%. The pooled average is 104,106.
select city,
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 San Francisco 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.