Skip to main content

Census American Community Survey (ACS)

Add demographic, economic, housing, and social data from the U.S. Census Bureau's American Community Survey to enrich your civic engagement analysis.

Overview

The American Community Survey (ACS) is the premier source for detailed population and housing information about America. It provides data for communities across the United States, Puerto Rico, and Island Areas.

What's Included

  • Demographics: Age, race, ethnicity, language, citizenship
  • Economics: Income, poverty, employment, occupation
  • Housing: Occupancy, value, rent, housing costs
  • Education: School enrollment, educational attainment
  • Health: Health insurance coverage by age and type
  • Social: Disability status, veteran status, commuting

ACS vs. Census of Governments

DatasetPurposeWhat it Measures
Census of GovernmentsJurisdiction discoveryLists all government entities (cities, counties, districts)
American Community Survey (ACS)Community demographicsPopulation characteristics, economics, housing

Use both together: Census of Governments tells you which jurisdictions exist, ACS tells you about the people who live there.

🚀 Quick Start

While optional, an API key increases your rate limit from 500 to 5,000 requests per day.

  1. Visit: https://api.census.gov/data/key_signup.html
  2. Enter your email and organization
  3. Check email for API key
  4. Add to .env file:
CENSUS_API_KEY=your_key_here

2. Run the ACS Ingestion Script

# Activate virtual environment
source .venv/bin/activate

# Navigate to script directory
cd scripts/datasources/census

# Run the example (downloads sample data)
python acs_ingestion.py

This will:

  • Download median household income for all U.S. counties
  • Download health insurance data for California
  • Cache data to data/cache/acs/

📊 Available Data Tables

Demographics

Table CodeDescriptionUse Case
B01001Sex by AgeIdentify communities with children (dental screening priority)
B02001RaceAnalyze health equity across racial groups
B03002Hispanic or Latino Origin by RaceUnderstand demographic composition
B05001Nativity and Citizenship StatusLanguage access planning
B16001Language Spoken at HomeMultilingual outreach needs

Economics

Table CodeDescriptionUse Case
B19013Median Household IncomeTarget low-income communities for programs
B17001Poverty StatusMedicaid eligibility analysis
B23025Employment StatusEconomic health assessment
C24010Sex by OccupationWorkforce composition

Health Insurance ⭐ Critical for Oral Health Policy

Table CodeDescriptionUse Case
B27001Health Insurance Coverage Status by AgeOverall insurance coverage rates
B27010Health Insurance Coverage (Under 19)Child dental insurance coverage
C27007Medicaid/Means-Tested Public CoverageMedicaid enrollment by community

Education

Table CodeDescriptionUse Case
B15003Educational AttainmentCommunity education levels
B14001School Enrollment by AgeNumber of school-aged children

💻 Usage Examples

Example 1: Download Data for All Counties

import asyncio
from pathlib import Path
from scripts.datasources.census.acs_ingestion import ACSDataIngestion

async def download_county_data():
# Initialize with default cache directory
acs = ACSDataIngestion()

# Download median household income for all U.S. counties
income_df = await acs.download_acs_data_api(
table="B19013", # Median household income
geography="county", # County level
state="*" # All states
)

print(f"Downloaded {len(income_df)} counties")
print(income_df.head())

asyncio.run(download_county_data())

Example 2: Child Health Insurance Coverage

Critical for oral health policy analysis!

async def analyze_child_insurance():
acs = ACSDataIngestion()

# Download health insurance for children under 19
child_insurance_df = await acs.download_acs_data_api(
table="B27010", # Health insurance (Under 19)
geography="county",
state="*"
)

# This table includes:
# - With health insurance
# - With public coverage (Medicaid/CHIP)
# - With private coverage
# - No health insurance

return child_insurance_df

df = asyncio.run(analyze_child_insurance())

Example 3: Download Multiple Tables at Once

async def download_comprehensive_data():
acs = ACSDataIngestion()

# Download all key demographic tables for California
ca_data = await acs.download_all_demographics(
geography="county",
state="06" # California FIPS code
)

# Returns dictionary with multiple DataFrames
for table_code, df in ca_data.items():
print(f"{table_code}: {len(df)} counties")

asyncio.run(download_comprehensive_data())

Example 4: Use Cached Data

acs = ACSDataIngestion()

# First call downloads from API
df1 = await acs.download_acs_data_api("B19013", "county", "*")

# Subsequent calls use cached Parquet file (instant!)
df2 = acs.get_cached_data("B19013", "county", "*")

print(f"Same data: {df1.equals(df2)}") # True

🗄️ Data Storage Options

# Uses data/cache/acs/ in project directory
acs = ACSDataIngestion()

Location: /home/developer/projects/open-navigator/data/cache/acs/

Option 2: D Drive (Windows)

from pathlib import Path

# Store all ACS data on D drive
acs = ACSDataIngestion(data_dir=Path("D:/open-navigator-data/acs"))

Location: D:\open-navigator-data\acs\

Option 3: External Drive (Linux/Mac)

# Mount external drive first, then:
acs = ACSDataIngestion(data_dir=Path("/mnt/external/acs-data"))

Location: /mnt/external/acs-data/

Option 4: Network Storage

# For shared team access
acs = ACSDataIngestion(data_dir=Path("//server/shared/acs"))

📁 Data File Format

Downloaded data is cached as Parquet files for fast loading:

