from cendat import CenDatHelper
from dotenv import load_dotenv
import os
from pprint import pprint
import polars as pl
load_dotenv()
= CenDatHelper(key=os.getenv('CENSUS_API_KEY')) cdh
✅ API key loaded successfully.
August 13, 2025
The U.S. Census Bureau makes available through its free, public API mountains of data, though navigating it and acquiring the data you need can be tricky, especially for complex, nested geographic summary levels like those available in American Community Survey (ACS) data products. The Census Bureau provides buckets of features and nuance in its data portal https://data.census.gov, but this doesn’t address the need to acquire data programmatically or in aggregate across the many ‘drill-down’ geographies required for certain summary levels. The API addresses the first problem, but on its own it does nothing to address the second. To acquire estimates at certain levels–even for a single state–may require several thousand API queries, and while data can be downloaded in bulk from https://www2.census.gov, you may have to sift through a lot of what you don’t need to get at what you do. It’s also not well-suited for obtaining data in-script.
cendat
aims to address the programmatic need while leveraging automatic query building and concurrency to quickly and easily explore the API’s data offerings, and pull in the data you need. There are other Census API wrapper libraries available for Python, and I honestly haven’t assessed their capabilities more than superficially, but I like building my own tools, so here we are!
cendat
can be installed with pip
pip install cendat
and you can also install pandas
and/or polars
at the same time to enable optional methods to work with acquired data
pip install cendat[pandas]
pip install cendat[polars]
pip install cendat[all]
You can check out its documentation at https://pypi.org/project/cendat/.
You need an API key to get the most out of cendat
–you can get one here: https://api.census.gov/data/key_signup.html.
cendat
includes two classes: CenDatHelper
for exploring the API, locking in product, vintage, variable, and geographic selections, and getting data, and CenDatResponse
to represent the returned data structure and provide methods for conversion to pandas
or polars
DataFrames.
These classes can be used if you already know exactly what you want and how to specify it, but they also streamline the process of figuring out and selecting what you want through a consistent list \(\rightarrow\) set two-step that works for products, geographies, and variables. Once selections are locked in, the get_data()
method builds the queries, issues them concurrently, and organizes the results into a digestible format.
A CenDatHelper
object can be instantiated without argument, or with the key
parameter (as shown below) and/or years
, which can be provided as an integer or list of integers. API key and years can also be provided later via the load_key()
and set_years()
methods, respectively. To start exploring, we’ll provide a key but not specify any years of interest.
cendat
(currently, as of ver. 0.2.2) supports all aggregate and microdata products available in the API (I’m open to adding timeseries product handling as well, if anyone requests it). To explore those products, we use the list_products()
method. Let’s start by looking for anything related to the ACS 5-year detailed tables.
potential_products = cdh.list_products(
patterns=["acs", "5-year", "detailed"]
)
for product in potential_products:
print(product['title'])
American Community Survey: 5-Year Estimates: Detailed Tables 5-Year (2009/acs/acs5)
American Community Survey: 5-Year Estimates: American Indian and Alaska Native Detailed Tables 5-Year (2010/acs/acs5/aian)
ACS 5-Year Detailed Tables (2010/acs/acs5)
American Community Survey: 5-Year Estimates: Selected Population Detailed Tables 5-Year (2010/acs/acs5/spt)
ACS 5-Year Detailed Tables (2011/acs/acs5)
ACS 5-Year Detailed Tables (2012/acs/acs5)
ACS 5-Year Detailed Tables (2013/acs/acs5)
ACS 5-Year Detailed Tables (2014/acs/acs5)
ACS 5-Year AIAN Detailed Tables (2015/acs/acs5/aian)
ACS 5-Year Detailed Tables (2015/acs/acs5)
American Community Survey: 5-Year Estimates: Selected Population Detailed Tables 5-Year (2015/acs/acs5/spt)
ACS 5-Year Detailed Tables (2016/acs/acs5)
ACS 5-Year Detailed Tables (2017/acs/acs5)
American Community Survey: 5-Year Estimates: Detailed Tables 5-Year (2018/acs/acs5)
American Community Survey: 5-Year Estimates: Detailed Tables 5-Year (2019/acs/acs5)
American Community Survey: 5-Year Estimates: Detailed Tables 5-Year (2020/acs/acs5)
American Community Survey: 5-Year Estimates: American Indian and Alaska Native Detailed Tables 5-Year (2021/acs/acs5/aian)
American Community Survey: 5-Year Estimates: Detailed Tables 5-Year (2021/acs/acs5)
American Community Survey: 5-Year Estimates: Selected Population Detailed Tables 5-Year (2021/acs/acs5/spt)
American Community Survey: 5-Year Estimates: Detailed Tables 5-Year (2022/acs/acs5)
ACS 5-Year Detailed Tables (2023/acs/acs5)
Okay, that gives us a good starting point. We’ll get to filtering those results down in a moment, but first I want to highlight a couple of things about the code block above. First, you’ll notice that I provided patterns to filter the products by via the patterns
parameter. patterns
takes a string or list of strings compiled into (case-insensitive) regular expressions. We can control whether the patterns all have to be met (the default) or only one or more through the logic
parameter. To employ any
logic set logic=any
. If you don’t speak regex, fear not–just supply substrings you’d like to match. We can also control which portion of the metadata the patterns will be used on. The default (which we’ve used here) is to operate on the titles. The alternative is to operate on the descriptions, which can be achieved by setting match_in='desc'
. One other point. You’ll notice that I selectively printed only product['title']
. By default, the list_products()
method returns a list of dictionaries of which the title is only one item (we can force it to return only the title by setting to_dicts=False
). We’ll see some more of what’s returned after we filter our products down a bit more.
Okay, back to the results. A couple of things are obvious: the detailed tables products aren’t named consistently across years, and there are some special population products that are being picked up as well. We can also see that there’s a parenthetical portion to each product title that looks like a directory path–that’s parsed from the JSON packet returned by the API and tacked on by list_products()
. So, rather than trying to refine our patterns to capture the inconsistent portion of the titles, we can focus on that and try again.
potential_products = cdh.list_products(
patterns=[r"acs/acs5\)"]
)
for product in potential_products:
print(product['title'])
American Community Survey: 5-Year Estimates: Detailed Tables 5-Year (2009/acs/acs5)
ACS 5-Year Detailed Tables (2010/acs/acs5)
ACS 5-Year Detailed Tables (2011/acs/acs5)
ACS 5-Year Detailed Tables (2012/acs/acs5)
ACS 5-Year Detailed Tables (2013/acs/acs5)
ACS 5-Year Detailed Tables (2014/acs/acs5)
ACS 5-Year Detailed Tables (2015/acs/acs5)
ACS 5-Year Detailed Tables (2016/acs/acs5)
ACS 5-Year Detailed Tables (2017/acs/acs5)
American Community Survey: 5-Year Estimates: Detailed Tables 5-Year (2018/acs/acs5)
American Community Survey: 5-Year Estimates: Detailed Tables 5-Year (2019/acs/acs5)
American Community Survey: 5-Year Estimates: Detailed Tables 5-Year (2020/acs/acs5)
American Community Survey: 5-Year Estimates: Detailed Tables 5-Year (2021/acs/acs5)
American Community Survey: 5-Year Estimates: Detailed Tables 5-Year (2022/acs/acs5)
ACS 5-Year Detailed Tables (2023/acs/acs5)
Now we’ve filtered down to the core product we want, but let’s subset to only a couple years to keep things simple. We’ll also take a look at the full contents of the products’ dictionaries.
potential_products = cdh.list_products(
patterns=[r"acs/acs5\)"],
years=[2022, 2023]
)
for product in potential_products:
pprint(product)
{'desc': 'The American Community Survey (ACS) is an ongoing survey that '
'provides data every year -- giving communities the current '
'information they need to plan investments and services. The ACS '
'covers a broad range of topics about social, economic, demographic, '
'and housing characteristics of the U.S. population. Summary files '
'include the following geographies: nation, all states (including DC '
'and Puerto Rico), all metropolitan areas, all congressional '
'districts, all counties, all places, and all tracts and block '
'groups. Summary files contain the most detailed cross-tabulations, '
'many of which are published down to block groups. The data are '
'population and housing counts. There are over 64,000 variables in '
'this dataset.',
'is_aggregate': True,
'is_microdata': False,
'title': 'American Community Survey: 5-Year Estimates: Detailed Tables 5-Year '
'(2022/acs/acs5)',
'type': 'acs/acs5',
'url': 'http://api.census.gov/data/2022/acs/acs5',
'vintage': [2022]}
{'desc': 'The American Community Survey (ACS) is an ongoing survey that '
'provides data every year -- giving communities the current '
'information they need to plan investments and services. The ACS '
'covers a broad range of topics about social, economic, demographic, '
'and housing characteristics of the U.S. population. Summary files '
'include the following geographies: nation, all states (including DC '
'and Puerto Rico), all metropolitan areas, all congressional '
'districts, all counties, all places, and all tracts and block '
'groups. Summary files contain the most detailed cross-tabulations, '
'many of which are published down to block groups. The data are '
'population and housing counts. There are over 64,000 variables in '
'this dataset.',
'is_aggregate': True,
'is_microdata': False,
'title': 'ACS 5-Year Detailed Tables (2023/acs/acs5)',
'type': 'acs/acs5',
'url': 'http://api.census.gov/data/2023/acs/acs5',
'vintage': [2023]}
Okay, great–now we’ve got what we want, let’s lock in our selection.
Now, let’s start to think about which variables we want–according to the descriptions above, we have over 64,000 to choose from! Let’s assume we’re interested in median household income and average household size. We’ll start by printing out the full dictionary for only the first variable, just to get a sense of the information available to us.
potential_variables = cdh.list_variables(
patterns=["median household income", "household size"],
logic=any
)
# look at the first to get a sense of what the full dictionaries have to offer
pprint(potential_variables[0])
{'concept': 'Average Household Size of Occupied Housing Units by Tenure '
'(Hispanic or Latino Householder)',
'group': 'B25010I',
'label': 'Estimate!!Average household size --!!Total:!!Owner occupied',
'name': 'B25010I_002E',
'product': 'American Community Survey: 5-Year Estimates: Detailed Tables '
'5-Year (2022/acs/acs5)',
'sugg_wgt': 'N/A',
'type': 'float',
'url': 'http://api.census.gov/data/2022/acs/acs5',
'values': 'N/A',
'vintage': [2022]}
When comparing the full dictionary of the first variable to just its label, we can see that there are nuances not captured in the label alone. When we print out the name and label for all found variables, we can see there is duplication in the label due to the lost nuance.
# print only the titles for all of the variables
for variable in potential_variables:
print(variable['name'], variable['label'])
B25010I_002E Estimate!!Average household size --!!Total:!!Owner occupied
B25010I_001E Estimate!!Average household size --!!Total:
B25010I_003E Estimate!!Average household size --!!Total:!!Renter occupied
B25010_003E Estimate!!Average household size --!!Total:!!Renter occupied
B25010_002E Estimate!!Average household size --!!Total:!!Owner occupied
B25010_001E Estimate!!Average household size --!!Total:
B19013E_001E Estimate!!Median household income in the past 12 months (in 2022 inflation-adjusted dollars)
B19013D_001E Estimate!!Median household income in the past 12 months (in 2022 inflation-adjusted dollars)
B22008_002E Estimate!!Median household income in the past 12 months (in 2022 inflation-adjusted dollars)--!!Total:!!Household received Food Stamps/SNAP in the past 12 months
B22008_001E Estimate!!Median household income in the past 12 months (in 2022 inflation-adjusted dollars)--!!Total:
B19013I_001E Estimate!!Median household income in the past 12 months (in 2022 inflation-adjusted dollars)
B22008_003E Estimate!!Median household income in the past 12 months (in 2022 inflation-adjusted dollars)--!!Total:!!Household did not receive Food Stamps/SNAP in the past 12 months
B25010D_003E Estimate!!Average household size --!!Total:!!Renter occupied
B25010D_002E Estimate!!Average household size --!!Total:!!Owner occupied
B25010D_001E Estimate!!Average household size --!!Total:
B19013A_001E Estimate!!Median household income in the past 12 months (in 2022 inflation-adjusted dollars)
B19013F_001E Estimate!!Median household income in the past 12 months (in 2022 inflation-adjusted dollars)
B25010F_001E Estimate!!Average household size --!!Total:
B25010F_002E Estimate!!Average household size --!!Total:!!Owner occupied
B25010F_003E Estimate!!Average household size --!!Total:!!Renter occupied
B25010A_001E Estimate!!Average household size --!!Total:
B25010A_002E Estimate!!Average household size --!!Total:!!Owner occupied
B25010A_003E Estimate!!Average household size --!!Total:!!Renter occupied
B25010E_003E Estimate!!Average household size --!!Total:!!Renter occupied
B25010E_001E Estimate!!Average household size --!!Total:
B25010E_002E Estimate!!Average household size --!!Total:!!Owner occupied
B25119_002E Estimate!!Median household income in the past 12 months (in 2022 inflation-adjusted dollars) --!!Total:!!Owner occupied (dollars)
B25119_001E Estimate!!Median household income in the past 12 months (in 2022 inflation-adjusted dollars) --!!Total:
B25119_003E Estimate!!Median household income in the past 12 months (in 2022 inflation-adjusted dollars) --!!Total:!!Renter occupied (dollars)
B19013G_001E Estimate!!Median household income in the past 12 months (in 2022 inflation-adjusted dollars)
B25010B_001E Estimate!!Average household size --!!Total:
B25010B_003E Estimate!!Average household size --!!Total:!!Renter occupied
B25010B_002E Estimate!!Average household size --!!Total:!!Owner occupied
B25099_003E Estimate!!Median household income --!!Total:!!Median household income for units without a mortgage
B25099_001E Estimate!!Median household income --!!Total:
B25099_002E Estimate!!Median household income --!!Total:!!Median household income for units with a mortgage
B25010G_003E Estimate!!Average household size --!!Total:!!Renter occupied
B25010G_002E Estimate!!Average household size --!!Total:!!Owner occupied
B25010G_001E Estimate!!Average household size --!!Total:
B19013B_001E Estimate!!Median household income in the past 12 months (in 2022 inflation-adjusted dollars)
B25010H_003E Estimate!!Average household size --!!Total:!!Renter occupied
B25010H_002E Estimate!!Average household size --!!Total:!!Owner occupied
B25010H_001E Estimate!!Average household size --!!Total:
B29004_001E Estimate!!Median household income in the past 12 months (in 2022 inflation-adjusted dollars)
B19049_004E Estimate!!Median household income in the past 12 months (in 2022 inflation-adjusted dollars) --!!Total:!!Householder 45 to 64 years
B19049_005E Estimate!!Median household income in the past 12 months (in 2022 inflation-adjusted dollars) --!!Total:!!Householder 65 years and over
B19049_002E Estimate!!Median household income in the past 12 months (in 2022 inflation-adjusted dollars) --!!Total:!!Householder under 25 years
B19049_003E Estimate!!Median household income in the past 12 months (in 2022 inflation-adjusted dollars) --!!Total:!!Householder 25 to 44 years
B19049_001E Estimate!!Median household income in the past 12 months (in 2022 inflation-adjusted dollars) --!!Total:
B19013C_001E Estimate!!Median household income in the past 12 months (in 2022 inflation-adjusted dollars)
B19013_001E Estimate!!Median household income in the past 12 months (in 2022 inflation-adjusted dollars)
B25010C_002E Estimate!!Average household size --!!Total:!!Owner occupied
B25010C_001E Estimate!!Average household size --!!Total:
B25010C_003E Estimate!!Average household size --!!Total:!!Renter occupied
B19013H_001E Estimate!!Median household income in the past 12 months (in 2022 inflation-adjusted dollars)
B25010I_002E Estimate!!Average household size --!!Total:!!Owner occupied
B25010I_001E Estimate!!Average household size --!!Total:
B25010I_003E Estimate!!Average household size --!!Total:!!Renter occupied
B25010_003E Estimate!!Average household size --!!Total:!!Renter occupied
B25010_002E Estimate!!Average household size --!!Total:!!Owner occupied
B25010_001E Estimate!!Average household size --!!Total:
B19013E_001E Estimate!!Median household income in the past 12 months (in 2023 inflation-adjusted dollars)
B19013D_001E Estimate!!Median household income in the past 12 months (in 2023 inflation-adjusted dollars)
B22008_002E Estimate!!Median household income in the past 12 months (in 2023 inflation-adjusted dollars)--!!Total:!!Household received Food Stamps/SNAP in the past 12 months
B22008_001E Estimate!!Median household income in the past 12 months (in 2023 inflation-adjusted dollars)--!!Total:
B19013I_001E Estimate!!Median household income in the past 12 months (in 2023 inflation-adjusted dollars)
B22008_003E Estimate!!Median household income in the past 12 months (in 2023 inflation-adjusted dollars)--!!Total:!!Household did not receive Food Stamps/SNAP in the past 12 months
B25010D_003E Estimate!!Average household size --!!Total:!!Renter occupied
B25010D_002E Estimate!!Average household size --!!Total:!!Owner occupied
B25010D_001E Estimate!!Average household size --!!Total:
B19013A_001E Estimate!!Median household income in the past 12 months (in 2023 inflation-adjusted dollars)
B19013F_001E Estimate!!Median household income in the past 12 months (in 2023 inflation-adjusted dollars)
B25010F_001E Estimate!!Average household size --!!Total:
B25010F_002E Estimate!!Average household size --!!Total:!!Owner occupied
B25010F_003E Estimate!!Average household size --!!Total:!!Renter occupied
B25010A_001E Estimate!!Average household size --!!Total:
B25010A_002E Estimate!!Average household size --!!Total:!!Owner occupied
B25010A_003E Estimate!!Average household size --!!Total:!!Renter occupied
B25010E_003E Estimate!!Average household size --!!Total:!!Renter occupied
B25010E_001E Estimate!!Average household size --!!Total:
B25010E_002E Estimate!!Average household size --!!Total:!!Owner occupied
B25119_002E Estimate!!Median household income in the past 12 months (in 2023 inflation-adjusted dollars) --!!Total:!!Owner occupied (dollars)
B25119_001E Estimate!!Median household income in the past 12 months (in 2023 inflation-adjusted dollars) --!!Total:
B25119_003E Estimate!!Median household income in the past 12 months (in 2023 inflation-adjusted dollars) --!!Total:!!Renter occupied (dollars)
B19013G_001E Estimate!!Median household income in the past 12 months (in 2023 inflation-adjusted dollars)
B25010B_001E Estimate!!Average household size --!!Total:
B25010B_003E Estimate!!Average household size --!!Total:!!Renter occupied
B25010B_002E Estimate!!Average household size --!!Total:!!Owner occupied
B25099_003E Estimate!!Median household income --!!Total:!!Median household income for units without a mortgage
B25099_001E Estimate!!Median household income --!!Total:
B25099_002E Estimate!!Median household income --!!Total:!!Median household income for units with a mortgage
B25010G_003E Estimate!!Average household size --!!Total:!!Renter occupied
B25010G_002E Estimate!!Average household size --!!Total:!!Owner occupied
B25010G_001E Estimate!!Average household size --!!Total:
B19013B_001E Estimate!!Median household income in the past 12 months (in 2023 inflation-adjusted dollars)
B25010H_003E Estimate!!Average household size --!!Total:!!Renter occupied
B25010H_002E Estimate!!Average household size --!!Total:!!Owner occupied
B25010H_001E Estimate!!Average household size --!!Total:
B29004_001E Estimate!!Median household income in the past 12 months (in 2023 inflation-adjusted dollars)
B19049_004E Estimate!!Median household income in the past 12 months (in 2023 inflation-adjusted dollars) --!!Total:!!Householder 45 to 64 years
B19049_005E Estimate!!Median household income in the past 12 months (in 2023 inflation-adjusted dollars) --!!Total:!!Householder 65 years and over
B19049_002E Estimate!!Median household income in the past 12 months (in 2023 inflation-adjusted dollars) --!!Total:!!Householder under 25 years
B19049_003E Estimate!!Median household income in the past 12 months (in 2023 inflation-adjusted dollars) --!!Total:!!Householder 25 to 44 years
B19049_001E Estimate!!Median household income in the past 12 months (in 2023 inflation-adjusted dollars) --!!Total:
B19013C_001E Estimate!!Median household income in the past 12 months (in 2023 inflation-adjusted dollars)
B19013_001E Estimate!!Median household income in the past 12 months (in 2023 inflation-adjusted dollars)
B25010C_002E Estimate!!Average household size --!!Total:!!Owner occupied
B25010C_001E Estimate!!Average household size --!!Total:
B25010C_003E Estimate!!Average household size --!!Total:!!Renter occupied
B19013H_001E Estimate!!Median household income in the past 12 months (in 2023 inflation-adjusted dollars)
In our next pass we’ll filter to promising variables based on label, but we’ll print the variable name and concept, as that seems to capture the nuance we need to be aware of.
potential_variables = cdh.list_variables(
patterns=[
r"Average household size --!!Total:$",
r"Median household income in the past 12 months \(in 202\d inflation-adjusted dollars\)$"
],
logic=any
)
for variable in potential_variables:
print(variable['name'], "\n", variable['concept'])
B25010I_001E
Average Household Size of Occupied Housing Units by Tenure (Hispanic or Latino Householder)
B25010_001E
Average Household Size of Occupied Housing Units by Tenure
B19013E_001E
Median Household Income in the Past 12 Months (in 2022 Inflation-Adjusted Dollars) (Native Hawaiian and Other Pacific Islander Alone Householder)
B19013D_001E
Median Household Income in the Past 12 Months (in 2022 Inflation-Adjusted Dollars) (Asian Alone Householder)
B19013I_001E
Median Household Income in the Past 12 Months (in 2022 Inflation-Adjusted Dollars) (Hispanic or Latino Householder)
B25010D_001E
Average Household Size of Occupied Housing Units by Tenure (Asian Alone Householder)
B19013A_001E
Median Household Income in the Past 12 Months (in 2022 Inflation-Adjusted Dollars) (White Alone Householder)
B19013F_001E
Median Household Income in the Past 12 Months (in 2022 Inflation-Adjusted Dollars) (Some Other Race Alone Householder)
B25010F_001E
Average Household Size of Occupied Housing Units by Tenure (Some Other Race Alone Householder)
B25010A_001E
Average Household Size of Occupied Housing Units by Tenure (White Alone Householder)
B25010E_001E
Average Household Size of Occupied Housing Units by Tenure (Native Hawaiian and Other Pacific Islander Alone Householder)
B19013G_001E
Median Household Income in the Past 12 Months (in 2022 Inflation-Adjusted Dollars) (Two or More Races Householder)
B25010B_001E
Average Household Size of Occupied Housing Units by Tenure (Black or African American Alone Householder)
B25010G_001E
Average Household Size of Occupied Housing Units by Tenure (Two or More Races Householder)
B19013B_001E
Median Household Income in the Past 12 Months (in 2022 Inflation-Adjusted Dollars) (Black or African American Alone Householder)
B25010H_001E
Average Household Size of Occupied Housing Units by Tenure (White Alone, Not Hispanic or Latino Householder)
B29004_001E
Median Household Income for Households With a Citizen, Voting-Age Householder (in 2022 Inflation-Adjusted Dollars)
B19013C_001E
Median Household Income in the Past 12 Months (in 2022 Inflation-Adjusted Dollars) (American Indian and Alaska Native Alone Householder)
B19013_001E
Median Household Income in the Past 12 Months (in 2022 Inflation-Adjusted Dollars)
B25010C_001E
Average Household Size of Occupied Housing Units by Tenure (American Indian and Alaska Native Alone Householder)
B19013H_001E
Median Household Income in the Past 12 Months (in 2022 Inflation-Adjusted Dollars) (White Alone, Not Hispanic or Latino Householder)
B25010I_001E
Average Household Size of Occupied Housing Units by Tenure (Hispanic or Latino Householder)
B25010_001E
Average Household Size of Occupied Housing Units by Tenure
B19013E_001E
Median Household Income in the Past 12 Months (in 2023 Inflation-Adjusted Dollars) (Native Hawaiian and Other Pacific Islander Alone Householder)
B19013D_001E
Median Household Income in the Past 12 Months (in 2023 Inflation-Adjusted Dollars) (Asian Alone Householder)
B19013I_001E
Median Household Income in the Past 12 Months (in 2023 Inflation-Adjusted Dollars) (Hispanic or Latino Householder)
B25010D_001E
Average Household Size of Occupied Housing Units by Tenure (Asian Alone Householder)
B19013A_001E
Median Household Income in the Past 12 Months (in 2023 Inflation-Adjusted Dollars) (White Alone Householder)
B19013F_001E
Median Household Income in the Past 12 Months (in 2023 Inflation-Adjusted Dollars) (Some Other Race Alone Householder)
B25010F_001E
Average Household Size of Occupied Housing Units by Tenure (Some Other Race Alone Householder)
B25010A_001E
Average Household Size of Occupied Housing Units by Tenure (White Alone Householder)
B25010E_001E
Average Household Size of Occupied Housing Units by Tenure (Native Hawaiian and Other Pacific Islander Alone Householder)
B19013G_001E
Median Household Income in the Past 12 Months (in 2023 Inflation-Adjusted Dollars) (Two or More Races Householder)
B25010B_001E
Average Household Size of Occupied Housing Units by Tenure (Black or African American Alone Householder)
B25010G_001E
Average Household Size of Occupied Housing Units by Tenure (Two or More Races Householder)
B19013B_001E
Median Household Income in the Past 12 Months (in 2023 Inflation-Adjusted Dollars) (Black or African American Alone Householder)
B25010H_001E
Average Household Size of Occupied Housing Units by Tenure (White Alone, Not Hispanic or Latino Householder)
B29004_001E
Median Household Income for Households With a Citizen, Voting-Age Householder (in 2023 Inflation-Adjusted Dollars)
B19013C_001E
Median Household Income in the Past 12 Months (in 2023 Inflation-Adjusted Dollars) (American Indian and Alaska Native Alone Householder)
B19013_001E
Median Household Income in the Past 12 Months (in 2023 Inflation-Adjusted Dollars)
B25010C_001E
Average Household Size of Occupied Housing Units by Tenure (American Indian and Alaska Native Alone Householder)
B19013H_001E
Median Household Income in the Past 12 Months (in 2023 Inflation-Adjusted Dollars) (White Alone, Not Hispanic or Latino Householder)
Based on this output we can see that we’re interested in B25010_001E
and B19013_001E
–we can set these variables explicitly.
✅ Variables set:
- Product: American Community Survey: 5-Year Estimates: Detailed Tables 5-Year (2022/acs/acs5) (Vintage: [2022])
Variables: B25010_001E, B19013_001E
- Product: ACS 5-Year Detailed Tables (2023/acs/acs5) (Vintage: [2023])
Variables: B25010_001E, B19013_001E
Next we need to select the geographic level at which we want to get our estimates. Let’s assume we’re interested in block groups and filter accordingly. That should give us a small number of options, so we’ll output and view the complete dictionaries.
[{'desc': 'block group',
'optionalWithWCFor': 'tract',
'product': 'American Community Survey: 5-Year Estimates: Detailed Tables '
'5-Year (2022/acs/acs5)',
'requires': ['state', 'county', 'tract'],
'sumlev': '150',
'url': 'http://api.census.gov/data/2022/acs/acs5',
'vintage': [2022],
'wildcard': ['county', 'tract']},
{'desc': 'tribal block group',
'optionalWithWCFor': None,
'product': 'American Community Survey: 5-Year Estimates: Detailed Tables '
'5-Year (2022/acs/acs5)',
'requires': ['american indian area/alaska native area/hawaiian home land',
'tribal census tract'],
'sumlev': '258',
'url': 'http://api.census.gov/data/2022/acs/acs5',
'vintage': [2022],
'wildcard': None},
{'desc': 'tribal block group (or part)',
'optionalWithWCFor': None,
'product': 'American Community Survey: 5-Year Estimates: Detailed Tables '
'5-Year (2022/acs/acs5)',
'requires': ['american indian area/alaska native area (reservation or '
'statistical entity only)',
'tribal census tract (or part)'],
'sumlev': '293',
'url': 'http://api.census.gov/data/2022/acs/acs5',
'vintage': [2022],
'wildcard': None},
{'desc': 'tribal block group (or part)',
'optionalWithWCFor': None,
'product': 'American Community Survey: 5-Year Estimates: Detailed Tables '
'5-Year (2022/acs/acs5)',
'requires': ['american indian area (off-reservation trust land '
'only)/hawaiian home land',
'tribal census tract (or part)'],
'sumlev': '294',
'url': 'http://api.census.gov/data/2022/acs/acs5',
'vintage': [2022],
'wildcard': None},
{'desc': 'block group',
'optionalWithWCFor': 'tract',
'product': 'ACS 5-Year Detailed Tables (2023/acs/acs5)',
'requires': ['state', 'county', 'tract'],
'sumlev': '150',
'url': 'http://api.census.gov/data/2023/acs/acs5',
'vintage': [2023],
'wildcard': ['county', 'tract']},
{'desc': 'tribal block group',
'optionalWithWCFor': None,
'product': 'ACS 5-Year Detailed Tables (2023/acs/acs5)',
'requires': ['american indian area/alaska native area/hawaiian home land',
'tribal census tract'],
'sumlev': '258',
'url': 'http://api.census.gov/data/2023/acs/acs5',
'vintage': [2023],
'wildcard': None},
{'desc': 'tribal block group (or part)',
'optionalWithWCFor': None,
'product': 'ACS 5-Year Detailed Tables (2023/acs/acs5)',
'requires': ['american indian area/alaska native area (reservation or '
'statistical entity only)',
'tribal census tract (or part)'],
'sumlev': '293',
'url': 'http://api.census.gov/data/2023/acs/acs5',
'vintage': [2023],
'wildcard': None},
{'desc': 'tribal block group (or part)',
'optionalWithWCFor': None,
'product': 'ACS 5-Year Detailed Tables (2023/acs/acs5)',
'requires': ['american indian area (off-reservation trust land '
'only)/hawaiian home land',
'tribal census tract (or part)'],
'sumlev': '294',
'url': 'http://api.census.gov/data/2023/acs/acs5',
'vintage': [2023],
'wildcard': None}]
A couple of things to note here. First, we can see in the first dictionary that it is the one we’re interested in (summary level 150). Second, the dictionary includes 'requires': ['state', 'county', 'tract']
which indicates that in order to get estimates for a set of block groups from the API, the query must specify the parent state, county, and tract. So, we will need multiple API calls–one for every tract, each of which also has to provide a specific state and county.
Notably, there are nearly 84,000 tracts in continental U.S.! To keep this example a little more bite-sized, we’ll focus on just three counties in Colorado and the entire state of Wyoming (mostly to illustrate the flexibility we have in providing nesting information in within
). Now, that’s still a lot of tracts, but CenDatHelper
does the hard work of figuring out all of the state-county-tract combinations and building/issuing the necessary API calls. First we set our summary level of interest, then we get the data. Let’s see how that works.
%%time
#| results: markup
cdh.set_geos("150")
response = cdh.get_data(
within=[
{"state": "08", "county": ["123", "013"]},
{"state": "08", "county": "069", "tract": ["001307", "001810","001308"]},
{"state": "56"}
]
)
✅ Geographies set: 'block group' (requires `within` for: county, state, tract)
✅ Parameters created for 2 geo-variable combinations.
✅ Found 1 combinations. Building API queries...
✅ Found 1 combinations. Building API queries...
✅ Found 1 combinations. Building API queries...
✅ Found 1 combinations. Building API queries...
✅ Found 1 combinations. Building API queries...
✅ Found 1 combinations. Building API queries...
✅ Found 1 combinations. Building API queries...
✅ Found 1 combinations. Building API queries...
✅ Found 1 combinations. Building API queries...
✅ Found 1 combinations. Building API queries...
✅ Found 1 combinations. Building API queries...
✅ Found 1 combinations. Building API queries...
ℹ️ Making 12 API call(s)...
CPU times: user 97.4 ms, sys: 26.4 ms, total: 124 ms
Wall time: 1.75 s
We can see that in the end 648 API calls were needed to satisfy our request, but due to the wonders of thread pooling, it didn’t take very long at all. Note that this example illustrates the flexibility of within
pretty well–we can specify parent geographies as a list of dictionaries at different levels of the geographic hierarchy above the summary level of interest. Since we’re pulling block groups, we can provide state, state and county, or state, county and tract. Within each dictionary, the last item’s values may be provided as a list. We see that above for counties in the first dictionary and tracts in the second. Technically, any item’s values can be provided as a list, we will just issue some API calls that won’t work as intended (we’ll get, say, counties from two different states because they have the same county code) or that won’t work at all (we’ll end up looking for parent geography combinations that don’t exist), and we’ll see messages to that effect in the output. For predictable output, it’s best to stick to lists only in the last item of a given dictionary.
Our final step is to convert our output to a more friendly format.
df = pl.concat(
response.to_polars(
schema_overrides={
"B25010_001E": pl.Float64,
"B19013_001E": pl.Float64,
}
)
)
print(df.head())
shape: (5, 10)
┌─────────────┬─────────────┬───────┬────────┬───┬────────────────┬─────────┬────────┬─────────────┐
│ B25010_001E ┆ B19013_001E ┆ state ┆ county ┆ … ┆ product ┆ vintage ┆ sumlev ┆ desc │
│ --- ┆ --- ┆ --- ┆ --- ┆ ┆ --- ┆ --- ┆ --- ┆ --- │
│ f64 ┆ f64 ┆ str ┆ str ┆ ┆ str ┆ i32 ┆ str ┆ str │
╞═════════════╪═════════════╪═══════╪════════╪═══╪════════════════╪═════════╪════════╪═════════════╡
│ 2.14 ┆ 143594.0 ┆ 08 ┆ 069 ┆ … ┆ American ┆ 2022 ┆ 150 ┆ block group │
│ ┆ ┆ ┆ ┆ ┆ Community ┆ ┆ ┆ │
│ ┆ ┆ ┆ ┆ ┆ Survey: 5-Y… ┆ ┆ ┆ │
│ 2.18 ┆ 131786.0 ┆ 08 ┆ 069 ┆ … ┆ American ┆ 2022 ┆ 150 ┆ block group │
│ ┆ ┆ ┆ ┆ ┆ Community ┆ ┆ ┆ │
│ ┆ ┆ ┆ ┆ ┆ Survey: 5-Y… ┆ ┆ ┆ │
│ 2.76 ┆ 82759.0 ┆ 08 ┆ 069 ┆ … ┆ American ┆ 2022 ┆ 150 ┆ block group │
│ ┆ ┆ ┆ ┆ ┆ Community ┆ ┆ ┆ │
│ ┆ ┆ ┆ ┆ ┆ Survey: 5-Y… ┆ ┆ ┆ │
│ 2.5 ┆ 145000.0 ┆ 08 ┆ 069 ┆ … ┆ American ┆ 2022 ┆ 150 ┆ block group │
│ ┆ ┆ ┆ ┆ ┆ Community ┆ ┆ ┆ │
│ ┆ ┆ ┆ ┆ ┆ Survey: 5-Y… ┆ ┆ ┆ │
│ 2.41 ┆ 87798.0 ┆ 08 ┆ 069 ┆ … ┆ American ┆ 2022 ┆ 150 ┆ block group │
│ ┆ ┆ ┆ ┆ ┆ Community ┆ ┆ ┆ │
│ ┆ ┆ ┆ ┆ ┆ Survey: 5-Y… ┆ ┆ ┆ │
└─────────────┴─────────────┴───────┴────────┴───┴────────────────┴─────────┴────────┴─────────────┘
Here I’ve chosen to convert to polars DataFrames (the response is a list with entries for each product vintage, so to_polars()
generates a list of DataFrames - pl.concat()
stacks them). I’ve also forced my estimate variables to float type, as they come from the API as strings.
That’s all for now! Development is ongoing, and I plan to add new features as they occur to me. Don’t hesitate to reach out of there’s anything you’d like to see (or if you spot any bugs).
@online{couzens2025,
author = {Couzens, Lance},
title = {Introducing Cendat, a {Python} {Library} for {Simplifying}
and {Speeding} up {Use} of the {Census} {API}},
date = {2025-08-13},
url = {https://mostlyunoriginal.github.io/posts/2025-08-13-Introducing-cendat/},
langid = {en}
}