Artículo

python-eia: Descarga datos energéticos de EE.UU. en pocas líneas de Python

Una biblioteca de Python que envuelve la API v2 de EIA en llamadas simples — obtén mix de generación, demanda, intercambios y precios de gas natural como DataFrames.

If you’ve worked with US energy data, you’ve probably dealt with the EIA API — building URLs by hand, parsing JSON responses, handling pagination, and converting everything into DataFrames yourself.

We built python-eia to skip all of that. Discover what data is available, inspect filtering options, and download DataFrames — all from Python.

from eia import EIAClient

client = EIAClient()  # reads EIA_API_KEY from environment
route = client.get_data_endpoint("electricity/rto/fuel-type-data")
route.facets.respondent.get_values()  # discover available grid operators
df = route.get(facets={"respondent": "CISO"}, frequency="hourly", start="2024-06-01", end="2024-06-08")

In this article, we’ll walk through the library’s main features with executable examples and Plotly visualizations. If you want to understand the raw API before using the library, read our EIA API tutorial.

Installation

pip install python-eia

You’ll need a free API key from the EIA website. Set it as an environment variable:

export EIA_API_KEY=your-token-here

Before vs after: the “aha” moment

If you’ve searched for a Python EIA library, you’ve probably found eiapy — the most downloaded option on PyPI. The problem? It targets API v1, which EIA shut down. It no longer works.

# Option 1: eiapy (targets deprecated API v1 — broken)
from eiapy import Series
cal_to_mex = Series('EBA.CISO-CFE.ID.H')
cal_to_mex.last(5)  # ❌ EIA API v1 was shut down

So you’re left writing raw requests against v2:

# Option 2: raw requests (works, but verbose)
import requests, pandas as pd

url = "https://api.eia.gov/v2/electricity/rto/fuel-type-data/data/"
params = {
    "api_key": API_KEY,
    "frequency": "hourly",
    "data[0]": "value",
    "facets[respondent][]": "CISO",
    "start": "2024-06-01",
    "end": "2024-06-08",
    "length": 5000,
}
response = requests.get(url, params=params)
data = response.json()["response"]["data"]
df = pd.DataFrame(data)
# Still need: pagination, type coercion, error handling...

With python-eia, the same task becomes:

# Option 3: python-eia (API v2, returns DataFrames)
from eia import EIAClient

client = EIAClient()
route = client.get_data_endpoint("electricity/rto/fuel-type-data")
df = route.get(facets={"respondent": "CISO"}, frequency="hourly", start="2024-06-01", end="2024-06-08")

Key differences:

  • API v2 — the only version that works today
  • Data discovery — explore available datasets, facets, and frequencies from Python
  • Automatic pagination — fetches all pages transparently
  • Returns DataFrames — no JSON parsing, no type coercion

Discovering data from Python

The EIA API organizes data as a tree. With python-eia, you navigate it interactively — no need to check the website or guess route strings.

Step 1: Browse top-level categories

client.route("").keys()
Out14 lines
['coal',
 'crude_oil_imports',
 'electricity',
 'international',
 'natural_gas',
 'nuclear_outages',
 'petroleum',
 'seds',
 'steo',
 'densified_biomass',
 'total_energy',
 'aeo',
 'ieo',
 'co2_emissions']

Step 2: Drill into a category

client.route("electricity").keys()
Out6 lines
['retail_sales',
 'electric_power_operational_data',
 'rto',
 'state_electricity_profiles',
 'operating_generator_capacity',
 'facility_fuel']
client.route("electricity/rto").keys()
Out8 lines
['region_data',
 'fuel_type_data',
 'region_sub_ba_data',
 'interchange_data',
 'daily_region_data',
 'daily_region_sub_ba_data',
 'daily_fuel_type_data',
 'daily_interchange_data']

Step 3: Reach a data endpoint

When keys() returns an empty list, you’ve reached a data endpoint. Access it with .data:

route = client.get_data_endpoint("electricity/rto/fuel-type-data")

Step 4: Discover filtering options (facets)

Facets are the filters you can apply to a query. Each endpoint has different ones:

route.facets
Out
FacetContainer(facets=[respondent, fueltype])

What values does each facet accept? Ask it:

route.facets.respondent.get_values()[:8]
Out8 lines
[FacetValue(id='CISO', name='California Independent System Operator'),
 FacetValue(id='CAR', name='Carolinas'),
 FacetValue(id='TEPC', name='Tucson Electric Power'),
 FacetValue(id='MIDA', name='Mid-Atlantic'),
 FacetValue(id='SCEG', name='Dominion Energy South Carolina, Inc.'),
 FacetValue(id='NY', name='New York'),
 FacetValue(id='NYIS', name='New York Independent System Operator'),
 FacetValue(id='NWMT', name='NorthWestern Corporation')]
route.facets.fueltype.get_values()
Out20 lines
[FacetValue(id='PS', name='Pumped storage'),
 FacetValue(id='UES', name='Unknown energy storage'),
 FacetValue(id='NUC', name='Nuclear'),
 FacetValue(id='OTH', name='Other'),
 FacetValue(id='NG', name='Natural Gas'),
 FacetValue(id='WND', name='Wind'),
 FacetValue(id='WNB', name='Wind with integrated battery storage'),
 FacetValue(id='BAT', name='Battery'),
 FacetValue(id='SUN', name='Solar'),
 FacetValue(id='OIL', name='Petroleum'),
 FacetValue(id='UES', name='Unknown Energy'),
 FacetValue(id='GEO', name='Geothermal'),
 FacetValue(id='SNB', name='Solar with integrated battery storage'),
 FacetValue(id='WAT', name='Hydro'),
 FacetValue(id='BAT', name='Battery storage'),
 FacetValue(id='OES', name='Other energy storage'),
 FacetValue(id='SNB', name='Solar Battery'),
 FacetValue(id='COL', name='Coal'),
 FacetValue(id='UNK', name='Unknown'),
 FacetValue(id='PS', name='Pumped Storage')]

Now you know: this endpoint has data for 80+ grid operators, broken down by 18 fuel types including Solar, Wind, Battery, and Nuclear.

Step 5: Check available frequencies

route.frequencies
Out
[FrequencyInfo(id='hourly', description='One data point for each hour in UTC time.', query='H', format='YYYY-MM-DD"T"HH24'),
 FrequencyInfo(id='local-hourly', description='One data point for each hour in local time.', query='LH', format='YYYY-MM-DD"T"HH24TZH')]

Step 6: Query

Now you have everything — no guessing, no documentation lookup:

df = route.get(
    facets={"respondent": "CISO"},
    frequency="hourly",
    start="2024-06-03",
    end="2024-06-06",
)
df.head()
Table with 5 rows

This entire workflow — from “what data exists?” to a DataFrame — happens without leaving Python.

Use case 1: California’s energy mix

Let’s use the data we just discovered to visualize California’s generation by fuel type.

Stacked area chart showing hourly electricity generation by fuel type in California for three days in June 2024

The solar bell curve is unmistakable — generation peaks at midday and drops to zero at sunset, when natural gas and imports ramp up.

Solar vs wind patterns

Line chart comparing solar and wind generation patterns over three days in California

Solar follows a predictable bell curve while wind picks up at night — a natural complementarity that California relies on.

Use case 2: Regional demand comparison

Let’s discover a different endpoint. The region-data route has demand, forecasts, and net generation:

demand_route = client.get_data_endpoint("electricity/rto/region-data")
demand_route.facets
Out
FacetContainer(facets=[respondent, type])
demand_route.facets.type.get_values()
Out
[FacetValue(id='TI', name='Total interchange'),
 FacetValue(id='NG', name='Net generation'),
 FacetValue(id='D', name='Demand'),
 FacetValue(id='DF', name='Day-ahead demand forecast')]