data/cache/acs/
├── B19013_county_*_2022.parquet # Median income, all counties
├── B27010_county_06_2022.parquet # Child insurance, CA only
├── B01001_place_*_2022.parquet # Age/sex, all cities
└── acs_2022_ALL/ # Bulk download (if used)

Parquet advantages:

  • 10x smaller than CSV
  • 100x faster to load
  • Preserves data types
  • Columnar storage (efficient queries)

🌍 Geography Levels

ACS data is available at multiple geographic levels:

LevelCodeExampleRecords (approx.)
NationalusUnited States1
StatestateCalifornia, Texas50
CountycountyLos Angeles County3,200
PlaceplaceSan Francisco city19,500
TracttractNeighborhood-level85,000
County SubdivisioncousubTownships36,000

Choose based on your analysis needs:

  • State-level: Policy comparison across states
  • County-level: Regional analysis
  • Place-level: City-specific programs
  • Tract-level: Neighborhood targeting (large datasets!)

🔗 Integration with Open Navigator

Enriching Jurisdiction Data

Combine ACS demographics with jurisdiction discovery:

from discovery.census_ingestion import CensusGovernmentIngestion
from scripts.datasources.census.acs_ingestion import ACSDataIngestion

# Step 1: Get list of all counties
census = CensusGovernmentIngestion()
counties_df = await census.download_census_data("counties")

# Step 2: Add demographic data from ACS
acs = ACSDataIngestion()
demographics = await acs.download_acs_data_api("B19013", "county", "*")

# Step 3: Join on FIPS code
enriched = counties_df.merge(demographics, on="fips", how="left")

# Now you have: county name, URL, population, AND median income!

Targeting High-Need Communities

Identify counties for oral health program targeting:

async def find_high_need_counties():
acs = ACSDataIngestion()

# Get poverty data
poverty_df = await acs.download_acs_data_api("B17001", "county", "*")

# Get child health insurance
child_insurance_df = await acs.download_acs_data_api("B27010", "county", "*")

# Combine datasets
combined = poverty_df.merge(child_insurance_df, on=["state", "county"])

# Filter for high poverty + low insurance coverage
high_need = combined[
(combined["poverty_rate"] > 0.15) & # > 15% poverty
(combined["uninsured_children"] > 100) # > 100 uninsured kids
]

return high_need

⚡ Performance Tips

1. Use State Filters

# ❌ Slow: Downloads all 3,200 counties
all_counties = await acs.download_acs_data_api("B19013", "county", "*")

# ✅ Fast: Downloads only California's 58 counties
ca_counties = await acs.download_acs_data_api("B19013", "county", "06")

2. Leverage Caching

# First run: Downloads from API (slow)
df1 = await acs.download_acs_data_api("B19013", "county", "*")

# Second run: Loads from Parquet cache (instant!)
df2 = acs.get_cached_data("B19013", "county", "*")

3. Download Multiple Tables in Parallel

async def parallel_download():
acs = ACSDataIngestion()

# Download 3 tables simultaneously
results = await asyncio.gather(
acs.download_acs_data_api("B19013", "county", "*"),
acs.download_acs_data_api("B27010", "county", "*"),
acs.download_acs_data_api("B17001", "county", "*"),
)

income_df, insurance_df, poverty_df = results

4. Avoid Bulk Downloads (Unless Necessary)

The Census Bureau offers bulk downloads of ALL ACS data:

# ⚠️ WARNING: This downloads 15 GB!
await acs.download_bulk_files(state="ALL")

Use bulk downloads only if:

  • You need 100+ tables
  • You need tract-level data for entire U.S.
  • You're doing large-scale research

Otherwise: Use targeted API downloads (much faster!)

📚 Resources

Official Documentation

Understanding ACS Data

State FIPS Codes

Common state codes for API queries:

StateFIPSStateFIPS
Alabama01Montana30
Alaska02Nebraska31
Arizona04Nevada32
Arkansas05New Hampshire33
California06New Jersey34
Colorado08New Mexico35
Connecticut09New York36
Delaware10North Carolina37
Florida12Ohio39
Georgia13Oklahoma40
Hawaii15Oregon41
Illinois17Pennsylvania42
Indiana18Texas48
Iowa19Utah49
Kansas20Virginia51
Louisiana22Washington53
Massachusetts25Wisconsin55
Michigan26

Full list: https://www.census.gov/library/reference/code-lists/ansi/ansi-codes-for-states.html

🆘 Troubleshooting

"API request failed: 403"

Cause: Rate limit exceeded (500 requests/day without API key)

Fix: Get a Census API key (see Quick Start above)

"Module 'config.settings' has no attribute 'CENSUS_API_KEY'"

Cause: API key not set in configuration

Fix: Add to .env file:

CENSUS_API_KEY=your_key_here

"No data returned for this geography"

Cause: Not all tables are available at all geography levels

Fix: Check Census API documentation for table availability by geography

Downloads are slow

Solutions:

  1. Use state filters instead of "*"
  2. Use cached data for repeated queries
  3. Download during off-peak hours (late night/early morning EST)
  4. Consider bulk downloads if you need many tables

🔮 Next Steps

  1. Explore Available Tables: Run acs.list_available_tables()
  2. Download Sample Data: Try the examples in this guide
  3. Join with Jurisdictions: Combine ACS demographics with jurisdiction URLs
  4. Build Dashboards: Create visualizations of demographic data
  5. Target Programs: Use poverty/insurance data to prioritize outreach