Toxic Release Totals Explained: Air, Water, Land and Off-Site

On this page

Problem statement

"Total releases" in the Toxics Release Inventory is not emissions. It adds air, water, underground injection, chemicals placed in landfills and surface impoundments on site, and chemicals sent off site for disposal. For mines the land part is waste rock and tailings, millions of pounds of metals that stay where they are put. And releases are only a small part of the toxic chemical waste facilities manage, most of which is recycled or treated. This page breaks the 2024 total into its routes, shows which columns hold the numbers, and compares releases with all production waste.

Quick answer

Split the total into air, water, underground injection, land on site and off site:

air = pounds(t24, "5.1 - FUGITIVE AIR") + pounds(t24, "5.2 - STACK AIR")
water = pounds(t24, "5.3 - WATER")
under = pounds(t24, "5.4.1 - UNDERGROUND CL I") + pounds(t24, "5.4.2 - UNDERGROUND C II-V")
onsite = pounds(t24, "ON-SITE RELEASE TOTAL")
tot = pd.Series({"air (stack and fugitive)": air.sum(), "water": water.sum(), "underground injection": under.sum(),
                 "land on site": (onsite - air - water - under).sum(), "off site": pounds(t24, "OFF-SITE RELEASE TOTAL").sum()}) / 1e6
print(tot.round(1).to_string())
print(f"sum {tot.sum():.1f}M lb; TOTAL RELEASES column {pounds(t24).sum() / 1e6:.1f}M lb")
air (stack and fugitive)     530.3
water                        188.7
underground injection        284.6
land on site                1838.7
off site                     448.2
sum 3290.5M lb; TOTAL RELEASES column 3290.5M lb

Of 3,290.5 million lb released in 2024, 1,838.7 million went onto land at the facility, 530.3 million to air, 284.6 million into underground injection wells, 188.7 million to water, and 448.2 million were sent off site for disposal. The routes add up exactly to the TOTAL RELEASES column.

Horizontal bars of 2024 total releases by route: land on site 1,838.7 million lb, air 530.3, off site 448.2, underground injection 284.6 and water 188.7. Horizontal bars of 2024 total releases by route: land on site 1,838.7 million lb, air 530.3, off site 448.2, underground injection 284.6 and water 188.7.
The 3,290.5 million lb of total releases, by route.

The columns behind the total

1. Which land columns hold numbers

for c in [c for c in t24.columns if c.startswith("5.5")]:
    print(f"{c:28} {pounds(t24, c).sum() / 1e6:10.1f}M lb, non-zero rows {(t24[c] > 0).sum():,}")
5.5.1 - LANDFILLS                   0.0M lb, non-zero rows 0
5.5.1A - RCRA C LANDFILL          141.3M lb, non-zero rows 855
5.5.1B - OTHER LANDFILLS          182.1M lb, non-zero rows 2,470
5.5.2 - LAND TREATMENT             14.4M lb, non-zero rows 482
5.5.3 - SURFACE IMPNDMNT            0.0M lb, non-zero rows 0
5.5.3A - RCRA SURFACE IM            0.3M lb, non-zero rows 45
5.5.3B - OTHER SURFACE I          772.9M lb, non-zero rows 1,486
5.5.4 - OTHER DISPOSAL            727.7M lb, non-zero rows 1,889

The parent columns 5.5.1 - LANDFILLS and 5.5.3 - SURFACE IMPNDMNT are empty; the numbers are in their sub-columns. Other surface impoundments hold 772.9 million lb and "other disposal" 727.7 million, the two largest land routes.

2. Releases are not the same as waste

print(f"production-related waste (8.1-8.7): {pounds(t24, 'PRODUCTION WSTE (8.1-8.7)').sum() / 1e9:.2f} billion lb")
for c in ["8.4 - RECYCLING ON SITE", "8.6 - TREATMENT ON SITE", "8.2 - ENERGY RECOVER ON", "8.1 - RELEASES"]:
    print(f"{c:28} {pounds(t24, c).sum() / 1e9:.2f}")
