Population Estimates¶
The Census Bureau’s Population Estimates Program (PEP) produces annual estimates of the population for the nation, states, counties, and other geographies in the years between decennial censuses. PyPUMS provides get_estimates() to pull these estimates from the Census API, including breakdowns by age, sex, race, and Hispanic origin.
Function signature¶
pypums.get_estimates(
geography,
*,
product=None,
variables=None,
breakdown=None,
breakdown_labels=False,
vintage=2023,
year=None,
state=None,
county=None,
time_series=False,
output="tidy",
geometry=False,
cache_table=False,
show_call=False,
key=None,
)
API key required
All Census API calls require a key. Request one for free at
https://api.census.gov/data/key_signup.html, then set the
CENSUS_API_KEY environment variable or pass key="YOUR_KEY" to
every call.
Products¶
The product parameter selects which estimates dataset to query.
If omitted, it defaults to "population".
| Product | product= |
Dataset | Description |
|---|---|---|---|
| Population | "population" |
pep/population |
Total population estimates |
| Components of Change | "components" |
pep/components |
Births, deaths, migration |
| Housing | "housing" |
pep/housing |
Housing unit estimates |
| Characteristics | "characteristics" |
pep/charagegroups |
Population by age, sex, race, Hispanic origin |
Getting started¶
State population estimates¶
import pypums
state_pop = pypums.get_estimates(
geography="state",
product="population",
vintage=2023,
)
print(state_pop.head())
GEOID NAME variable value
0 01 Alabama POP_2023 5108468
1 02 Alaska POP_2023 733406
2 04 Arizona POP_2023 7303398
3 05 Arkansas POP_2023 3067732
4 06 California POP_2023 38965193
County population estimates¶
tx_counties = pypums.get_estimates(
geography="county",
product="population",
state="TX",
vintage=2023,
)
print(tx_counties.head())
GEOID NAME variable value
0 48001 Anderson County, Texas POP_2023 57245
1 48003 Andrews County, Texas POP_2023 20536
2 48005 Angelina County, Texas POP_2023 87032
3 48007 Aransas County, Texas POP_2023 24425
4 48009 Archer County, Texas POP_2023 8560
Vintage vs. year¶
Population estimates use a vintage model. The vintage parameter
specifies which release of estimates to query, while the optional
year parameter filters to a specific data year within that vintage.
# All data from the 2023 vintage (default)
df = pypums.get_estimates(
geography="state",
vintage=2023,
)
# Only the 2021 estimate from the 2023 vintage
df_2021 = pypums.get_estimates(
geography="state",
vintage=2023,
year=2021,
)
What is a vintage?
A vintage is the reference year for an estimates release. The 2023 vintage contains estimates for July 1 of each year from the last decennial census (April 2020) through July 2023. Later vintages may revise earlier estimates based on updated data.
Breakdown dimensions¶
The breakdown parameter adds demographic dimensions to the query.
This is primarily used with product="characteristics".
Available breakdown dimensions:
| Dimension | breakdown= |
Values |
|---|---|---|
| Age group | "AGEGROUP" |
0=All ages, 1=0-4, 2=5-13, 3=14-17, 4=18-24, 5=25-44, 6=45-64, 7=65+, … |
| Sex | "SEX" |
0=Both, 1=Male, 2=Female |
| Race | "RACE" |
0=All, 1=White alone, 2=Black alone, 3=AIAN alone, 4=Asian alone, … |
| Hispanic origin | "HISP" |
0=Both, 1=Non-Hispanic, 2=Hispanic |
Single breakdown¶
by_sex = pypums.get_estimates(
geography="state",
product="characteristics",
breakdown="SEX",
vintage=2023,
)
print(by_sex.head(6))
GEOID NAME variable value SEX
0 01 Alabama POP_2023 5108468 0
1 01 Alabama POP_2023 2467520 1
2 01 Alabama POP_2023 2640948 2
3 02 Alaska POP_2023 733406 0
4 02 Alaska POP_2023 377621 1
5 02 Alaska POP_2023 355785 2
Multiple breakdowns¶
Pass a list to cross-tabulate by multiple dimensions:
by_age_sex = pypums.get_estimates(
geography="state",
product="characteristics",
breakdown=["AGEGROUP", "SEX"],
vintage=2023,
)
print(f"{len(by_age_sex)} rows (states × age groups × sexes)")
Breakdown labels¶
Numeric breakdown codes are not always intuitive. Set
breakdown_labels=True to add human-readable label columns:
labeled = pypums.get_estimates(
geography="state",
product="characteristics",
breakdown=["AGEGROUP", "SEX"],
breakdown_labels=True,
vintage=2023,
)
print(labeled[["NAME", "AGEGROUP", "AGEGROUP_label", "SEX", "SEX_label"]].head())
NAME AGEGROUP AGEGROUP_label SEX SEX_label
0 Alabama 0 All ages 0 Both sexes
1 Alabama 0 All ages 1 Male
2 Alabama 0 All ages 2 Female
3 Alabama 1 Age 0 to 4 years 0 Both sexes
4 Alabama 1 Age 0 to 4 years 1 Male
Available label mappings¶
| Code | Label |
|---|---|
| 0 | All ages |
| 1 | Age 0 to 4 years |
| 2 | Age 5 to 13 years |
| 3 | Age 14 to 17 years |
| 4 | Age 18 to 24 years |
| 5 | Age 25 to 44 years |
| 6 | Age 45 to 64 years |
| 7 | Age 65 years and over |
| 8 | Age 85 years and over |
| 9 | Age 0 to 17 years |
| 10 | Age 18 to 64 years |
| 11 | Age 18 years and over |
| 12 | Age 65 years and over |
| 13 | Under 18 years |
| 14 | 5 to 13 years |
| 15 | 14 to 17 years |
| 16 | 18 to 64 years |
| 17 | 16 years and over |
| 18 | Under 5 years |
| 29 | Age 0 to 14 years |
| 30 | Age 15 to 44 years |
| 31 | Age 16 years and over |
| Code | Label |
|---|---|
| 0 | Both sexes |
| 1 | Male |
| 2 | Female |
| Code | Label |
|---|---|
| 0 | All races |
| 1 | White alone |
| 2 | Black alone |
| 3 | American Indian and Alaska Native alone |
| 4 | Asian alone |
| 5 | Native Hawaiian and Other Pacific Islander alone |
| 6 | Two or more races |
| 7 | White alone or in combination |
| 8 | Black alone or in combination |
| 9 | American Indian and Alaska Native alone or in combination |
| 10 | Asian alone or in combination |
| 11 | Native Hawaiian and Other Pacific Islander alone or in combination |
| Code | Label |
|---|---|
| 0 | Both Hispanic Origins |
| 1 | Non-Hispanic |
| 2 | Hispanic |
Time series¶
Set time_series=True to retrieve estimates across multiple years
within a single vintage. This adds DATE_CODE and DATE_DESC columns
to the output.
pop_trend = pypums.get_estimates(
geography="state",
product="population",
vintage=2023,
time_series=True,
)
print(pop_trend[["NAME", "DATE_CODE", "DATE_DESC"]].head(8))
NAME DATE_CODE DATE_DESC
0 Alabama 1 4/1/2020 Census population
1 Alabama 2 4/1/2020 estimates base
2 Alabama 3 7/1/2020 population estimate
3 Alabama 4 7/1/2021 population estimate
4 Alabama 5 7/1/2022 population estimate
5 Alabama 6 7/1/2023 population estimate
6 Alaska 1 4/1/2020 Census population
7 Alaska 2 4/1/2020 estimates base
Time series is only available for population estimates
Setting time_series=True with any product other than
"population" will raise a ValueError. The Census API only
supports the time-series endpoint for the pep/population dataset.
Plotting a time series¶
import pandas as pd
ts = pypums.get_estimates(
geography="state",
product="population",
vintage=2023,
time_series=True,
)
# Filter to July 1 estimates (DATE_CODE >= 3)
july_ests = ts[ts["DATE_CODE"] >= 3].copy()
# Extract year from DATE_DESC
july_ests["year"] = july_ests["DATE_DESC"].str.extract(r"(\d{4})")
# Pivot for a specific state
ca = july_ests[july_ests["NAME"] == "California"]
print(ca[["year", "value"]])
Tidy vs. wide output¶
Components of change¶
The "components" product provides the building blocks of population
change: births, deaths, domestic migration, and international migration.
components = pypums.get_estimates(
geography="state",
product="components",
vintage=2023,
)
print(components.head(10))
GEOID NAME variable value
0 01 Alabama BIRTHS 57827
1 01 Alabama DEATHS 63415
2 01 Alabama NATURALCHG -5588
3 01 Alabama DOMESTICMIG 19826
4 01 Alabama INTERNATIONALMIG 5634
5 01 Alabama NETMIG 25460
6 02 Alaska BIRTHS 9821
7 02 Alaska DEATHS 5142
8 02 Alaska NATURALCHG 4679
9 02 Alaska DOMESTICMIG -10523
Common component variables include:
| Variable | Description |
|---|---|
BIRTHS |
Number of births |
DEATHS |
Number of deaths |
NATURALCHG |
Natural change (births minus deaths) |
DOMESTICMIG |
Net domestic migration |
INTERNATIONALMIG |
Net international migration |
NETMIG |
Net migration (domestic + international) |
Housing unit estimates¶
housing = pypums.get_estimates(
geography="county",
product="housing",
state="FL",
vintage=2023,
)
print(housing.head(3))
GEOID NAME variable value
0 12001 Alachua County, Florida HUEST_2023 125420
1 12003 Baker County, Florida HUEST_2023 11892
2 12005 Bay County, Florida HUEST_2023 102318
Geometry support¶
Set geometry=True to get a GeoDataFrame with cartographic boundary shapes:
pop_geo = pypums.get_estimates(
geography="county",
product="population",
state="WA",
vintage=2023,
geometry=True,
)
import altair as alt
alt.Chart(pop_geo).mark_geoshape(stroke="white", strokeWidth=0.5).encode(
color=alt.Color("value:Q", scale=alt.Scale(scheme="blues"), legend=alt.Legend(title="Population")),
tooltip=["NAME:N", alt.Tooltip("value:Q", format=",")],
).project("albersUsa").properties(width=500, height=400)
Interactive preview — state-level population estimates
The code above fetches county-level data for Washington state. The chart
below uses state-level data to show what geometry=True + Altair
looks like at a national scale.
Optional dependency
geometry=True requires geopandas. Install with
uv add "pypums[spatial]".
Caching¶
Cache responses locally with cache_table=True:
df = pypums.get_estimates(
geography="state",
product="population",
vintage=2023,
cache_table=True,
)
Debugging API calls¶
Set show_call=True to see the exact Census API URL and parameters:
Census API call: https://api.census.gov/data/2023/pep/population
Parameters: {'get': 'NAME', 'for': 'state:*', 'key': '...'}
Common patterns¶
Population change between two vintages¶
pop_2022 = pypums.get_estimates(
geography="state",
product="population",
vintage=2022,
)
pop_2023 = pypums.get_estimates(
geography="state",
product="population",
vintage=2023,
)
merged = pop_2022.merge(
pop_2023,
on=["GEOID", "NAME"],
suffixes=("_2022", "_2023"),
)
merged["change"] = merged["value_2023"] - merged["value_2022"]
merged["pct_change"] = merged["change"] / merged["value_2022"] * 100
print(merged.nlargest(5, "pct_change")[["NAME", "value_2022", "value_2023", "pct_change"]])
NAME value_2022 value_2023 pct_change
Texas 29527941 30503301 3.30
South Carolina 5282634 5373555 1.72
Florida 22244823 22610726 1.65
Idaho 1939033 1964726 1.33
Montana 1122867 1132812 0.89
Population by age group for a state¶
age_groups = pypums.get_estimates(
geography="state",
product="characteristics",
breakdown=["AGEGROUP"],
breakdown_labels=True,
vintage=2023,
state="CA",
)
# Filter to non-overlapping age groups
primary_ages = age_groups[age_groups["AGEGROUP"].isin([1, 2, 3, 4, 5, 6, 7])]
print(primary_ages[["NAME", "AGEGROUP_label", "value"]])
NAME AGEGROUP_label value
California Age 0 to 4 years 2285340
California Age 5 to 13 years 4812530
California Age 14 to 17 years 2012450
California Age 18 to 24 years 3521870
California Age 25 to 44 years 11234560
California Age 45 to 64 years 9542130
California Age 65 years and over 6556313
County-level race breakdown¶
race_data = pypums.get_estimates(
geography="county",
product="characteristics",
breakdown=["RACE", "HISP"],
breakdown_labels=True,
state="TX",
vintage=2023,
)
print(f"{len(race_data)} rows")
print(race_data[["NAME", "RACE_label", "HISP_label", "value"]].head(4))
7620 rows
NAME RACE_label HISP_label value
0 Anderson County, Texas All races Both Hispanic Origins 57245
1 Anderson County, Texas All races Non-Hispanic 43210
2 Anderson County, Texas All races Hispanic 14035
3 Anderson County, Texas White alone Both Hispanic Origins 39812
Components of change for fast-growing states¶
components = pypums.get_estimates(
geography="state",
product="components",
vintage=2023,
output="wide",
cache_table=True,
)
fastest = components.nlargest(10, "NETMIG")
print(fastest[["NAME", "NATURALCHG", "DOMESTICMIG", "INTERNATIONALMIG"]])
NAME NATURALCHG DOMESTICMIG INTERNATIONALMIG
Texas 152345 372456 115890
Florida -25412 318745 92534
North Carolina 25310 135420 28560
Arizona 18230 102345 15670
South Carolina 3520 85640 12340
Georgia 41230 78950 35120
Tennessee 10250 72560 14230
Nevada 8910 68540 12780
Idaho 4820 37280 3540
Colorado 18950 32450 15670
County population map¶
county_map = pypums.get_estimates(
geography="county",
product="population",
state="CO",
vintage=2023,
geometry=True,
)
import altair as alt
alt.Chart(county_map).mark_geoshape(stroke="white", strokeWidth=0.5).encode(
color=alt.Color("value:Q", scale=alt.Scale(scheme="yellowgreenblue"), legend=alt.Legend(title="Population")),
tooltip=["NAME:N", alt.Tooltip("value:Q", format=",")],
).project("albersUsa").properties(width=500, height=400)
See Also¶
- API Reference — Full
get_estimates()function signature - Multi-Year Analysis — Time series patterns and best practices for longitudinal work
- Geography & FIPS — Understanding geography levels and FIPS code lookups