Saltar a contenido

API Reference

Esta página documenta la API pública del paquete estadistica_ambiental. La documentación se genera automáticamente a partir de los docstrings con mkdocstrings.

Solo se documentan las funciones y clases con docstrings públicos. Los módulos internos (__pycache__, helpers privados con _ inicial) se omiten.


I/O

io.connectors

connectors

Conectores a fuentes de datos ambientales colombianas públicas.

Provee acceso estandarizado a APIs y portales de datos abiertos usados en las 17 líneas temáticas. Cada función devuelve un DataFrame con columnas normalizadas compatibles con el pipeline del repositorio.

Fuentes incluidas: - OpenAQ: calidad del aire (PM2.5, PM10, O3, NO2, SO2, CO) - RMCAB: Red de Monitoreo de Calidad del Aire de Bogotá - SIATA: Sistema de Alerta Temprana de Medellín/Antioquia - IDEAM DHIME: datos hidrometeorológicos (instrucciones de acceso manual) - SMByC: alertas de deforestación IDEAM (instrucciones de acceso)

list_datasets_co
list_datasets_co(query: str = 'calidad aire', limit: int = 10) -> pd.DataFrame

Busca datasets en datos.gov.co relacionados con el query ambiental.

Parameters:

Name Type Description Default
query str

Término de búsqueda (ej. 'calidad agua', 'deforestacion', 'caudal').

'calidad aire'
limit int

Número máximo de resultados.

10

Returns:

Type Description
DataFrame

DataFrame con name, description, url, organization, updated (epoch,

DataFrame

último refresco de datos) y created (epoch, fecha de alta del dataset

DataFrame

en el portal — la señal correcta para detectar fuentes nuevas).

load_datos_gov_co_dataset
load_datos_gov_co_dataset(dataset_id: str, where: Optional[str] = None, select: Optional[str] = None, limit: int = 50000, app_token: Optional[str] = None) -> pd.DataFrame

Descarga filas de un dataset SODA en datos.gov.co.

Cliente genérico de la Socrata Open Data API (SODA). A diferencia de list_datasets_co (que solo lista metadatos), esta función descarga las filas reales del dataset identificado por dataset_id (p. ej. "7g8z-fpvn"). El endpoint usado es: https://www.datos.gov.co/resource/<dataset_id>.json.

Parameters:

Name Type Description Default
dataset_id str

ID del dataset (parte después de /d/ en la URL pública).

required
where Optional[str]

Cláusula SoQL $where (p. ej. "departamento = 'Antioquia'").

None
select Optional[str]

Cláusula SoQL $select con columnas separadas por coma.

None
limit int

Tope total de filas a descargar (paginación interna por 50000).

50000
app_token Optional[str]

Token opcional X-App-Token para evitar throttling de SODA.

None

Returns:

Name Type Description
DataFrame

DataFrame con las columnas tal como las devuelve el dataset (sin

normalización DataFrame

los datasets son heterogéneos).

Notas

El endpoint SODA pagina con $offset y un máximo de 50000 filas por consulta. Esta función itera hasta alcanzar limit o agotar resultados.

load_ideam_dhime
load_ideam_dhime(path: str, date_col: str = 'Fecha', value_col: Optional[str] = None, station_col: Optional[str] = None, variable: Optional[str] = None) -> pd.DataFrame

Carga datos de IDEAM DHIME desde archivo descargado manualmente.

El portal DHIME no tiene API pública. Los datos deben descargarse en: http://dhime.ideam.gov.co/atencionciudadano/

Pasos
  1. Registrarse en el portal (gratuito).
  2. Buscar la estación por código, nombre o municipio.
  3. Seleccionar variable (precipitación, temperatura, caudal, etc.)
  4. Descargar en formato Excel o CSV.
  5. Cargar con esta función.

Parameters:

Name Type Description Default
path str

Ruta al archivo XLSX o CSV descargado.

required
date_col str

Nombre de la columna de fechas en el archivo DHIME.

'Fecha'
value_col Optional[str]

Nombre de la columna del valor. Si None, infiere automáticamente.

None
station_col Optional[str]

Nombre de la columna de estación.

None
variable Optional[str]

Nombre descriptivo de la variable (para documentar).

None

Returns:

Type Description
DataFrame

DataFrame con columnas: fecha, estacion, variable, valor.

load_ideam_dhime_csv
load_ideam_dhime_csv(path: str, parametro: str, fecha_col_candidates: Optional[list[str]] = None) -> pd.DataFrame

Lee CSV de IDEAM DHIME con líneas de metadatos previas al encabezado.

Variante robusta de load_ideam_dhime específica para los CSV exportados por DHIME, que típicamente traen varias filas de metadatos (estación, municipio, coordenadas, etc.) antes del encabezado real, y nombran la columna de valor de forma localizada (VALOR, Valor [mm], valor_observado...).

La función inspecciona las primeras líneas hasta detectar una fila que contenga alguno de los candidatos de fecha y, a partir de ahí, parsea el CSV. Luego identifica la columna de valor por: (a) coincidencia con el parametro solicitado en el nombre o (b) primera columna numérica.

Parameters:

Name Type Description Default
path str

Ruta al CSV exportado desde DHIME.

required
parametro str

Nombre descriptivo del parámetro ("precipitacion", "temperatura", "caudal", ...). Se intentará usar como pista para localizar la columna de valor.

required
fecha_col_candidates Optional[list[str]]

Nombres alternativos para la columna de fecha. Default: ["Fecha", "FECHA", "fecha"].

None

Returns:

Type Description
DataFrame

DataFrame con columnas: fecha, estacion, parametro, valor.

Raises:

Type Description
FileNotFoundError

Si path no existe.

load_openaq
load_openaq(location_id: Optional[int] = None, location_name: Optional[str] = None, country: str = 'CO', parameter: str = 'pm25', date_from: Optional[str] = None, date_to: Optional[str] = None, limit: int = 10000) -> pd.DataFrame

Descarga mediciones de calidad del aire desde OpenAQ v3.

Parameters:

Name Type Description Default
location_id Optional[int]

ID numérico de la estación en OpenAQ (preferido).

None
location_name Optional[str]

Sin soporte del lado servidor -- la API v3 de OpenAQ no ofrece un parámetro de búsqueda de estaciones por nombre (solo coordenadas/radio, país o ID). Pasar este parámetro sin location_id levanta ValueError en vez de ignorarlo en silencio; no se implementa un filtrado client-side (listar todas las estaciones y buscar substring) por falta de un uso real hoy. Buscar el ID en https://explore.openaq.org/.

None
country str

Código ISO de país. Solo aplica (y solo se valida) cuando no se pasa location_id -- la consulta sin ID está cableada a Colombia (countries_id=170); con location_id este parámetro no lo usa el cuerpo de la función, así que no se valida.

'CO'
parameter str

Variable a descargar ('pm25', 'pm10', 'o3', 'no2', 'so2', 'co').

'pm25'
date_from Optional[str]

Fecha inicio 'YYYY-MM-DD' (default: últimos 30 días).

None
date_to Optional[str]

Fecha fin 'YYYY-MM-DD' (default: hoy).

None
limit int

Máximo de registros por consulta.

10000

Returns:

Type Description
DataFrame

DataFrame con columnas: fecha, estacion, parametro, valor, unidad, lat, lon.

Ejemplos de location_id para Colombia (OpenAQ): - 225433 → Estación Kennedy, RMCAB Bogotá - 64510 → Estación El Poblado, SIATA Medellín Buscar IDs en: https://explore.openaq.org/

load_rmcab
load_rmcab(station: str, variable: str = 'PM25', date_from: str = '2024-01-01', date_to: Optional[str] = None, token: Optional[str] = None) -> pd.DataFrame

Descarga datos de calidad del aire desde RMCAB Bogotá.

Requiere token gratuito de la Secretaría Distrital de Ambiente (SDA). Solicitar en: http://rmcab.ambientebogota.gov.co/

Parameters:

Name Type Description Default
station str

Código de la estación (ej. 'Kennedy', 'Usme', 'Fontibon').

required
variable str

Variable ('PM25', 'PM10', 'O3', 'NO2', 'SO2', 'CO').

'PM25'
date_from str

Fecha inicio 'YYYY-MM-DD'.

'2024-01-01'
date_to Optional[str]

Fecha fin (default: hoy).

None
token Optional[str]

Token de acceso SDA.

None

Returns:

Type Description
DataFrame

DataFrame con columnas: fecha, estacion, variable, valor, unidad.

Nota

Sin token, los datos pueden consultarse manualmente en: http://rmcab.ambientebogota.gov.co/Report/stationreport

load_siata_aire
load_siata_aire(path: Optional[str] = None, variable: str = 'PM2.5') -> pd.DataFrame

Carga datos de calidad del aire de SIATA (Medellín/Antioquia).

Puede cargar desde archivo local (descarga previa) o intentar descargar desde datos.gov.co. Dado que la URL puede cambiar, se recomienda descarga manual.

Parameters:

Name Type Description Default
path Optional[str]

Ruta al CSV descargado desde datos.gov.co o portal SIATA.

None
variable str

Variable a filtrar en la columna 'variable' o 'parametro'.

'PM2.5'
Portales de descarga
  • https://www.datos.gov.co/ (buscar "SIATA calidad aire")
  • https://siata.gov.co/siata_nuevo/index.php/descargaData

Returns:

Type Description
DataFrame

DataFrame con columnas: fecha, estacion, variable, valor, unidad, lat, lon.

load_sisaire
load_sisaire(estacion: Optional[str], parametro: str = 'PM2.5', fecha_ini: str = '2024-01-01', fecha_fin: Optional[str] = None, timeout: float = 30.0) -> pd.DataFrame

Descarga CSV de SISAIRE (IDEAM) con fallback de encoding utf-8 → latin-1.

Parameters:

Name Type Description Default
estacion Optional[str]

Código o nombre de estación SISAIRE; None consulta todas.

required
parametro str

Variable a descargar (PM2.5, PM10, O3, NO2, SO2, CO).

'PM2.5'
fecha_ini str

Fecha inicio YYYY-MM-DD.

'2024-01-01'
fecha_fin Optional[str]

Fecha fin YYYY-MM-DD (default: hoy).

None
timeout float

Timeout HTTP en segundos.

30.0

Returns:

Type Description
DataFrame

DataFrame con columnas normalizadas según NOMBRES_CORRECTOS

DataFrame

(p. ej. PM2_5 → pm25, FECHA → fecha).

Notas

El portal SISAIRE entrega CSV usualmente codificados en latin-1; esta función intenta primero utf-8 y, si falla con UnicodeDecodeError, reintenta con latin-1 para evitar mojibake (U+FFFD).

load_sisaire_local
load_sisaire_local(anios: int | list[int] | None = None, parametro: str = 'pm25', estaciones: list[str] | None = None, path: 'str | None' = None) -> pd.DataFrame

Lee descargas SISAIRE locales (CAR_<año>.csv) sin duplicar archivos.

Pensado para uso con datos descargados manualmente del portal SISAIRE / CAR (formato: "Estacion","Fecha inicial","Fecha final","PM2.5"). La carpeta se referencia por config.SISAIRE_LOCAL_DIR (variable de entorno SISAIRE_LOCAL_DIR); el repo nunca asume una ruta fija.

Parameters:

Name Type Description Default
anios int | list[int] | None

Año (int), lista de años o None para leer todos los CAR_*.csv presentes.

None
parametro str

Variable normalizada esperada (pm25, pm10, ...). Debe coincidir con la columna del CSV tras normalizar headers.

'pm25'
estaciones list[str] | None

Si se pasa, filtra estacion in estaciones.

None
path 'str | None'

Carpeta explícita. Si es None usa config.SISAIRE_LOCAL_DIR.

None

Returns:

Type Description
DataFrame

DataFrame con columnas fecha (datetime), estacion (str) y

DataFrame

la columna del parámetro como float (p. ej. pm25).

Raises:

Type Description
FileNotFoundError

Si no se configuró SISAIRE_LOCAL_DIR ni se pasó path, o si la carpeta no contiene archivos coincidentes.

load_smbyc_alertas
load_smbyc_alertas(path: str) -> pd.DataFrame

Carga alertas tempranas de deforestación del SMByC desde archivo local.

El SMByC publica alertas trimestrales como Shapefile o GeoJSON en: http://smbyc.ideam.gov.co/MonitoreoBC-WEB/reg/indexLogOn.jsp

También disponibles en el portal SIAC: http://www.siac.gov.co/