production-related waste (8.1-8.7): 41.10 billion lb
8.4 - RECYCLING ON SITE      25.01
8.6 - TREATMENT ON SITE      7.09
8.2 - ENERGY RECOVER ON      2.38
8.1 - RELEASES               0.00

Production-related waste was 41.10 billion lb in 2024, more than twelve times the releases. 25.01 billion was recycled on site and 7.09 billion treated on site. The 8.1 - RELEASES column is empty in this file; its sub-columns 8.1A to 8.1D carry the numbers.

Horizontal bars of production-related waste management in 2024: 25.01 billion lb recycled on site, 7.09 billion treated on site, then releases, energy recovery and off-site recycling and treatment. Horizontal bars of production-related waste management in 2024: 25.01 billion lb recycled on site, 7.09 billion treated on site, then releases, energy recovery and off-site recycling and treatment.
Releases are a small part of the toxic waste facilities manage.

3. Each sector's route

t24["air"] = pounds(t24, "5.1 - FUGITIVE AIR") + pounds(t24, "5.2 - STACK AIR")
t24["water"] = pounds(t24, "5.3 - WATER")
t24["all"] = pounds(t24)
sec = t24.groupby("INDUSTRY SECTOR")[["all", "air", "water"]].sum()
sec = sec.assign(air_share=sec.air / sec["all"], water_share=sec.water / sec["all"]).nlargest(6, "all")
print(sec[["all", "air_share", "water_share"]].assign(all=lambda d: (d["all"] / 1e6).round(1)).round(3).to_string())
                           all  air_share  water_share
INDUSTRY SECTOR                                       
Metal Mining            1345.4      0.001        0.001
Chemicals                445.8      0.382        0.058
Primary Metals           344.5      0.053        0.048
Hazardous Waste          205.0      0.001        0.000
Electric Utilities       191.1      0.243        0.014
Natural Gas Processing   159.9      0.029        0.000

Metal mining's 1,345.4 million lb are 0.1% air and 0.1% water. Chemical plants send 38.2% of their releases to air and electric utilities 24.3%. A sector's total says little about what its neighbours breathe.

A single facility, and the off-site part

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: one mine's report

red = t24[t24["FACILITY NAME"].str.contains("RED DOG", na=False)]
print(red.groupby("CHEMICAL")["TOTAL RELEASES"].sum().nlargest(4).div(1e6).round(1).to_string())
print(f"Red Dog: {pounds(red).sum() / 1e6:.1f}M lb, {pounds(red).sum() / pounds(t24).sum():.1%} of the US total")
CHEMICAL
Zinc compounds                                                                             380.8
Lead  And Lead Compounds                                                                   319.4
Manganese  And Manganese Compounds                                                          15.9
Chromium  and Chromium Compounds(except for chromite ore mined in the Transvaal Region)      2.6
Red Dog: 723.9M lb, 22.0% of the US total

Red Dog's report is mostly zinc and lead compounds, 380.8 and 319.4 million lb of its 723.9 million lb, which alone are 22.0% of the US total.

Example 2: where off-site releases go

off = list(t24.columns[t24.columns.get_loc("6.2 - M10"):t24.columns.get_loc("OFF-SITE RELEASE TOTAL")])
o = pd.Series({c: pounds(t24, c).sum() for c in off}).sort_values(ascending=False) / 1e6
print(o.head(5).round(1).to_string())
6.2 - M64    184.8
6.2 - M65     50.3
6.2 - M41     36.2
6.2 - M82     35.6
6.2 - M90     25.5
Stacked bars for eight sectors showing the share of releases to air, water, land on site and off site: metal mining almost entirely land, chemicals 38.2% air, food mostly water, paper mostly air. Stacked bars for eight sectors showing the share of releases to air, water, land on site and off site: metal mining almost entirely land, chemicals 38.2% air, food mostly water, paper mostly air.
Where each large sector's releases go.

Off-site releases go mainly to landfills at other facilities: codes M64 and M65, 184.8 and 50.3 million lb, are the largest.