Skip to article frontmatterSkip to article content

El Niño - Oscilación del Sur (ENOS)

El Niño - Oscilación del Sur (ENOS)


Introducción

En este cuadernillo (Notebook) aprenderemos:

  1. Breve introducción al fenómeno ENOS.

  2. Acceso a datos públicos de la NOAA.

  3. Generación de mapas con anomalías de temperatura superficial del Océano Pacífico Tropical.

  4. Reproducción de la gráfica del índice ONI en la región Niño 3.4

📚 Descripción general

En este cuaderno explorarás el fenómeno El Niño-Oscilación del Sur (ENOS), uno de los patrones climáticos más importantes que afecta el clima global y regional. Aprenderás a acceder y procesar datos de temperatura superficial del mar (TSM) de NOAA para identificar eventos El Niño y La Niña mediante el cálculo del índice ONI (Oceanic Niño Index) en la región Niño 3.4.

Utilizarás técnicas de análisis climático estándar como el cálculo de climatologías, anomalías, y ponderación por latitud. Al final, reproducirás gráficas operacionales similares a las que usa NOAA para monitorear el estado actual de ENOS, una habilidad fundamental para estudios de variabilidad climática y predicción estacional.

✅ Requisitos previos

ConceptosImportanciaNotas
XarrayNecesarioManejo de datos multidimensionales espacializados
MatplotlibNecesarioGeneración de gráficas
CartopyNecesarioGeneración de mapas
NetCDFÚtilFamiliaridad con la estructura de datos y metadatos.

⏱️ Tiempo estimado de aprendizaje: 30 minutos

✍️ Formato: Interactivo


1. El Niño/La Niña

Para entender los eventos El Niño o La Niña debemos, primero, introducir que es ENOS. De acuerdo con la Organización Meteorológica Mundial (WMO por sus siglas en inglés de World Meteorological Organization) podemos decir que:

El Niño/Oscilación del Sur (ENOS) es un fenómeno natural caracterizado por la fluctuación de las temperaturas del océano en la parte central y oriental del Pacífico ecuatorial, asociada a cambios en la atmósfera. Este fenómeno tiene una gran influencia en las condiciones climáticas de diversas partes del mundo.

Actualmente es de gran interés en nuestra comunidad científica entender y predecir los efectos de corto y largo alcance de estas fluctuaciones, pues estas suelen estar asociadas a fuertes lluvias o sequías en algunas partes del mundo.

Se dice que, cuando ENOS está en su fase cálida hay un evento El Niño, mientras que, su fase fría se denomina La Niña.

1.1 ¿Cómo saber si hay un evento El Niño o La Niña?

Durante los episodios El Niño, la temperatura de la superficie del mar (TSM) en las partes central y oriental del Pacífico Tropical suele ser muy superior a la normal, mientras que, en esas mismas regiones, durante los episodios de La Niña la temperatura es inferior a la normal. Para decir que, efectivamente hay un evento del uno o del otro, se utilizan índices Oceánicos de medición de ENOS que cuantifican el estado de la anomalía de TSM.

Por ejemplo, en la figura se pueden ver varias regiones de cálculo de la anomalía. Nosotros nos concentraremos en la región Niño 3.4 a lo largo de este taller, la cual se define como la región entre +/- 5° latitud y 170° W, 120° W longitud.

ENSO

Las fases cálidas o frías del Índice del Niño Oceánico (ONI) se definen por cinco anomalías medias consecutivas de TSM durante cinco meses consecutivos en la región elegida -en nuestro caso Niño 3.4- que están por encima del umbral de +0,5 °C (cálido), o por debajo del umbral de -0,5°C (frío). Las oscilaciones de episodios ENOS pueden presentarse en intervalos que varían entre 2 y 7 años.


Librerías

Importamos las librerías que usaremos a lo largo de este cuadernillo.

