This repository organizes health measures and disease rates by geographic level, aligned with PopHIVE data conventions:
- United States (national)
- State
- U.S. territory
- County
Each state (and, separately, each U.S. territory — see U.S. Territories) has its own folder. Within each, every county (or territory subdivision) has its own subfolder named using the 5-digit FIPS code followed by the name. All data files use compressed CSV format (.csv.gz) and follow PopHIVE's long format standard for bundles.
us-rates/
│
├── README.md
├── measure_info.json
├── us-rates-geographies.json
│
├── national/
│ └── national_rates.csv.gz
│
├── states/
│ ├── alabama/
│ │ ├── state_rates.csv.gz
│ │ └── counties/
│ │ ├── 01001_autauga/
│ │ │ └── county_rates.csv.gz
│ │ ├── 01003_baldwin/
│ │ │ └── county_rates.csv.gz
│ │ └── ...
│ │
│ ├── alaska/
│ │ ├── state_rates.csv.gz
│ │ └── counties/
│ │ └── ...
│ │
│ ├── arizona/
│ └── ...
│
└── territories/
├── puerto_rico/
│ ├── commonwealth_rates.csv.gz
│ └── counties/
│ ├── 72001_adjuntas_municipio/
│ │ └── county_rates.csv.gz
│ └── ...
│
├── guam/
│ ├── territory_rates.csv.gz
│ └── counties/
│ └── ...
│
└── ...
DC is a state-equivalent for this repo's purposes and lives under states/district_of_columbia/, not territories/.
Applies identically to county-equivalent subdivisions under territories/ (e.g. Puerto Rico's municipios). Folders use the 5-digit FIPS code followed by the county name, all lowercase with underscores:
[5-digit FIPS]_[county_name]
Examples:
| Folder Name | State | County |
|---|---|---|
01001_autauga |
Alabama | Autauga |
01003_baldwin |
Alabama | Baldwin |
09001_fairfield |
Connecticut | Fairfield |
09003_hartford |
Connecticut | Hartford |
36061_new_york |
New York | New York |
Note: Multi-word county names use underscores (e.g.,
36061_new_york, not36061_newyork). Do not include the word "county" in the folder name.
PopHIVE uses FIPS codes as the standard geography identifier in all data files:
| Level | FIPS Format | Example |
|---|---|---|
| National | "00" |
00 |
| State | 2-digit string | 01 |
| Territory | 2-digit string | 72 |
| County | 5-digit string | 01001 |
FIPS codes must always be stored as zero-padded strings, never as integers.
DC and the 50 states aren't the only 2-digit geographies — tidycensus::fips_codes also carries 2-digit codes for 6 non-state U.S. territories. DC is treated as a state-equivalent and lives under states/; the other 6 live under their own top-level territories/ folder instead, since they aren't states:
| Territory | Abbreviation | FIPS | Political Status | Top-level file |
|---|---|---|---|---|
| Puerto Rico | PR |
72 |
Commonwealth (organized, unincorporated) | commonwealth_rates.csv.gz |
| Northern Mariana Islands | MP |
69 |
Commonwealth (organized, unincorporated) | commonwealth_rates.csv.gz |
| Guam | GU |
66 |
Territory (organized, unincorporated) | territory_rates.csv.gz |
| U.S. Virgin Islands | VI |
78 |
Territory (organized, unincorporated) | territory_rates.csv.gz |
| American Samoa | AS |
60 |
Territory (unorganized, unincorporated) | territory_rates.csv.gz |
| U.S. Minor Outlying Islands | UM |
74 |
No organized government or population | territory_rates.csv.gz |
Each territory's folder (territories/american_samoa/, territories/guam/, etc.) follows the exact same shape as a state folder — a top-level rates file plus a counties/ subfolder for its FIPS-coded subdivisions (e.g. Puerto Rico's 78 municipios) — except the top-level file is named for that territory's actual political status rather than reusing state_rates.csv.gz, since territories aren't states: commonwealth_rates.csv.gz for Puerto Rico and the Northern Mariana Islands (the two organized, unincorporated commonwealths), territory_rates.csv.gz for the rest. code/geography_helpers.R's is_territory() and territory_rates_filename() are the single source of truth for this; scaffold_structure.R, populate_state_rates.R, populate_county_rates.R, and generate_geography_manifest.R all route on them, so a geography's folder and filename are never hand-picked per script.
Most territories have little to no data across the sources in measure_info.json (health-workforce and mortality sources being the most likely to report any territory-level values) — an empty or missing territories/{name}/ file just means no source reported data for it, not a pipeline bug.
Connecticut retired its 8 counties in favor of 9 planning regions as county equivalents, effective with the Census Bureau's 2022 vintage. Because sources migrate to this convention at different times, states/connecticut/counties/ holds folders for both conventions at once:
| Convention | FIPS range | Folders |
|---|---|---|
| Legacy counties | 09001-09015 |
09001_fairfield ... 09015_windham (8) |
| Planning regions | 09110-09190 |
09110_capitol ... 09190_western_connecticut (9) |
Both sets come from tidycensus::fips_codes, which lists both conventions simultaneously — no CT-specific branching is needed elsewhere in the pipeline as a result.
Sources migrate to planning regions on their own schedules, so the same Ingest bundle can carry both conventions for different years or even different measures within the same year. As of the last refresh:
| Source | Convention |
|---|---|
| Census (ACS) | Legacy counties through 2021, planning regions from 2022 on — a clean cutover |
| County Health Rankings | Legacy counties 2010-2024; the 2025 release splits measures across both conventions (never the same measure under both) |
| Epic Cosmos, NCHS mortality, CMS MMD | Legacy counties throughout, including releases after 2022 |
| WaPo vaccination rates, HealthMap | Planning regions throughout, including years before 2022 (these aggregate by Council-of-Governments boundary, which predates the 2022 FIPS change) |
Because the two conventions carve up the same towns, summing county folders for a given (measure, time) pair double-counts if that pair is ever reported under both conventions. code/check_geography_renaming.R checks for exactly that after every populate_county_rates.R run and fails the pipeline if it finds one — see Alaska: Historical Borough and Census Area Changes for the same issue elsewhere in the pipeline.
Alaska's boroughs and census areas have been renamed, split, and merged repeatedly, and tidycensus::fips_codes carries every historical version at once, so states/alaska/counties/ holds folders for defunct geographies too:
| Old (defunct) | FIPS(es) | Replaced by | FIPS(es) |
|---|---|---|---|
| Prince of Wales-Outer Ketchikan Census Area (2008) | 02201 |
Prince of Wales-Hyder Census Area | 02198 |
| Skagway-Yakutat-Angoon Census Area (pre-1992) -> Skagway-Hoonah-Angoon Census Area (1992-2007) | 02231, 02232 |
Skagway Municipality, Hoonah-Angoon Census Area | 02230, 02105 |
| Valdez-Cordova Census Area (2019) | 02261 |
Chugach Census Area, Copper River Census Area | 02063, 02066 |
| Wade Hampton Census Area (renamed 2015) | 02270 |
Kusilvak Census Area | 02158 |
| Wrangell-Petersburg Census Area (2008) | 02280 |
Wrangell City and Borough, Petersburg Census Area | 02275, 02195 |
code/check_geography_renaming.R checks each of these 5 events the same way as the CT check above. Yakutat City and Borough (02282) isn't part of the Skagway lineage above despite the "Skagway-Yakutat-Angoon" name -- it's been separately tracked since at least 1992 and never overlaps with 02231/02232.
Two sources duplicate data onto these retired codes rather than cutting over cleanly, resolved in populate_county_rates.R:
vaccine_exemptions_fattahalways writes the exact same value to a retired code and its successor(s), so the retired code's copy is dropped unconditionally (drop_alaska_defunct_duplicates()).area_health_resource_filesometimes duplicates and sometimes disagrees between a retired code and its successor(s) for the same (measure, time) -- the disagreements are almost always a newly-created code's placeholder0before real tracking starts, or a retired code's value decaying to0in its last year or two before being dropped from the file. Each of the 79 affected rows is resolved by an explicit, documented override table (ahrf_alaska_overrides) rather than a general rule, since guessing wrong here means silently reporting the wrong health-workforce count.
All data files must follow PopHIVE's long format with compressed CSV encoding:
- File extension:
.csv.gz(gzip-compressed CSV) - One row per unique
(geography, time, measure)combination geography— FIPS code stringtime—YYYY-MM-DD(annual:YYYY-12-31, monthly: last day of month)measure— measure name string following{prefix}_{measure_name}conventionvalue— numeric
Example — county_rates.csv.gz (first few rows):
geography,time,measure,value
01001,2022-12-31,chr_diabetes_prevalence,12.4
01001,2022-12-31,brfss_pct_obesity,34.1
01001,2022-12-31,cdc_heart_disease_rate,198.6
01001,2021-12-31,chr_diabetes_prevalence,12.1
01001,2021-12-31,brfss_pct_obesity,33.7
01001,2021-12-31,cdc_heart_disease_rate,201.2
The measure column follows the {prefix}_{measure_name} pattern:
{prefix}— Short identifier for the data source (e.g.,cdc,brfss,acs,cms). Consistent across all measures from the same source.{measure_name}— Short, descriptive name for the specific measure (e.g.,diabetes_prevalence,pct_obesity,heart_disease_rate).
Rules:
- All lowercase with underscores — no spaces or special characters
- Prefix must be consistent across all measures from the same source
- Names should be short but unambiguous
- Avoid redundancy (e.g., don't repeat
rateif the measure type is already a rate)
Examples:
| Measure Name | Source | Description |
|---|---|---|
cdc_diabetes_prevalence |
CDC | Diabetes prevalence |
cdc_cancer_incidence |
CDC | Cancer incidence rate |
cdc_heart_disease_rate |
CDC | Heart disease mortality rate |
brfss_pct_obesity |
BRFSS | Percent obese |
brfss_pct_smoking |
BRFSS | Percent current smokers |
acs_pct_poverty |
ACS | Percent below poverty line |
acs_pop_total |
ACS | Total population |
cms_pct_medicare |
CMS | Percent enrolled in Medicare |
Each county folder contains a single rates file:
01001_autauga/
└── county_rates.csv.gz ← all measures for this county, long format
All counties share the same measure definitions. Measure documentation lives in the top-level measure_info.json only — there are no per-county metadata files.
Each state folder contains state-level rates and a counties subfolder:
alabama/
├── state_rates.csv.gz ← state-level measures for Alabama (geography = "01")
└── counties/
├── 01001_autauga/
├── 01003_baldwin/
└── ...
The root measure_info.json documents every measure used across the repository. It follows the PopHIVE schema:
{
"_sources": {
"cdc": {
"name": "Centers for Disease Control and Prevention",
"url": "https://www.cdc.gov",
"organization": "CDC",
"organization_url": "https://www.cdc.gov",
"description": "Federal public health agency providing national disease surveillance data.",
"restrictions": "Public domain unless otherwise noted."
}
},
"cdc_diabetes_prevalence": {
"id": "cdc_diabetes_prevalence",
"short_name": "Diabetes Prevalence",
"long_name": "Diagnosed Diabetes Prevalence",
"category": "chronic",
"short_description": "Percentage of adults with diagnosed diabetes.",
"long_description": "Age-adjusted percentage of adults ever told by a doctor they have diabetes, excluding gestational diabetes. Source: CDC PLACES.",
"statement": "In {location}, {value} of adults have been diagnosed with diabetes.",
"measure_type": "percent",
"scale": "0-100",
"time_resolution": "Year",
"sources": [{ "id": "cdc" }]
}
}Required fields for each measure:
| Field | Description |
|---|---|
id |
Measure name (matches the measure column value exactly) |
short_name |
Human-readable short label |
long_name |
Full descriptive name |
category |
One of the categories listed below |
subcategory |
A subcategory from the table below, or null if the category has none |
short_description |
One-sentence description |
long_description |
Detailed description including methodology notes |
statement |
Template string for display: "In {location}, ..." — {value} holds a pre-formatted value; do not wrap it in %, $, or other symbols |
measure_type |
One of: percent, number, dollars — indicates how to format {value} |
scale |
Only present when measure_type is percent. One of: 0-1, 0-100 — the scale the raw value is stored on |
unit |
Optional. A data label to show alongside the value, only when necessary to contextualize it (e.g. Micrograms per cubic meter). Omitted when measure_type already conveys enough (e.g. plain percents/dollars) |
time_resolution |
One of: Week, Month, Year |
sources |
Array of source IDs matching entries in _sources |
Categories and their subcategories:
| Category | Subcategories |
|---|---|
chronic_disease |
cancer, cardiovascular_metabolic, musculoskeletal, neurological, other_chronic_conditions, respiratory_disease |
environmental_health |
air_and_water_quality, other_environmental_hazards |
infectious_disease |
none (null) |
injury_and_violence |
none (null) |
maternal_and_infant_health |
none (null) |
mental_health |
none (null) |
overall_health_status_and_mortality |
length_and_quality_of_life, mortality_data_and_completeness |
population_demographics |
none (null) |
preventive_care |
clinical_screenings, immunizations, nutrition_and_exercise, sexual_and_reproductive_health |
social_determinants_of_health |
economic_stability, education_access_and_quality, health_care_access_and_quality, neighborhood_and_built_environment, social_and_community_context |
substance_abuse |
alcohol_use, drug_use_and_overdose, tobacco_use |
The repo is built by a pipeline of R scripts that pull from a sibling Ingest clone (expected at ../Ingest relative to this repo) and write into the folder structure described above. Run everything at once with the wrapper script, from the repo root:
Rscript code/update_all.R
This runs, in order:
all_fips.R— refreshesresources/all_fips.csv.gz, the FIPS-code-to-name reference table (viatidycensus).scaffold_structure.R— creates any newstates|territories/{name}/counties/{fips}_{name}/folders (safe to re-run; existing folders are never overwritten). Skip with--skip-scaffoldonce the tree is already up to date.code/populate_national_rates.R— writesnational/national_rates.csv.gz.code/populate_state_rates.R— writesstates/*/state_rates.csv.gzandterritories/*/{commonwealth,territory}_rates.csv.gz.code/populate_county_rates.R— writesstates|territories/*/counties/*/county_rates.csv.gz.code/check_geography_renaming.R— fails the pipeline if any(measure, time)pair is reported under both an old and new FIPS convention for a renamed/split/merged geography (see Connecticut: Counties vs. Planning Regions and Alaska: Historical Borough and Census Area Changes).code/generate_geography_manifest.R— refreshesus-rates-geographies.json, a flat manifest of every geography (national/state/county) with its display name, slug, and the relative path to its rate file, for front-end/site consumption.
To skip the (usually unnecessary) scaffolding step on a routine data refresh:
Rscript code/update_all.R --skip-scaffold
Before running: make sure the Ingest repo alongside this one is up to date, since the populate_* scripts read directly from ../Ingest/data/. Any script can also be run individually (see the Usage comment at the top of each file) if you only need to refresh one part of the pipeline.
Adding a new data source: add a block to the relevant populate_*_rates.R script(s) that reads the new Ingest source, reshapes it into long format (geography, time, measure, value), and joins it into that script's combined bind_rows() call — then add a corresponding entry to measure_info.json for every new measure name.
- FIPS-first: All geographies are identified by FIPS codes, never by name alone.
- Long format: One row per
(geography, time, measure)combination. Sparse measures have no rows rather thanNAvalues. - Compressed files: All data files use
.csv.gzto minimize storage across thousands of county folders. - Consistent naming: The
measurecolumn follows{prefix}_{measure_name}across all files without exception. - Documented measures: Every measure must have a corresponding entry in the top-level
measure_info.json. - No integers for FIPS: FIPS codes are always stored as zero-padded strings (e.g.,
"01"not1,"01001"not1001). - Folder names are stable: Once created, county folder names should not change. The FIPS code ensures uniqueness even if county names change over time.