Parameters:

Name Type Description Default
path str

Ruta al Shapefile (.shp) o GeoJSON descargado del SMByC.

required

Returns:

Type Description
DataFrame

GeoDataFrame (si geopandas disponible) o DataFrame con columnas:

DataFrame

fecha_alerta, area_ha, municipio, departamento, geometry (si aplica).

io.loaders

loaders

Ingesta multiformato para datos ambientales. Soporta: CSV, Excel, Parquet, NetCDF, Shapefile / GeoPackage / GeoJSON.

load
load(path: Union[str, Path], date_col: Optional[str] = None, **kwargs) -> pd.DataFrame

Carga un archivo ambiental detectando el formato por extensión.

Para Shapefile / GeoPackage / GeoJSON devuelve un GeoDataFrame (requiere geopandas). Para el resto, un DataFrame de pandas.

load_csv
load_csv(path: Union[str, Path], date_col: Optional[str] = None, sep: str = ',', encoding: Optional[str] = None, **kwargs) -> pd.DataFrame

Carga CSV con detección automática de encoding si no se especifica.

load_excel
load_excel(path: Union[str, Path], date_col: Optional[str] = None, sheet_name: Union[str, int] = 0, **kwargs) -> pd.DataFrame

Carga archivo Excel (.xlsx / .xls).

load_netcdf
load_netcdf(path: Union[str, Path], variable: Optional[str] = None, date_col: str = 'time', lat_col: str = 'lat', lon_col: str = 'lon', **kwargs) -> pd.DataFrame

Carga NetCDF y aplana a DataFrame de pandas.

Si variable es None, carga todas las variables del archivo. Requiere: netCDF4 o h5netcdf (instalar con pip install netcdf4).

load_parquet
load_parquet(path: Union[str, Path], date_col: Optional[str] = None, **kwargs) -> pd.DataFrame

Carga archivo Parquet.

load_shapefile
load_shapefile(path: Union[str, Path], date_col: Optional[str] = None, **kwargs)

Carga Shapefile, GeoPackage o GeoJSON a GeoDataFrame.

Requiere: geopandas (pip install geopandas).

io.validators

validators

Validación de plausibilidad física y calidad de datos ambientales.

Detecta: duplicados, rangos físicos imposibles, fechas inconsistentes, coordenadas fuera de área de estudio. No lanza excepciones — devuelve un ValidationReport con todos los hallazgos para que el analista decida.

validate
validate(df: DataFrame, date_col: Optional[str] = None, key_cols: Optional[List[str]] = None, ranges: Optional[Dict[str, Tuple[float, float]]] = None, colombia_coords: bool = True, linea_tematica: Optional[str] = None) -> ValidationReport

Valida un DataFrame ambiental y devuelve un reporte con todos los hallazgos.

Parameters:

Name Type Description Default
df DataFrame

DataFrame a validar.

required
date_col Optional[str]

Nombre de la columna de fechas.

None
key_cols Optional[List[str]]

Columnas que forman la llave lógica (e.g. ['estacion', 'fecha']).

None
ranges Optional[Dict[str, Tuple[float, float]]]

Rangos personalizados {col: (min, max)} que sobreescriben los defaults.

None
colombia_coords bool

Si True, valida latitud/longitud en el bounding box de Colombia.

True
linea_tematica Optional[str]

Nombre de la línea temática ('paramos', 'calidad_aire', 'oferta_hidrica', 'recurso_hidrico', 'humedales'). Aplica rangos específicos de dominio sobre los rangos base.

None

EDA

eda.profiling

profiling

Reporte automático de EDA para datasets ambientales.

Genera un HTML autocontenido que integra
  • Catálogo de variables (variables.py)
  • Análisis de calidad (quality.py)
  • Validación de rangos físicos (validators.py)
  • Estadísticas descriptivas básicas

Integración opcional con ydata-profiling o sweetviz cuando están instalados.

run_eda
run_eda(df: DataFrame, output: str = 'reports/eda_report.html', title: str = 'EDA Ambiental', date_col: Optional[str] = None, use_ydata: bool = True, use_sweetviz: bool = False) -> Path

Genera el reporte completo de EDA y lo guarda en disco.

Siempre produce el reporte propio. Si use_ydata=True y ydata-profiling está instalado, genera también el reporte completo de ydata en un archivo separado (_ydata.html).

Parameters:

Name Type Description Default
df DataFrame

DataFrame ambiental.

required
output str

Ruta del HTML de salida.

'reports/eda_report.html'
title str

Título del reporte.

'EDA Ambiental'
date_col Optional[str]

Columna de fechas para análisis temporal.

None
use_ydata bool

Intentar generar reporte ydata-profiling si está disponible.

True
use_sweetviz bool

Intentar generar reporte sweetviz si está disponible.

False

Returns:

Type Description
Path

Path al HTML generado.

eda.quality

quality

Análisis profundo de calidad de datos para series ambientales.

Complementa io/validators.py (plausibilidad física) con análisis estadístico: patrones de faltantes, gaps temporales, congelamiento de sensor y outliers. No elimina ni imputa datos — solo diagnostica para informar la estrategia.

MissingPattern

Bases: str, Enum

Clasificación heurística del mecanismo de datos faltantes.

assess_quality
assess_quality(df: DataFrame, date_col: Optional[str] = None, numeric_cols: Optional[List[str]] = None, freeze_min_length: int = 5, cross_checks: bool = True) -> QualityReport

Análisis profundo de calidad para un DataFrame ambiental.

Parameters:

Name Type Description Default
df DataFrame

DataFrame a analizar.

required
date_col Optional[str]

Columna de fechas para análisis temporal.

None
numeric_cols Optional[List[str]]

Columnas numéricas a analizar (None = todas).

None
freeze_min_length int

Mínimo de registros iguales consecutivos para considerar congelamiento de sensor.

5
cross_checks bool

Si True, evalúa inconsistencias entre columnas (e.g., PM2.5 > PM10).

True

eda.variables

variables

Tipificación automática de variables en datasets ambientales.

Clasifica cada columna en: numérica continua, numérica discreta, categórica nominal, categórica ordinal, temporal, espacial o texto libre. Permite sobreescrituras manuales para casos que la heurística no resuelve.

VariableCatalog dataclass
VariableCatalog(variables: Dict[str, VariableInfo] = dict())

Catálogo de variables tipificadas para un DataFrame ambiental.

classify
classify(df: DataFrame, overrides: Optional[Dict[str, VariableType]] = None) -> VariableCatalog

Clasifica automáticamente las columnas de un DataFrame ambiental.

Parameters:

Name Type Description Default
df DataFrame

DataFrame a tipificar.

required
overrides Optional[Dict[str, VariableType]]

Mapa {columna: VariableType} para sobreescribir la heurística. Ejemplo: {"calidad_agua": VariableType.CATEGORICAL_ORDINAL}

None

eda.viz

viz

Visualizaciones estándar de EDA para datos ambientales.

Todas las funciones devuelven matplotlib Figure para que el caller decida si mostrar, guardar o incrustar en un notebook/reporte. No llaman plt.show() — control queda en el usuario.

plot_boxplot
plot_boxplot(df: DataFrame, value_col: str, group_col: Optional[str] = None, title: Optional[str] = None, figsize: tuple = (8, 4)) -> plt.Figure

Boxplot por grupo o global.

plot_correlation_heatmap
plot_correlation_heatmap(df: DataFrame, method: str = 'pearson', cols: Optional[List[str]] = None, figsize: tuple = (8, 7)) -> plt.Figure

Heatmap de correlaciones con anotaciones numéricas.

plot_histogram
plot_histogram(df: DataFrame, col: str, bins: int = 30, group_col: Optional[str] = None, title: Optional[str] = None, figsize: tuple = (7, 4)) -> plt.Figure

Histograma con KDE superpuesta, opcional por grupo.

plot_missing_heatmap
plot_missing_heatmap(df: DataFrame, date_col: Optional[str] = None, figsize: tuple = (12, 5)) -> plt.Figure

Mapa de calor de datos faltantes: filas=tiempo, columnas=variables.

plot_multi_series
plot_multi_series(df: DataFrame, date_col: str, value_cols: Sequence[str], title: Optional[str] = None, figsize: tuple = (12, 3)) -> plt.Figure

Subplots independientes, uno por variable, compartiendo eje X.

plot_scatter
plot_scatter(df: DataFrame, x_col: str, y_col: str, color_col: Optional[str] = None, title: Optional[str] = None, figsize: tuple = (6, 5)) -> plt.Figure

Scatter plot con línea de regresión y coloreado opcional por categoría.

plot_seasonal_means
plot_seasonal_means(df: DataFrame, date_col: str, value_col: str, period: str = 'month', figsize: tuple = (10, 4)) -> plt.Figure

Promedio mensual o por día de semana para detectar estacionalidad.

plot_series
plot_series(df: DataFrame, date_col: str, value_col: str, group_col: Optional[str] = None, title: Optional[str] = None, ylabel: Optional[str] = None, figsize: tuple = (12, 4)) -> plt.Figure

Serie temporal simple o por grupo (una línea por categoría).


Preprocesamiento

preprocessing.air_quality

air_quality

Funciones específicas para preprocesamiento de calidad del aire.

Incluye
  • Categorización ICA según Resolución 2254/2017 (Colombia)
  • Detección de episodios contaminantes con validación espacial
  • Corrección de sesgo estacional en modelos de pronóstico

Nota: ICA_CATEGORIES en config.py corresponde al Índice de Calidad del Agua (IDEAM, escala 0-1) y es distinto del ICA de calidad del aire de Res. 2254/2017.

categorize_ica
categorize_ica(series: Series, pollutant: str = 'pm25', standard: str = 'res_2254_2017') -> pd.Series

Categoriza una serie de concentraciones según el ICA de Res. 2254/2017.

Parameters:

Name Type Description Default
series Series

Serie de valores de concentración del contaminante.

required
pollutant str

Contaminante. Opciones: 'pm25', 'pm10', 'o3', 'no2', 'so2', 'co'. Si no se reconoce, usa los breakpoints de PM2.5 con aviso.

'pm25'
standard str

Norma de referencia. Solo 'res_2254_2017' está implementada; el parámetro se reserva para versiones futuras (ej. estándar OMS).

'res_2254_2017'

Returns:

Type Description
Series

pd.Series de tipo Categorical con etiquetas:

Series

"Buena", "Aceptable", "Dañina sensibles", "Dañina", "Muy dañina", "Peligrosa".

Series

NaN donde la entrada sea NaN.

Raises:

Type Description
ValueError

Si standard no es reconocido.

Example

import pandas as pd from estadistica_ambiental.preprocessing.air_quality import categorize_ica s = pd.Series([8.0, 20.0, 40.0, 60.0, 160.0, 300.0]) categorize_ica(s, pollutant='pm25') 0 Buena 1 Aceptable 2 Dañina sensibles 3 Dañina 4 Muy dañina 5 Peligrosa dtype: category

correct_seasonal_bias
correct_seasonal_bias(predictions: Series, actuals: Series, time_col: Series, by: Literal['month', 'quarter', 'week'] = 'month') -> Tuple[pd.Series, pd.DataFrame]

Calcula y aplica corrección de sesgo estacional a predicciones de modelo.

El sesgo sistemático ocurre cuando el modelo sobreestima o subestima de forma consistente en ciertos períodos (e.g., meses secos vs. lluviosos). Esta función calcula el sesgo promedio por período y lo descuenta:

predicción_corregida = predicción − sesgo_del_período

Un sesgo positivo (sobreestimación) resulta en corrección hacia abajo; un sesgo negativo (subestimación) eleva la predicción.

Parameters:

Name Type Description Default
predictions Series

Serie de valores predichos por el modelo.

required
actuals Series

Serie de valores reales observados (mismo índice que predictions).

required
time_col Series

Serie de fechas/horas correspondiente a cada observación. Debe ser parseable como datetime. Mismo largo que predictions.

required
by Literal['month', 'quarter', 'week']

Granularidad temporal del sesgo: - 'month' → sesgo por mes del año (1-12). - 'quarter' → sesgo por trimestre (1-4). - 'week' → sesgo por semana del año (1-53).

'month'

Returns:

Type Description
Series

Tupla (predictions_corrected, bias_table):

DataFrame
  • predictions_corrected: pd.Series con los valores corregidos, mismo índice que predictions. Períodos sin datos de calibración se devuelven sin corrección (sesgo = 0) con aviso en log.