import cartopy.crs as ccrs
import hvplot.xarray  # noqa
import matplotlib.ticker as mticker
import numpy as np
import pandas as pd
import xarray as xr
from matplotlib import pyplot as plt
Loading...
# Configuración de Dask para procesamiento paralelo (opcional)
# Esto acelera el cálculo de climatologías con datos grandes
from dask.distributed import Client, LocalCluster

cluster = LocalCluster(
    n_workers=4,           # Número de procesos paralelos
    memory_limit='2GB'     # Límite de memoria por worker
)
client = Client(cluster)
client
Loading...

2. Acceso a los datos de temperatura de la NOAA ERSST

Usaremos los datos de temperaturas superficiales del mar de la Oficina Nacional de Administración Océanica y Atmosférica de Estados Unidos (NOAA por sus siglas en inglés).

# Cargar datos de temperatura superficial del mar (NOAA ERSST v5)
url = '../data/sst.mnmean.nc'
ds = xr.open_dataset(
    url,
    engine='netcdf4',
    chunks={'time': 120}  # Chunks grandes para soportar rolling window de 3 meses
)
ds
Loading...
ds
Loading...

Hagamos algunas visualizaciones básicas de los datos, solo para asegurarnos de que parezcan razonables.

ds.sst[-1].plot(vmin=-2, vmax=30, cmap="coolwarm", robust=True);
<Figure size 640x480 with 2 Axes>

Podemos usar Cartopy para “embellecer” el gráfico

# Definir el tamaño de la figura
fig = plt.figure(figsize=(9, 4))

# Asignar un eje y projección del mapa
ax = plt.axes(
    projection=ccrs.InterruptedGoodeHomolosine(central_longitude=180, globe=None)
)

# Agregar líneas costeras
ax.coastlines()

# Agregar las líneas de retícula (lon and lat)
ax.gridlines()
ds.sst.sel(time="1998-01").plot(
    vmin=-2, vmax=30, cmap="coolwarm", transform=ccrs.PlateCarree()
)
<cartopy.mpl.geocollection.GeoQuadMesh at 0x7f02588cd810>
/home/runner/micromamba/envs/cdh-python/lib/python3.13/site-packages/cartopy/io/__init__.py:242: DownloadWarning: Downloading: https://naturalearth.s3.amazonaws.com/110m_physical/ne_110m_coastline.zip
  warnings.warn(f'Downloading: {url}', DownloadWarning)
<Figure size 900x400 with 2 Axes>

3. Anomalías de temperatura superficial del Océano Pacífico Tropical

“Anomalía” significa que se ha eliminado el ciclo estacional, también llamado “climatología”.

3.1 Climatología

Para estimar la climatología podemos usar la funcionalidad de xarray denominada groupby donde agrupamos por meses del año y luego tomamos la media a lo largo de cada mes usando el metodo mean

%%time
sst_clim = ds.sst.groupby("time.month").mean("time", skipna=True)
CPU times: user 46.6 ms, sys: 2.18 ms, total: 48.7 ms
Wall time: 47.9 ms

Ahora podemos visualizar la climatología media zonal

%%time
sst_clim.mean(dim="lon").transpose().plot.contourf(
    levels=12, vmin=-2, vmax=30, cmap="coolwarm"
);
CPU times: user 330 ms, sys: 44.2 ms, total: 374 ms
Wall time: 2.19 s
<Figure size 640x480 with 2 Axes>

3.2 Cálculo de la anomalía

La anomalía esta dada por el valor de la TSM - SST (por sus siglas en inglés) media para cada mes

sst_anom = ds.sst.groupby("time.month") - sst_clim
# Definir el tamaño de la figura
fig = plt.figure(figsize=(9, 4))

# Asignar un eje y proyección del mapa
ax = plt.axes(projection=ccrs.Robinson(central_longitude=180))

# Añadir líneas costeras
ax.coastlines()

# Añadir líneas de la cuadrícula (longitud y latitud)
ax.gridlines()
sst_anom.sel(time="1998-01").plot(
    vmin=-2, vmax=2, cmap="coolwarm", transform=ccrs.PlateCarree()
);
<Figure size 900x400 with 2 Axes>

