Fixing FAA Registry County Codes That No Longer Exist
On this page
Problem statement
The FAA registry gives each owner's county as a three-digit code, which with the state's two-digit FIPS code makes the usual five-digit county GEOID. Join it to current county boundaries and about one record in a hundred finds nothing. The codes are not wrong so much as old: the registry still uses county codes that the Census Bureau has since retired, so those aircraft drop off any county map without an error. This page finds the retired codes in the 16 October 2026 file and puts the records back by placing each owner's ZIP code in a 2024 county.
Quick answer
Build the GEOID and look for codes the 2024 boundary file does not have:
us = reg[(reg.COUNTRY == "US") & reg.STATE.isin(STATEFP.index)].copy()
us["GEOID"] = us.STATE.map(STATEFP) + us.COUNTY
miss = us[~us.GEOID.isin(counties.GEOID)]
print(f"US records: {len(us):,}; county code not in the 2024 boundaries: {len(miss):,} ({len(miss) / len(us):.1%})")
print(miss.GEOID.value_counts().head(8).to_string())
US records: 311,085; county code not in the 2024 boundaries: 3,624 (1.2%)
GEOID
12025 1651
09001 410
09003 334
09009 289
02261 224
09011 178
09005 159
09007 96
3,624 US records, 1.2%, have a county code that does not exist in the 2024 boundaries. Three groups account for nearly all of them: Florida's 12025, Connecticut's eight 09001 to 09015 codes, and Alaska's 02261.
Three retired codes and a better key
1. Who the missing codes are
for g in ["12025", "02261"]:
print(g, miss[miss.GEOID == g].CITY.value_counts().head(3).to_dict())
print("Connecticut:", miss[miss.STATE == "CT"].COUNTY.value_counts().to_dict())
12025 {'MIAMI': 767, 'SARASOTA': 148, 'CORAL GABLES': 90}
02261 {'CORDOVA': 64, 'GLENNALLEN': 57, 'COPPER CENTER': 41}
Connecticut: {'001': 410, '003': 334, '009': 289, '011': 178, '005': 159, '007': 96, '013': 81, '015': 57}
12025 was Dade County, Florida, renumbered 12086 when it became Miami-Dade in 1997; the registry still uses the old code for 1,651 records, mostly in Miami and Coral Gables, though 148 give Sarasota as the city. 02261 was Alaska's Valdez-Cordova Census Area, split in 2019 into Chugach and Copper River (02063 and 02066). Connecticut's eight codes are the counties the Census Bureau replaced with nine planning regions in 2022.
2. Place each ZIP code instead
gaz = pd.read_csv(zipfile.ZipFile(Z.open("2024_Gaz_zcta_national.zip")).open("2024_Gaz_zcta_national.txt"), sep="\t", dtype={"GEOID": str})
gaz.columns = gaz.columns.str.strip()
pts = gpd.GeoDataFrame(gaz[["GEOID"]].rename(columns={"GEOID": "zcta"}), geometry=gpd.points_from_xy(gaz.INTPTLONG, gaz.INTPTLAT), crs=4326)
zcta_county = gpd.sjoin(pts, counties[["GEOID", "geometry"]].to_crs(4326), predicate="within").set_index("zcta").GEOID
us["zip_county"] = us["ZIP CODE"].map(zcta_county)
print(f"ZCTAs placed in a county: {len(zcta_county):,}; US records with a ZIP county: {us.zip_county.notna().mean():.1%}")
ZCTAs placed in a county: 33,736; US records with a ZIP county: 97.1%
The 2024 ZCTA gazetteer gives each ZIP code area an internal point. A spatial join puts 33,736 of those points in a 2024 county, and 97.1% of US records have a ZIP code that can be placed this way.
3. How often the two agree
both = us[us.GEOID.isin(counties.GEOID) & us.zip_county.notna()]
print(f"records with both: {len(both):,}; same county: {(both.GEOID == both.zip_county).mean():.1%}")
fixed = miss.assign(zip_county=us.zip_county)
print(fixed.groupby("GEOID").zip_county.agg(lambda s: s.value_counts().head(2).to_dict()).loc[["12025", "02261"]].to_string())
records with both: 298,495; same county: 93.6%
GEOID
12025 {'12086': 1393, '12115': 119}
02261 {'02063': 89, '02066': 73}
Where a record has both a valid FAA county and a ZIP county, they agree 93.6% of the time; ZIP areas cross county lines, so some disagreement is expected. For the retired codes the ZIP points give the answer: most 12025 records land in Miami-Dade (12086), and the 119 in Sarasota County (12115) show the old code was wrong for them anyway. 02261 divides between 02063 and 02066.
A rule for the whole file
The data used on this page
The FAA's Releasable Aircraft Database, public domain, downloaded on 16 October 2026 and saved on this site, because the FAA replaces it every day. The saved copy leaves out the owners' names and street addresses and keeps five-digit ZIP codes; every other column is as published. With it come the Census Bureau's Vintage 2025 county population estimates, 2024 county boundaries and 2024 ZCTA gazetteer.
from pathlib import Path
import zipfile
import geopandas as gpd
import numpy as np
import pandas as pd
import requests
Path("data").mkdir(exist_ok=True)
# FAA Releasable Aircraft Database, 16 October 2026: MASTER.txt without the NAME, STREET, STREET2 and OTHER NAMES
# columns and with five-digit ZIP codes (MASTER-no-names.txt), ACFTREF.txt and ENGINE.txt as published; Census
# Vintage 2025 county estimates, 2024 cartographic boundary counties and 2024 ZCTA gazetteer. Saved here unchanged
# otherwise, because the FAA replaces the file daily.
# Live: https://registry.faa.gov/database/ReleasableAircraft.zip (needs a browser User-Agent),
# https://www2.census.gov/geo/docs/maps-data/data/gazetteer/2024_Gazetteer/2024_Gaz_zcta_national.zip
ARCHIVE = "faa-aircraft-registry-2026-10-16.zip"
if not Path(f"data/{ARCHIVE}").exists():
Path(f"data/{ARCHIVE}").write_bytes(requests.get(f"https://data.spatialworkflow.io/{ARCHIVE}", timeout=600).content)
Z = zipfile.ZipFile(f"data/{ARCHIVE}")
def faa(name):
"""One registry file: comma-separated, every field padded with spaces, a byte-order mark and a trailing comma."""
d = pd.read_csv(Z.open(name), dtype=str, encoding="utf-8-sig", keep_default_na=False)
d.columns = d.columns.str.strip()
d = d.loc[:, [c for c in d.columns if c and not c.startswith("Unnamed")]]
return d.apply(lambda c: c.str.strip())
reg = faa("MASTER-no-names.txt")
ref = faa("ACFTREF.txt")
TYPE_AIRCRAFT = {"1": "glider", "2": "balloon", "3": "blimp", "4": "fixed wing single engine", "5": "fixed wing multi engine",
"6": "rotorcraft", "7": "weight-shift", "8": "powered parachute", "9": "gyroplane", "H": "hybrid lift", "O": "other"}
REGISTRANT = {"1": "individual", "2": "partnership", "3": "corporation", "4": "co-owned", "5": "government", "7": "LLC",
"8": "non-citizen corporation", "9": "non-citizen co-owned", "": "blank"}
Path("data/cb_2024_us_county_20m.zip").write_bytes(Z.read("cb_2024_us_county_20m.zip"))
counties = gpd.read_file("data/cb_2024_us_county_20m.zip")
STATEFP = counties.drop_duplicates("STUSPS").set_index("STUSPS").STATEFP
pop = pd.read_csv(Z.open("co-est2025-alldata.csv"), encoding="latin-1", dtype={"STATE": str, "COUNTY": str})
pop = pop[pop.SUMLEV == 50].assign(GEOID=lambda p: p.STATE + p.COUNTY)
print(f"FAA registry, 16 October 2026: {len(reg):,} aircraft, {len(ref):,} make-and-model codes")
FAA registry, 16 October 2026: 317,604 aircraft, 94,115 make-and-model codes
Example 1: keep the FAA code where it exists, fall back to the ZIP
us["county_fixed"] = us.GEOID.where(us.GEOID.isin(counties.GEOID), us.zip_county)
print(f"records with a 2024 county: before {us.GEOID.isin(counties.GEOID).mean():.2%}, after {us.county_fixed.notna().mean():.2%}")
print(us[us.county_fixed.isna()].STATE.value_counts().head(4).to_dict())
records with a 2024 county: before 98.84%, after 99.98%
{'FL': 54, 'CT': 19, 'AK': 4}
Keeping the FAA code where it is valid and falling back to the ZIP county raises the share of records with a 2024 county from 98.84% to 99.98%. The few left have a ZIP code with no ZCTA to place.
Example 2: Connecticut by planning region
ct = us[us.STATE == "CT"].county_fixed.value_counts()
names = counties.set_index("GEOID").NAME
print(ct.rename(index=names).to_string())
county_fixed
Capitol 373
Western Connecticut 299
Naugatuck Valley 216
Southeastern Connecticut 167
South Central Connecticut 153
Greater Bridgeport 118
Lower Connecticut River Valley 118
Northwest Hills 97
Northeastern Connecticut 49
Connecticut's records now fall into all nine planning regions, from 373 in Capitol to 49 in Northeastern Connecticut, so the state can be mapped with current boundaries.
Internal links
- Registered aircraft per resident β the county map these rows were missing from
- Read the FAA registry β the STATE and COUNTY columns
- Why Connecticut is missing from a county map β the planning regions in other data
- Fly-in communities by ZIP code β the gazetteer used here