El Niño - Oscilación del Sur (ENOS)
Introducción
En este cuadernillo (Notebook) aprenderemos:
Breve introducción al fenómeno ENOS.
Acceso a datos públicos de la NOAA.
Generación de mapas con anomalías de temperatura superficial del Océano Pacífico Tropical.
Reproducción de la gráfica del índice ONI en la región Niño 3.4
Prerequisitos
Conceptos |
Importancia |
Notas |
---|---|---|
Necesario |
Manejo de datos multidimensionales espacializados |
|
Necesario |
Generación de gráficas |
|
Necesario |
Generación de mapas |
|
Útil |
Familiaridad con la estructura de datos y metadatos. |
Tiempo de aprendizaje: 30 minutos.
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 indices 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.
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
2. Accesso 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).
# url = 'http://www.esrl.noaa.gov/psd/thredds/dodsC/Datasets/noaa.ersst.v5/sst.mnmean.nc'
# # url = "http://psl.noaa.gov/thredds/dodsC/Datasets/noaa.ersst.v5/sst.mnmean.nc"
# ds = xr.open_dataset(url, drop_variables=['time_bnds'], engine='netcdf4')
Algunas veces el servidor de Thredds de NCAR presenta algunos inconvenientes con el bytestreaming. Si el computo de la climatología y la anomalía toma mucho tiempo, utilice el siguiente archivo:
url = "../data/sst.mnmean.nc"
ds = xr.open_dataset(url, engine="netcdf4")
0.3.0
display(ds)
<xarray.Dataset> Dimensions: (lat: 89, lon: 180, time: 2037) Coordinates: * lat (lat) float32 88.0 86.0 84.0 82.0 80.0 ... -82.0 -84.0 -86.0 -88.0 * lon (lon) float32 0.0 2.0 4.0 6.0 8.0 ... 350.0 352.0 354.0 356.0 358.0 * time (time) datetime64[ns] 1854-01-01 1854-02-01 ... 2023-09-01 Data variables: sst (time, lat, lon) float32 ... Attributes: (12/37) climatology: Climatology is based on 1971-2000 SST, Xue, Y.... description: In situ data: ICOADS2.5 before 2007 and NCEP i... keywords_vocabulary: NASA Global Change Master Directory (GCMD) Sci... keywords: Earth Science > Oceans > Ocean Temperature > S... instrument: Conventional thermometers source_comment: SSTs were observed by conventional thermometer... ... ... creator_url_original: https://www.ncei.noaa.gov license: No constraints on data access or use comment: SSTs were observed by conventional thermometer... summary: ERSST.v5 is developed based on v4 after revisi... dataset_title: NOAA Extended Reconstructed SST V5 data_modified: 2023-10-03
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")
<matplotlib.collections.QuadMesh at 0x7fdae53f3610>
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 0x7fdadd301d10>
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")
CPU times: user 2 µs, sys: 1e+03 ns, total: 3 µs
Wall time: 5.25 µs
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 2 µs, sys: 0 ns, total: 2 µs
Wall time: 5.01 µs
<matplotlib.contour.QuadContourSet at 0x7fdadc22a850>
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()
)
<cartopy.mpl.geocollection.GeoQuadMesh at 0x7fdadc109950>
Cuidado!!!
Debemos ponderar la anomalía con respecto a su posición en latitud
¿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 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
Esta gráfica parece tener una tendencia. El incremento en la temperatura superficial del mar se puede observar despues de ~1950 (CC?). Ahora debememos remover la tendencia!!!
from scipy.signal import detrend
sst_anom_wgt_dtd = xr.apply_ufunc(
detrend, sst_anom_wgt.fillna(0), 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
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}"
)
Text(0.5, 1.0, 'Anomalía TSM 1998-01')
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}"
)
Text(0.5, 1.0, 'Anomalía TSM 1998-01')
3.3 Tratemos de reproducir la gráfica de la NOAA
Tratemos de usar nuestros datos para generar una gráfica como esta:
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}"
)
Text(0.5, 1.0, 'Anomalía TSM 1998-01')
4. Índice ONI en la región Niño 3.4
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())
<cartopy.mpl.gridliner.Gridliner at 0x7fdad5b01c90>
Ahora podemos generar nuestro índice ONI con una ventana móvil de 3 meses
oni = nino_34.mean(["lat", "lon"]).rolling(time=3, center=True).mean()
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()
<matplotlib.legend.Legend at 0x7fdad5ed8e50>
Podemos selesccionar los ultimos 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()
Bonus!
Podemos crear una animación usando xmovie
from xmovie import Movie
def custom_plotfunc(ds, fig, t0, *args, **kwargs):
ax = fig.subplots(
1, 1, subplot_kw={"projection": ccrs.PlateCarree(central_longitude=180)}
)
ds_sub = ds.isel(time=t0)
ds_sub.plot(
vmin=-2,
vmax=2,
cmap="coolwarm",
transform=ccrs.PlateCarree(),
cbar_kwargs={
"label": r"$SST \ Anomaly \ [°C]$",
"orientation": "horizontal",
"aspect": 50,
},
ax=ax,
)
ax.coastlines()
ax.gridlines(draw_labels=True, crs=ccrs.PlateCarree())
ax.set_title(f"Anomalía TSM {pd.to_datetime(ds_sub.time.values): %Y-%m}")
subset = sst_anom_wgt_dtd.sel(time=slice("2019-08", "2023-08")).chunk({"time": 1})
mov_custom = Movie(
da=subset,
plotfunc=custom_plotfunc,
framedim="time",
input_check=False,
pixelwidth=1220,
pixelheight=920,
dpi=150,
)
# mov_custom.save('enso1.gif', progress=True, overwrite_existing=True, framerate=10)
from IPython.display import Video
Video("../images/enso1.mov")
Conclusiones
En este cuadernillo brevemente describimos el fenómeno ENOS y sus fases cálida (Niño) y fría (Niña). Apredimos a importar los datos de NOAA y reproducir las gráficas que vemos comunmente para ENOS.
Recursos y referencias
Abernathey, R. 2023. An Introduction to Earth and Environmental Data Science. Retrieved from Earth and Environmental Data Science: https://earth-env-data-science.github.io/intro.html
Climatematch Acadimy: Computational tools for climate science. Página web: https://academy.climatematch.io/, repositorio GitHub: https://github.com/ClimatematchAcademy/course-content