Data in pieces: partitions and mergeable summaries
Split, summarize and combine; which statistics merge exactly, merging variances, sketches for the rest, and partitioning well.
Create a free account Try part of it here
Why it matters
Log files, click streams and training data quickly outgrow one machine's memory. Tools like Spark, BigQuery, Snowflake and DuckDB handle it with one idea: split the data into pieces, summarize each piece where it lives, and combine the small summaries. Whether that gives the right answer depends on the statistic. Average three averages and you're usually wrong; take the median of medians and you're wrong in a way nobody notices. This lesson shows which numbers combine exactly, how to combine them, and what to do about the ones that don't.
What you'll be able to do
- Explain how big-data systems split data into partitions and combine small summaries
- Tell mergeable statistics (count, sum, mean, variance) from ones that aren't (median, distinct count)
- Merge means and variances from partitions exactly, without seeing the raw data again
- Choose partitions sensibly, and know when data isn't really big at all
What it covers
- Split, summarize, combine
- Mergeable summaries
- Merge two basic summaries
- Merging variances exactly
- Sketches for what won't merge
- Find the bug: the average of averages
- Partitioning well, and checking whether it's big at all
Show the answer
Weight each average by its server's request count
A server that handled ten times as many requests should count ten times as much. With each average and its count, the overall mean is exact; the plain average of averages is only right when the counts are equal.
Split, summarize, combine
When data won't fit on one machine, or would take too long to read on one, systems split it into partitions: files by day, by customer, or by a hash of some key. Each worker reads its partitions and produces a small summary, such as a count and a total. The summaries travel to one place and are merged into the answer. Moving a few numbers is cheap; moving terabytes isn't, so the whole design rests on this shape, often called map and reduce.
import statistics as st partitions = [[12, 15, 22, 30], [41, 38, 25], [14, 9, 61, 33, 20]] # response times, three servers everything = [x for p in partitions for x in p] means = [st.mean(p) for p in partitions] print("mean of the means:", round(st.mean(means), 2)) print("true mean:", round(st.mean(everything), 2)) print("weighted by counts:", round(sum(m * len(p) for m, p in zip(means, partitions)) / len(everything), 2))
The plain average of averages is off because the partitions have different sizes. Carry each mean's count along, and the merge is exact.
import statistics as st partitions = [[12, 15, 22, 30], [41, 38, 25], [14, 9, 61, 33, 20]] everything = [x for p in partitions for x in p] print(st.median([st.median(p) for p in partitions]), st.median(everything))
Show the answer
20 23.5
The medians are 18.5, 38 and 20, whose median is 20, but the true median of all twelve values is 23.5. No small summary of each partition lets you merge medians exactly.
Mergeable summaries
A summary is mergeable when the summary of two partitions together can be computed from their two summaries alone. Some are easy:
- count: add them; sum: add them;
- min and max: the smaller of the minimums, the larger of the maximums;
- mean: keep the sum and the count, and divide only at the end.
Some aren't. The median needs to know where every value sits relative to every other, and the number of distinct values can't be added either: two partitions with 1,000 distinct customers each might share all of them or none. These need either all the raw data in one place, which is expensive, or an approximate sketch, which comes later in this lesson.
The rule of thumb: if you can compute it with a running total that you update one value at a time, there's usually a way to merge it.
Watch for rates and percentages, which look like single numbers but merge like means. A refund rate of 5% in one store and 20% in another doesn't average to 12.5% unless the stores sold the same amount. Carry the numerator and the denominator, refunds and orders, and divide at the very end, only after every partition has been merged in.
Merge two basic summaries
A summary is a tuple (count, total, low, high). Write merge(a, b) that returns the summary of both partitions together, and mean(s) that returns the mean from a summary.
def merge(a, b): return a def mean(s): return 0 s = merge((4, 79, 12, 30), (3, 104, 25, 41)) print(s, round(mean(s), 2))
Response times with the server that handled each request. Group by server and compare the mean with the count.
Write a sentence or two, then compare it with a good answer in the lesson.
Merging variances exactly
Variance looks unmergeable, since it's built from distances to the mean and each partition has a different mean. It merges exactly if each partition keeps three numbers: its count , its mean , and , the sum of squared distances from its own mean. For partitions and , with :
In plain words: add the two partitions' own spreads, then add the spread between their two means, weighted by how many values sit on each side.
The last term adds back the spread between the two partitions' means, which each partition alone can't see. The sample variance is then .
import statistics as st def summary(xs): m = sum(xs) / len(xs) return len(xs), m, sum((x - m) ** 2 for x in xs) def merge(a, b): (na, ma, m2a), (nb, mb, m2b) = a, b n, d = na + nb, mb - ma return n, ma + d * nb / n, m2a + m2b + d * d * na * nb / n partitions = [[12, 15, 22, 30], [41, 38, 25], [14, 9, 61, 33, 20]] s = summary(partitions[0]) for p in partitions[1:]: s = merge(s, summary(p)) print("merged:", round(s[2] / (s[0] - 1), 3), "direct:", round(st.variance([x for p in partitions for x in p]), 3))
The merged variance matches the direct calculation exactly, without any partition sharing its raw values.
na, ma, m2a = 2, 10, 2 nb, mb, m2b = 2, 20, 2 n, delta = na + nb, mb - ma print(m2a + m2b + delta ** 2 * na * nb / n)
Show the answer
104.0
Each partition is tight (M2 = 2), but their means are 10 apart: δ² · nA · nB / n = 100 · 2 · 2 / 4 = 100 more. Most of the spread is between the partitions, which neither could see alone.
Show the answer
A total and a count for each server
Grouping computes a summary per group, which is exactly a per-partition summary. Carrying sum and count, rather than the mean, keeps the results mergeable.
Write a sentence or two, then compare it with a good answer in the lesson.
Sketches for what won't merge
For the statistics that won't merge, there are sketches: small, mergeable structures that give approximate answers with known error.
- Distinct counts: HyperLogLog keeps a few kilobytes per partition and estimates the number of distinct values within about 2%, merging by taking element-wise maximums. Databases use it for "unique visitors".
- Quantiles such as the median and the 95th percentile: t-digest and similar sketches keep a compressed outline of the distribution that merges and answers any quantile closely, especially near the tails.
- Most frequent items: Count-Min and similar sketches track the heaviest hitters in fixed memory.
When an answer must be exact, the system has to bring related data together, called a shuffle: every row moves to the worker responsible for its key. Shuffles are the most expensive step in most big-data jobs, so good pipelines filter and pre-aggregate before them.
Show the answer
A HyperLogLog sketch of visitor IDs
Distinct counts don't add up across servers, since one visitor can hit many. HyperLogLog sketches merge, in a few kilobytes each, into a close estimate.
Find the bug: the average of averages
overall_mean(parts) takes each partition's (count, mean) and should return the mean of all the values together. It's off whenever partitions have different sizes. Fix it.
def overall_mean(parts): return sum(mean for count, mean in parts) / len(parts) print(round(overall_mean([(4, 19.75), (3, 34.67), (5, 27.4)]), 2))
Partitioning well, and checking whether it's big at all
Choose partitions by how the data is used. Partition logs by day, and a question about last week reads only seven partitions. Partition orders by a hash of the customer ID, and everything about one customer lands in one place, so per-customer summaries need no shuffle. Watch for skew: if one customer has 40% of the rows, the worker that gets them finishes last and the whole job waits for it. Store data in a columnar format such as Parquet, so a query that needs two columns reads two columns.
And check before you build. A few gigabytes fit comfortably on a laptop: DuckDB queries Parquet and CSV files larger than memory, and pandas can read a file in chunks and merge chunk summaries exactly as above. A cluster is worth its cost and complexity only when one machine really can't keep up.
Mergeable summaries also make data that arrives late cheap to handle. When yesterday's partition gets a few extra events this morning, summarize only the new events and merge them into yesterday's summary, instead of reprocessing the whole day. Streaming systems are built on this: each window keeps a mergeable summary and folds new events in as they come.
Show the answer
Try one machine: DuckDB, or pandas in chunks
3 GB is small for one modern machine. DuckDB or chunked pandas will answer most questions in seconds, with far less to set up and pay for.
Write a sentence or two, then compare it with a good answer in the lesson.
Practice what you just read
- 2 code examples you can edit and run
- a practice exercise that checks your code
- a 5-question quiz
- 6 review cards for spaced repetition
Try a quiz question
Which statistic can be merged exactly from per-partition summaries?