Tuple[Series, DataFrame]
  • bias_table: pd.DataFrame con columnas ['periodo', 'sesgo', 'n_obs', 'sesgo_std']. sesgo > 0 indica sobreestimación.

Raises:

Type Description
ValueError

Si by no es 'month', 'quarter' o 'week'.

ValueError

Si predictions, actuals y time_col tienen longitudes distintas.

Example

preds_ok, bias_df = correct_seasonal_bias( ... predictions=df["predicho"], ... actuals=df["real"], ... time_col=df["datetime"], ... by="month", ... ) bias_df.head() periodo sesgo n_obs sesgo_std 0 1 2.314 720 1.203 1 2 -0.891 672 0.987 ...

flag_spatial_episodes
flag_spatial_episodes(df: DataFrame, value_col: str, station_col: str, datetime_col: str, lat_col: str, lon_col: str, iqr_multiplier: float = 3.0, min_consecutive_hours: int = 4, distance_km: float = 50.0, min_neighbor_stations: int = 1) -> pd.DataFrame

Detecta episodios contaminantes reales distinguiéndolos de errores de sensor.

Aplica un pipeline de tres pasos con trazabilidad completa (ADR-002): 1. Cap absoluto: valores > percentil 99.9 global se marcan 'cap_absoluto'. 2. Episodios críticos: secuencias ≥ min_consecutive_hours horas consecutivas sobre IQR×iqr_multiplier, confirmadas por al menos min_neighbor_stations estaciones vecinas (a ≤ distance_km km) con elevación simultánea. Se marcan 'episodio_critico' y se PRESERVAN. 3. Outliers IQR por estación×mes: valores moderados (1.5×IQR) se marcan 'iqr_soft'; valores extremos (iqr_multiplier×IQR) no episódicos se marcan 'iqr_hard'.

Los valores marcados como 'episodio_critico' NO se modifican: son eventos reales de contaminación que deben preservarse para análisis normativo.

Parameters:

Name Type Description Default
df DataFrame

DataFrame con al menos las columnas indicadas en los parámetros.

required
value_col str

Columna con la concentración del contaminante.

required
station_col str

Columna identificadora de estación.

required
datetime_col str

Columna de fecha/hora (dtype datetime o parseable).

required
lat_col str

Columna de latitud de la estación (decimal, WGS-84).

required
lon_col str

Columna de longitud de la estación (decimal, WGS-84).

required
iqr_multiplier float

Multiplicador IQR para umbral extremo. Default 3.0.

3.0
min_consecutive_hours int

Mínimo de horas consecutivas para episodio crítico. Default 4.

4
distance_km float

Radio máximo (km) para considerar estaciones vecinas. Default 50.

50.0
min_neighbor_stations int

Mínimo de vecinas con elevación simultánea para confirmar episodio. Default 1. Si una estación no tiene vecinas, el criterio temporal es suficiente.

1

Returns:

Type Description
DataFrame

Copia del DataFrame con columna adicional flag_episode ∈

DataFrame

{'original', 'iqr_soft', 'iqr_hard', 'episodio_critico', 'cap_absoluto'}.

Raises:

Type Description
KeyError

Si alguna de las columnas requeridas no existe en df.

Notes
  • Requiere pandas y numpy. No requiere geopandas.
  • El cálculo IQR se realiza por combinación estación×mes para capturar estacionalidad. Se requieren al menos 10 observaciones por grupo.
  • La detección de horas consecutivas usa un umbral de 1.5 h de brecha para tolerar datos faltantes aislados.

preprocessing.imputation

imputation

Imputación de datos faltantes para series ambientales.

Estrategias ordenadas de menor a mayor complejidad

forward_fill → linear → mean/median → rolling_mean → kalman → mice

impute
impute(df: DataFrame, cols: Optional[List[str]] = None, method: str = 'linear', **kwargs) -> pd.DataFrame

Aplica el método de imputación seleccionado a las columnas numéricas.

Métodos disponibles

'ffill' Forward fill (última observación conocida). 'bfill' Backward fill. 'linear' Interpolación lineal temporal. 'mean' Reemplaza por media de la columna. 'median' Reemplaza por mediana de la columna. 'rolling_mean' Media rodante (window kwarg, default=24). 'kalman' Filtro de Kalman simple (requiere pykalman). 'mice' Imputación múltiple (requiere scikit-learn).

preprocessing.outliers

outliers

Detección y tratamiento OPCIONAL de outliers en series ambientales.

Por defecto solo detecta y marca (ADR-002): los picos ambientales son señal real. El clipping es opt-in explícito con treat=True.

Funciones
  • flag_outliers: detección puntual univariada (IQR / z-score / modified z-score).
  • detect_regional_episodes: episodios sostenidos con confirmación espacial entre estaciones vecinas. Variante genérica de la lógica de calidad del aire, aplicable a cualquier variable ambiental (PM2.5, caudal, temperatura, etc.).
detect_regional_episodes
detect_regional_episodes(df: DataFrame, value_col: str, station_col: str, datetime_col: str, lat_col: str, lon_col: str, iqr_hard_mult: float = 3.0, iqr_soft_mult: float = 1.5, min_duration_hours: int = 4, neighbor_radius_km: float = 50.0, min_neighbors_confirmed: int = 1, max_gap_hours: float = 1.5) -> pd.DataFrame

Detecta episodios regionales sostenidos con confirmación espacial.

Algoritmo (variante genérica de flag_spatial_episodes para cualquier variable ambiental, validado en producción CAR §3.1.A):

  1. Por estación: calcular Q1, Q3, IQR sobre los valores no-NaN.
  2. Marcar como candidatos los puntos > Q3 + iqr_hard_mult × IQR.
  3. Agrupar candidatos consecutivos en episodios (brecha máxima max_gap_hours) y descartar episodios con duración < min_duration_hours.
  4. Para cada episodio sobreviviente, validar contra estaciones vecinas dentro de neighbor_radius_km (Haversine): se requieren al menos min_neighbors_confirmed vecinos con valor > Q3_vecino + iqr_soft_mult × IQR en la misma ventana temporal.
  5. Episodios confirmados → flag_episode = "regional" (señal real). Candidatos no confirmados → flag_episode = "puntual" (probable falla de sensor o variabilidad local).

A diferencia de flag_spatial_episodes (calidad del aire), esta función: - NO modifica los valores (no imputa, no recorta). - NO aplica cap absoluto (eso es decisión opt-in del usuario). - NO segmenta por mes (asume estacionariedad por estación). - Es genérica: aplica a PM2.5, caudal, temperatura, precipitación, etc.

Parameters:

Name Type Description Default
df DataFrame

DataFrame con observaciones de múltiples estaciones.

required
value_col str

Columna con la variable a evaluar.

required
station_col str

Columna identificadora de estación.

required
datetime_col str

Columna temporal (datetime o parseable).

required
lat_col str

Latitud de la estación (decimal, WGS-84).

required
lon_col str

Longitud de la estación (decimal, WGS-84).

required
iqr_hard_mult float

Multiplicador IQR para umbral fuerte (default 3.0).

3.0
iqr_soft_mult float

Multiplicador IQR para confirmación en vecinos (default 1.5).

1.5
min_duration_hours int

Duración mínima del episodio en horas (default 4).

4
neighbor_radius_km float

Radio de búsqueda de vecinas en km (default 50).

50.0
min_neighbors_confirmed int

Vecinas mínimas con elevación simultánea para confirmar el episodio (default 1). Si una estación queda aislada (ningún vecino dentro del radio), el criterio temporal basta.

1
max_gap_hours float

Brecha máxima en horas para considerar candidatos como parte del mismo episodio (default 1.5 — tolera 1 dato faltante).

1.5

Returns:

Type Description
DataFrame

Copia del DataFrame con columna adicional flag_episode ∈

DataFrame

{"original", "puntual", "regional"}.

Raises:

Type Description
KeyError

Si alguna columna requerida no existe en df.

Example

