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.

Bar chart of facilities by form filed for 2024: 17,497 only Form R, 2,251 both and 1,734 only Form A. Bar chart of facilities by form filed for 2024: 17,497 only Form R, 2,251 both and 1,734 only Form A.
Which form each facility filed.

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.

A bar splitting the 4,104 facilities whose 2024 releases add up to zero into those that reported zero on Form R and 1,734 that filed only Form A. A bar splitting the 4,104 facilities whose 2024 releases add up to zero into those that reported zero on Form R and 1,734 that filed only Form A.
Two kinds of zero.

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%
Bar chart of the share of each state's rows filed on Form A, lowest in Louisiana and highest in Georgia at 17.3%. Bar chart of the share of each state's rows filed on Form A, lowest in Louisiana and highest in Georgia at 17.3%.
Share of rows filed on Form A by state.

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.