How to Aggregate Survey Estimates and Their Margins of Error

Problem statement

Census tracts rarely match the area you care about. A neighbourhood, a service area or a school catchment is usually a set of tracts, and the estimate for the set is easy: add the tract estimates. The margin of error is not. Margins do not add, and there is no exact way to combine them from published tables.

The Census Bureau's standard approximation is the root sum of squares. Measured by aggregating 2023 ACS tracts to counties and comparing with the margins the Bureau publishes for those counties:

  • For people below the poverty line it works reasonably well: the approximation was a median 4.7% wider than the published margin, and within 10% for 67.5% of counties.
  • For zero estimates it overstates badly unless you apply the zero rule: 36.4% too wide at the median, down to 5.1% with it.
  • For controlled totals it is nonsense: the aggregated margin of the poverty universe was a median 12.6 times the published one.
  • Medians cannot be aggregated at all: a household-weighted average of tract median incomes missed the published county median by a median 2.3%, and by more than 7.2% in a tenth of counties.

Quick answer

Sum the estimates, combine the margins by root sum of squares, and among zero estimates keep only the largest margin:

import numpy as np

def combine_moe(estimates, moes):
    nonzero = moes[estimates > 0]
    zeros = moes[estimates == 0]
    extra = zeros.max() ** 2 if len(zeros) else 0.0
    return np.sqrt((nonzero ** 2).sum() + extra)

total = tracts["B17001_E002"].sum()
moe = combine_moe(tracts["B17001_E002"], tracts["B17001_M002"])

For a rate, aggregate the numerator and denominator first, then compute the rate and its margin from the aggregates. Never average tract rates, and never average tract medians.

Five steps for aggregating ACS estimates: sum the estimates, combine margins by root sum of squares with the zero rule, flag controlled totals, compute rates from aggregates, and check against a published aggregate.
Each step has a measured failure behind it when it is skipped.

Step-by-step solution

1. Sum the estimates

Counts add exactly. Summing tract estimates reproduced the published county estimate for 99.9% of counties, and the exceptions were the two New York counties with tracts missing from the table. If a sum does not match a published parent, rows are missing.

2. Combine margins by root sum of squares

For independent estimates, variances add, and a 90% margin is a scaled standard error, so:

MOE(A + B + C) โ‰ˆ sqrt(MOE_Aยฒ + MOE_Bยฒ + MOE_Cยฒ)

How good the approximation is depends on the level and the variable:

aggregation                                  RSS รท published MOE   within 10%
block groups โ†’ tract, renter households             1.049              66.1%
tracts โ†’ county, people below poverty               1.047              67.5%
counties โ†’ state, people below poverty              0.930              55.8%
tracts โ†’ county, poverty universe                  12.592               7.1%

For most counties it is within about 10% and errs on the wide side. At state level it errs narrow: 38.5% of states came out more than 10% too narrow.

3. Apply the zero rule

A zero estimate still has a margin โ€” often the same small number in every tract. Summing all of them in quadrature counts the same "found nobody" uncertainty dozens of times. The Census Bureau's guidance is to include only the largest margin among the zero estimates.

The effect is large for sparse cells. B17001_E019, one of the age groups below poverty, is zero in 84.5% of tracts, and most of those zeros carry a margin of 13, 14, 15 or 19:

B17001_E019, tracts โ†’ county        RSS รท published   within 10%
all margins included                     1.364            18.8%
zero rule                                1.051            50.0%

Among the 2,137 counties whose estimate for that cell is under 50, the median ratio fell from 1.477 to 1.086.

4. Do not aggregate the margins of controlled totals

The poverty universe, total population and housing totals are controlled to official estimates at county level. Their tract margins describe sampling variation that cancels out once the tracts are combined into the controlled area, so root sum of squares inflates them โ€” to 12.6 times the published margin for the poverty universe.

For a custom region inside one county, report the controlled total with its aggregated margin marked as an overstatement, or use the published parent where the region matches one.

5. Build rates from aggregated numerators and denominators

Aggregate the count below poverty and the poverty universe separately, then compute the proportion and its margin from the aggregates. The inflated universe margin from step 4 matters less than you might expect, because the proportion formula subtracts its term: the county poverty-rate margins derived from aggregated tracts matched the ones derived from published county counts with a median ratio of 1.013.

Averaging tract rates instead weights a tract of 1,200 people the same as one of 8,000, and gives no margin at all.