df = pd.DataFrame({ ... "fecha": pd.date_range("2024-01-01", periods=10, freq="h").tolist() * 2, ... "estacion": ["A"] * 10 + ["B"] * 10, ... "lat": [4.6] * 10 + [4.65] * 10, ... "lon": [-74.1] * 10 + [-74.15] * 10, ... "pm25": [...] # con pico sostenido en ambas estaciones ... }) out = detect_regional_episodes(df, "pm25", "estacion", "fecha", "lat", "lon") out["flag_episode"].value_counts()

flag_outliers
flag_outliers(df: DataFrame, cols: Optional[List[str]] = None, method: str = 'iqr', threshold: float = 1.5, treat: bool = False, treatment: str = 'clip') -> pd.DataFrame

Marca columnas con outliers. Opcionalmente los trata.

Parameters:

Name Type Description Default
df DataFrame

DataFrame de entrada.

required
cols Optional[List[str]]

Columnas a analizar (None = todas numéricas).

None
method str

'iqr' | 'zscore' | 'modified_zscore'.

'iqr'
threshold float

Multiplicador IQR o número de desviaciones.

1.5
treat bool

Si True aplica treatment (clip o nan). Default False (ADR-002).

False
treatment str

'clip' recorta a los límites | 'nan' reemplaza por NaN.

'clip'

Returns:

_outlier' (bool).

Type Description
DataFrame

DataFrame con columnas adicionales '

preprocessing.resampling

resampling

Remuestreo y agregación temporal de series ambientales.

align_frequencies
align_frequencies(dfs: List[DataFrame], date_cols: List[str], target_freq: str = 'D', agg: str = 'mean') -> List[pd.DataFrame]

Alinea múltiples DataFrames a la misma frecuencia temporal.

fill_missing_timestamps
fill_missing_timestamps(df: DataFrame, date_col: str, freq: str) -> pd.DataFrame

Inserta timestamps faltantes con NaN para hacer la serie regular.

resample
resample(df: DataFrame, date_col: str, value_cols: Optional[List[str]] = None, freq: str = 'D', agg: Union[str, Dict[str, str]] = 'mean', min_count: int = 1) -> pd.DataFrame

Remuestrea a una frecuencia objetivo.

Parameters:

Name Type Description Default
df DataFrame

DataFrame con serie temporal.

required
date_col str

Columna de fechas.

required
value_cols Optional[List[str]]

Columnas a agregar (None = todas numéricas).

None
freq str

Frecuencia destino (e.g. 'H', 'D', 'W', 'ME', 'YE').

'D'
agg Union[str, Dict[str, str]]

Función de agregación ('mean', 'sum', 'max', 'min') o dict por columna {'pm25': 'mean', 'lluvia': 'sum'}.

'mean'
min_count int

Mínimo de observaciones válidas para producir resultado.

1

Estadística descriptiva

descriptive.univariate

univariate

Estadística descriptiva univariada para variables ambientales.

frequency_table
frequency_table(series: Series, normalize: bool = True) -> pd.DataFrame

Tabla de frecuencias para variable categórica u ordinal.

summarize
summarize(df: DataFrame, cols: Optional[List[str]] = None, group_col: Optional[str] = None, percentiles: List[float] = [0.05, 0.25, 0.5, 0.75, 0.95]) -> pd.DataFrame

Resumen estadístico completo para columnas numéricas.

Incluye: n, media, mediana, moda, desv. estándar, varianza, CV, IQR, MAD, asimetría, curtosis, percentiles, min, max. Soporta agrupación por variable categórica (e.g. estación).

descriptive.bivariate

bivariate

Estadística descriptiva bivariada para datos ambientales.

chi2_test
chi2_test(df: DataFrame, col1: str, col2: str) -> dict

Chi-cuadrado de independencia entre dos variables categóricas.

contingency_table
contingency_table(df: DataFrame, col1: str, col2: str, normalize: Optional[str] = None) -> pd.DataFrame

Tabla de contingencia. normalize: None | 'index' | 'columns' | 'all'.

correlation_matrix
correlation_matrix(df: DataFrame, cols: Optional[List[str]] = None, method: str = 'pearson') -> pd.DataFrame

Matriz de correlación con p-valores para columnas numéricas.

Returns DataFrame con MultiIndex (coef, pval) por par de variables.

correlation_table
correlation_table(df: DataFrame, cols: Optional[List[str]] = None, method: str = 'pearson', min_abs_corr: float = 0.0) -> pd.DataFrame

Tabla larga con pares (var1, var2, corr, pval) — más legible que la matriz.

descriptive.temporal

temporal

Estadística descriptiva temporal: STL, ACF, PACF, rolling stats.

acf_values
acf_values(series: Series, nlags: int = 40) -> pd.Series

Autocorrelación (ACF) hasta nlags.

decompose_stl
decompose_stl(series: Series, period: int, robust: bool = True) -> pd.DataFrame

Descomposición STL (Seasonal-Trend decomposition using LOESS).

Returns DataFrame con columnas: observed, trend, seasonal, residual.

pacf_values
pacf_values(series: Series, nlags: int = 40) -> pd.Series

Autocorrelación parcial (PACF) hasta nlags.

rolling_stats
rolling_stats(series: Series, window: int, stats: Tuple[str, ...] = ('mean', 'std')) -> pd.DataFrame

Estadísticas rodantes para detectar cambios de nivel o varianza.

seasonal_summary
seasonal_summary(df: DataFrame, date_col: str, value_col: str, freq: str = 'M') -> pd.DataFrame

Agrega la serie por frecuencia (M=mensual, Y=anual, W=semanal).

Returns: media, mediana, std, min, max por período.


Inferencia

inference.distributions

distributions

Pruebas de normalidad y ajuste a distribuciones para datos ambientales.

fit_distribution
fit_distribution(series: Series, distributions: Optional[List[str]] = None) -> pd.DataFrame

Ajusta varias distribuciones teóricas y devuelve ranking por AIC.

normality_tests
normality_tests(series: Series, alpha: float = 0.05) -> pd.DataFrame

Batería de pruebas de normalidad: Shapiro-Wilk, K-S, Anderson-Darling.

inference.hypothesis

hypothesis

Pruebas de hipótesis paramétricas y no paramétricas.

anova
anova(df: DataFrame, value_col: str, group_col: str, alpha: float = 0.05) -> dict

ANOVA de una vía para múltiples grupos.

kruskalwallis
kruskalwallis(df: DataFrame, value_col: str, group_col: str, alpha: float = 0.05) -> dict

Kruskal-Wallis — alternativa no paramétrica a ANOVA.

mannwhitney
mannwhitney(df: DataFrame, value_col: str, group_col: str, groups: Optional[List] = None, alpha: float = 0.05) -> dict

Mann-Whitney U — alternativa no paramétrica al t-test.

ttest
ttest(df: DataFrame, value_col: str, group_col: str, groups: Optional[List] = None, alpha: float = 0.05, equal_var: bool = False) -> dict

t-test de Welch (equal_var=False) o Student entre dos grupos.

inference.intervals

intervals

Intervalos de confianza paramétricos y bootstrap, y análisis de excedencias normativas.

ci_bootstrap
ci_bootstrap(series: Series, statistic: Callable, confidence: float = 0.95, n_boot: int = 2000, random_state: int = 42) -> Tuple[float, float]

IC bootstrap genérico para cualquier estadístico.

ci_mean
ci_mean(series: Series, confidence: float = 0.95) -> Tuple[float, float]

IC paramétrico para la media (t de Student).

ci_median_bootstrap
ci_median_bootstrap(series: Series, confidence: float = 0.95, n_boot: int = 2000, random_state: int = 42) -> Tuple[float, float]

IC bootstrap para la mediana (no paramétrico).

ci_quantile_bootstrap
ci_quantile_bootstrap(series: Series, q: float = 0.95, confidence: float = 0.9, n_boot: int = 2000, random_state: int = 42) -> Tuple[float, float]

IC bootstrap para un cuantil arbitrario (útil para excedencias de norma).

exceedance_probability
exceedance_probability(series: Series, threshold: float) -> dict

Probabilidad de excedencia de un umbral (e.g. norma ambiental).

Útil para PM2.5 vs norma OMS (15 µg/m³) o norma colombiana (37 µg/m³).

exceedance_report
exceedance_report(series: Series, variable: str, frecuencia: str = '24h') -> pd.DataFrame

Genera un reporte de cumplimiento normativo para una variable ambiental colombiana.

Compara la serie contra todas las normas colombianas relevantes para esa variable.

Parameters:

Name Type Description Default
series Series

Serie de observaciones (ya deben estar en la unidad correcta).

required
variable str

Nombre de la variable ('pm25', 'dbo5', 'ph', etc.).

required
frecuencia str

Frecuencia de los datos ('1h', '24h', 'anual', etc.) — informativo.

'24h'

Returns:

Type Description
DataFrame

DataFrame con una fila por norma, columnas:

DataFrame

norma, umbral, n_exceedances, pct_exceed, cumple, return_period_days.

ruido_exceedance_report
ruido_exceedance_report(series: Series, sector: str, natural_noise: bool = False) -> pd.DataFrame

Reporte de cumplimiento de ruido ambiental contra la Tabla 2 (Res. 627/2006).

Calcula L_Aeq (Art. 4) por día y ventana horaria (diurna/nocturna, Art. 2) y lo compara contra el estándar máximo permisible del sector (Tabla 2, Art. 17). Requiere una serie con DatetimeIndex -- sin fecha/hora no se puede determinar la ventana horaria aplicable.

Parameters:

Name Type Description Default
series Series

Mediciones de nivel de presión sonora en dB(A), indexadas por fecha/hora (idealmente sub-horarias; con menos resolución el L_Aeq calculado es una aproximación).

required
sector str

Uno de los sectores de la Tabla 2 -- ver _SECTORES_RUIDO (equivalente a las claves de config.NORMA_RUIDO sin el sufijo _dia/_noche, ej. 'sector_a', 'sector_c_industrial').

required
natural_noise bool

Si True, aplica la excepción del Art. 17 Parágrafo Segundo (el nivel se supera por fuentes naturales sin intervención humana -- cascadas, fauna, etc., relevante sobre todo en sector_d). La detección de si el ruido es "natural" NO se hace algorítmicamente (no es posible desde una sola serie numérica) -- es una aserción del analista, y las filas quedan marcadas como no evaluadas en vez de reportar una excedencia que podría ser jurídicamente inválida.

False

Returns:

Type Description
DataFrame

DataFrame con una fila por (día de ruido, horario): dia_ruido,

DataFrame

horario, sector, l_aeq_dba, umbral_dba, excede, margen_db,

DataFrame

n_mediciones (cantidad de lecturas agregadas en esa ventana --

DataFrame

una ventana con pocas mediciones es una aproximación pobre del

DataFrame

L_Aeq,T real, ver Art. 4), nota.

inference.stationarity

stationarity

Pruebas de estacionariedad para series ambientales.

ADF y KPSS son obligatorias antes de aplicar ARIMA (ADR-004).

adf_test
adf_test(series: Series, alpha: float = 0.05, regression: str = 'c') -> dict

Augmented Dickey-Fuller.

H0: la serie tiene raíz unitaria (no estacionaria). Rechazar H0 → estacionaria.

kpss_test
kpss_test(series: Series, alpha: float = 0.05, regression: str = 'c') -> dict

KPSS (Kwiatkowski-Phillips-Schmidt-Shin).

H0: la serie ES estacionaria. Rechazar H0 → no estacionaria.

stationarity_report
stationarity_report(series: Series, alpha: float = 0.05) -> pd.DataFrame

Ejecuta ADF + KPSS y devuelve diagnóstico consolidado.

Interpretación conjunta (tabla de Hylleberg-Mizon): ADF no rechaza H0 + KPSS rechaza H0 → claramente no estacionaria ADF rechaza H0 + KPSS no rechaza H0 → claramente estacionaria Ambos rechazados / ninguno rechazado → evidencia mixta

inference.trend

trend

Detección de tendencia en series ambientales.

Mann-Kendall + Sen's slope son el estándar en hidrología y calidad del aire para series largas con distribución no normal.

mann_kendall
mann_kendall(series: Series, alpha: float = 0.05) -> dict

Test de Mann-Kendall para tendencia monotónica.

Requiere: pip install pymannkendall

pettitt_test
pettitt_test(series: Series, alpha: float = 0.05) -> dict

Test de Pettitt para detección de punto de cambio.

Requiere: pip install pymannkendall (incluye pettitt)

sens_slope
sens_slope(series: Series) -> dict

Sen's slope estimator — magnitud robusta de tendencia.


Modelos predictivos

predictive.base

base

Clase base abstracta y protocolo ModelSpec para todos los modelos predictivos ambientales.

BaseModel
BaseModel(**hyperparams: Any)

Bases: ABC

Interfaz común para todos los modelos del catálogo.

Subclases deben implementar :meth:fit y :meth:predict. Para participar en el pipeline de optimización bayesiana, las subclases también pueden implementar el protocolo :class:ModelSpec añadiendo :attr:warm_starts, :meth:suggest_params y :meth:build_model.

is_fitted property
is_fitted: bool

True si el modelo ha sido ajustado con :meth:fit.

fit abstractmethod
fit(y: Series, X: Optional[DataFrame] = None) -> 'BaseModel'

Ajusta el modelo a la serie objetivo y exógenas opcionales.

fit_predict
fit_predict(y: Series, horizon: int, X: Optional[DataFrame] = None, X_future: Optional[DataFrame] = None) -> np.ndarray

Ajusta el modelo y genera el pronóstico en un solo paso.

get_params
get_params() -> Dict[str, Any]

Devuelve copia de los hiperparámetros actuales.

predict abstractmethod
predict(horizon: int, X_future: Optional[DataFrame] = None) -> np.ndarray

Genera pronóstico para horizon pasos adelante.

ModelSpec

Bases: Protocol

Protocolo estructural que todo modelo optimizable debe satisfacer.

Permite agregar nuevos modelos al pipeline de optimización sin modificar el optimizador. Basta con que la clase implemente los miembros requeridos; no es necesario heredar de esta clase (duck-typing estructural).

Uso típico::

class MiModelo:
    name = "MiModelo"

    @property
    def warm_starts(self) -> list[dict]:
        return [{"n_estimators": 100, "max_depth": 5}]

    def suggest_params(self, trial) -> dict:
        return {"n_estimators": trial.suggest_int("n_estimators", 50, 500)}

    def build_model(self, params: dict):
        return MiEstimador(**params)

assert isinstance(MiModelo(), ModelSpec)  # True sin herencia
search_space property
search_space: Dict[str, Any]

Descripción del espacio de búsqueda (opcional, para documentación).

Returns:

Type Description
Dict[str, Any]

Dict con metadatos del espacio: nombre → (tipo, min, max, ...).

warm_starts property
warm_starts: List[Dict[str, Any]]

Configuraciones iniciales conocidas para acelerar Optuna.

Cada dict se encola como trial inicial antes de la búsqueda aleatoria, reduciendo el número de evaluaciones necesarias para converger.

Returns:

Type Description
List[Dict[str, Any]]

Lista de dicts con hiperparámetros (puede estar vacía).

build_model
build_model(params: Dict[str, Any]) -> Any

Construye e instancia el modelo con los parámetros dados.

Parameters:

Name Type Description Default
params Dict[str, Any]

Dict de hiperparámetros, típicamente obtenido de :meth:suggest_params o de :attr:warm_starts.

required

Returns:

Type Description
Any

Instancia del modelo (sklearn, statsmodels, etc.) lista para

Any

ser ajustada con .fit().

suggest_params
suggest_params(trial: Any) -> Dict[str, Any]

Sugiere hiperparámetros a partir de un Trial de Optuna.

Parameters:

Name Type Description Default
trial Any

optuna.Trial activo en la búsqueda.

required

Returns:

Type Description
Dict[str, Any]

Dict con los hiperparámetros sugeridos para este trial.

OptimizationResult dataclass
OptimizationResult(best_params: Dict[str, Any], best_score: float, n_trials: int, model: Any = None, study: Any = None, model_name: str = '', cv_scores: List[float] = list(), fallback: bool = False)

Resultado estándar de una corrida de optimización bayesiana.

Attributes:

Name Type Description
best_params Dict[str, Any]

Mejores hiperparámetros encontrados por Optuna.

best_score float

Valor de la métrica objetivo para best_params.

n_trials int

Número de trials ejecutados.

model Any

Instancia del modelo construida con best_params (None si no se construyó explícitamente).

study Any

Objeto optuna.Study con el historial completo (None si no está disponible o hubo fallback).

model_name str

Nombre identificador del modelo optimizado.

cv_scores List[float]

Puntuaciones individuales por trial (para análisis).

fallback bool

True si Optuna falló y se usó el primer warm_start.

predictive.classical

classical

Modelos estadísticos clásicos: ARIMA, SARIMA, SARIMAX, ETS/Holt-Winters. Adaptado de boa-sarima-forecaster/model.py por Dan Méndez — 2026-04-22

Cambios respecto al original: - Interfaz BaseModel unificada (fit/predict). - Soporte SARIMAX con exógenas meteorológicas (crítico para calidad del aire). - Validación de estacionariedad obligatoria via stationarity.py (ADR-004). - Sin terminología financiera (SKU/country → variable/estacion).

ARIMAModel
ARIMAModel(order: Tuple[int, int, int] = (1, 1, 1))

Bases: SARIMAXModel

ARIMA puro (sin estacionalidad ni exógenas).

warm_starts property
warm_starts: List[Dict[str, Any]]

Configuraciones ARIMA iniciales (sin componentes estacionales) para Optuna.

ETSModel
ETSModel(trend: Optional[str] = 'add', seasonal: Optional[str] = 'add', seasonal_periods: int = 12, damped_trend: bool = False)

Bases: BaseModel

ETS / Holt-Winters vía statsmodels ExponentialSmoothing.

warm_starts property
warm_starts: List[Dict[str, Any]]

Configuraciones ETS / Holt-Winters iniciales para Optuna.

SARIMAModel
SARIMAModel(order: Tuple[int, int, int] = (1, 1, 1), seasonal_order: Tuple[int, int, int, int] = (1, 1, 1, 12))

Bases: SARIMAXModel

SARIMA sin exógenas.

warm_starts property
warm_starts: List[Dict[str, Any]]

Configuraciones SARIMA iniciales (con estacionalidad) para Optuna.

SARIMAXModel
SARIMAXModel(order: Tuple[int, int, int] = (1, 1, 1), seasonal_order: Tuple[int, int, int, int] = (0, 0, 0, 0), trend: str = 'n', enforce_stationarity: bool = False, enforce_invertibility: bool = False)

Bases: BaseModel

SARIMAX — SARIMA con regresores exógenos opcionales.

Envuelve statsmodels.tsa.statespace.sarimax.SARIMAX con la interfaz BaseModel.

aic property
aic: float

Akaike Information Criterion del ajuste; inf si no se ha ajustado.

summary property
summary: Optional[Any]

Resumen estadístico de statsmodels; None si no se ha ajustado.

warm_starts property
warm_starts: List[Dict[str, Any]]

Configuraciones SARIMAX iniciales para arrancar la búsqueda de Optuna.

predictive.ml

ml

Modelos de Machine Learning: XGBoost, Random Forest, LightGBM, SVR.

Todos usan features de lags internamente (feature engineering mínimo).

LightGBMModel
LightGBMModel(lags: List[int] = None, **lgb_params)

Bases: _SklearnModel

LightGBM para pronóstico ambiental.

RandomForestModel
RandomForestModel(lags: List[int] = None, **rf_params)

Bases: _SklearnModel

Random Forest para pronóstico ambiental.

XGBoostModel
XGBoostModel(lags: List[int] = None, **xgb_params)

Bases: _SklearnModel

XGBoost para pronóstico ambiental.

predictive.deep

Requiere pip install estadistica-ambiental[deep] (PyTorch).

deep

Modelos de Deep Learning: LSTM, BiLSTM y GRU para series temporales ambientales.

Requiere: pip install estadistica-ambiental[deep] (torch) Si PyTorch no está disponible, el import falla gracefully con ImportError.

Todas las clases implementan el contrato :class:BaseModel y, además, exponen los miembros del protocolo :class:ModelSpec (warm_starts, suggest_params, build_model, search_space) para integrarse en el pipeline de optimización bayesiana y en :func:estadistica_ambiental.evaluation.backtesting.walk_forward.

BiLSTMModel
BiLSTMModel(lookback: int = 24, hidden_size: int = 64, n_layers: int = 2, dropout: float = 0.1, epochs: int = 50, lr: float = 0.001)

Bases: LSTMModel

LSTM bidireccional. Misma interfaz que :class:LSTMModel.

GRUModel
GRUModel(lookback: int = 24, hidden_size: int = 64, n_layers: int = 2, dropout: float = 0.1, epochs: int = 50, lr: float = 0.001)

Bases: LSTMModel

GRU — misma interfaz que LSTM, arquitectura más ligera.

LSTMModel
LSTMModel(lookback: int = 24, hidden_size: int = 64, n_layers: int = 2, dropout: float = 0.1, epochs: int = 50, lr: float = 0.001)

Bases: BaseModel

LSTM para pronóstico ambiental univariado con ventana deslizante.

Implementa :class:BaseModel (fit/predict/is_fitted) y el protocolo :class:ModelSpec (warm_starts/suggest_params/ build_model/search_space) para uso directo con :func:walk_forward y el optimizador bayesiano.

Requiere: pip install torch (o el extra [deep]).

summary
summary() -> Dict[str, Any]

Resumen rápido del modelo (compatible con clásicos).

predictive.bayesian

Requiere pip install estadistica-ambiental[bayes] (PyMC + ArviZ). Ver ADR-016.

bayesian

Modelos bayesianos para series y panel ambiental con PyMC.

Requiere: pip install estadistica-ambiental[bayes] (pymc + arviz).

Implementaciones:

  • :class:BayesianARIMA: ARIMA(p, d, q) bayesiano con priors Normal(0, 1) sobre coeficientes AR/MA y HalfNormal(1) sobre la innovación. Pronóstico vía simulación recursiva del posterior (q=0 usa la matriz de lags AR; q>0 simula los errores MA junto con los lags).
  • :class:HierarchicalModel: modelo normal jerárquico (partial pooling) sobre un panel [group_col, value_col] con hiperprior global y media por grupo.

Si PyMC no está instalado el módulo se carga igual y la instanciación de las clases lanza ImportError con instrucciones claras (mismo patrón que predictive/deep.py). El registro central solo expone estos modelos cuando PyMC está disponible.

BayesianARIMA
BayesianARIMA(p: int = 1, d: int = 0, q: int = 0, samples: int = 1000, tune: int = 500, chains: int = 2, seed: int = 42, *, order: Optional[Tuple[int, int, int]] = None, draws: Optional[int] = None, random_seed: Optional[int] = None)

Bases: BaseModel

ARIMA(p, d, q) bayesiano con PyMC.

Priors
  • intercept ~ Normal(0, 1)
  • phi[i] ~ Normal(0, 1) (coeficientes AR)
  • theta[j] ~ Normal(0, 1) (coeficientes MA, solo si q > 0)
  • sigma ~ HalfNormal(1) (innovación)

El modelo se ajusta sobre la serie diferenciada d veces. La verosimilitud es Normal(mu, sigma) con mu igual a la suma autoregresiva (más MA si procede) sobre los lags observados.

Parameters

p, d, q : int, opcional Órdenes del proceso ARIMA. Pueden pasarse posicionalmente o vía order=(p, d, q). samples : int Número de draws por cadena (alias: draws). tune : int Pasos de tuneo de NUTS por cadena. chains : int Número de cadenas MCMC. seed : int Semilla aleatoria (alias: random_seed).

fit
fit(y: Series, X: Optional[DataFrame] = None, samples: Optional[int] = None) -> 'BayesianARIMA'

Ajusta ARIMA(p, d, q) bayesiano a la serie y.

X se acepta por compatibilidad con la interfaz :class:BaseModel pero se ignora en esta versión. Aplica diferenciación d antes de ajustar y guarda los anclajes necesarios para invertirla en :meth:predict.

plot_trace
plot_trace(**kwargs: Any) -> Any

Wrapper sobre arviz.plot_trace del posterior ajustado.

posterior_predictive_interval
posterior_predictive_interval(horizon: int, hdi_prob: float = 0.95) -> pd.DataFrame

Intervalo HDI del posterior predictivo (mean, lower, upper).

predict
predict(horizon: int, X_future: Optional[DataFrame] = None, n_samples: Optional[int] = None, return_samples: bool = False) -> Union[pd.Series, np.ndarray]

Pronóstico posterior predictivo a horizon pasos.

Por defecto devuelve una :class:pandas.Series con la media del posterior predictivo (indexada por fechas futuras si la serie original era datetime).

Si return_samples=True o si se pasa n_samples explícito, devuelve un np.ndarray de forma (n_samples, horizon) con las trayectorias muestreadas (compatible con la API histórica).

Si n_samples (o el default interno de 500 cuando no se pasa) supera los draws del posterior (chains * draws), las trayectorias se completan por bootstrap con reemplazo de ese posterior: la forma pedida siempre se respeta, pero la incertidumbre de parámetros sigue acotada por el posterior real -- solo se agregan nuevas realizaciones de la innovación. Con n_samples muy grande, el costo crece linealmente (el simulador es un loop en Python puro).

predict_interval
predict_interval(horizon: int, credible_interval: float = 0.94) -> pd.DataFrame

Intervalo de credibilidad por horizonte (mean, lower, upper).

summary
summary() -> pd.DataFrame

Resumen del posterior con mean, sd, HDI, r_hat y ess.

HierarchicalModel
HierarchicalModel(group_col: str = 'estacion', samples: int = 1000, tune: int = 500, chains: int = 2, seed: int = 42, *, draws: Optional[int] = None, random_seed: Optional[int] = None)

Bases: BaseModel

Modelo normal jerárquico con partial pooling por grupo.

Estructura del modelo::

mu_global   ~ Normal(0, 10)
sigma_global ~ HalfNormal(5)
mu_group[i] ~ Normal(mu_global, sigma_global)
sigma       ~ HalfNormal(5)
y[i]        ~ Normal(mu_group[group[i]], sigma)

Acepta entrada en formato largo (DataFrame con columnas [group_col, value_col]) o, retro-compatiblemente, una serie y y un array de grupos vía groups= o X['estacion'].

fit
fit(y: Union[Series, DataFrame], X: Optional[DataFrame] = None, groups: Optional[Sequence] = None, value_col: Optional[str] = None, samples: Optional[int] = None) -> 'HierarchicalModel'

Ajusta el modelo jerárquico.

Dos formas de uso:

  1. Formato largo: fit(df, value_col="y") con df que tiene la columna self.group_col y la columna value_col.
  2. Compatibilidad: fit(y_series, groups=array) o fit(y_series, X=df_con_columna_estacion).
group_estimates
group_estimates() -> pd.DataFrame

Tabla por grupo con media posterior y HDI 95% de mu_group[i].

plot_forest
plot_forest(**kwargs: Any) -> Any

Wrapper sobre arviz.plot_forest para los efectos por grupo.

predict
predict(horizon: int, X_future: Optional[DataFrame] = None, n_samples: int = 500) -> np.ndarray

Trayectorias por grupo: array (n_samples, horizon, n_groups).

El modelo no es temporal: cada paso muestrea independientemente del posterior conjunto sobre (mu_group, sigma).

Si n_samples (500 por defecto) supera los draws del posterior (chains * draws), se completa por bootstrap con reemplazo -- la forma pedida siempre se respeta, pero la incertidumbre de parámetros sigue acotada por el posterior real.

summary
summary() -> pd.DataFrame

Resumen ArviZ del posterior con mean, sd, HDI, r_hat, ess.

predictive.prophet_model

Requiere pip install estadistica-ambiental[prophet].

prophet_model

Modelo Prophet (Meta) con interfaz BaseModel.

ProphetModel
ProphetModel(changepoint_prior_scale: float = 0.05, seasonality_prior_scale: float = 10.0, yearly_seasonality: bool = True, weekly_seasonality: bool = False, daily_seasonality: bool = False, interval_width: float = 0.8)

Bases: BaseModel

Envuelve Prophet con la interfaz BaseModel.

Requiere: pip install prophet

predictive.residual_ar

residual_ar

Calibrador AR(1) horario doble-escala sobre residuos de modelos ML.

Implementa el patrón validado en el proyecto CAR (calidad del aire, Cundinamarca): añade autocorrelación realista a pronósticos deterministas (RF/XGB) ajustando 24 modelos AR(1) (uno por hora del día) con varianza de innovación estacional (σ por mes). Genera trayectorias Monte Carlo para construir bandas P10/P50/P90, y expone una suite de tests formales (T1-T4 + KS + Ljung-Box + Jarque-Bera) que devuelven PASS/FAIL.

Adaptado de boa-sarima-forecaster / proyecto CAR (06_Pronostico_2026/08_escalar_ar1_todas_estaciones.py) por Dan Méndez — 2026-05-07. Ver Plan/Plan.md §13 / M-02 y Plan/Feedback/repo_estadistica_ambiental_feedback.md §3.1.B.

CalibrationReport dataclass
CalibrationReport(phi_by_hour: ndarray, sigma_by_month: ndarray, tests: Dict[str, TestResult] = dict())

Resumen tabular del estado de calibración y validación.

all_pass property
all_pass: bool

True si todos los tests devolvieron PASS.

to_frame
to_frame() -> pd.DataFrame

Devuelve un DataFrame con un test por fila.

HourlyAR1Calibrator
HourlyAR1Calibrator(n_trajectories: int = DEFAULT_TRAJECTORIES, alpha: float = DEFAULT_ALPHA, random_state: Optional[int] = None)

AR(1) horario doble-escala sobre residuos: 24 phi por hora, σ por mes.

Ajusta una familia de 24 modelos AR(1) (uno por hora del día) sobre los residuos r(t) = y(t) - base_ML(t) y modela la varianza de innovación σ²(mes) para captar la estacionalidad mensual andina (clima bimodal).

Modelo::

e_AR(t) = phi_h(t) · e_AR(t-1) + ε(t),   ε ~ N(0, σ_mes(t))

Use :meth:fit para calibrar phi_h y σ_mes desde una serie de residuos indexada por hora, :meth:simulate para generar K trayectorias Monte Carlo, y :meth:validate para correr la suite de 7 tests formales.

is_fitted property
is_fitted: bool

True si el calibrador ya fue ajustado.

fit
fit(residuals: Series | ndarray, datetime_index: Optional[DatetimeIndex] = None) -> 'HourlyAR1Calibrator'

Ajusta 24 phi (uno por hora) y 12 σ (uno por mes) desde residuos.

percentiles
percentiles(trajectories: ndarray, levels: Tuple[float, float, float] = (10.0, 50.0, 90.0)) -> Dict[str, np.ndarray]

Devuelve percentiles P10/P50/P90 (o niveles dados) por paso.

simulate
simulate(horizon: int, n_trajectories: Optional[int] = None, start_datetime: Optional[Timestamp] = None, e0: float = 0.0, random_state: Optional[int] = None) -> np.ndarray

Genera K trayectorias Monte Carlo de longitud horizon.

validate
validate(residuals: Optional[Series | ndarray] = None, datetime_index: Optional[DatetimeIndex] = None, alpha: Optional[float] = None) -> Dict[str, TestResult]

Ejecuta T1-T4 + KS + Ljung-Box + Jarque-Bera sobre las innovaciones.

TestResult dataclass
TestResult(name: str, passed: bool, statistic: float, p_value: float, detail: str = '')

Resultado de un test estadístico individual.

status property
status: str

Cadena 'PASS' o 'FAIL' según passed.

to_dict
to_dict() -> Dict[str, Any]

Serialización plana para reporte tabular.

predictive.spatial_models

spatial_models

Modelos espaciales: Kriging y Gaussian Process con interfaz BaseModel.

KrigingInterpolator
KrigingInterpolator(variogram_model: str = 'spherical', lat_col: str = 'lat', lon_col: str = 'lon', value_col: str = 'valor')

Wrapper de pykrige.OrdinaryKriging para uso en pipelines del repo.

No hereda BaseModel porque opera en espacio 2D, no en tiempo. Interfaz: fit(points_df) → predict(grid_lat, grid_lon).

predict
predict(grid_lat: ndarray, grid_lon: ndarray) -> Tuple[np.ndarray, np.ndarray]

Interpola sobre la grilla. Returns (z_values, z_variance).

predict_points
predict_points(lats: ndarray, lons: ndarray) -> np.ndarray

Interpola en puntos arbitrarios (no en grilla).

SpatioTemporalKriging
SpatioTemporalKriging(variogram_model: str = 'spherical')

Kriging espacio-temporal simplificado: ajusta un Kriging por período.

Para cada timestamp, interpola espacialmente las estaciones disponibles. Útil para mapas de PM2.5 o temperatura por fecha.

predictive.registry

registry

Registro central de modelos predictivos del catálogo.

get_model
get_model(name: str, **kwargs) -> BaseModel

Instancia un modelo por nombre. kwargs pasan al constructor.

list_models
list_models() -> List[str]

Devuelve la lista de modelos disponibles.

register
register(name: str, model_class: Type[BaseModel]) -> None

Registra un modelo personalizado en el catálogo.


Evaluación

evaluation.metrics

metrics

Métricas de evaluación para modelos ambientales. Adaptado de boa-sarima-forecaster/metrics.py por Dan Méndez — 2026-04-22

Extiende las métricas financieras originales con NSE y KGE (hidrología) y desactiva RMSLE para variables que pueden ser negativas (ADR-003).

compare_models
compare_models(results: dict) -> pd.DataFrame

Tabla comparativa de modelos. results = {model_name: metrics_dict}.

evaluate
evaluate(y_true: ndarray, y_pred: ndarray, domain: str = 'general', y_train: Optional[ndarray] = None, ica_breakpoints: Optional[dict] = None, pollutant: str = 'pm25') -> dict

Calcula todas las métricas relevantes según el dominio.

Parameters:

Name Type Description Default
domain str

'general' | 'hydrology' | 'air_quality'

'general'
y_train Optional[ndarray]

serie de entrenamiento para MASE (opcional).

None
ica_breakpoints Optional[dict]

puntos de corte ICA custom para hit_rate_ica. Si se provee, tiene precedencia sobre pollutant.

None
pollutant str

contaminante para seleccionar breakpoints ICA correctos ('pm25', 'pm10', 'o3', 'no2', 'so2', 'co'). Solo aplica con domain='air_quality' y sin ica_breakpoints explícito.

'pm25'
hit_rate_ica
hit_rate_ica(y_true: ndarray, y_pred: ndarray, breakpoints: Optional[dict] = None, pollutant: str = 'pm25') -> float

Porcentaje de predicciones en la misma categoría ICA que el valor real.

Parameters:

Name Type Description Default
y_true ndarray

valores reales de concentración (µg/m³, o mg/m³ para CO).

required
y_pred ndarray

valores predichos de concentración.

required
breakpoints Optional[dict]

dict con {categoria: (min_excl, max_incl)} personalizado. Si se provee, tiene precedencia sobre pollutant.

None
pollutant str

contaminante para seleccionar breakpoints de Res. 2254/2017. Opciones: 'pm25', 'pm10', 'o3', 'no2', 'so2', 'co'. Default: 'pm25'.

'pm25'

Returns:

Type Description
float

float entre 0.0 y 100.0 (porcentaje de coincidencias de categoría).

kge
kge(y_true: ndarray, y_pred: ndarray) -> float

Kling-Gupta Efficiency. Rango (-∞, 1]; 1=perfecto.

mape
mape(y_true: ndarray, y_pred: ndarray) -> float

MAPE — solo válido cuando y_true ≠ 0.

mase
mase(y_true: ndarray, y_pred: ndarray, y_train: Optional[ndarray] = None, m: int = 1) -> float

Mean Absolute Scaled Error. Requiere serie de entrenamiento o usa naive.

nrmse
nrmse(y_true: ndarray, y_pred: ndarray) -> float

RMSE normalizado por desviación estándar. Estable con ceros y negativos.

NRMSE = RMSE / std(y_true). Rango (0, inf), escala-invariante. NRMSE < 1.0 indica mejor desempeño que el modelo de la media. Reemplaza sMAPE cuando la serie contiene valores cercanos a cero. Retorna nan si y_true tiene varianza cero (sensor saturado, fold degenerado).

nse
nse(y_true: ndarray, y_pred: ndarray) -> float

Nash-Sutcliffe Efficiency. Rango (-∞, 1]; 1=perfecto, <0=peor que media.

pbias
pbias(y_true: ndarray, y_pred: ndarray) -> float

Percent Bias (%). Positivo = sobreestimación.

smape
smape(y_true: ndarray, y_pred: ndarray) -> float

sMAPE — solo válido cuando y_true ≥ 0.

evaluation.backtesting

backtesting

Backtesting walk-forward para modelos predictivos ambientales.

compare_backtests
compare_backtests(results: Dict[str, Dict]) -> pd.DataFrame

Tabla comparativa de modelos backtested.

results = {model_name: walk_forward_result_dict}

walk_forward
walk_forward(model: BaseModel, y: Series, horizon: int = 1, n_splits: int = 5, min_train_size: Optional[int] = None, X: Optional[DataFrame] = None, domain: str = 'general', strategy: str = 'expanding', pollutant: str = 'pm25', gap: int = 0) -> Dict

Walk-forward backtesting con ventana expansiva o deslizante.

Parameters:

Name Type Description Default
model BaseModel

Instancia de BaseModel (se re-entrena en cada fold).

required
y Series

Serie temporal objetivo.

required
horizon int

Pasos a predecir en cada fold.

1
n_splits int

Número de folds.

5
min_train_size Optional[int]

Tamaño mínimo del conjunto de entrenamiento.

None
X Optional[DataFrame]

Exógenas (si el modelo las soporta).

None
domain str

'general' | 'hydrology' | 'air_quality' para métricas.

'general'
strategy str

'expanding' (crece) | 'sliding' (tamaño fijo).

'expanding'
pollutant str

Contaminante para breakpoints ICA ('pm25', 'pm10', 'o3', 'no2', 'so2', 'co'). Solo aplica con domain='air_quality'.

'pm25'
gap int

Observaciones de purga entre fin de train y comienzo de test. Evita leakage por autocorrelación alta (PM2.5 horario, caudal diario). Default 0 (sin purga) para compatibilidad retroactiva.

0

Returns:

Type Description
Dict

Dict con 'metrics' (promedio), 'folds' (lista por fold) y 'predictions'.

evaluation.anomaly

anomaly

Detección de anomalías en predicciones de modelos. Adaptado de tesis-renare-v4/residual_analysis.py por Dan Méndez — 2026-04-22

El error relativo |real - pred| / (|real| + 1) es estable con valores cercanos a cero y negativos, lo que lo hace adecuado para variables ambientales de escala variable (emisiones, caudal, concentraciones).

anomaly_summary
anomaly_summary(anomaly_df: DataFrame, threshold: float = 2.0) -> dict

Resumen estadístico del DataFrame producido por detect_anomalies().

Parameters:

Name Type Description Default
anomaly_df DataFrame

salida de detect_anomalies().

required
threshold float

mismo valor usado en detect_anomalies(); se usa solo como fallback si anomaly_df.attrs no tiene 'umbral' guardado.

2.0

Returns:

Type Description
dict

dict con: - n_total: número total de registros evaluados - n_anomalies: cantidad de anomalías detectadas - pct_anomalies: porcentaje de anomalías sobre el total - mean_error_rel: error relativo medio (toda la serie) - std_error_rel: desviación estándar del error relativo (ddof=0) - threshold_value: umbral exacto aplicado en detect_anomalies - max_error_rel: error relativo máximo observado

detect_anomalies
detect_anomalies(y_true: Union[ndarray, Series], y_pred: Union[ndarray, Series], threshold: float = 2.0, relative: bool = True, index: Optional[Index] = None) -> pd.DataFrame

Detecta registros donde el error del modelo supera threshold desviaciones estándar.

Lógica
  • Si relative=True: error_rel = |real - pred| / (|real| + 1)
  • Si relative=False: error_rel = |real - pred| (error absoluto)
  • Anomalía: error_rel > mean(error_rel) + threshold * std(error_rel)

Parameters:

Name Type Description Default
y_true Union[ndarray, Series]

valores reales observados.

required
y_pred Union[ndarray, Series]

valores predichos por el modelo.

required
threshold float

número de desviaciones estándar sobre la media para declarar anomalía.

2.0
relative bool

si True usa error relativo (recomendado para series con ceros).

True
index Optional[Index]

índice opcional (ej. fechas) para el DataFrame resultante.

None

Returns:

Type Description
DataFrame

DataFrame con columnas: - y_true: valor real - y_pred: valor predicho - error: residual (y_true - y_pred) - error_rel: error relativo o absoluto según parámetro relative - is_anomaly: booleano — True si supera el umbral

DataFrame

Ordenado por error_rel descendente.

evaluation.comparison

comparison

Comparación multi-criterio y selección del mejor modelo predictivo.

rank_models
rank_models(results: Dict[str, Dict], domain: str = 'general', weights: Optional[Dict[str, float]] = None) -> pd.DataFrame

Ranking multi-criterio de modelos backtestados.

Parameters:

Name Type Description Default
results Dict[str, Dict]

{model_name: walk_forward_result} — salida de walk_forward().

required
domain str

'general' | 'hydrology' | 'air_quality'.

'general'
weights Optional[Dict[str, float]]

Pesos custom {metric: weight}. Sobreescribe defaults.

None

Returns:

Type Description
DataFrame

DataFrame ordenado por score compuesto (menor es mejor).

select_best
select_best(results: Dict[str, Dict], domain: str = 'general') -> str

Devuelve el nombre del mejor modelo según ranking compuesto.


Features

features.climate

climate

Covariables de cambio climático: índices ENSO, ONI, escenarios.

Estas covariables se usan en modelos predictivos de oferta hídrica, páramos, humedales y gestión de riesgo cuando hay influencia climática.

enso_dummy
enso_dummy(df: DataFrame, oni_df: DataFrame, date_col: str) -> pd.DataFrame

Une el índice ONI al DataFrame principal y agrega dummies de fase.

enso_lagged
enso_lagged(df: DataFrame, oni_df: DataFrame, date_col: str, linea_tematica: Optional[str] = None, lag_meses: Optional[int] = None) -> pd.DataFrame

Une el índice ONI con el lag específico por línea temática colombiana.

El lag captura el retraso entre la señal ENSO y su respuesta en la variable ambiental local (e.g. caudal, PM2.5, nivel de humedal).

Parameters:

Name Type Description Default
df DataFrame

DataFrame con datos ambientales.

required
oni_df DataFrame

DataFrame devuelto por load_oni().

required
date_col str

Columna de fechas en df.

required
linea_tematica Optional[str]

Nombre de la línea ('oferta_hidrica', 'calidad_aire', etc.). Determina el lag automáticamente desde config.ENSO_LAG_MESES.

None
lag_meses Optional[int]

Override manual del lag. Tiene precedencia sobre linea_tematica.

None

Returns:

Type Description
DataFrame

df con columnas oni_lag{n}, fase_lag{n}, intensidad_lag{n}, dummies.

load_oni
load_oni(path: Optional[str] = None, start_year: int = 1950) -> pd.DataFrame

Carga el Índice Oceánico Niño (ONI) de NOAA o desde archivo local.

El ONI es el índice estándar para clasificar El Niño/La Niña, con impacto directo en precipitación y temperatura en Colombia.

Returns:

Type Description
DataFrame

DataFrame con columnas: fecha, oni, fase, intensidad.

features.lags

lags

Feature engineering: lags, rolling stats y diferencias para series ambientales.

add_diff_features
add_diff_features(df: DataFrame, col: str, orders: List[int] = (1,)) -> pd.DataFrame

Agrega diferencias de orden N (útil para eliminar tendencia).

add_lags
add_lags(df: DataFrame, col: str, lags: List[int], drop_na: bool = False) -> pd.DataFrame

Agrega columnas lag_N para la variable objetivo.

add_rolling_features
add_rolling_features(df: DataFrame, col: str, windows: List[int], stats: List[str] = ('mean', 'std', 'min', 'max'), drop_na: bool = False) -> pd.DataFrame

Agrega estadísticas rodantes para capturar tendencia local.

features.calendar

calendar

Features de calendario para series ambientales colombianas.

add_calendar_features
add_calendar_features(df: DataFrame, date_col: str, features: tuple = ('hour', 'dayofweek', 'month', 'quarter', 'dayofyear'), cyclical: bool = True) -> pd.DataFrame

Agrega variables de calendario como features numéricas.

Con cyclical=True codifica como seno/coseno para preservar la estructura cíclica (e.g. hora 23 ≈ hora 0).

features.exogenous

exogenous

Preparación de variables exógenas para modelos SARIMAX y ML.

align_exogenous
align_exogenous(df_target: DataFrame, df_exog: DataFrame, date_col_target: str, date_col_exog: str, exog_cols: Optional[List[str]] = None, freq: str = 'D', fill_method: str = 'linear') -> pd.DataFrame

Alinea un DataFrame de exógenas con la serie objetivo por fecha.

Remuestrea las exógenas a la frecuencia de la serie objetivo, rellena faltantes y hace merge. Útil para combinar datos de calidad del aire con meteorología de diferente resolución temporal.

create_exog_matrix
create_exog_matrix(df: DataFrame, cols: List[str], date_col: str, train_end_idx: int) -> Dict[str, pd.DataFrame]

Divide las exógenas en train y future para SARIMAX.

meteorological_features
meteorological_features(df: DataFrame, temp_col: Optional[str] = None, wind_col: Optional[str] = None, humidity_col: Optional[str] = None, rain_col: Optional[str] = None) -> pd.DataFrame

Genera features derivados de variables meteorológicas.

  • Índice de confort térmico (temperatura × humedad)
  • Indicador de lluvia binario
  • Velocidad de viento al cuadrado (proxy de dispersión)

Optimización

optimization.bayes_opt

bayes_opt

Motor de optimización bayesiana con Optuna TPE. Adaptado de boa-sarima-forecaster/optimizer.py por Dan Méndez — 2026-04-22

Generalizado: soporta cualquier función objetivo y espacio de búsqueda, no solo SARIMA. Usado por predictive/classical.py y demás modelos.

Mejoras incorporadas (v2): - optimize_model: acepta cualquier objeto que satisfaga el protocolo ModelSpec (warm_starts, suggest_params, build_model). - Warm-start: encola configuraciones conocidas antes de la búsqueda TPE. - Graceful fallback: si Optuna falla N veces seguidas usa warm_start[0]. - MedianPruner: descarta trials malos temprano para ahorrar tiempo. - Devuelve OptimizationResult tipado en lugar del Study crudo.

best_params
best_params(study: Study) -> Dict[str, Any]

Devuelve los mejores hiperparámetros del estudio.

optimization_history
optimization_history(study: Study) -> 'pd.DataFrame'

Historial de trials como DataFrame (requiere optuna visualización).

optimize
optimize(objective: Callable[[Trial], float], n_trials: int = 50, timeout: Optional[int] = None, direction: str = 'minimize', study_name: Optional[str] = None, storage: Optional[str] = None, show_progress: bool = False, warm_starts: Optional[List[Dict[str, Any]]] = None) -> optuna.Study

Ejecuta optimización bayesiana TPE sobre la función objetivo.

Parameters:

Name Type Description Default
objective Callable[[Trial], float]

Función que recibe un Trial de Optuna y devuelve el valor a minimizar (o maximizar si direction='maximize').

required
n_trials int

Número de evaluaciones.

50
timeout Optional[int]

Tiempo límite en segundos (None = sin límite).

None
direction str

'minimize' | 'maximize'.

'minimize'
study_name Optional[str]

Nombre del estudio (opcional).

None
storage Optional[str]

URL de base de datos Optuna para persistencia (opcional).

None
show_progress bool

Mostrar barra de progreso en consola.

False
warm_starts Optional[List[Dict[str, Any]]]

Lista de dicts de hiperparámetros que se encolan como trials iniciales antes de la exploración aleatoria. Si se provee, Optuna parte de configuraciones conocidas en lugar de explorar ciegamente.

None

Returns:

Type Description
Study

optuna.Study con el historial completo de la búsqueda.

optimize_model
optimize_model(model_spec: Any, objective: Callable[[Trial], float], n_trials: int = 50, timeout: Optional[int] = None, direction: str = 'minimize', study_name: Optional[str] = None, storage: Optional[str] = None, show_progress: bool = False, max_fallos: int = _MAX_FALLOS_CONSECUTIVOS) -> 'OptimizationResult'

Optimiza los hiperparámetros de un modelo que sigue el protocolo ModelSpec.

Esta función extiende :func:optimize con soporte explícito para el protocolo ModelSpec:

  • Si model_spec tiene warm_starts, los inyecta como trials iniciales antes de la exploración aleatoria de Optuna.
  • Si Optuna acumula max_fallos excepciones consecutivas (o falla completamente), activa el fallback usando warm_starts[0] como parámetros por defecto en lugar de propagar la excepción.
  • Construye el modelo final con model_spec.build_model(best_params) si el método está disponible.

Parameters:

Name Type Description Default
model_spec Any

Objeto que implementa el protocolo ModelSpec (warm_starts, suggest_params, build_model). También acepta objetos sin esos atributos (funciona como :func:optimize en ese caso).

required
objective Callable[[Trial], float]

Función (optuna.Trial) → float a minimizar/maximizar.

required
n_trials int

Número total de evaluaciones de Optuna.

50
timeout Optional[int]

Tiempo límite en segundos (None = sin límite).

None
direction str

'minimize' | 'maximize'.

'minimize'
study_name Optional[str]

Nombre del estudio Optuna (opcional).

None
storage Optional[str]

URL de base de datos Optuna para persistencia (opcional).

None
show_progress bool

Mostrar barra de progreso en consola.

False
max_fallos int

Fallos consecutivos tolerados antes de activar fallback.

_MAX_FALLOS_CONSECUTIVOS

Returns:

Type Description
'OptimizationResult'

class:~estadistica_ambiental.predictive.base.OptimizationResult

'OptimizationResult'

con best_params, best_score, n_trials, model,

'OptimizationResult'

study y el flag fallback.

optimize_sarima
optimize_sarima(series: 'pd.Series', n_trials: int = 50, max_p: int = 5, max_q: int = 5, seasonal_period: int = 12, n_splits: int = 3, horizon: int = 1, gap: int = 0, timeout: Optional[int] = None) -> 'OptimizationResult'

Wrapper convenience para optimizar hiperparámetros SARIMA con Optuna.

Uso::

from estadistica_ambiental.optimization.bayes_opt import optimize_sarima
result = optimize_sarima(serie_caudal, n_trials=50)
mejor_modelo = result.model  # SARIMAModel listo para fit()

Parameters:

Name Type Description Default
series 'pd.Series'

Serie temporal objetivo (pd.Series con DatetimeIndex).

required
n_trials int

Evaluaciones de Optuna.

50
max_p int

Máximo orden AR (p).

5
max_q int

Máximo orden MA (q).

5
seasonal_period int

Período estacional S (default 12 para datos mensuales).

12
n_splits int

Folds del walk-forward dentro del objetivo.

3
horizon int

Horizonte de pronóstico en el objetivo.

1
gap int

Purga entre train y test (ver walk_forward).

0
timeout Optional[int]

Tiempo límite en segundos.

None

Returns:

Type Description
'OptimizationResult'

OptimizationResult con best_params, best_score y model construido.

sarima_search_space
sarima_search_space(trial: Trial, max_p: int = 5, max_d: int = 2, max_q: int = 5, max_P: int = 2, max_D: int = 1, max_Q: int = 2, seasonal_period: int = 12) -> Dict[str, int]

Espacio de búsqueda estándar para SARIMA (p,d,q)(P,D,Q,s).

xgboost_search_space
xgboost_search_space(trial: Trial) -> Dict[str, Any]

Espacio de búsqueda estándar para XGBoost.


Análisis espacial

Requiere pip install estadistica-ambiental[spatial] (geopandas + rasterio + pysal + esda + folium). Ver ADR-013 en decisiones.md.

spatial.io

io

I/O espacial: lectura de SHP, GeoPackage, GeoJSON, NetCDF y raster.

Complementa io/loaders.py para formatos con componente geométrico.

list_gpkg_layers
list_gpkg_layers(path: Union[str, Path]) -> List[str]

Lista las capas disponibles en un GeoPackage.

load_netcdf_spatial
load_netcdf_spatial(path: Union[str, Path], variable: Optional[str] = None, time_slice: Optional[slice] = None) -> 'xarray.Dataset'

Carga un NetCDF como xarray Dataset (datos espacio-temporales).

Útil para datos Copernicus, IDEAM o modelos climáticos. Requiere: xarray + netcdf4.

load_raster
load_raster(path: Union[str, Path], band: int = 1) -> dict

Carga un raster GeoTIFF/NetCDF y devuelve datos + metadatos.

Returns dict con: data (np.ndarray), transform, crs, shape, nodata. Requiere: rasterio.

load_vector
load_vector(path: Union[str, Path], layer: Optional[str] = None, to_epsg: Optional[int] = None) -> 'geopandas.GeoDataFrame'

Carga un archivo vectorial (SHP, GeoPackage, GeoJSON) a GeoDataFrame.

Parameters:

Name Type Description Default
path Union[str, Path]

Ruta al archivo.

required
layer Optional[str]

Capa específica (para GeoPackage con múltiples capas).

None
to_epsg Optional[int]

Reprojecta al EPSG indicado tras la carga.

None
vector_to_parquet
vector_to_parquet(gdf, output: Union[str, Path]) -> Path

Guarda un GeoDataFrame como GeoParquet (formato eficiente para SIG).

spatial.projections

projections

Manejo de proyecciones geográficas para datos ambientales colombianos.

Sistemas de referencia más usados en Colombia

MAGNA-SIRGAS geográfico EPSG:4686 MAGNA-SIRGAS / CTM12 EPSG:9377 (proyección oficial desde 2020) WGS84 EPSG:4326 (GPS, OpenStreetMap) Web Mercator EPSG:3857 (visualización web)

bounding_box_colombia
bounding_box_colombia(buffer_deg: float = 0.0) -> Tuple[float, float, float, float]

Bounding box de Colombia en WGS84 (lon_min, lat_min, lon_max, lat_max).

clip_to_colombia
clip_to_colombia(gdf, buffer_deg: float = 0.5)

Recorta un GeoDataFrame al territorio colombiano.

points_to_geodataframe
points_to_geodataframe(df: DataFrame, lat_col: str = 'lat', lon_col: str = 'lon', epsg: int = EPSG_WGS84)

Convierte un DataFrame con lat/lon a GeoDataFrame de puntos.

reproject
reproject(gdf, from_epsg: int, to_epsg: int)

Reprojecta un GeoDataFrame entre sistemas de referencia.

spatial.analysis

analysis

Análisis espacial: traslapes poligonales y estadísticas zonales.

Operaciones fundamentales para análisis de iniciativas GEI, áreas protegidas y agregación de variables ambientales por unidades político-administrativas.

intersection_area
intersection_area(gdf1: 'geopandas.GeoDataFrame', gdf2: 'geopandas.GeoDataFrame', id_col1: str, id_col2: str) -> 'geopandas.GeoDataFrame'

Calcula áreas de traslape entre dos capas poligonales.

Reprojecta a CTM12 (EPSG:9377) para precisión métrica en Colombia. Usa overlay vectorizado de geopandas (no bucle anidado).

Parameters:

Name Type Description Default
gdf1 'geopandas.GeoDataFrame'

Primera capa poligonal (ej: iniciativas REDD+).

required
gdf2 'geopandas.GeoDataFrame'

Segunda capa poligonal (ej: áreas protegidas, páramos).

required
id_col1 str

Columna identificadora única en gdf1.

required
id_col2 str

Columna identificadora única en gdf2.

required

Returns:

Type Description
'geopandas.GeoDataFrame'

GeoDataFrame con columnas: id_col1, id_col2, intersection_area_m2,

'geopandas.GeoDataFrame'

pct_of_, pct_of_, geometry (CRS de gdf1).

zonal_statistics
zonal_statistics(raster_path: Union[str], zones_gdf: 'geopandas.GeoDataFrame', zone_id_col: str, stats: List[str] = None) -> 'geopandas.GeoDataFrame'

Agrega valores de raster por zonas poligonales (municipios, departamentos).

Equivalente a QGIS "Zonal Statistics" o ArcGIS "Zonal Statistics as Table". Caso de uso típico: emisiones GEI o deforestación por municipio/departamento.

Parameters:

Name Type Description Default
raster_path Union[str]

Ruta a GeoTIFF (deforestación, emisiones, NDVI, etc.)

required
zones_gdf 'geopandas.GeoDataFrame'

GeoDataFrame con polígonos de zonas.

required
zone_id_col str

Columna de identificación única (ej: 'CODIGO_DANE').

required
stats List[str]

Estadísticas a calcular. Default: ['mean', 'sum', 'std', 'min', 'max']. Opciones adicionales: 'count', 'median'.

None

Returns:

Type Description
'geopandas.GeoDataFrame'

GeoDataFrame original con columnas adicionales para cada estadística.

spatial.autocorrelation

autocorrelation

Autocorrelación espacial: I de Moran, C de Geary.

Previo a modelos espaciales para verificar que existe dependencia espacial.

geary_c
geary_c(gdf, value_col: str, weight_type: str = 'queen', significance: float = 0.05) -> dict

Índice C de Geary para autocorrelación espacial.

Complementa a I de Moran: más sensible a disimilitud local. C ≈ 1 → aleatoriedad; C < 1 → clustering; C > 1 → dispersión.

Requiere: pip install pysal esda libpysal (incluido en [spatial]).

getis_ord_g
getis_ord_g(gdf, value_col: str, weight_type: str = 'queen') -> 'geopandas.GeoDataFrame'

G* de Getis-Ord local — detección de hotspots y coldspots.

Identifica clusters de valores altos (hotspots) y bajos (coldspots) estadísticamente significativos. Útil para deforestación, contaminación.

Agrega columnas 'g_z' (z-score) y 'hotspot' ('hot'/'cold'/'ns') al GeoDataFrame. Requiere: esda, libpysal (incluido en [spatial]).

local_morans_i
local_morans_i(gdf, value_col: str, weight_type: str = 'queen')

LISA — Moran local para identificar clusters y outliers espaciales.

Parameters:

Name Type Description Default
weight_type str

'queen', 'rook' o 'kN' (ej. 'k5') — ver _build_weights().

'queen'

Requiere: pip install pysal esda libpysal (incluido en [spatial]). Agrega columnas 'lisa_q' (cuadrante) y 'lisa_p' al GeoDataFrame.

morans_i
morans_i(gdf, value_col: str, weight_type: str = 'queen', significance: float = 0.05) -> dict

Índice de Moran global para autocorrelación espacial.

Requiere: pip install pysal esda libpysal (incluido en [spatial]).

H0: distribución espacialmente aleatoria. I > 0 → clustering; I < 0 → dispersión.

spatial.interpolation

interpolation

Interpolación espacial: IDW y Kriging (ordinario, universal).

Usado para mapear contaminantes o variables hídricas entre estaciones dispersas.

idw
idw(points: DataFrame, lat_col: str, lon_col: str, value_col: str, grid_lat: ndarray, grid_lon: ndarray, power: float = 2.0) -> np.ndarray

Interpolación por Distancia Inversa Ponderada (IDW).

Parameters:

Name Type Description Default
points DataFrame

DataFrame con estaciones de observación.

required
lat_col str

Nombre de la columna de latitud en points.

required
lon_col str

Nombre de la columna de longitud en points.

required
value_col str

Nombre de la columna con el valor a interpolar.

required
grid_lat ndarray

Grid 2D de latitudes objetivo (meshgrid).

required
grid_lon ndarray

Grid 2D de longitudes objetivo (meshgrid).

required
power float

Exponente del IDW (2 = estándar).

2.0

Returns:

Type Description
ndarray

Array 2D con valores interpolados en la grilla.

ordinary_kriging
ordinary_kriging(points: DataFrame, lat_col: str, lon_col: str, value_col: str, grid_lat: ndarray, grid_lon: ndarray, variogram_model: str = 'spherical') -> Tuple[np.ndarray, np.ndarray]

Kriging ordinario vía pykrige.

Requiere: pip install pykrige (incluido en [spatial]).

Returns:

Type Description
Tuple[ndarray, ndarray]

(z_interpolated, z_variance) — ambos arrays 2D.

universal_kriging
universal_kriging(points: DataFrame, lat_col: str, lon_col: str, value_col: str, grid_lat: ndarray, grid_lon: ndarray, variogram_model: str = 'linear', drift_order: int = 1) -> Tuple[np.ndarray, np.ndarray]

Kriging universal con tendencia espacial (drift polinomial).

Extiende el Kriging ordinario modelando un drift determinístico. Ideal para variables con gradiente espacial claro: temperatura, elevación, presión antrópica sobre ecosistemas.

Parameters:

Name Type Description Default
drift_order int

1=lineal (default), 2=cuadrático.

1
variogram_model str

'linear', 'spherical', 'exponential', 'gaussian'.

'linear'

Returns:

Type Description
Tuple[ndarray, ndarray]

(z_interpolated, z_variance) — ambos arrays 2D.

spatial.viz

viz

Visualización espacial: mapas de estaciones, interpolación y coropletas.

choropleth_map
choropleth_map(gdf, value_col: str, title: str = 'Mapa', cmap: str = 'YlOrRd', zoom: int = 6) -> 'folium.Map'

Mapa coroplético de un GeoDataFrame con folium + branca.

Requiere: pip install folium branca.

map_stations
map_stations(df: DataFrame, lat_col: str = 'lat', lon_col: str = 'lon', label_col: Optional[str] = None, value_col: Optional[str] = None, title: str = 'Estaciones de monitoreo', zoom: int = 6) -> 'folium.Map'

Mapa interactivo de estaciones con folium.

Requiere: pip install folium (incluido en [spatial]).

plot_kriging_map
plot_kriging_map(grid_lat: ndarray, grid_lon: ndarray, z_values: ndarray, points_df: Optional[DataFrame] = None, lat_col: str = 'lat', lon_col: str = 'lon', title: str = 'Kriging', figsize: tuple = (8, 7)) -> 'plt.Figure'

Mapa estático (matplotlib) del resultado del Kriging.


Reporting

reporting.compliance_report

compliance_report

Reporte HTML de cumplimiento normativo ambiental colombiano.

Genera un documento HTML que muestra, por variable, el porcentaje de excedencias respecto a las normas colombianas relevantes (Res. 2254/2017, Res. 2115/2007, Res. 631/2015, IUA, ICA, etc.).

Uso típico

from estadistica_ambiental.reporting.compliance_report import compliance_report

compliance_report( df, variables=["pm25", "od", "dbo5"], date_col="fecha", linea_tematica="calidad_aire", output="data/output/reports/cumplimiento_aire.html", )

compliance_report
compliance_report(df: DataFrame, variables: List[str], date_col: str = 'fecha', linea_tematica: Optional[str] = None, estacion_col: Optional[str] = None, output: str = 'data/output/reports/compliance_report.html', title: Optional[str] = None, custom_thresholds: Optional[Dict[str, float]] = None) -> Path

Genera reporte HTML de cumplimiento normativo ambiental colombiano.

Compara cada variable del DataFrame contra todas las normas colombianas registradas para esa variable y produce un HTML con: - Semáforo de cumplimiento global - Tabla de excedencias por variable y norma - Serie temporal con excedencias resaltadas por variable - Contexto de la línea temática (ficha de dominio)

Parameters:

Name Type Description Default
df DataFrame

DataFrame con series temporales ambientales.

required
variables List[str]

Lista de columnas a evaluar (ej. ['pm25', 'od', 'dbo5']).

required
date_col str

Columna de fechas.

'fecha'
linea_tematica Optional[str]

Nombre de la línea temática para agregar contexto ('calidad_aire', 'recurso_hidrico', 'oferta_hidrica', etc.).

None
estacion_col Optional[str]

Columna de estación para reportar en el encabezado.

None
output str

Ruta del HTML de salida.

'data/output/reports/compliance_report.html'
title Optional[str]

Título del reporte (default: auto-generado).

None
custom_thresholds Optional[Dict[str, float]]

Umbrales adicionales {variable: umbral} no incluidos en las normas estándar.

None

Returns:

Type Description
Path

Path al archivo HTML generado.

reporting.forecast_report

forecast_report

Reporte HTML automático de pronósticos con comparación de modelos.

forecast_report
forecast_report(y_true: Series, predictions: Dict[str, ndarray], metrics: Dict[str, dict], output: str = 'reports/forecast_report.html', title: str = 'Reporte de Pronóstico', variable_name: str = 'variable', unit: str = '') -> Path

Genera reporte HTML con series reales vs. pronósticos y tabla de métricas.

Parameters:

Name Type Description Default
y_true Series

Serie real (valores observados en el período de test).

required
predictions Dict[str, ndarray]

{model_name: array_de_predicciones}.

required
metrics Dict[str, dict]

{model_name: metrics_dict} — salida de evaluate().

required
output str

Ruta del HTML de salida.

'reports/forecast_report.html'
title str

Título del reporte.

'Reporte de Pronóstico'
variable_name str

Nombre de la variable pronosticada.

'variable'
unit str

Unidad de medida (e.g. 'µg/m³').

''

reporting.stats_report

stats_report

Reporte HTML de estadística descriptiva e inferencial.

stats_report
stats_report(df: DataFrame, output: str = 'reports/stats_report.html', title: str = 'Reporte Estadístico', date_col: Optional[str] = None, value_cols: Optional[list] = None) -> Path

Genera reporte HTML con estadística descriptiva + inferencial.

Incluye: resumen univariado, estacionariedad y tendencia por columna numérica.


Configuración

config

config

Configuración central del proyecto Estadística Ambiental. Adaptado de boa-sarima-forecaster/config.py por Dan Méndez — 2026-04-22