COUNTY INCOME & POVERTY / PYTHON + CSV

Compare county income and poverty in Python

Add annual child-poverty counts, rates, confidence bounds and household income to a county service-area list using prepared Census SAIPE data.

Add economic context to a county service area

Suppose a nonprofit is preparing a needs assessment for three North Carolina counties. It needs the number of children living in poverty, the child-poverty rate and median household income. The counts describe the scale of the population; the rates describe how widespread poverty is within each county.

The optional input.csv contains fictional service-area labels paired with real FIPS codes for Durham, Robeson and Wake counties. Those same records appear in the territories list in the Python code below. The project labels are invented to represent an analyst’s own records; the Census estimates come unchanged from the product.

Download the product samples

Save county_income_poverty.csv in your working folder. It contains the three selected counties and all 34 columns from the paid reference: seven estimates, their published lower and upper 90% confidence bounds, availability statuses and county identifiers. The optional county_measures.csv sample contains all seven measures for each selected county in long form, including original source cells.

The full $29 edition covers 3,144 county/equivalent identifiers across the 50 states and DC. Estimates are available for 3,143; Kalawao County, Hawaii, has explicit missing values. Inspect the complete fields and coverage.

Code example

This example reads county FIPS as text, verifies the estimate year and joins one reference record to each service-area record. It prints child-poverty counts, rates and confidence intervals alongside median household income. The resulting results list is also available for further analysis in your notebook.

Paths are relative to your working folder; use from pathlib import Path; print(Path.cwd()) to check it, or supply full paths.

Python 3.10+ · Jupyter, IPython or a script
import csv
from pathlib import Path

# Fictional service-area labels paired with real county identifiers.
territories = [
    {'territory': 'Triangle west', 'county_fips': '37063'},
    {'territory': 'Lumber River', 'county_fips': '37155'},
    {'territory': 'Triangle east', 'county_fips': '37183'},
]
reference_file = Path('county_income_poverty.csv')

with reference_file.open(newline='', encoding='utf-8-sig') as source:
    reference = {}
    for row in csv.DictReader(source):
        if row['year'] != '2024':
            raise ValueError('Expected the 2024 SAIPE county reference')
        if row['county_fips'] in reference:
            raise ValueError('Duplicate county in reference')
        reference[row['county_fips']] = row

results = []
for item in territories:
    match = reference.get(item['county_fips'])
    result = {**item, 'county_name': '', 'child_poverty_count': '',
              'child_poverty_pct': '', 'child_poverty_pct_lower_90': '',
              'child_poverty_pct_upper_90': '', 'median_household_income_usd': '',
              'status': 'county_not_in_reference'}
    if match:
        for field in ('county_name', 'child_poverty_count', 'child_poverty_pct',
                      'child_poverty_pct_lower_90', 'child_poverty_pct_upper_90',
                      'median_household_income_usd'):
            result[field] = match[field]
        result['status'] = match['child_poverty_pct_status']
    results.append(result)
    if result['status'] == 'published':
        print(f"{result['territory']} | {result['county_name']}: "
              f"{int(result['child_poverty_count']):,} children in poverty; "
              f"{result['child_poverty_pct']}% "
              f"(90% interval {result['child_poverty_pct_lower_90']}–"
              f"{result['child_poverty_pct_upper_90']}%); "
              f"median household income ${int(result['median_household_income_usd']):,}")
    else:
        print(f"{result['territory']} | {result['county_fips']}: {result['status']}")

Analysis of the output

Triangle west | Durham County: 10,348 children in poverty; 15.7% (90% interval 11.2–20.2%); median household income $84,375
Lumber River | Robeson County: 9,385 children in poverty; 30.8% (90% interval 25.6–36.0%); median household income $46,927
Triangle east | Wake County: 27,180 children in poverty; 10.1% (90% interval 8.6–11.6%); median household income $107,083

Wake has the largest estimated number of children in poverty among these counties: 27,180. Robeson has the highest estimated child-poverty rate, 30.8%, while its count is 9,385. A service plan based on the size of the population can therefore look different from one based on the prevalence of poverty.

Durham’s 15.7% rate has a published 90% confidence interval of 11.2%–20.2%. The code prints Census’s lower and upper bounds directly; it does not calculate a symmetric margin of error. These are model-based annual estimates, and point-estimate differences alone do not establish statistical significance.

Median household income adds a separate perspective: $84,375 in Durham, $46,927 in Robeson and $107,083 in Wake, measured in 2024 dollars. It describes households, whereas the child-poverty measures describe people under 18 in the poverty universe. Do not add or average county medians to estimate a combined household median. The full file also includes confidence bounds for income and the poverty counts.

A missing estimate remains blank with a source_missing status. An identifier absent from the reference receives county_not_in_reference. Neither means zero poverty.

Using the full dataset

Extract the purchased ZIP and replace the sample with the complete county_income_poverty.csv. The filename and columns are the same. Replace territories with your own county list, or load a CSV with territory and county_fips columns:

with open("my_counties.csv", newline="", encoding="utf-8-sig") as file:
    territories = list(csv.DictReader(file))

For all-age poverty, use poverty_count and poverty_pct. The separate related_child_poverty_* fields describe related children ages 5–17 in families. These age groups overlap, so their counts must not be added together. Rates use each group’s poverty universe rather than the full county population.

If your analysis prefers one measure per row, use county_measures.csv with measures.csv. Filter to the required measure before joining to avoid repeating each input county seven times. School-district and county under-five estimates are not part of this product.

The county identifiers match our 2024 County Health Insurance Coverage Reference, including Connecticut’s nine planning regions. That product can add annual coverage measures. Our 2020–2024 ACS County Demographic Profile adds broader population and housing context, but its five-year period differs from SAIPE’s annual estimates. Shared identifiers do not make the populations or statistical definitions equivalent.

Sources and versions

Source: U.S. Census Bureau, 2024 Small Area Income and Poverty Estimates, released January 27, 2026. The source-file creation tag is January 7. ACD captured the files October 17 and packaged this preparation 2026-10-17. These dates describe different stages; this is not a claim that the estimates measure conditions in October 2026.

The pipeline retains the original text and Excel files, reconciles their numerical cells and county names, and preserves both confidence bounds and missing statuses. Read the source and preparation record and Census’s interpretation guidance for scope and uncertainty. This first ACD edition includes no historical comparison or grant-eligibility determination.

Example checked October 17, 2026 against both these samples and the complete purchased files. Source and processing records, sample checksums · Expected result CSV. Independent preparation; no agency endorsement.