Type D is demand, DF is day-ahead forecast. Let’s compare demand across the four largest grid operators:

df_demand = demand_route.get(
    facets={"respondent": ["CISO", "PJM", "ERCO", "MISO"], "type": "D"},
    frequency="hourly",
    start="2024-06-03",
    end="2024-06-05",
)
df_demand.head()
Table with 5 rows
Line chart comparing hourly electricity demand across CAISO, PJM, ERCOT, and MISO

PJM (the Mid-Atlantic region) consistently shows the highest demand, while ERCOT (Texas) shows the sharpest peaks — driven by air conditioning during summer heat.

Demand vs forecast accuracy

Since we discovered that type has both D (actual) and DF (forecast), we can compare them:

df_actual_forecast = demand_route.get(
    facets={"respondent": "CISO", "type": ["D", "DF"]},
    frequency="hourly",
    start="2024-06-01",
    end="2024-06-08",
)
Line chart comparing actual electricity demand with day-ahead forecast in California

CAISO’s demand forecasts track reality closely — forecast errors typically stay within a few percent.

Use case 3: Interstate electricity flows

The interchange endpoint shows electricity flowing between grid operators:

interchange_route = client.get_data_endpoint("electricity/rto/interchange-data")
interchange_route.facets
Out
FacetContainer(facets=[fromba, toba])
df_interchange = interchange_route.get(
    facets={"fromba": "CISO"},
    frequency="hourly",
    start="2024-06-03",
    end="2024-06-04",
)
df_interchange.head()
Table with 5 rows
Line chart showing hourly electricity interchange flows from CAISO to its top 5 trading partners

Positive values mean California is exporting; negative values mean importing. The midday solar surplus often gets exported to neighboring grids.

Use case 4: Natural gas prices

The EIA API isn’t just electricity — let’s navigate to natural gas:

client.route("natural-gas").keys()
Out
['sum', 'pri', 'enr', 'prod', 'move', 'stor', 'cons']
client.route("natural-gas/pri").keys()
Out
['sum', 'fut', 'rescom']
gas_route = client.get_data_endpoint("natural-gas/pri/fut")
gas_route.facets
Out
FacetContainer(facets=[duoarea, product, process, series])
gas_route.facets.series.get_values()[:5]
Out5 lines
[FacetValue(id='RNGWHHD', name='Henry Hub Natural Gas Spot Price (Dollars per Million Btu)'),
 FacetValue(id='RNGC3', name='Natural Gas Futures Contract 3 (Dollars per Million Btu)'),
 FacetValue(id='RNGC2', name='Natural Gas Futures Contract 2 (Dollars per Million Btu)'),
 FacetValue(id='RNGC4', name='Natural Gas Futures Contract 4 (Dollars per Million Btu)'),
 FacetValue(id='RNGC1', name='Natural Gas Futures Contract 1 (Dollars per Million Btu)')]

RNGWHHD is the Henry Hub spot price — the benchmark for US natural gas:

df_gas = gas_route.get(
    facets={"series": "RNGWHHD"},
    frequency="daily",
    start="2024-01-01",
    end="2024-12-31",
    data_columns=["value"],
)
df_gas.head()
Table with 5 rows
Line chart showing daily Henry Hub natural gas spot prices throughout 2024
Box plot showing monthly distribution of Henry Hub natural gas prices in 2024

Winter months show higher prices and more volatility — driven by heating demand and weather uncertainty.

Conclusions

python-eia simplifies access to US energy data:

  1. Data discovery — browse categories, endpoints, facets, and valid filter values without leaving Python
  2. API v2 native — the only version that works since EIA deprecated v1
  3. Automatic pagination — large queries are fetched transparently across multiple pages
  4. Returns DataFrames — no JSON parsing needed, ready for analysis

Resources

Suscríbete a nuestro newsletter

Recibe insights semanales sobre datos, automatización e IA.

© 2026 Datons. All rights reserved.