Cuidado

Debemos ponderar la anomalía con respecto a su posición en latitud

ENSO

¿Por qué necesitamos ponderar nuestros datos?

Las celdas de la cuadrícula más cercanas al ecuador son mucho más grandes que las cercanas a los polos, como se ve en la figura anterior (Djexplo, 2011, CC-BY)

weights = np.cos(np.deg2rad(ds.lat)).where(~sst_anom[0].isnull())
weights /= weights.mean()
sst_anom_wgt = sst_anom * weights

Veamos cómo se ve el promedio global vs el promedio ponderado de la anomalía de la temperatura superficial del océano usando ahora hvplot

# Promedio global de la anomalía
gb_anom = sst_anom.mean(dim=["lon", "lat"])
_anom = gb_anom.hvplot.line(x="time", y="sst", label="Inicial")

# Promedio global de la anomalía ponderada
gb_anom_wtd = sst_anom_wgt.mean(dim=["lon", "lat"])
gb_anom_wtd.name = "sst"
_anom_wtg = gb_anom_wtd.hvplot.line(x="time", label="Ponderado")
# _anom_wtg
_anom * _anom_wtg
Loading...

Esta gráfica parece tener una tendencia. El incremento en la temperatura superficial del mar se puede observar después de ~1950 (¿CC?). Ahora debemos remover la tendencia

from scipy.signal import detrend

sst_anom_wgt_dtd = xr.apply_ufunc(
    detrend, sst_anom_wgt.fillna(0).load(), kwargs={"axis": 0}
).where(~sst_anom.isnull())
sst_anom_wgt_dtd.name = "sst"
# Promedio global de la anomalía ponderada sin tendencia
gb_anom_wtd_dtd = sst_anom_wgt_dtd.mean(dim=["lon", "lat"])
_anom_wtd_dtd = sst_anom_wgt_dtd.mean(dim=["lon", "lat"]).hvplot.line(
    x="time", y="sst", label="Ponderado sin tendencia"
)
_anom * _anom_wtg * _anom_wtd_dtd
Loading...

Miremos ahora la anomalía de la SST para enero de 1998

fig, ax = plt.subplots(
    figsize=(6, 4),
    subplot_kw={"projection": ccrs.Robinson(central_longitude=180)},
    dpi=150,
)
sst_anom_wgt_dtd.sel(time="1998-01").plot(
    vmin=-2,
    vmax=2,
    cmap="coolwarm",
    transform=ccrs.PlateCarree(),
    cbar_kwargs={
        "label": r"$SST \ Anomaly \ [°C]$",
        "orientation": "horizontal",
        "aspect": 50,
    },
)
ax.coastlines()
gl = ax.gridlines(draw_labels=True, crs=ccrs.PlateCarree())
gl.xlocator = mticker.FixedLocator([-180, -60, 60, 180])
gl.ylocator = mticker.FixedLocator([-60, -30, 0, 30, 60])
plt.title(
    f"Anomalía TSM {pd.to_datetime(sst_anom_wgt_dtd.sel(time='1998-01').time[0].values): %Y-%m}"
);
/home/runner/micromamba/envs/cdh-python/lib/python3.13/site-packages/distributed/client.py:3374: UserWarning: Sending large graph of size 125.22 MiB.
This may cause some slowdown.
Consider loading the data with Dask directly
 or using futures or delayed objects to embed the data into the graph without repetition.