6. Rebuild medians from distributions, not from medians

A median of medians is not a median. A household-weighted mean of tract median household incomes missed the published county median by a median of 2.3%, by more than 7.2% for a tenth of counties, and was biased upwards by 2.4% on average.

For a custom region, aggregate the bracket counts from the distribution table โ€” household income brackets are in B19001 โ€” and interpolate the median within the bracket that contains the middle household.

7. Check the method against a published aggregate

Where your region matches a published geography, compare. New York City is five counties and is also published as a place:

                        people below poverty   MOE      poverty rate     rate MOE
five counties, RSS          1,454,320          17,545     17.36%         0.21 pts
published NYC place         1,454,320          17,927     17.36%         0.21 pts

The estimates are identical, the aggregated margin is 2.1% narrower than the published one, and the rate and its margin match to two decimal places.

Table of how closely the root-sum-of-squares margin matched published margins for four aggregations, including a controlled total that came out 12.6 times too wide.
The approximation is good for ordinary counts and meaningless for totals the survey controls to official estimates.

Code examples

Example 1 โ€” aggregate estimates and margins by group

import numpy as np
import pandas as pd


def aggregate_estimates(frame, groups, pairs, zero_rule=True):
    """Sum estimates by group and combine MOEs by root-sum-of-squares.

    pairs: [(estimate_column, moe_column), ...]
    zero_rule: among zero estimates in a group, keep only the largest MOE (Census Bureau guidance).
    """
    out = {}
    for est, moe in pairs:
        g = frame[[est, moe]].groupby(groups)
        out[est] = g[est].sum()

        def combine(part):
            nonzero = part.loc[part[est] > 0, moe]
            zeros = part.loc[part[est] == 0, moe]
            if not zero_rule:
                return np.sqrt((part[moe] ** 2).sum())
            extra = zeros.max() ** 2 if len(zeros) else 0.0
            return np.sqrt((nonzero ** 2).sum() + extra)

        out[moe] = g.apply(combine)
    return pd.DataFrame(out)

groups can be anything pandas can group by: a column of region names, or a slice of the GEOID such as tracts.index.str[:5] for counties. Replace sentinel values with missing values before calling it, or a โˆ’555555555 margin will dominate the sum.

Example 2 โ€” a rate and its margin for the aggregate

def aggregate_rate(agg, num, moe_num, den, moe_den):
    """Rate and its MOE for an aggregated numerator that is a subset of the denominator."""
    p = agg[num] / agg[den]
    radicand = agg[moe_num] ** 2 - p ** 2 * agg[moe_den] ** 2
    ratio = agg[moe_num] ** 2 + p ** 2 * agg[moe_den] ** 2
    moe = np.sqrt(np.where(radicand < 0, ratio, radicand)) / agg[den]
    return pd.DataFrame({"rate": p, "rate_moe": moe}, index=agg.index)

Applied to New York City's five counties:

nyc_counties = ["36005", "36047", "36061", "36081", "36085"]
sub = counties.loc[nyc_counties]
nyc = aggregate_estimates(sub, pd.Series("NYC", index=sub.index),
                          [("B17001_E001", "B17001_M001"), ("B17001_E002", "B17001_M002")])
print(nyc.join(aggregate_rate(nyc, "B17001_E002", "B17001_M002", "B17001_E001", "B17001_M001")))
{'B17001_E001': 8378514, 'B17001_M001': 2699.9776, 'B17001_E002': 1454320,
 'B17001_M002': 17544.6869, 'rate': 0.1736, 'rate_moe': 0.0021}

The published place row for New York City has margins of 2,546 and 17,927 for the same two estimates, and the same rate and rate margin.

Example 3 โ€” test the approximation on your own variables

def against_published(agg, published, est, moe):
    ratio = (agg[moe] / published[moe]).replace([np.inf, -np.inf], np.nan).dropna()
    ratio = ratio[published.loc[ratio.index, moe] > 0]
    print(f"{moe}: RSS / published median {ratio.median():.3f}, within 10% {ratio.between(0.9, 1.1).mean():.1%}, "
          f"more than 10% too wide {(ratio > 1.1).mean():.1%}, too narrow {(ratio < 0.9).mean():.1%}")
    return ratio
