TRI Form A Explained: Facilities That Report No Quantities
On this page
Problem statement
A facility that uses a listed chemical but releases and manages only small amounts of it can file EPA's short Form A instead of the full Form R. Form A certifies that the facility is below the threshold; it reports no quantities at all. In the Toxics Release Inventory data files those rows look like any other, with zeros or blanks in every amount column, so a facility that filed only Form A looks like a facility that released nothing. This page finds the Form A rows in the 2024 file and shows what they do to counts and averages.
Quick answer
The FORM TYPE column says which form each row came from:
print(t24["FORM TYPE"].value_counts().to_string())
fa = t24[t24["FORM TYPE"] == "A"]
print(f"Form A rows with a non-zero amount in any release column: {(fa.loc[:, '5.1 - FUGITIVE AIR':'TOTAL RELEASES'].fillna(0) != 0).any(axis=1).sum()}")
FORM TYPE
R 68567
A 8728
Form A rows with a non-zero amount in any release column: 0
8,728 of the 77,295 rows came from Form A, and not one of them has a non-zero amount in any release or transfer column.
What Form A says, and what it does to counts
1. Zero is not zero
r = t24[t24["FORM TYPE"] == "R"]
print(f"Form R rows reporting 0 total releases: {(r['TOTAL RELEASES'] == 0).sum():,} of {len(r):,}")
print(f"Form A rows: {len(fa):,}, all with 0 or blank in every amount column")
Form R rows reporting 0 total releases: 8,172 of 68,567
Form A rows: 8,728, all with 0 or blank in every amount column
A zero on Form R is a reported zero: 8,172 Form R rows report 0 total releases. A zero on Form A means "below the threshold, not reported". Both look the same in the amount columns.
2. Facilities that file only Form A
kinds = t24.groupby("TRIFD")["FORM TYPE"].agg(lambda s: "".join(sorted(set(s))))
print(kinds.map({"A": "only Form A", "R": "only Form R", "AR": "both"}).value_counts().to_string())
FORM TYPE
only Form R 17497
both 2251
only Form A 1734
1,734 facilities filed only Form A, 2,251 filed both forms for different chemicals, and 17,497 only Form R.
3. Which chemicals and sectors use it
print(fa.CHEMICAL.value_counts().head(5).to_string())
print((fa["INDUSTRY SECTOR"].value_counts(normalize=True).head(3) * 100).round(1).to_string())
CHEMICAL
Zinc compounds 647
Diisocyanates 499
Copper compounds 439
Manganese compounds 438
Ethylene glycol 377
INDUSTRY SECTOR
Chemicals 31.5
Food 12.1
Petroleum Bulk Terminals 9.6
Zinc compounds, diisocyanates, copper and manganese compounds and ethylene glycol are the chemicals most often filed on Form A, and 31.5% of Form A rows come from chemical plants.
Counting facilities and averages
The data used on this page
The US EPA's Toxics Release Inventory basic data files for 2014 and 2024, public domain, downloaded on 16 October 2026 and saved on this site, because EPA revises past years when facilities resubmit. With them come the Census Bureau's Vintage 2025 county population estimates and 2024 county boundaries.
from pathlib import Path
import re
import zipfile
import geopandas as gpd
import numpy as np
import pandas as pd
import requests
Path("data").mkdir(exist_ok=True)
# EPA Toxics Release Inventory basic data files, reporting years 2014 and 2024, all states, xz-compressed and
# otherwise unchanged; Census Vintage 2025 county estimates and 2024 cartographic boundary counties.
# Downloaded 16 October 2026 and saved here, because EPA revises past years.
# Live: https://data.epa.gov/efservice/downloads/tri/mv_tri_basic_download/2024_US/csv (and 2014_US)
ARCHIVE = "epa-tri-2014-2024-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 tri(year):
"""One year's TRI file: a row per facility and chemical, columns numbered '1. YEAR' to '122. ...'."""
d = pd.read_csv(Z.open(f"tri_{year}_us.csv.xz"), compression="xz", dtype=str, low_memory=False)
d.columns = [re.sub(r"^\d+\.\s*", "", c) for c in d.columns]
amounts = list(d.columns[d.columns.get_loc("5.1 - FUGITIVE AIR"):d.columns.get_loc("8.8 - ONE-TIME RELEASE") + 1])
d[amounts + ["LATITUDE", "LONGITUDE"]] = d[amounts + ["LATITUDE", "LONGITUDE"]].apply(pd.to_numeric, errors="coerce")
return d
def pounds(d, col="TOTAL RELEASES"):
"""A quantity in pounds: dioxin and dioxin-like compounds are reported in grams."""
return d[col].where(d["UNIT OF MEASURE"] == "Pounds", d[col] / 453.59237)
t24 = tri(2024)
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)
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")
print(f"TRI 2024: {len(t24):,} facility-chemical rows from {t24.TRIFD.nunique():,} facilities; {pounds(t24).sum() / 1e9:.2f} billion lb released")
TRI 2024: 77,295 facility-chemical rows from 21,482 facilities; 3.29 billion lb released
Example 1: an average that Form A drags down
by = t24.groupby("TRIFD").agg(lb=("TOTAL RELEASES", "sum"), onlyA=("FORM TYPE", lambda s: (s == "A").all()))
print(f"median release per facility, all: {by.lb.median():,.0f} lb; excluding only-Form-A facilities: {by[~by.onlyA].lb.median():,.0f} lb")
print(f"facilities releasing 0 lb: {(by.lb == 0).sum():,}, of which only Form A: {(by.lb == 0)[by.onlyA].sum():,}")
median release per facility, all: 409 lb; excluding only-Form-A facilities: 760 lb
facilities releasing 0 lb: 4,104, of which only Form A: 1,734
Counted as zero, Form A facilities pull the median release per facility down to 409 lb; leaving out the facilities that filed only Form A, it is 760 lb. Of the 4,104 facilities that released 0 lb, 1,734 are Form A filers whose amounts are unknown but small.
Example 2: by state
st = t24.groupby("ST")["FORM TYPE"].apply(lambda s: (s == "A").mean())
cnt = t24.ST.value_counts()
print(st[cnt >= 1000].sort_values(ascending=False).head(5).map("{:.1%}".format).to_string())
ST
GA 17.3%
IA 16.8%
MO 16.3%
FL 15.6%
MN 15.3%
Georgia, Iowa, Missouri, Florida and Minnesota have the highest share of Form A rows, 15% to 17% of their rows, so averages and zero counts there are the most affected.
Internal links
- Read the TRI files β the
FORM TYPEcolumn - Release totals explained β what Form R rows add up to
- Change since 2014 β facilities with no amounts in either year
- Facilities by amount released β the zero-release circles