See also https://docs.dask.org/en/stable/best-practices.html#load-data-with-dask for more information.
  warnings.warn(
<Figure size 900x600 with 2 Axes>

Veamos qué pasa si cambiamos la proyección del mapa

# Definir el tamaño de la figura
fig = plt.figure(figsize=(12, 6))

# Asignar un eje y proyección del mapa
ax = plt.axes(projection=ccrs.PlateCarree(central_longitude=180))

# Añadir líneas costeras
ax.coastlines()

# Añadir líneas de cuadricula (lon y lat)
ax.gridlines()
sst_anom_wgt_dtd.sel(time="1998-01").plot(
    vmin=-2, vmax=2, cmap="coolwarm", transform=ccrs.PlateCarree()
)
plt.title(
    f"Anomalía TSM {pd.to_datetime(sst_anom_wgt_dtd.sel(time='1998-01').time[0].values): %Y-%m}"
);
/home/runner/micromamba/envs/cdh-python/lib/python3.13/site-packages/distributed/client.py:3374: UserWarning: Sending large graph of size 125.22 MiB.
This may cause some slowdown.
Consider loading the data with Dask directly
 or using futures or delayed objects to embed the data into the graph without repetition.
See also https://docs.dask.org/en/stable/best-practices.html#load-data-with-dask for more information.
  warnings.warn(
<Figure size 1200x600 with 2 Axes>

3.3 Tratemos de reproducir la gráfica de la NOAA

Tratemos de usar nuestros datos para generar una gráfica como esta:

ENSO

sst_anom_sub = sst_anom_wgt_dtd.sel(lat=slice(60, -60), lon=slice(25, 360))
fig, ax = plt.subplots(
    figsize=(6, 4),
    subplot_kw={"projection": ccrs.PlateCarree(central_longitude=180)},
    dpi=150,
)
sst_anom_sub.sel(time="1998-01").plot(
    vmin=-2,
    vmax=2,
    cmap="coolwarm",
    transform=ccrs.PlateCarree(),
    cbar_kwargs={
        "label": r"$SST \ Anomaly \ [°C]$",
        "orientation": "horizontal",
        "aspect": 50,
    },
)
ax.coastlines()
ax.gridlines(draw_labels=True, crs=ccrs.PlateCarree())
plt.title(
    f"Anomalía TSM {pd.to_datetime(sst_anom_wgt_dtd.sel(time='1998-01').time[0].values): %Y-%m}"
);
/home/runner/micromamba/envs/cdh-python/lib/python3.13/site-packages/distributed/client.py:3374: UserWarning: Sending large graph of size 125.37 MiB.
This may cause some slowdown.
Consider loading the data with Dask directly
 or using futures or delayed objects to embed the data into the graph without repetition.
See also https://docs.dask.org/en/stable/best-practices.html#load-data-with-dask for more information.
  warnings.warn(
<Figure size 900x600 with 2 Axes>

4. Índice ONI en la región Niño 3.4

ENSO

La región Niño 3.4 se define como la región entre +/- 5 grados. latitud, 170 W - 120 W longitud.

nino_34 = sst_anom_wgt_dtd.sel(
    lat=slice(5, -5), lon=slice(180 - (180 - 170), 180 + (180 - 120))
)
fig, ax = plt.subplots(
    figsize=(5, 5),
    subplot_kw={"projection": ccrs.PlateCarree(central_longitude=180)},
    dpi=150,
)
nino_34.sel(time="1998-01").plot(
    vmin=-2,
    vmax=2,
    cmap="coolwarm",
    transform=ccrs.PlateCarree(),
    cbar_kwargs={
        "label": r"$SST \ Anomaly \ [°C]$",
        "orientation": "horizontal",
        "aspect": 50,
    },
)
ax.coastlines()
ax.set_extent([120, 290, -30, 30], crs=ccrs.PlateCarree())
ax.gridlines(draw_labels=True, crs=ccrs.PlateCarree());
/home/runner/micromamba/envs/cdh-python/lib/python3.13/site-packages/distributed/client.py:3374: UserWarning: Sending large graph of size 125.37 MiB.
This may cause some slowdown.
Consider loading the data with Dask directly
 or using futures or delayed objects to embed the data into the graph without repetition.
See also https://docs.dask.org/en/stable/best-practices.html#load-data-with-dask for more information.
  warnings.warn(
<Figure size 750x750 with 2 Axes>

Ahora podemos generar nuestro índice ONI con una ventana móvil de 3 meses

# Calcular índice ONI con ventana móvil de 3 meses
# Agregamos .compute() para evitar problemas con chunks pequeños
oni = nino_34.mean(['lat', 'lon']).compute().rolling(time=3, center=True).mean()
/home/runner/micromamba/envs/cdh-python/lib/python3.13/site-packages/distributed/client.py:3374: UserWarning: Sending large graph of size 125.45 MiB.
This may cause some slowdown.
Consider loading the data with Dask directly
 or using futures or delayed objects to embed the data into the graph without repetition.
See also https://docs.dask.org/en/stable/best-practices.html#load-data-with-dask for more information.
  warnings.warn(
fig, ax = plt.subplots(figsize=(10, 3), dpi=150)
ax.plot(oni.time, oni, lw=1, c="k")
ax.axhline(0, c="grey", lw=0.5, ls="--")
ax.axhline(0.5, c="r", lw=0.5, ls="--", label="El Niño")
ax.axhline(-0.5, c="b", lw=0.5, ls="--", label="La Niña")
ax.set_ylim(-2.5, 2.5)
ax.set_xlabel("$Año$")
ax.set_ylabel(r"$Anomalía \ [°C]$")
ax.legend();
<Figure size 1500x450 with 1 Axes>

Podemos seleccionar los últimos 70 años para efectos de visualización

oni_sub = oni.sel(time=slice("1950", "2023"))
fig, ax = plt.subplots(figsize=(8, 3), dpi=150)
ax.plot(oni_sub.time, oni_sub, lw=1, c="k")
ax.axhline(0, c="grey", lw=0.5, ls="--")
ax.axhline(0.5, c="r", lw=0.5, ls="--", label="El Niño")
ax.axhline(-0.5, c="b", lw=0.5, ls="--", label="La Niña")
ax.fill_between(oni_sub.time, 0.5, oni_sub.where(oni_sub >= 0.5), color="C01")
ax.fill_between(oni_sub.time, -0.5, oni_sub.where(oni_sub <= -0.5), color="C00")

ax.set_ylim(-3.5, 3.5)
ax.set_xlabel("$Año$")
ax.set_ylabel(r"$Anomalía \ [°C]$")
ax.legend()
plt.show()
<Figure size 1200x450 with 1 Axes>

Actividades Prácticas

🏋️ Práctica: Identificando eventos ENOS

Usa el gráfico del índice ONI para responder:

  1. ¿Cuántos eventos El Niño (ONI > 0.5°C) observas en el período 1950-2023?

  2. ¿Cuál fue el evento El Niño más intenso? ¿En qué año ocurrió?

  3. Identifica un evento La Niña reciente (después de 2010)

Pistas:

  • El Niño: Barras rojas que superan +0.5°C

  • La Niña: Barras azules que caen bajo -0.5°C

  • Un evento requiere al menos 5 meses consecutivos sobre el umbral

Resumen

En este cuadernillo brevemente describimos el fenómeno ENOS y sus fases cálida (El Niño) y fría (La Niña). Aprendimos a:

Acceder a datos de TSM desde servidores OPENDAP de NOAA

Calcular climatologías y anomalías de temperatura superficial del mar

Aplicar correcciones (ponderación por latitud, remoción de tendencia)

Calcular el índice ONI para identificar eventos El Niño y La Niña

Reproducir gráficas operacionales similares a las de NOAA

¿Qué sigue?

Con estos conocimientos sobre ENOS, puedes explorar:

  • [3.3. Datos de reanálisis ERA5] - Análisis de patrones atmosféricos durante eventos ENSO

  • [2.1. Datos de estaciones] - Validar impactos de ENSO en precipitación local

  • Pronóstico estacional - Usar índices ENSO para predicción climática

Proyecto sugerido:

Analiza cómo ENSO afecta la precipitación en Colombia:

  1. Descarga datos de estaciones del IDEAM durante eventos El Niño y La Niña

  2. Compara anomalías de precipitación con el índice ONI

  3. Identifica patrones regionales (Pacífico vs Caribe vs Andino)

Recursos y Referencias