B17001_M002: RSS / published median 1.047, within 10% 67.5%, more than 10% too wide 30.8%, too narrow 1.7%
B17001_M001: RSS / published median 12.592, within 10% 7.1%, more than 10% too wide 92.8%, too narrow 0.1%
zero_rule False  B17001_M019: RSS / published median 1.364, within 10% 18.8%, more than 10% too wide 81.2%, too narrow 0.1%
zero_rule True  B17001_M019: RSS / published median 1.051, within 10% 50.0%, more than 10% too wide 36.1%, too narrow 13.9%

Run it with tracts aggregated to counties for any variable before trusting aggregated margins for a custom region. A variable that fails at county level โ€” like the controlled universe on the second line โ€” will fail for your region too.

Explanation

Why root sum of squares is only an approximation

Adding variances is exact for independent estimates. ACS estimates for neighbouring areas are not independent: they share weighting steps, and county-level controls tie tract estimates together. When tracts are forced to add up to a controlled county total, an overestimate in one tract tends to come with underestimates in others. That negative correlation makes the true margin of the sum smaller than the root sum of squares, which is why the approximation errs wide for counties.

At state level the published margins were more often larger than the approximation, consistent with county estimates within a state being positively correlated. The formula has no way to know either.

Why controlled totals break it completely

A controlled total has almost no sampling error at the controlled level, so its published county margin is tiny or absent. The tract margins still reflect sampling within the county, and they cancel exactly when summed back to the county โ€” but root sum of squares assumes they never cancel. The result for the poverty universe was a margin 12.6 times too large.

Why zeros need a special rule

A margin on a zero estimate is not derived from a sample of people; there were none in the sample to measure. The Bureau publishes a floor that depends on the survey design, which is why the same values โ€” 13, 14, 15, 19 โ€” repeat across thousands of tracts. Treating each as independent evidence multiplies one piece of uncertainty by the number of empty tracts. Keeping only the largest is a pragmatic correction, and it brought the median ratio for a sparse cell from 1.364 to 1.051.

Why medians do not aggregate

A median depends on the full distribution of households, and two tracts with the same median can have very different spreads. Averaging medians โ€” even weighted by households โ€” ignores the spread, so the result drifts from the true combined median, and in these counties it drifted upwards on average. Only the underlying distribution, aggregated bracket by bracket, contains the information needed.

Bar chart of the root-sum-of-squares margin as a multiple of the published margin for a sparse ACS cell, with and without the zero rule.
For a cell that is zero in 84.5% of tracts, the zero rule removes most of the overstatement.

Edge cases or notes

  • Replace sentinels before aggregating. A single โˆ’555555555 margin squared overwhelms any sum.
  • The approximation errs wide for tracts within a county and narrow more often at state level; say which in any report.
  • Large aggregations drift more. The median ratio for counties with more than 20 tracts was about 1.06โ€“1.08, against 1.016 for counties with five or fewer.
  • Derived rates tolerate the controlled-total problem better than totals do, because the proportion formula subtracts the denominator term.
  • Do not aggregate across years. Five-year periods overlap, and their margins cannot be combined this way.
  • Medians, means and ratios published directly cannot be summed. Aggregate their components instead.
  • Check a published parent whenever one exists. A county, place or metropolitan area that matches your region is a free test of the method.
  • Aggregating improves reliability. Combining tracts until the CV falls below your threshold is often the point of the exercise.

FAQ

How do I combine margins of error for several census tracts?

Take the square root of the sum of the squared margins, including only the largest margin among tracts whose estimate is zero. For tract poverty counts aggregated to counties, that was within 10% of the published margin for 67.5% of counties.

Can I add margins of error together?

No. Adding them directly overstates the uncertainty far more than root sum of squares does. The squares add, approximately, not the margins.

How do I calculate a poverty rate for a group of tracts?

Sum the people below poverty and the poverty universe across the tracts, then divide. Compute the rate's margin from the two aggregated margins with the proportion formula; averaging tract rates gives the wrong answer and no margin.

Why is my aggregated margin of error so large for population totals?

Total population and similar totals are controlled to official estimates at county level, so their tract margins cancel when summed. Root sum of squares cannot see that and gave margins 12.6 times the published value for the poverty universe.

Can I average median household income across tracts?

No. A household-weighted mean of tract medians missed the published county median by a median of 2.3%, and by more than 7.2% for one county in ten. Aggregate the income bracket counts and interpolate a new median.

How accurate is the root-sum-of-squares approximation?

It depends on the variable and the level. Aggregating New York City's five counties gave a margin 2.1% narrower than the published city margin; for tracts to counties it ran a median 4.7% wide.