I90 files are daily settlement reports published by REE, containing detailed energy market data. The library provides specialized parsing to extract DataFrames from these complex XLS workbooks.
What are I90 Files?
I90DIA files are multi-sheet Excel workbooks (.xls) published daily with:
- Settlement data and adjustments
- Generation by technology and province
- Demand and consumption breakdowns
- Imbalances and deviations
- Market prices and tariffs
Each file contains 50+ sheets with hierarchical data structures.
Downloading I90 Files
I90 files are Archive ID 34:
from esios import ESIOSClient
client = ESIOSClient(token="your_api_key")
# Get the I90DIA archive handle
archive = client.archives.get(34)
# Download a single day
files = archive.download(date="2024-01-15")
print(files[0]) # Path to I90DIA_20240115.xls
# Download a date range
files = archive.download(
start="2024-01-01",
end="2024-01-31"
)
print(f"Downloaded {len(files)} I90 files")
Downloads are cached in ~/.cache/esios/archives/34/I90DIA_YYYYMMDD/. Subsequent calls return the cached files instantly.
Opening an I90 Workbook
Use the I90Book class to parse an I90 file:
from esios.processing.i90 import I90Book
from pathlib import Path
# Open from a file path
book = I90Book("/path/to/I90DIA_20240115.xls")
print(f"File: {book.path.name}")
print(f"Data date: {book.metadata['date_data']}")
print(f"Publication date: {book.metadata['date_publication']}")
The workbook opens lazily - sheets are only parsed when accessed.
Viewing the Table of Contents
I90 files include a table of contents describing each sheet:
book = I90Book("/path/to/I90DIA_20240115.xls")
# View all available sheets
for sheet_name, description in book.table_of_contents.items():
print(f"{sheet_name}: {description}")
# Output:
# 3.1: Energía total del sistema por tecnologías
# 3.2: Energía total del sistema por provincias
# 3.3: Energía horaria por tecnologías
# ...
The table of contents helps you identify which sheet contains the data you need. Sheet names like “3.1”, “3.2” correspond to sections in the official I90 documentation.
Access individual sheets by name:
book = I90Book("/path/to/I90DIA_20240115.xls")
# Get a specific sheet
sheet = book["3.1"] # Energía total por tecnologías
print(f"Sheet: {sheet.sheet_name}")
print(f"Frequency: {sheet.frequency}")
print(f"DataFrame shape: {sheet.df.shape}")
# Access the data
df = sheet.df
print(df.head())
The sheet automatically:
- Detects hourly vs quarter-hourly frequency
- Normalizes column headers to datetime
- Handles DST transitions (Europe/Madrid timezone)
- Pivots to a tidy format with
DatetimeIndex
Understanding Sheet Structure
I90 sheets have complex layouts:
Hourly Data
Columns represent hours 1-24 (or 1-25 during DST transitions):
sheet = book["3.1"]
df = sheet.df
print(df.index) # DatetimeIndex with hourly timestamps
print(df.columns) # Technology names or other variables
Quarter-Hourly Data
Some sheets have 15-minute resolution (96 periods):
sheet = book["3.3"]
print(sheet.frequency) # "hourly-quarterly"
df = sheet.df
print(len(df.index)) # Up to 96 timestamps
Working with Multi-Variable Sheets
Sheets with multiple variables use a MultiIndex:
sheet = book["3.1"]
df = sheet.df
if isinstance(df.columns, pd.MultiIndex):
# Access specific variable
technology_data = df.xs("Carbón", level="variable", axis=1)
print(technology_data.head())
else:
# Single variable sheet
print(df.head())
Batch Processing Multiple Files
Process a date range of I90 files:
from esios.processing.i90 import I90Book
import pandas as pd
archive = client.archives.get(34)
files = archive.download(
start="2024-01-01",
end="2024-01-07"
)
# Extract sheet 3.1 from all files
all_data = []
for file in files:
try:
book = I90Book(file)
sheet = book["3.1"]
all_data.append(sheet.df)
except Exception as e:
print(f"Failed to parse {file.name}: {e}")
# Combine into single DataFrame
if all_data:
combined = pd.concat(all_data, axis=0).sort_index()
print(f"Combined shape: {combined.shape}")
print(combined.head())
Using the Convenience Method
Load I90 files directly from an archive handle:
from esios.processing.i90 import I90Book
archive = client.archives.get(34)
# Download and parse in one step
books = I90Book.from_archive(
archive,
start="2024-01-01",
end="2024-01-07"
)
print(f"Loaded {len(books)} workbooks")
# Access sheets from each book
for book in books:
sheet = book["3.1"]
print(f"{book.path.name}: {sheet.df.shape}")
This method:
- Downloads files (or uses cache)
- Parses each into an
I90Book
- Handles parse failures gracefully (logs warning, skips file)
- Returns sorted list of successfully parsed books
Handling Parse Errors
Some I90 files have irregular layouts. The parser logs errors:
import logging
logging.basicConfig(level=logging.WARNING)
book = I90Book("/path/to/problematic_file.xls")
try:
sheet = book["3.1"]
if sheet.df.empty:
print("Sheet parsed but returned empty DataFrame")
else:
print(f"Successfully parsed: {sheet.df.shape}")
except KeyError:
print("Sheet '3.1' not found in this file")
except Exception as e:
print(f"Parse error: {e}")
Advanced: Sheet Internals
Access raw sheet data before preprocessing:
sheet = book["3.1"]
# Raw cell values as numpy array
print(sheet.rows.shape)
# Frequency detection result
print(sheet.frequency) # "hourly" or "hourly-quarterly"
# Original workbook metadata
print(sheet.metadata)
Example: Daily Generation Report
Extract total generation by technology:
from esios import ESIOSClient
from esios.processing.i90 import I90Book
import pandas as pd
client = ESIOSClient(token="your_api_key")
archive = client.archives.get(34)
books = I90Book.from_archive(
archive,
start="2024-01-01",
end="2024-01-31"
)
# Extract sheet 3.1 from all books
monthly_gen = []
for book in books:
sheet = book["3.1"]
monthly_gen.append(sheet.df)
if monthly_gen:
full_month = pd.concat(monthly_gen).sort_index()
# Aggregate to daily totals
daily = full_month.resample("D").sum()
print("Daily generation by technology (MWh):")
print(daily.head(10))
# Plot
daily.plot(kind="bar", stacked=True, figsize=(14, 6))
Next Steps