Updated September 19, 2026 for the rebuilt release. This tutorial uses reporting years 2022 and 2023 only. Earlier references to 37 years of data, a PFAS exposure index and ready-to-use mapping were unsupported and have been removed.
The ClarityStorm EPA TRI snapshot contains 158,687 source form records covering 23,081 distinct TRI facility IDs. It describes reported chemical quantities within the TRI program’s coverage and reporting thresholds. It does not measure exposure, health effects or all pollution.
Two Tables and Their Keys
- epa_tri_releases: 158,687 records, uniquely identified by year plus doc_ctrl_num. A facility, chemical and year can have multiple source forms.
- epa_tri_facilities: 44,385 attribute versions across 43,982 facility/year groups. These are versions of 23,081 distinct facility IDs, not 44,385 separate facilities.
- Use facility_record_id to join a report to its exact facility attributes. Joining on TRIFID alone can multiply reports.
- All 122 source fields are mapped in the package documentation. Quantities use exact decimals; text, leading-zero identifiers and literal NA markers are preserved.
Load and Join the Package
import pandas as pd
releases = pd.read_parquet("epa_tri_releases.parquet")
facilities = pd.read_parquet("epa_tri_facilities.parquet")
assert not releases.duplicated(["year", "doc_ctrl_num"]).any()
assert facilities["facility_record_id"].is_unique
joined = releases.merge(
facilities[["facility_record_id", "facility_year_variant_count"]],
on="facility_record_id",
how="left",
validate="many_to_one",
)
assert len(joined) == len(releases)
assert joined["facility_year_variant_count"].notna().all()
print(f"Reports: {len(releases):,}")
print(f"Distinct facility IDs: {releases['trifid'].nunique():,}")Keep Units and Reporting Status Visible
Dioxin and dioxin-like compounds are reported in grams; other chemical quantities are in pounds. Never add those units together. Form A does not report release quantities: its source zeroes are flagged as not_reported_form_a. Other source zeroes can also represent blanks or not-applicable values. The production_ratio field is dimensionless.
# Restrict quantity summaries to Form R and retain each source unit.
reported = releases.loc[releases["form_type"].eq("R")]
annual = reported.groupby(["year", "unit_of_measure"])[
["release_fugitive_air", "release_stack_air", "release_water", "total_releases"]
].sum()
print(annual)
print(releases.groupby(["year", "quantity_reporting_status"]).size())These are summaries of the two delivered reporting years, not a long-term trend study. Total reported mass is not a toxicity-weighted score. Changes can reflect reporting coverage and source revisions as well as changes in activity.
Summarize Reported PFAS Quantities
The repaired dataset preserves EPA’s YES/NO flags as strings. The following example reports quantities by state, year and unit. It does not identify population exposure or rank health risk.
pfas = reported.loc[reported["pfas"].eq("YES")]
pfas_by_state = pfas.groupby(
["year", "state", "unit_of_measure"]
)["total_releases"].sum().sort_values(ascending=False)
print(pfas_by_state.head(15))
# Inspect documented formula comparisons without silently changing source totals.
comparisons = [c for c in releases if c.endswith("_matches_components")]
print(releases[comparisons].eq(False).sum())Interpret Totals and Coordinates Carefully
Reported totals are preserved. The difference and matches_components fields compare them with literal formulas in EPA’s guide; a false flag does not prove a source report is invalid. Primary release-total differences are at most 0.002 source units. Treatment totals include POTW treatment omitted from the stated formula. Energy and transfer formulas have unresolved differences up to 142,991 pounds, disclosed in the package.
All coordinate pairs pass numeric bounds, but the source horizontal_datum field is blank throughout. Raw coordinate text and status fields remain available. Establish coordinate reference and geographic accuracy before overlaying these points on a map; the dataset does not infer a datum or geocode exposures.
The free sample contains 1,000 report rows and cannot estimate national totals. The complete 2022–2023 package contains both tables as CSV and Parquet, a field dictionary, exact source manifest and documented limitations. Future updates are not included.