How to Analyse Power Plant Retirements and Planned Closures
On this page
Problem statement
The US power fleet is being rebuilt: coal plants close, gas peakers built in the 1970s retire, and solar and batteries take their place. EIA-860 records both sides β every retired generator with its retirement year, and every operating generator whose owner has filed a planned retirement date β and the monthly EIA-860M shows what has happened since. This guide measures retirements from 2011 to 2025 by technology and state, how old plants were when they closed, how much of today's coal fleet has a closing date, and how planned dates compare with what actually retired in 2026.
Quick answer
Read the "Retired and Canceled" sheet of the EIA-860 generator workbook, keep status RE (retired, not canceled), and pivot nameplate capacity by Retirement Year and technology. Planned closures are the Planned Retirement Year column of the operable sheet.
ret = read_860("3_1_Generator_Y2025.xlsx", "Retired and Canceled")
ret = ret[ret.Status == "RE"].assign(MW=lambda d: pd.to_numeric(d["Nameplate Capacity (MW)"], errors="coerce"),
year=lambda d: pd.to_numeric(d["Retirement Year"], errors="coerce"))
ret["group"] = ret.Technology.map(GROUP).fillna("other")
by = ret[ret.year.between(2011, 2025)].pivot_table(index="year", columns="group", values="MW", aggfunc="sum", fill_value=0) / 1000
print(f"retired 2011-2025: {by.values.sum():,.1f} GW, of which coal {by['coal'].sum():,.1f} GW and gas {by['gas'].sum():,.1f} GW")
print("coal retired by year, GW:", by["coal"].round(1).to_dict())
retired 2011-2025: 176.9 GW, of which coal 103.1 GW and gas 49.7 GW
coal retired by year, GW: {2011: 2.3, 2012: 8.1, 2013: 4.5, 2014: 3.8, 2015: 13.0, 2016: 7.7, 2017: 4.3, 2018: 11.6, 2019: 9.0, 2020: 8.6, 2021: 6.1, 2022: 9.4, 2023: 7.8, 2024: 4.2, 2025: 2.6}
176.9 GW of generating capacity retired from 2011 to 2025, of which coal was 103.1 GW and gas 49.7 GW. Coal retirements peaked at 13.0 GW in 2015, when federal mercury rules took effect, and 11.6 GW in 2018, and fell to 2.6 GW in 2025.
Retired, planned and left
1. Retired, canceled, and the status codes
r_all = read_860("3_1_Generator_Y2025.xlsx", "Retired and Canceled")
print("status codes:", r_all.Status.value_counts().to_dict())
print(f"retired generators with a retirement year: {ret.year.notna().mean():.1%}; earliest {ret.year.min():.0f}")
status codes: {'RE': 4787, 'CN': 890, 'IP': 128}
retired generators with a retirement year: 100.0%; earliest 1950
The sheet mixes three statuses: 4,787 retired generators (RE), 890 canceled projects (CN) that never ran, and 128 indefinitely postponed (IP). Counting all of them as retirements would add capacity that was never built. Every retired generator has a retirement year; the earliest is 1950.
2. How old plants are when they retire
ret["age"] = ret.year - pd.to_numeric(ret["Operating Year"], errors="coerce")
a = ret[ret.year.between(2015, 2025)].groupby("group").apply(
lambda d: pd.Series({"GW": d.MW.sum() / 1000, "median_age": d.age.median()}), include_groups=False)
print(a.sort_values("GW", ascending=False).head(6).round(1).to_string())
GW median_age
group
coal 84.4 56.0
gas 35.9 47.0
oil 8.5 42.0
biomass 1.6 18.0
nuclear 1.1 46.0
wind 1.0 21.0
Coal generators that retired from 2015 to 2025 β 84.4 GW β were a median of 56 years old; gas units 47 years. Wind turbines retire much younger (21 years), usually to be replaced by larger ones on the same site.
3. Planned retirements in the operating fleet
gen["planned"] = pd.to_numeric(gen["Planned Retirement Year"], errors="coerce")
pl = gen[gen.planned.notna()].pivot_table(index="planned", columns="group", values="MW", aggfunc="sum", fill_value=0) / 1000
print(f"capacity with a planned retirement date: {pl.values.sum():,.1f} GW; coal {pl['coal'].sum():,.1f} GW")
print("planned coal retirements, GW:", pl["coal"].loc[2026:2032].round(1).to_dict())
capacity with a planned retirement date: 82.1 GW; coal 47.0 GW
planned coal retirements, GW: {2026.0: 4.1, 2027.0: 7.1, 2028.0: 12.9, 2029.0: 3.5, 2030.0: 3.8, 2031.0: 3.7, 2032.0: 4.3}
82.1 GW of operating capacity has a planned retirement date, 47.0 GW of it coal. The coal schedule peaks in 2028 (12.9 GW) after 4.1 GW in 2026 and 7.1 GW in 2027.
4. What is left of coal
coal = gen[gen.group == "coal"]
left = coal.MW.sum() / 1000
sched = coal[coal.planned.notna()].MW.sum() / 1000
print(f"operating coal: {left:,.1f} GW at {coal['Plant Code'].nunique()} plants; with a retirement date: {sched:,.1f} GW ({sched / left:.0%})")
operating coal: 182.5 GW at 213 plants; with a retirement date: 47.0 GW (26%)
At the end of 2025, 182.5 GW of coal capacity was still operable at 213 plants, and 26% of it had a retirement date. The rest has none filed, which does not mean it will run indefinitely β dates are filed when an owner decides, often only a few years ahead.
Where coal closed, and 2026 so far
The data used on this page
The US Energy Information Administration's EIA-860 for 2025 (operable and retired generators) and EIA-860M for August 2026, saved copies; Census Bureau 2023 cartographic boundary states.
import io
import zipfile
from pathlib import Path
import geopandas as gpd
import numpy as np
import pandas as pd
import requests
Path("data").mkdir(exist_ok=True)
# EIA-860 (2025, released September 2026), EIA-923 (2025 final) and EIA-860M (August 2026), saved on 15 October 2026.
# Live: https://www.eia.gov/electricity/data/eia860/xls/eia8602025.zip, .../eia923/xls/f923_2025.zip and
# .../eia860m/xls/august_generator2026.xlsx; the annual files move to archive/xls/ when the next year's data appear.
FILES = {"eia860": "eia-860-2025-2026-10-15.zip", "eia923": "eia-923-2025-final-2026-10-15.zip",
"eia860m": "eia-860m-august-2026-2026-10-15.xlsx"}
for name in FILES.values():
if not Path(f"data/{name}").exists():
Path(f"data/{name}").write_bytes(requests.get(f"https://data.spatialworkflow.io/{name}", timeout=600).content)
def read_860(member, sheet=0):
"""One sheet of an EIA-860 workbook; the first row is a title, the last row of some sheets a note."""
with zipfile.ZipFile(f"data/{FILES['eia860']}") as z:
d = pd.read_excel(io.BytesIO(z.read(member)), sheet_name=sheet, skiprows=1)
return d[pd.to_numeric(d["Plant Code"], errors="coerce").notna()]
def read_923(sheet="Page 1 Generation and Fuel Data"):
with zipfile.ZipFile(f"data/{FILES['eia923']}") as z:
d = pd.read_excel(io.BytesIO(z.read("EIA923_Schedules_2_3_4_5_M_12_2025_Final.xlsx")), sheet_name=sheet, skiprows=5)
d.columns = [c.replace("\n", " ") for c in d.columns]
return d
GROUP = {"Conventional Steam Coal": "coal", "Coal Integrated Gasification Combined Cycle": "coal",
"Natural Gas Fired Combined Cycle": "gas", "Natural Gas Fired Combustion Turbine": "gas",
"Natural Gas Steam Turbine": "gas", "Natural Gas Internal Combustion Engine": "gas",
"Natural Gas with Compressed Air Storage": "gas", "Other Natural Gas": "gas", "Nuclear": "nuclear",
"Onshore Wind Turbine": "wind", "Offshore Wind Turbine": "wind", "Solar Photovoltaic": "solar",
"Solar Thermal with or without Energy Storage": "solar", "Conventional Hydroelectric": "hydro",
"Batteries": "storage", "Hydroelectric Pumped Storage": "storage", "Flywheels": "storage",
"Petroleum Liquids": "oil", "Petroleum Coke": "oil", "Wood/Wood Waste Biomass": "biomass",
"Landfill Gas": "biomass", "Municipal Solid Waste": "biomass", "Other Waste Biomass": "biomass",
"Geothermal": "geothermal"}
gen = read_860("3_1_Generator_Y2025.xlsx", "Operable")
gen["MW"] = pd.to_numeric(gen["Nameplate Capacity (MW)"], errors="coerce")
gen["group"] = gen.Technology.map(GROUP).fillna("other")
plants = read_860("2___Plant_Y2025.xlsx")
print(f"{len(plants):,} plants, {len(gen):,} operable generators, {gen.MW.sum() / 1000:,.1f} GW nameplate")
17,349 plants, 27,918 operable generators, 1,376.7 GW nameplate
Example 1: retirements by state
st = ret[ret.year.between(2011, 2025) & (ret.group == "coal")].groupby("State").MW.sum().sort_values(ascending=False) / 1000
print("coal retired 2011-2025 by state, GW:", st.head(6).round(1).to_dict())
coal retired 2011-2025 by state, GW: {'GA': 7.3, 'IL': 6.9, 'TX': 6.6, 'KY': 6.4, 'IN': 6.3, 'MI': 5.9}
Georgia retired the most coal capacity from 2011 to 2025 (7.3 GW), followed by Illinois (6.9), Texas (6.6), Kentucky (6.4) and Indiana (6.3) β states that were large coal users, not only states with policies against it.
Example 2: what happened in 2026 so far
m = pd.read_excel(f"data/{FILES['eia860m']}", sheet_name="Retired", skiprows=2)
m = m[pd.to_numeric(m["Plant ID"], errors="coerce").notna()]
m26 = m[pd.to_numeric(m["Retirement Year"], errors="coerce") == 2026]
print(f"retired January-August 2026 (EIA-860M): {pd.to_numeric(m26['Nameplate Capacity (MW)'], errors='coerce').sum() / 1000:.1f} GW;",
"by technology:", (m26.groupby("Technology")["Nameplate Capacity (MW)"].sum() / 1000).sort_values(ascending=False).head(4).round(2).to_dict())
print(f"coal among them: {pd.to_numeric(m26.loc[m26.Technology == 'Conventional Steam Coal', 'Nameplate Capacity (MW)'], errors='coerce').sum():.0f} MW")
retired January-August 2026 (EIA-860M): 0.9 GW; by technology: {'Natural Gas Fired Combustion Turbine': 0.82, 'Natural Gas Steam Turbine': 0.04, 'Batteries': 0.01, 'Landfill Gas': 0.01}
coal among them: 0 MW
EIA-860M for August 2026 shows 0.9 GW retired in the first eight months of 2026, almost all of it gas combustion turbines, and no coal β against 4.1 GW of coal scheduled for 2026 in the annual file. Planned dates slip: owners postpone closures, and federal emergency orders since 2025 have kept some coal units running past their dates.
Reading retirement data
A retirement in EIA-860 is a generator removed from service permanently, dated by month and year. It is recorded once the owner reports it, so the latest year in an annual file is complete but the current year needs EIA-860M. Planned retirement dates are the owner's stated intention at the time of reporting, revised as plans change; they are a forecast, and the 2026 comparison shows how unreliable it can be in the short run.
Capacity retired is also not energy lost. The coal units that closed were mostly old and little used; a gigawatt of retired capacity running at a low capacity factor removes less generation than a gigawatt of new solar adds, and far less than a gigawatt of nuclear. Pair retirement capacity with the units' recent generation from EIA-923 when the question is about energy.
Traps in the retired sheet
- Canceled is not retired. Filter
Status == "RE". - Repowered units. A coal unit converted to gas appears as a retirement of the coal generator and a new gas generator, or as a changed fuel, depending on how the owner reported it.
- Partial retirements. Derates reduce capacity without retiring a generator; they are in the uprate/derate columns.
- Blank years.
Planned Retirement Yearis a space when there is none; convert witherrors="coerce".
Internal links
- How to track new generating capacity β what replaces it
- How to map US power plants from EIA-860 β the operating fleet
- Nameplate or summer capacity β which megawatts to count
FAQ
How much coal capacity has retired in the US?
103.1 GW from 2011 to 2025, according to EIA-860; 182.5 GW was still operable at the end of 2025.
How many coal plants are scheduled to close?
Owners had filed retirement dates for 47.0 GW of coal capacity, 26% of the operating fleet, with the largest year 2028.