morpc-census connects to the US Census Bureau API, retrieves survey data, and returns it as a normalized long-format DataFrame ready for analysis. Every request is defined by:
an Endpoint — survey dataset and vintage year
a scope — geographic extent (a region, county, state, etc.)
a Group or a list of variables — what to retrieve
an optional sumlevel — resolution within the scope (county, tract, place, etc.)
This notebook walks through a typical workflow from discovery to analysis to saving output.
from morpc.logs import config_logs
config_logs('morpc-census-demo.log', 'debug')2026-05-12 10:58:46,838 | INFO | morpc.logs.config_logs: Set up logging save to file "morpc-census-demo.log", log level "debug"
from morpc_census import (
Endpoint, Group, CensusAPI, DimensionTable,
get_all_avail_endpoints,
IMPLEMENTED_ENDPOINTS, SCOPES,
)
import pandas as pd1. Endpoints — choosing a survey and year¶
IMPLEMENTED_ENDPOINTS lists every survey/table the package supports. Endpoint validates the survey name and vintage year at construction time, so you find out immediately if you’ve specified something unavailable.
# Surveys the package supports
IMPLEMENTED_ENDPOINTS['acs/acs1',
'acs/acs1/profile',
'acs/acs1/subject',
'acs/acs5',
'acs/acs5/profile',
'acs/acs5/subject',
'dec/pl',
'dec/dhc',
'dec/ddhca',
'dec/ddhcb',
'dec/sf1',
'dec/sf2',
'dec/sf3',
'geoinfo']# Construct an endpoint — raises ValueError for unknown surveys or unavailable years
ep = Endpoint('acs/acs5', 2023)
ep2026-05-12 10:58:51,990 | DEBUG | morpc.req.get_json_safely: Getting data from https://api.census.gov/data with parameters {'key': '83269ff2739cb3bd485c75b091dcee493ad6fe70'}.
2026-05-12 10:58:51,996 | DEBUG | urllib3.connectionpool._new_conn: Starting new HTTPS connection (1): api.census.gov:443
2026-05-12 10:58:52,210 | DEBUG | urllib3.connectionpool._make_request: https://api.census.gov:443 "GET /data?key=83269ff2739cb3bd485c75b091dcee493ad6fe70 HTTP/1.1" 302 None
2026-05-12 10:58:52,281 | DEBUG | urllib3.connectionpool._make_request: https://api.census.gov:443 "GET /data/?key=83269ff2739cb3bd485c75b091dcee493ad6fe70 HTTP/1.1" 200 None
2026-05-12 10:58:54,968 | DEBUG | morpc.req.get_json_safely: Request successful. Decoding return JSON.
Endpoint('acs/acs5', 2023)# All vintage years available for this survey
ep.vintages[2009,
2010,
2011,
2012,
2013,
2014,
2015,
2016,
2017,
2018,
2019,
2020,
2021,
2022,
2023,
2024]2. Groups — browsing available variables¶
Network required — the cells below make live calls to the Census API.
A group is a table within a survey — a collection of related variables. endpoint.groups returns every group with its description. Fetched once, then cached on the object.
# Browse available groups — descriptions keyed by group code
{k: v['description'] for k, v in list(ep.groups.items())[:10]}2026-05-12 10:59:02,357 | DEBUG | morpc_census.api.groups: Fetching groups for 2023 acs/acs5
2026-05-12 10:59:02,359 | DEBUG | morpc.req.get_json_safely: Getting data from https://api.census.gov/data/2023/acs/acs5/groups.json with parameters {'key': '83269ff2739cb3bd485c75b091dcee493ad6fe70'}.
2026-05-12 10:59:02,361 | DEBUG | urllib3.connectionpool._new_conn: Starting new HTTPS connection (1): api.census.gov:443
2026-05-12 10:59:02,589 | DEBUG | urllib3.connectionpool._make_request: https://api.census.gov:443 "GET /data/2023/acs/acs5/groups.json?key=83269ff2739cb3bd485c75b091dcee493ad6fe70 HTTP/1.1" 200 None
2026-05-12 10:59:02,973 | DEBUG | morpc.req.get_json_safely: Request successful. Decoding return JSON.
{'B01001': 'Sex by Age',
'B01001A': 'Sex by Age (White Alone)',
'B01001B': 'Sex by Age (Black or African American Alone)',
'B01001C': 'Sex by Age (American Indian and Alaska Native Alone)',
'B01001D': 'Sex by Age (Asian Alone)',
'B01001E': 'Sex by Age (Native Hawaiian and Other Pacific Islander Alone)',
'B01001F': 'Sex by Age (Some Other Race Alone)',
'B01001G': 'Sex by Age (Two or More Races)',
'B01001H': 'Sex by Age (White Alone, Not Hispanic or Latino)',
'B01001I': 'Sex by Age (Hispanic or Latino)'}# Construct a Group — validates the code against endpoint.groups
group = Group(ep, 'B01001')
print(group.description)
print(group.universe)Sex by Age
Total population
# Variable codes and labels for this group
{k: v.get('label', '') for k, v in list(group.variables.items())[:8]}2026-05-12 10:59:05,520 | DEBUG | morpc.req.get_json_safely: Getting data from https://api.census.gov/data/2023/acs/acs5/groups/B01001.json with parameters {'key': '83269ff2739cb3bd485c75b091dcee493ad6fe70'}.
2026-05-12 10:59:05,523 | DEBUG | urllib3.connectionpool._new_conn: Starting new HTTPS connection (1): api.census.gov:443
2026-05-12 10:59:05,738 | DEBUG | urllib3.connectionpool._make_request: https://api.census.gov:443 "GET /data/2023/acs/acs5/groups/B01001.json?key=83269ff2739cb3bd485c75b091dcee493ad6fe70 HTTP/1.1" 200 19404
2026-05-12 10:59:05,771 | DEBUG | morpc.req.get_json_safely: Request successful. Decoding return JSON.
{'B01001_001E': 'Estimate!!Total:',
'B01001_001EA': 'Annotation of Estimate!!Total:',
'B01001_001M': 'Margin of Error!!Total:',
'B01001_001MA': 'Annotation of Margin of Error!!Total:',
'B01001_002E': 'Estimate!!Total:!!Male:',
'B01001_002EA': 'Annotation of Estimate!!Total:!!Male:',
'B01001_002M': 'Margin of Error!!Total:!!Male:',
'B01001_002MA': 'Annotation of Margin of Error!!Total:!!Male:'}3. Scopes — choosing a geographic extent¶
A scope names the geographic coverage of the request — which places to include. SCOPES lists all built-in scopes. Pass a scope key to CensusAPI as a string.
# Available scope keys
list(SCOPES.keys())['us',
'columbuscbsa',
'alabama',
'alaska',
'arizona',
'arkansas',
'california',
'colorado',
'connecticut',
'delaware',
'district of columbia',
'florida',
'georgia',
'hawaii',
'idaho',
'illinois',
'indiana',
'iowa',
'kansas',
'kentucky',
'louisiana',
'maine',
'maryland',
'massachusetts',
'michigan',
'minnesota',
'mississippi',
'missouri',
'montana',
'nebraska',
'nevada',
'new hampshire',
'new jersey',
'new mexico',
'new york',
'north carolina',
'north dakota',
'ohio',
'oklahoma',
'oregon',
'pennsylvania',
'rhode island',
'south carolina',
'south dakota',
'tennessee',
'texas',
'utah',
'vermont',
'virginia',
'washington',
'west virginia',
'wisconsin',
'wyoming',
'puerto rico',
'lake',
'hancock',
'allen',
'morgan',
'portage',
'butler',
'fayette',
'vinton',
'paulding',
'scioto',
'fulton',
'henry',
'logan',
'brown',
'monroe',
'trumbull',
'pike',
'pickaway',
'muskingum',
'lawrence',
'adams',
'crawford',
'guernsey',
'van wert',
'ottawa',
'harrison',
'richland',
'champaign',
'wayne',
'clark',
'hocking',
'miami',
'darke',
'lucas',
'ashtabula',
'preble',
'jackson',
'stark',
'greene',
'ashland',
'mahoning',
'tuscarawas',
'highland',
'carroll',
'belmont',
'meigs',
'medina',
'gallia',
'coshocton',
'huron',
'wood',
'franklin',
'seneca',
'williams',
'union',
'cuyahoga',
'summit',
'warren',
'madison',
'geauga',
'fairfield',
'hardin',
'hamilton',
'knox',
'shelby',
'clermont',
'noble',
'putnam',
'holmes',
'columbiana',
'licking',
'ross',
'montgomery',
'marion',
'perry',
'defiance',
'erie',
'auglaize',
'athens',
'jefferson',
'morrow',
'clinton',
'lorain',
'sandusky',
'wyandot',
'mercer',
'region15',
'region10',
'region7',
'regioncorpo',
'regionceds',
'regioncbsa',
'regionmobility',
'regionmpo']4. Fetching data¶
CensusAPI validates parameters, builds the request, fetches the data, and transforms it into a normalized long-format table in one step. api.data holds the raw wide response from the API; api.long is the normalized output.
# Sex by age for all counties in the 15-county MORPC region
b01001 = CensusAPI(ep, 'region15', group='b01001')
b01001.data2026-05-12 10:59:12,855 | INFO | morpc_census.api.CensusAPI.census-acs-acs5-2023-region15-b01001.__init__: Initializing CensusAPI for census-acs-acs5-2023-region15-b01001.
2026-05-12 10:59:12,856 | INFO | morpc_census.api.CensusAPI.census-acs-acs5-2023-region15-b01001.__init__: Building request URL and parameters.
2026-05-12 10:59:12,858 | DEBUG | morpc_census.geos.geoids_from_scope: Fetching geoids from scope 'region15'.
2026-05-12 10:59:12,860 | DEBUG | morpc.req.get_json_safely: Getting data from https://api.census.gov/data/2023/geoinfo?get=GEO_ID with parameters {'for': 'county:041,045,049,089,097,129,159,083,101,117,047,073,091,127,141', 'in': 'state:39', 'key': '83269ff2739cb3bd485c75b091dcee493ad6fe70'}.
2026-05-12 10:59:12,862 | DEBUG | urllib3.connectionpool._new_conn: Starting new HTTPS connection (1): api.census.gov:443
2026-05-12 10:59:13,311 | DEBUG | urllib3.connectionpool._make_request: https://api.census.gov:443 "GET /data/2023/geoinfo?get=GEO_ID&for=county%3A041%2C045%2C049%2C089%2C097%2C129%2C159%2C083%2C101%2C117%2C047%2C073%2C091%2C127%2C141&in=state%3A39&key=83269ff2739cb3bd485c75b091dcee493ad6fe70 HTTP/1.1" 200 None
2026-05-12 10:59:13,313 | DEBUG | morpc.req.get_json_safely: Request successful. Decoding return JSON.
2026-05-12 10:59:13,317 | DEBUG | morpc_census.geos.geoinfo_from_scope_sumlevel: Building parameters for scope 'region15' at sumlevel None.
2026-05-12 10:59:13,318 | INFO | morpc_census.geos.geoinfo_from_scope_sumlevel: No sumlevel specified; using scope 'region15' parameters.
2026-05-12 10:59:13,319 | INFO | morpc_census.api.CensusAPI.census-acs-acs5-2023-region15-b01001.__init__: Fetching data from https://api.census.gov/data/2023/acs/acs5? with params {'get': 'group(B01001)', 'for': 'county:041,045,049,089,097,129,159,083,101,117,047,073,091,127,141', 'in': 'state:39'}.
2026-05-12 10:59:13,321 | INFO | morpc_census.api.CensusAPI.census-acs-acs5-2023-region15-b01001._fetch_group: Fetching group(B01001) — all variables, no limit.
2026-05-12 10:59:13,322 | DEBUG | morpc.req.get_text_safely: Getting data from https://api.census.gov/data/2023/acs/acs5?get=group(B01001)&for=county:041,045,049,089,097,129,159,083,101,117,047,073,091,127,141&in=state:39&key=83269ff2739cb3bd485c75b091dcee493ad6fe70 with parameters None.
2026-05-12 10:59:13,326 | DEBUG | urllib3.connectionpool._new_conn: Starting new HTTPS connection (1): api.census.gov:443
2026-05-12 10:59:13,843 | DEBUG | urllib3.connectionpool._make_request: https://api.census.gov:443 "GET /data/2023/acs/acs5?get=group(B01001)&for=county:041,045,049,089,097,129,159,083,101,117,047,073,091,127,141&in=state:39&key=83269ff2739cb3bd485c75b091dcee493ad6fe70 HTTP/1.1" 200 None
2026-05-12 10:59:13,876 | DEBUG | morpc.req.get_text_safely: Request successful. Returning plain text.
2026-05-12 10:59:13,917 | INFO | morpc_census.api.CensusAPI.census-acs-acs5-2023-region15-b01001.melt: Melting data to long format.
2026-05-12 10:59:13,938 | DEBUG | morpc.req.get_json_safely: Getting data from https://api.census.gov/data/2023/acs/acs5/groups/B01001.json with parameters {'key': '83269ff2739cb3bd485c75b091dcee493ad6fe70'}.
2026-05-12 10:59:13,940 | DEBUG | urllib3.connectionpool._new_conn: Starting new HTTPS connection (1): api.census.gov:443
2026-05-12 10:59:14,138 | DEBUG | urllib3.connectionpool._make_request: https://api.census.gov:443 "GET /data/2023/acs/acs5/groups/B01001.json?key=83269ff2739cb3bd485c75b091dcee493ad6fe70 HTTP/1.1" 200 19404
2026-05-12 10:59:14,170 | DEBUG | morpc.req.get_json_safely: Request successful. Decoding return JSON.
5. The long-format result¶
api.long is the normalized output: one row per geography × variable. Type suffixes are split into separate columns (estimate, moe), variable labels are extracted from the raw label strings, and Census missing-value codes are converted to NaN.
# Long-format output — one row per geography × variable
b01001.long# Column types in the long-format output
b01001.long.dtypesgeoidfq object
name object
reference_period int64
survey object
concept object
universe object
variable_label object
variable object
estimate int64
moe int64
dtype: object6. GEOIDFQs — parsing geography identifiers¶
The GEOIDFQ column holds fully-qualified geography identifiers (e.g. 0500000US39049). api.geoidfqs parses each one into a GeoIDFQ object with typed fields for summary level, variant, and component codes.
# First three geography IDs parsed as GeoIDFQ objects
b01001.geoidfqs[:3][GeoIDFQ(sumlevel='050', variant='00', geocomp='00', state='39', county='041'),
GeoIDFQ(sumlevel='050', variant='00', geocomp='00', state='39', county='045'),
GeoIDFQ(sumlevel='050', variant='00', geocomp='00', state='39', county='047')]# Fields on a GeoIDFQ object
g = b01001.geoidfqs[0]
print('summary level:', g.sumlevel)
print('geoid: ', g.geoid)
print('parts: ', g.parts)
print('string form: ', str(g))summary level: '050'
geoid: 39041
parts: {'state': '39', 'county': '041'}
string form: 0500000US39041
7. Drilling down with sumlevel¶
The sumlevel parameter fetches child geographies within the scope. For example, sumlevel='tract' with scope='franklin' returns all census tracts in Franklin County.
# Place of birth for all tracts in Franklin County
b05006_tracts = CensusAPI(ep, 'franklin', group='B05006', sumlevel='tract')
b05006_tracts.long2026-05-12 10:59:26,158 | INFO | morpc_census.api.CensusAPI.census-acs-acs5-2023-countytract-franklin-b05006.__init__: Initializing CensusAPI for census-acs-acs5-2023-countytract-franklin-b05006.
2026-05-12 10:59:26,160 | INFO | morpc_census.api.CensusAPI.census-acs-acs5-2023-countytract-franklin-b05006.__init__: Building request URL and parameters.
2026-05-12 10:59:26,161 | DEBUG | morpc_census.geos.geoids_from_scope: Fetching geoids from scope 'franklin'.
2026-05-12 10:59:26,163 | DEBUG | morpc.req.get_json_safely: Getting data from https://api.census.gov/data/2023/geoinfo?get=GEO_ID with parameters {'for': 'county:049', 'in': 'state:39', 'key': '83269ff2739cb3bd485c75b091dcee493ad6fe70'}.
2026-05-12 10:59:26,166 | DEBUG | urllib3.connectionpool._new_conn: Starting new HTTPS connection (1): api.census.gov:443
2026-05-12 10:59:26,623 | DEBUG | urllib3.connectionpool._make_request: https://api.census.gov:443 "GET /data/2023/geoinfo?get=GEO_ID&for=county%3A049&in=state%3A39&key=83269ff2739cb3bd485c75b091dcee493ad6fe70 HTTP/1.1" 200 None
2026-05-12 10:59:26,626 | DEBUG | morpc.req.get_json_safely: Request successful. Decoding return JSON.
2026-05-12 10:59:26,630 | DEBUG | morpc_census.geos.geoinfo_from_scope_sumlevel: Building parameters for scope 'franklin' at sumlevel '140'.
2026-05-12 10:59:26,631 | INFO | morpc_census.geos.geoinfo_from_scope_sumlevel: SumLevel 'tract' specified for scope 'franklin'.
2026-05-12 10:59:26,633 | DEBUG | morpc_census.geos.pseudos_from_scope_sumlevel: Getting pseudo combinations for parents in 'franklin' at sumlevel 'tract'
2026-05-12 10:59:26,634 | INFO | morpc_census.geos.pseudos_from_scope_sumlevel: Returning pseudos for 1400000 in [GeoIDFQ(sumlevel='050', variant='00', geocomp='00', state='39', county='049')]
2026-05-12 10:59:26,636 | INFO | morpc_census.api.CensusAPI.census-acs-acs5-2023-countytract-franklin-b05006.__init__: Fetching data from https://api.census.gov/data/2023/acs/acs5? with params {'get': 'group(B05006)', 'ucgid': 'pseudo(0500000US39049$1400000)'}.
2026-05-12 10:59:26,638 | INFO | morpc_census.api.CensusAPI.census-acs-acs5-2023-countytract-franklin-b05006._fetch_group: Fetching group(B05006) — all variables, no limit.
2026-05-12 10:59:26,639 | DEBUG | morpc.req.get_text_safely: Getting data from https://api.census.gov/data/2023/acs/acs5?get=group(B05006)&ucgid=pseudo(0500000US39049$1400000)&key=83269ff2739cb3bd485c75b091dcee493ad6fe70 with parameters None.
2026-05-12 10:59:26,641 | DEBUG | urllib3.connectionpool._new_conn: Starting new HTTPS connection (1): api.census.gov:443
2026-05-12 10:59:27,465 | DEBUG | urllib3.connectionpool._make_request: https://api.census.gov:443 "GET /data/2023/acs/acs5?get=group(B05006)&ucgid=pseudo(0500000US39049$1400000)&key=83269ff2739cb3bd485c75b091dcee493ad6fe70 HTTP/1.1" 200 None
2026-05-12 10:59:28,617 | DEBUG | morpc.req.get_text_safely: Request successful. Returning plain text.
2026-05-12 10:59:28,708 | INFO | morpc_census.api.CensusAPI.census-acs-acs5-2023-countytract-franklin-b05006.melt: Melting data to long format.
2026-05-12 10:59:28,894 | DEBUG | morpc.req.get_json_safely: Getting data from https://api.census.gov/data/2023/acs/acs5/groups/B05006.json with parameters {'key': '83269ff2739cb3bd485c75b091dcee493ad6fe70'}.
2026-05-12 10:59:28,895 | DEBUG | urllib3.connectionpool._new_conn: Starting new HTTPS connection (1): api.census.gov:443
2026-05-12 10:59:29,063 | DEBUG | urllib3.connectionpool._make_request: https://api.census.gov:443 "GET /data/2023/acs/acs5/groups/B05006.json?key=83269ff2739cb3bd485c75b091dcee493ad6fe70 HTTP/1.1" 200 26163
2026-05-12 10:59:29,131 | DEBUG | morpc.req.get_json_safely: Request successful. Decoding return JSON.
8. Fetching specific variables without a group¶
When you only need a few variables — possibly from different groups — pass variables directly instead of a group. The Census API allows at most 50 fields per request; the package batches automatically when you exceed that limit.
# Total population, male, and female — fetched directly without a group
pop = CensusAPI(ep, 'region15', variables=['B01001_001E', 'B01001_002E', 'B01001_026E'])
pop.long2026-05-12 10:59:33,273 | INFO | morpc_census.api.CensusAPI.census-acs-acs5-2023-region15-select-variables.__init__: Initializing CensusAPI for census-acs-acs5-2023-region15-select-variables.
2026-05-12 10:59:33,274 | INFO | morpc_census.api.CensusAPI.census-acs-acs5-2023-region15-select-variables.__init__: Building request URL and parameters.
2026-05-12 10:59:33,275 | DEBUG | morpc_census.geos.geoids_from_scope: Fetching geoids from scope 'region15'.
2026-05-12 10:59:33,279 | DEBUG | morpc.req.get_json_safely: Getting data from https://api.census.gov/data/2023/geoinfo?get=GEO_ID with parameters {'for': 'county:041,045,049,089,097,129,159,083,101,117,047,073,091,127,141', 'in': 'state:39', 'key': '83269ff2739cb3bd485c75b091dcee493ad6fe70'}.
2026-05-12 10:59:33,282 | DEBUG | urllib3.connectionpool._new_conn: Starting new HTTPS connection (1): api.census.gov:443
2026-05-12 10:59:33,715 | DEBUG | urllib3.connectionpool._make_request: https://api.census.gov:443 "GET /data/2023/geoinfo?get=GEO_ID&for=county%3A041%2C045%2C049%2C089%2C097%2C129%2C159%2C083%2C101%2C117%2C047%2C073%2C091%2C127%2C141&in=state%3A39&key=83269ff2739cb3bd485c75b091dcee493ad6fe70 HTTP/1.1" 200 None
2026-05-12 10:59:33,716 | DEBUG | morpc.req.get_json_safely: Request successful. Decoding return JSON.
2026-05-12 10:59:33,718 | DEBUG | morpc_census.geos.geoinfo_from_scope_sumlevel: Building parameters for scope 'region15' at sumlevel None.
2026-05-12 10:59:33,718 | INFO | morpc_census.geos.geoinfo_from_scope_sumlevel: No sumlevel specified; using scope 'region15' parameters.
2026-05-12 10:59:33,719 | INFO | morpc_census.api.CensusAPI.census-acs-acs5-2023-region15-select-variables.__init__: Fetching data from https://api.census.gov/data/2023/acs/acs5? with params {'get': 'B01001_001E,B01001_002E,B01001_026E', 'for': 'county:041,045,049,089,097,129,159,083,101,117,047,073,091,127,141', 'in': 'state:39'}.
2026-05-12 10:59:33,720 | INFO | morpc_census.api.CensusAPI.census-acs-acs5-2023-region15-select-variables._fetch_variables: Fetching 3 variable(s) in 1 batch(es).
2026-05-12 10:59:33,721 | INFO | morpc_census.api.CensusAPI.census-acs-acs5-2023-region15-select-variables._fetch_variables: Batch 1/1: 3 variable(s).
2026-05-12 10:59:33,722 | DEBUG | morpc.req.get_json_safely: Getting data from https://api.census.gov/data/2023/acs/acs5? with parameters {'get': 'GEO_ID,B01001_001E,B01001_002E,B01001_026E', 'for': 'county:041,045,049,089,097,129,159,083,101,117,047,073,091,127,141', 'in': 'state:39', 'key': '83269ff2739cb3bd485c75b091dcee493ad6fe70'}.
2026-05-12 10:59:33,723 | DEBUG | urllib3.connectionpool._new_conn: Starting new HTTPS connection (1): api.census.gov:443
2026-05-12 10:59:34,344 | DEBUG | urllib3.connectionpool._make_request: https://api.census.gov:443 "GET /data/2023/acs/acs5?get=GEO_ID%2CB01001_001E%2CB01001_002E%2CB01001_026E&for=county%3A041%2C045%2C049%2C089%2C097%2C129%2C159%2C083%2C101%2C117%2C047%2C073%2C091%2C127%2C141&in=state%3A39&key=83269ff2739cb3bd485c75b091dcee493ad6fe70 HTTP/1.1" 200 None
2026-05-12 10:59:34,345 | DEBUG | morpc.req.get_json_safely: Request successful. Decoding return JSON.
2026-05-12 10:59:34,351 | INFO | morpc_census.api.CensusAPI.census-acs-acs5-2023-region15-select-variables.melt: Melting data to long format.
2026-05-12 10:59:34,357 | DEBUG | morpc.req.get_json_safely: Getting data from https://api.census.gov/data/2023/acs/acs5/groups/B01001.json with parameters {'key': '83269ff2739cb3bd485c75b091dcee493ad6fe70'}.
2026-05-12 10:59:34,358 | DEBUG | urllib3.connectionpool._new_conn: Starting new HTTPS connection (1): api.census.gov:443
2026-05-12 10:59:34,579 | DEBUG | urllib3.connectionpool._make_request: https://api.census.gov:443 "GET /data/2023/acs/acs5/groups/B01001.json?key=83269ff2739cb3bd485c75b091dcee493ad6fe70 HTTP/1.1" 200 19404
2026-05-12 10:59:34,611 | DEBUG | morpc.req.get_json_safely: Request successful. Decoding return JSON.
9. Analyzing with DimensionTable¶
DimensionTable parses each variable’s !!-delimited label into named dimension columns and pivots the long-format data into a readable wide table.
Each label segment is classified as a subtotal (ends with :) or a leaf (no trailing :). Subtotals are left-aligned into the first columns; leaves into the last. This alignment keeps the same concept in the same column regardless of path depth — for example, in B05004 (Nativity and Citizenship × Sex), Male and Female always land in the last column whether there are one or two nativity levels before them.
For B01001 (Sex by Age), Total:!!Male:!!Under 5 years parses as:
| dim | value |
|---|---|
total | Total: |
sex | Male: |
age | Under 5 years |
Key methods:
drop(dim, method='summarize')— remove a dimension level before pivotingremap(variable_map)— relabel and collapse variables, then re-parse dimensionswide()— produce the wide pivot tablepercent()— column percentages relative to the grand total row
# Construct a DimensionTable; dim_names labels the parsed dimension columns
dim = DimensionTable(b01001.long, dim_names=['total', 'sex', 'age'])
# Each row is one Census variable; each column is a level of the label hierarchy
# ':' suffix is preserved here — it marks subtotals (rows that have sub-dimensions)
dim.dims.head(8)# Full wide table — all dimension levels as row index, geography × value type as columns
# ':' is stripped from displayed index values (Total: → Total, Male: → Male, etc.)
dim.wide()# Drop the age dimension: keep only the pre-computed sex-level rows
# (grand total + Male total + Female total — one row per sex per county)
dim.drop('age').wide()from morpc_census import AGEGROUP_MAP
# B01001 uses uneven age bins: "15 to 17 years" and "18 and 19 years" are separate
# variables, as are "20 years", "21 years", and "22 to 24 years".
# AGEGROUP_MAP collapses these into standard 5-year groups. remap() applies the
# substitutions and sums estimates for any rows that share a new label.
dim_remapped = DimensionTable(b01001.long, dim_names=['total', 'sex', 'age'])
dim_remapped.remap(AGEGROUP_MAP)
# Population by standardized age group — aggregate sex out (sum Male + Female)
dim_remapped.drop('sex', method='aggregate').wide()2026-05-12 11:00:06,920 | INFO | morpc_census.api.DimensionTable.remap: Remapping variables: ['Under 5 years', '5 to 9 years', '10 to 14 years', '15 to 17 years', '18 and 19 years', '20 years', '21 years', '22 to 24 years', '25 to 29 years', '30 to 34 years', '35 to 39 years', '40 to 44 years', '45 to 49 years', '50 to 54 years', '55 to 59 years', '60 and 61 years', '62 to 64 years', '65 and 66 years', '67 to 69 years', '70 to 74 years', '75 to 79 years', '80 to 84 years', '85 years and over'].
# Sex percentages relative to the county total (age dropped; grand total = 100%)
dim.drop('age').percent()10. Time series¶
DimensionTable accepts any long-format DataFrame with the standard schema. Concatenating long from multiple vintages produces a time-series table where reference_period becomes a column dimension.
# Fetch the same group for an earlier vintage
ep2018 = Endpoint('acs/acs5', 2018)
b01001_2018 = CensusAPI(ep2018, 'region15', group=Group(ep2018, 'B01001'))
# Stack the two years and build a dimension table
long_ts = pd.concat([b01001_2018.long, b01001.long])
DimensionTable(long_ts).wide()2026-05-12 11:00:09,909 | DEBUG | morpc_census.api.groups: Fetching groups for 2018 acs/acs5
2026-05-12 11:00:09,912 | DEBUG | morpc.req.get_json_safely: Getting data from https://api.census.gov/data/2018/acs/acs5/groups.json with parameters {'key': '83269ff2739cb3bd485c75b091dcee493ad6fe70'}.
2026-05-12 11:00:09,915 | DEBUG | urllib3.connectionpool._new_conn: Starting new HTTPS connection (1): api.census.gov:443
2026-05-12 11:00:10,117 | DEBUG | urllib3.connectionpool._make_request: https://api.census.gov:443 "GET /data/2018/acs/acs5/groups.json?key=83269ff2739cb3bd485c75b091dcee493ad6fe70 HTTP/1.1" 200 None
2026-05-12 11:00:10,478 | DEBUG | morpc.req.get_json_safely: Request successful. Decoding return JSON.
2026-05-12 11:00:10,486 | INFO | morpc_census.api.CensusAPI.census-acs-acs5-2018-region15-b01001.__init__: Initializing CensusAPI for census-acs-acs5-2018-region15-b01001.
2026-05-12 11:00:10,488 | INFO | morpc_census.api.CensusAPI.census-acs-acs5-2018-region15-b01001.__init__: Building request URL and parameters.
2026-05-12 11:00:10,489 | DEBUG | morpc_census.geos.geoids_from_scope: Fetching geoids from scope 'region15'.
2026-05-12 11:00:10,492 | DEBUG | morpc.req.get_json_safely: Getting data from https://api.census.gov/data/2023/geoinfo?get=GEO_ID with parameters {'for': 'county:041,045,049,089,097,129,159,083,101,117,047,073,091,127,141', 'in': 'state:39', 'key': '83269ff2739cb3bd485c75b091dcee493ad6fe70'}.
2026-05-12 11:00:10,495 | DEBUG | urllib3.connectionpool._new_conn: Starting new HTTPS connection (1): api.census.gov:443
2026-05-12 11:00:10,899 | DEBUG | urllib3.connectionpool._make_request: https://api.census.gov:443 "GET /data/2023/geoinfo?get=GEO_ID&for=county%3A041%2C045%2C049%2C089%2C097%2C129%2C159%2C083%2C101%2C117%2C047%2C073%2C091%2C127%2C141&in=state%3A39&key=83269ff2739cb3bd485c75b091dcee493ad6fe70 HTTP/1.1" 200 None
2026-05-12 11:00:10,901 | DEBUG | morpc.req.get_json_safely: Request successful. Decoding return JSON.
2026-05-12 11:00:10,905 | DEBUG | morpc_census.geos.geoinfo_from_scope_sumlevel: Building parameters for scope 'region15' at sumlevel None.
2026-05-12 11:00:10,906 | INFO | morpc_census.geos.geoinfo_from_scope_sumlevel: No sumlevel specified; using scope 'region15' parameters.
2026-05-12 11:00:10,908 | INFO | morpc_census.api.CensusAPI.census-acs-acs5-2018-region15-b01001.__init__: Fetching data from https://api.census.gov/data/2018/acs/acs5? with params {'get': 'group(B01001)', 'for': 'county:041,045,049,089,097,129,159,083,101,117,047,073,091,127,141', 'in': 'state:39'}.
2026-05-12 11:00:10,910 | INFO | morpc_census.api.CensusAPI.census-acs-acs5-2018-region15-b01001._fetch_group: Fetching group(B01001) — all variables, no limit.
2026-05-12 11:00:10,911 | DEBUG | morpc.req.get_text_safely: Getting data from https://api.census.gov/data/2018/acs/acs5?get=group(B01001)&for=county:041,045,049,089,097,129,159,083,101,117,047,073,091,127,141&in=state:39&key=83269ff2739cb3bd485c75b091dcee493ad6fe70 with parameters None.
2026-05-12 11:00:10,914 | DEBUG | urllib3.connectionpool._new_conn: Starting new HTTPS connection (1): api.census.gov:443
2026-05-12 11:00:11,379 | DEBUG | urllib3.connectionpool._make_request: https://api.census.gov:443 "GET /data/2018/acs/acs5?get=group(B01001)&for=county:041,045,049,089,097,129,159,083,101,117,047,073,091,127,141&in=state:39&key=83269ff2739cb3bd485c75b091dcee493ad6fe70 HTTP/1.1" 200 None
2026-05-12 11:00:11,412 | DEBUG | morpc.req.get_text_safely: Request successful. Returning plain text.
2026-05-12 11:00:11,433 | INFO | morpc_census.api.CensusAPI.census-acs-acs5-2018-region15-b01001.melt: Melting data to long format.
2026-05-12 11:00:11,458 | DEBUG | morpc.req.get_json_safely: Getting data from https://api.census.gov/data/2018/acs/acs5/groups/B01001.json with parameters {'key': '83269ff2739cb3bd485c75b091dcee493ad6fe70'}.
2026-05-12 11:00:11,461 | DEBUG | urllib3.connectionpool._new_conn: Starting new HTTPS connection (1): api.census.gov:443
2026-05-12 11:00:11,680 | DEBUG | urllib3.connectionpool._make_request: https://api.census.gov:443 "GET /data/2018/acs/acs5/groups/B01001.json?key=83269ff2739cb3bd485c75b091dcee493ad6fe70 HTTP/1.1" 200 18623
2026-05-12 11:00:11,712 | DEBUG | morpc.req.get_json_safely: Request successful. Decoding return JSON.
2026-05-12 11:00:11,737 | DEBUG | morpc_census.api.groups: Fetching groups for 2023 acs/acs5
2026-05-12 11:00:11,740 | DEBUG | morpc.req.get_json_safely: Getting data from https://api.census.gov/data/2023/acs/acs5/groups.json with parameters {'key': '83269ff2739cb3bd485c75b091dcee493ad6fe70'}.
2026-05-12 11:00:11,746 | DEBUG | urllib3.connectionpool._new_conn: Starting new HTTPS connection (1): api.census.gov:443
2026-05-12 11:00:11,908 | DEBUG | urllib3.connectionpool._make_request: https://api.census.gov:443 "GET /data/2023/acs/acs5/groups.json?key=83269ff2739cb3bd485c75b091dcee493ad6fe70 HTTP/1.1" 200 None
2026-05-12 11:00:12,352 | DEBUG | morpc.req.get_json_safely: Request successful. Decoding return JSON.
11. Saving output¶
api.save() writes three files to the output directory:
{name}.long.csv— the long-format data{name}.schema.yaml— a frictionless Schema describing every column{name}.resource.yaml— a frictionless Resource descriptor linking the CSV to its schema
The resource is validated immediately after writing, so any schema mismatch surfaces here.
import os
b01001.save('./temp_data')
print('filename:', b01001.filename)
print('exists: ', os.path.exists(f'./temp_data/{b01001.filename}'))2026-05-12 11:00:17,318 | INFO | morpc_census.api.CensusAPI.census-acs-acs5-2023-region15-b01001.save: Writing data to temp_data/census-acs-acs5-2023-region15-b01001.long.csv.
2026-05-12 11:00:17,326 | INFO | morpc_census.api.CensusAPI.census-acs-acs5-2023-region15-b01001.save: Writing schema to temp_data/census-acs-acs5-2023-region15-b01001.schema.yaml.
2026-05-12 11:00:17,327 | INFO | morpc_census.api.CensusAPI.census-acs-acs5-2023-region15-b01001.define_schema: Defining schema for acs/acs5 / 2023 / B01001.
2026-05-12 11:00:18,572 | INFO | morpc_census.api.CensusAPI.census-acs-acs5-2023-region15-b01001.save: Writing resource to temp_data/census-acs-acs5-2023-region15-b01001.resource.yaml.
2026-05-12 11:00:18,613 | INFO | morpc_census.api.CensusAPI.census-acs-acs5-2023-region15-b01001.save: Save complete and resource validated.
filename: census-acs-acs5-2023-region15-b01001.long.csv
exists: True
from frictionless import Resource
Resource(f'temp_data/{b01001.name}.resource.yaml'){'name': 'census-acs-acs5-2023-region15-b01001',
'type': 'table',
'title': '2023 Sex by Age for region15',
'description': 'Census API data for B01001: Sex by Age from acs/acs5 2023 for '
'region15.',
'sources': [{'title': 'US Census Bureau API',
'path': 'https://api.census.gov/data/2023/acs/acs5?',
'_params': {'get': 'group(B01001)',
'for': 'county:041,045,049,089,097,129,159,083,101,117,047,073,091,127,141',
'in': 'state:39'}}],
'path': 'census-acs-acs5-2023-region15-b01001.long.csv',
'scheme': 'file',
'format': 'csv',
'mediatype': 'text/csv',
'schema': 'census-acs-acs5-2023-region15-b01001.schema.yaml'}from frictionless import Schema
Schema(f'temp_data/{b01001.name}.schema.yaml'){'fields': [{'name': 'geoidfq',
'type': 'string',
'description': 'Census geography fully-qualified identifier'},
{'name': 'name', 'type': 'string', 'description': 'Geography name'},
{'name': 'reference_period',
'type': 'integer',
'description': 'Reference year'},
{'name': 'survey',
'type': 'string',
'description': 'Census survey endpoint'},
{'name': 'concept',
'type': 'string',
'description': 'Table concept description'},
{'name': 'universe',
'type': 'string',
'description': 'Universe for the table'},
{'name': 'variable_label',
'type': 'string',
'description': 'Human-readable variable label'},
{'name': 'variable',
'type': 'string',
'description': 'Base variable code'},
{'name': 'estimate',
'type': 'number',
'description': 'Estimate value for the variable'},
{'name': 'moe',
'type': 'number',
'description': 'Margin of error for the estimate'}],
'missingValues': ['',
'-222222222',
'-333333333',
'-555555555',
'-666666666',
'-888888888',
'-999999999',
'*****'],
'primaryKey': ['geoidfq', 'reference_period', 'variable']}