02. Datos multidimensionales con xarray y un análisis reproducible¶

Mini-curso de Introducción a herramientas computacionales para Oceanografía Física¶

Oceanografía Física, CICESE¶

21 de septiembre de 2026

La introducción a NetCDF y xarray de este notebook está basada en una versión traducida y modificada de los tutoriales de fundamentos de xarray de Anderson Banihirwe, disponibles en xarray-contrib/xarray-tutorial. La serie original también cuenta con un video tutorial. Adaptación al proyecto del curso y modificaciones por Karina Ramos Musalem.

Pregunta de este notebook: ¿qué estaba ocurriendo en la temperatura superficial del mar alrededor de Baja California mientras variaba el índice ONI, y cómo transformamos una exploración en notebook en código que podamos volver a ejecutar?

Antes de empezar¶

Al terminar este notebook:

  • reconocerán la estructura de un archivo NetCDF;
  • distinguirán un xarray.Dataset de un xarray.DataArray;
  • seleccionarán datos por posición y por coordenadas;
  • reducirán dimensiones usando operaciones como mean;
  • seleccionarán una región geográfica;
  • convertirán una serie de xarray a pandas;
  • combinarán datos provenientes de NetCDF y CSV;
  • reutilizarán una función para repetir un análisis en distintas regiones;
  • separarán código reutilizable en un módulo;
  • entenderán la diferencia entre notebook, función y script.

Este notebook es autocontenido: vuelve a leer ONI y precipitación aunque ya hayas ejecutado el notebook 1. Esto permite cerrar el kernel, volver otro día y reproducir el análisis desde cero.

1. Archivos NetCDF y xarray¶

En Oceanografía y Ciencias de la Tierra trabajamos frecuentemente con datos de varias dimensiones: por ejemplo, una variable puede depender simultáneamente de tiempo, profundidad, latitud y longitud. Guardar estos campos como tablas de texto es poco práctico.

El formato NetCDF permite almacenar:

  • variables: los datos que queremos analizar;
  • dimensiones/coordenadas: por ejemplo time, lat y lon;
  • atributos: unidades, descripción, fuente y otra información sobre los datos.

xarray extiende el modelo de arreglos N-dimensionales de NumPy añadiendo etiquetas, coordenadas y metadatos. Sus dos estructuras fundamentales son:

  • DataArray: una variable multidimensional junto con sus dimensiones, coordenadas y atributos;
  • Dataset: una colección de variables que pueden compartir coordenadas.

Nuestro archivo contiene SST y SSTA mensuales de NOAA OISST V2.1 en una región que incluye Baja California y el Pacífico adyacente. Datos propocionado por la Dra. Marisol García Reyes.

In [1]:
import xarray as xr
import pandas as pd
import matplotlib.pyplot as plt

Abrimos el NetCDF con xr.open_dataset(). Xarray lee la estructura y los metadatos del archivo y nos entrega un Dataset.

In [2]:
ds = xr.open_dataset("../data/sst_baja_1981_2026.nc")

ds
Out[2]:
<xarray.Dataset> Size: 17MB
Dimensions:  (time: 540, lat: 58, lon: 66)
Coordinates:
  * time     (time) datetime64[ns] 4kB 1981-09-01 1981-10-01 ... 2026-08-01
  * lat      (lat) float32 232B 21.12 21.38 21.62 21.88 ... 34.88 35.12 35.38
  * lon      (lon) float32 264B -124.9 -124.6 -124.4 ... -109.1 -108.9 -108.6
Data variables:
    sst      (time, lat, lon) float32 8MB ...
    ssta     (time, lat, lon) float32 8MB ...
Attributes: (12/15)
    description:                ENSO related variables. Monthly. Oceanic Nino...
    title:                      Monthly sea surface temperature and SST anoma...
    summary:                    Monthly sea surface temperature (SST) and sea...
    source:                     NOAA Optimum Interpolation Sea Surface Temper...
    institution:                CICESE
    Conventions:                CF-1.8
    ...                         ...
    geospatial_lat_min:         21.125
    geospatial_lat_max:         35.375
    geospatial_lon_min:         -124.875
    geospatial_lon_max:         -108.625
    geospatial_lat_resolution:  0.25
    geospatial_lon_resolution:  0.25
xarray.Dataset
    • time: 540
    • lat: 58
    • lon: 66
    • time
      (time)
      datetime64[ns]
      1981-09-01 ... 2026-08-01
      standard_name :
      time
      long_name :
      time
      axis :
      T
      array(['1981-09-01T00:00:00.000000000', '1981-10-01T00:00:00.000000000',
             '1981-11-01T00:00:00.000000000', ..., '2026-06-01T00:00:00.000000000',
             '2026-07-01T00:00:00.000000000', '2026-08-01T00:00:00.000000000'],
            shape=(540,), dtype='datetime64[ns]')
    • lat
      (lat)
      float32
      21.12 21.38 21.62 ... 35.12 35.38
      standard_name :
      latitude
      long_name :
      latitude
      units :
      degrees_north
      axis :
      Y
      array([21.125, 21.375, 21.625, 21.875, 22.125, 22.375, 22.625, 22.875, 23.125,
             23.375, 23.625, 23.875, 24.125, 24.375, 24.625, 24.875, 25.125, 25.375,
             25.625, 25.875, 26.125, 26.375, 26.625, 26.875, 27.125, 27.375, 27.625,
             27.875, 28.125, 28.375, 28.625, 28.875, 29.125, 29.375, 29.625, 29.875,
             30.125, 30.375, 30.625, 30.875, 31.125, 31.375, 31.625, 31.875, 32.125,
             32.375, 32.625, 32.875, 33.125, 33.375, 33.625, 33.875, 34.125, 34.375,
             34.625, 34.875, 35.125, 35.375], dtype=float32)
    • lon
      (lon)
      float32
      -124.9 -124.6 ... -108.9 -108.6
      standard_name :
      longitude
      long_name :
      longitude
      units :
      degrees_east
      axis :
      X
      array([-124.875, -124.625, -124.375, -124.125, -123.875, -123.625, -123.375,
             -123.125, -122.875, -122.625, -122.375, -122.125, -121.875, -121.625,
             -121.375, -121.125, -120.875, -120.625, -120.375, -120.125, -119.875,
             -119.625, -119.375, -119.125, -118.875, -118.625, -118.375, -118.125,
             -117.875, -117.625, -117.375, -117.125, -116.875, -116.625, -116.375,
             -116.125, -115.875, -115.625, -115.375, -115.125, -114.875, -114.625,
             -114.375, -114.125, -113.875, -113.625, -113.375, -113.125, -112.875,
             -112.625, -112.375, -112.125, -111.875, -111.625, -111.375, -111.125,
             -110.875, -110.625, -110.375, -110.125, -109.875, -109.625, -109.375,
             -109.125, -108.875, -108.625], dtype=float32)
    • sst
      (time, lat, lon)
      float32
      ...
      long_name :
      Monthly mean sea surface temperature
      standard_name :
      sea_surface_temperature
      units :
      degree_Celsius
      source :
      NOAA OISST V2.1
      cell_methods :
      time: mean
      [2067120 values with dtype=float32]
    • ssta
      (time, lat, lon)
      float32
      ...
      long_name :
      Monthly sea surface temperature anomaly
      units :
      degree_Celsius
      source :
      NOAA OISST V2.1
      cell_methods :
      time: mean
      comment :
      Anomaly field retained from the original course dataset. The climatological reference period is not documented in the source file.
      [2067120 values with dtype=float32]
  • description :
    ENSO related variables. Monthly. Oceanic Nino (ONI), NOAA OISST, Rain Ensenada Dam
    title :
    Monthly sea surface temperature and SST anomaly around Baja California
    summary :
    Monthly sea surface temperature (SST) and sea surface temperature anomaly (SSTA) for a regional domain around Baja California. SST data are from NOAA OISST V2.1. Prepared for the 2026 CICESE Physical Oceanography introductory scientific computing minicourse.
    source :
    NOAA Optimum Interpolation Sea Surface Temperature (OISST) Version 2.1
    institution :
    CICESE
    Conventions :
    CF-1.8
    history :
    2026-09-16: Course dataset created from Datos_cursoPython_2026.nc; ONI and precipitation were exported to separate CSV files.
    time_coverage_start :
    1981-09-01
    time_coverage_end :
    2026-08-01
    geospatial_lat_min :
    21.125
    geospatial_lat_max :
    35.375
    geospatial_lon_min :
    -124.875
    geospatial_lon_max :
    -108.625
    geospatial_lat_resolution :
    0.25
    geospatial_lon_resolution :
    0.25

Antes de continuar, lee la representación que muestra Jupyter e identifica:

  1. ¿cuáles son las dimensiones?
  2. ¿cuáles son las coordenadas?
  3. ¿cuáles son las variables?
  4. ¿qué periodo temporal cubre el archivo?

También podemos revisar los atributos globales:

In [3]:
ds.attrs
Out[3]:
{'description': 'ENSO related variables. Monthly. Oceanic Nino (ONI), NOAA OISST, Rain Ensenada Dam',
 'title': 'Monthly sea surface temperature and SST anomaly around Baja California',
 'summary': 'Monthly sea surface temperature (SST) and sea surface temperature anomaly (SSTA) for a regional domain around Baja California. SST data are from NOAA OISST V2.1. Prepared for the 2026 CICESE Physical Oceanography introductory scientific computing minicourse.',
 'source': 'NOAA Optimum Interpolation Sea Surface Temperature (OISST) Version 2.1',
 'institution': 'CICESE',
 'Conventions': 'CF-1.8',
 'history': '2026-09-16: Course dataset created from Datos_cursoPython_2026.nc; ONI and precipitation were exported to separate CSV files.',
 'time_coverage_start': '1981-09-01',
 'time_coverage_end': '2026-08-01',
 'geospatial_lat_min': np.float64(21.125),
 'geospatial_lat_max': np.float64(35.375),
 'geospatial_lon_min': np.float64(-124.875),
 'geospatial_lon_max': np.float64(-108.625),
 'geospatial_lat_resolution': np.float64(0.25),
 'geospatial_lon_resolution': np.float64(0.25)}

2. De Dataset a DataArray¶

Podemos seleccionar una variable del Dataset mediante su nombre:

In [4]:
ds.sst
Out[4]:
<xarray.DataArray 'sst' (time: 540, lat: 58, lon: 66)> Size: 8MB
[2067120 values with dtype=float32]
Coordinates:
  * time     (time) datetime64[ns] 4kB 1981-09-01 1981-10-01 ... 2026-08-01
  * lat      (lat) float32 232B 21.12 21.38 21.62 21.88 ... 34.88 35.12 35.38
  * lon      (lon) float32 264B -124.9 -124.6 -124.4 ... -109.1 -108.9 -108.6
Attributes:
    long_name:      Monthly mean sea surface temperature
    standard_name:  sea_surface_temperature
    units:          degree_Celsius
    source:         NOAA OISST V2.1
    cell_methods:   time: mean
xarray.DataArray
'sst'
  • time: 540
  • lat: 58
  • lon: 66
  • ...
    [2067120 values with dtype=float32]
    • time
      (time)
      datetime64[ns]
      1981-09-01 ... 2026-08-01
      standard_name :
      time
      long_name :
      time
      axis :
      T
      array(['1981-09-01T00:00:00.000000000', '1981-10-01T00:00:00.000000000',
             '1981-11-01T00:00:00.000000000', ..., '2026-06-01T00:00:00.000000000',
             '2026-07-01T00:00:00.000000000', '2026-08-01T00:00:00.000000000'],
            shape=(540,), dtype='datetime64[ns]')
    • lat
      (lat)
      float32
      21.12 21.38 21.62 ... 35.12 35.38
      standard_name :
      latitude
      long_name :
      latitude
      units :
      degrees_north
      axis :
      Y
      array([21.125, 21.375, 21.625, 21.875, 22.125, 22.375, 22.625, 22.875, 23.125,
             23.375, 23.625, 23.875, 24.125, 24.375, 24.625, 24.875, 25.125, 25.375,
             25.625, 25.875, 26.125, 26.375, 26.625, 26.875, 27.125, 27.375, 27.625,
             27.875, 28.125, 28.375, 28.625, 28.875, 29.125, 29.375, 29.625, 29.875,
             30.125, 30.375, 30.625, 30.875, 31.125, 31.375, 31.625, 31.875, 32.125,
             32.375, 32.625, 32.875, 33.125, 33.375, 33.625, 33.875, 34.125, 34.375,
             34.625, 34.875, 35.125, 35.375], dtype=float32)
    • lon
      (lon)
      float32
      -124.9 -124.6 ... -108.9 -108.6
      standard_name :
      longitude
      long_name :
      longitude
      units :
      degrees_east
      axis :
      X
      array([-124.875, -124.625, -124.375, -124.125, -123.875, -123.625, -123.375,
             -123.125, -122.875, -122.625, -122.375, -122.125, -121.875, -121.625,
             -121.375, -121.125, -120.875, -120.625, -120.375, -120.125, -119.875,
             -119.625, -119.375, -119.125, -118.875, -118.625, -118.375, -118.125,
             -117.875, -117.625, -117.375, -117.125, -116.875, -116.625, -116.375,
             -116.125, -115.875, -115.625, -115.375, -115.125, -114.875, -114.625,
             -114.375, -114.125, -113.875, -113.625, -113.375, -113.125, -112.875,
             -112.625, -112.375, -112.125, -111.875, -111.625, -111.375, -111.125,
             -110.875, -110.625, -110.375, -110.125, -109.875, -109.625, -109.375,
             -109.125, -108.875, -108.625], dtype=float32)
  • long_name :
    Monthly mean sea surface temperature
    standard_name :
    sea_surface_temperature
    units :
    degree_Celsius
    source :
    NOAA OISST V2.1
    cell_methods :
    time: mean

Compáremos los tipos de los dos objetos:

In [5]:
type(ds)
Out[5]:
xarray.core.dataset.Dataset
In [6]:
type(ds.sst)
Out[6]:
xarray.core.dataarray.DataArray

Un Dataset puede contener varias variables; ds.sst es un solo DataArray.

Podemos preguntar explícitamente por sus dimensiones, forma y atributos:

In [7]:
ds.sst.dims
Out[7]:
('time', 'lat', 'lon')
In [8]:
ds.sst.shape
Out[8]:
(540, 58, 66)
In [9]:
ds.sst.attrs
Out[9]:
{'long_name': 'Monthly mean sea surface temperature',
 'standard_name': 'sea_surface_temperature',
 'units': 'degree_Celsius',
 'source': 'NOAA OISST V2.1',
 'cell_methods': 'time: mean'}

Un modelo mental útil es:

Dataset
 ├── DataArray: sst
 └── DataArray: ssta
        ↓
 valores + dimensiones + coordenadas + atributos

A diferencia de un arreglo NumPy, no necesitamos recordar que "el eje 0 es tiempo y el eje 1 es latitud": xarray conserva esos nombres.

SST y SSTA¶

El archivo contiene:

  • sst: temperatura superficial del mar mensual, en °C;
  • ssta: anomalía mensual de temperatura superficial del mar, en °C.

Consulta también los metadatos de ssta:

In [10]:
ds.ssta.attrs
Out[10]:
{'long_name': 'Monthly sea surface temperature anomaly',
 'units': 'degree_Celsius',
 'source': 'NOAA OISST V2.1',
 'cell_methods': 'time: mean',
 'comment': 'Anomaly field retained from the original course dataset. The climatological reference period is not documented in the source file.'}

El archivo preparado para el curso conserva la anomalía del conjunto original. El periodo climatológico de referencia de ssta no está documentado en el archivo fuente, así que no debemos asumir uno.

Esta es una buena regla general: si un metadato importante no está documentado, no lo inventamos.

3. Seleccionar por posición y por coordenadas¶

Preguntas a responder¶

  1. ¿Cómo seleccionamos el primer elemento de una dimensión?
  2. ¿Cómo seleccionamos usando una fecha o una coordenada con significado físico?

xarray ofrece dos formas principales:

  • .isel() selecciona por posición del índice;
  • .sel() selecciona por valor de coordenada.

Por posición:

In [11]:
ds.sst.isel(time=0)
Out[11]:
<xarray.DataArray 'sst' (lat: 58, lon: 66)> Size: 15kB
[3828 values with dtype=float32]
Coordinates:
  * lat      (lat) float32 232B 21.12 21.38 21.62 21.88 ... 34.88 35.12 35.38
  * lon      (lon) float32 264B -124.9 -124.6 -124.4 ... -109.1 -108.9 -108.6
    time     datetime64[ns] 8B 1981-09-01
Attributes:
    long_name:      Monthly mean sea surface temperature
    standard_name:  sea_surface_temperature
    units:          degree_Celsius
    source:         NOAA OISST V2.1
    cell_methods:   time: mean
xarray.DataArray
'sst'
  • lat: 58
  • lon: 66
  • ...
    [3828 values with dtype=float32]
    • lat
      (lat)
      float32
      21.12 21.38 21.62 ... 35.12 35.38
      standard_name :
      latitude
      long_name :
      latitude
      units :
      degrees_north
      axis :
      Y
      array([21.125, 21.375, 21.625, 21.875, 22.125, 22.375, 22.625, 22.875, 23.125,
             23.375, 23.625, 23.875, 24.125, 24.375, 24.625, 24.875, 25.125, 25.375,
             25.625, 25.875, 26.125, 26.375, 26.625, 26.875, 27.125, 27.375, 27.625,
             27.875, 28.125, 28.375, 28.625, 28.875, 29.125, 29.375, 29.625, 29.875,
             30.125, 30.375, 30.625, 30.875, 31.125, 31.375, 31.625, 31.875, 32.125,
             32.375, 32.625, 32.875, 33.125, 33.375, 33.625, 33.875, 34.125, 34.375,
             34.625, 34.875, 35.125, 35.375], dtype=float32)
    • lon
      (lon)
      float32
      -124.9 -124.6 ... -108.9 -108.6
      standard_name :
      longitude
      long_name :
      longitude
      units :
      degrees_east
      axis :
      X
      array([-124.875, -124.625, -124.375, -124.125, -123.875, -123.625, -123.375,
             -123.125, -122.875, -122.625, -122.375, -122.125, -121.875, -121.625,
             -121.375, -121.125, -120.875, -120.625, -120.375, -120.125, -119.875,
             -119.625, -119.375, -119.125, -118.875, -118.625, -118.375, -118.125,
             -117.875, -117.625, -117.375, -117.125, -116.875, -116.625, -116.375,
             -116.125, -115.875, -115.625, -115.375, -115.125, -114.875, -114.625,
             -114.375, -114.125, -113.875, -113.625, -113.375, -113.125, -112.875,
             -112.625, -112.375, -112.125, -111.875, -111.625, -111.375, -111.125,
             -110.875, -110.625, -110.375, -110.125, -109.875, -109.625, -109.375,
             -109.125, -108.875, -108.625], dtype=float32)
    • time
      ()
      datetime64[ns]
      1981-09-01
      standard_name :
      time
      long_name :
      time
      axis :
      T
      array('1981-09-01T00:00:00.000000000', dtype='datetime64[ns]')
  • long_name :
    Monthly mean sea surface temperature
    standard_name :
    sea_surface_temperature
    units :
    degree_Celsius
    source :
    NOAA OISST V2.1
    cell_methods :
    time: mean

Por coordenada temporal:

In [12]:
ds.sst.sel(time="1997-12")
Out[12]:
<xarray.DataArray 'sst' (time: 1, lat: 58, lon: 66)> Size: 15kB
[3828 values with dtype=float32]
Coordinates:
  * time     (time) datetime64[ns] 8B 1997-12-01
  * lat      (lat) float32 232B 21.12 21.38 21.62 21.88 ... 34.88 35.12 35.38
  * lon      (lon) float32 264B -124.9 -124.6 -124.4 ... -109.1 -108.9 -108.6
Attributes:
    long_name:      Monthly mean sea surface temperature
    standard_name:  sea_surface_temperature
    units:          degree_Celsius
    source:         NOAA OISST V2.1
    cell_methods:   time: mean
xarray.DataArray
'sst'
  • time: 1
  • lat: 58
  • lon: 66
  • ...
    [3828 values with dtype=float32]
    • time
      (time)
      datetime64[ns]
      1997-12-01
      standard_name :
      time
      long_name :
      time
      axis :
      T
      array(['1997-12-01T00:00:00.000000000'], dtype='datetime64[ns]')
    • lat
      (lat)
      float32
      21.12 21.38 21.62 ... 35.12 35.38
      standard_name :
      latitude
      long_name :
      latitude
      units :
      degrees_north
      axis :
      Y
      array([21.125, 21.375, 21.625, 21.875, 22.125, 22.375, 22.625, 22.875, 23.125,
             23.375, 23.625, 23.875, 24.125, 24.375, 24.625, 24.875, 25.125, 25.375,
             25.625, 25.875, 26.125, 26.375, 26.625, 26.875, 27.125, 27.375, 27.625,
             27.875, 28.125, 28.375, 28.625, 28.875, 29.125, 29.375, 29.625, 29.875,
             30.125, 30.375, 30.625, 30.875, 31.125, 31.375, 31.625, 31.875, 32.125,
             32.375, 32.625, 32.875, 33.125, 33.375, 33.625, 33.875, 34.125, 34.375,
             34.625, 34.875, 35.125, 35.375], dtype=float32)
    • lon
      (lon)
      float32
      -124.9 -124.6 ... -108.9 -108.6
      standard_name :
      longitude
      long_name :
      longitude
      units :
      degrees_east
      axis :
      X
      array([-124.875, -124.625, -124.375, -124.125, -123.875, -123.625, -123.375,
             -123.125, -122.875, -122.625, -122.375, -122.125, -121.875, -121.625,
             -121.375, -121.125, -120.875, -120.625, -120.375, -120.125, -119.875,
             -119.625, -119.375, -119.125, -118.875, -118.625, -118.375, -118.125,
             -117.875, -117.625, -117.375, -117.125, -116.875, -116.625, -116.375,
             -116.125, -115.875, -115.625, -115.375, -115.125, -114.875, -114.625,
             -114.375, -114.125, -113.875, -113.625, -113.375, -113.125, -112.875,
             -112.625, -112.375, -112.125, -111.875, -111.625, -111.375, -111.125,
             -110.875, -110.625, -110.375, -110.125, -109.875, -109.625, -109.375,
             -109.125, -108.875, -108.625], dtype=float32)
  • long_name :
    Monthly mean sea surface temperature
    standard_name :
    sea_surface_temperature
    units :
    degree_Celsius
    source :
    NOAA OISST V2.1
    cell_methods :
    time: mean

La segunda forma suele ser mucho más legible: "1997-12" expresa directamente qué queremos seleccionar.

Como xarray conoce las coordenadas espaciales, puede producir una figura rápidamente:

In [13]:
ds.ssta.sel(time="1997-12").plot()
plt.title("SSTA - diciembre de 1997")
plt.show()
No description has been provided for this image

Pon en práctica lo que aprendiste¶

Usa el archivo de ONI para identificar otro periodo con valores positivos grandes y haz un mapa de SSTA para uno de esos meses.

Pista: primero lee oni_mensual_1981_2026.csv con pandas, como hicimos en el notebook 1. No busques todavía una respuesta "correcta": el objetivo es practicar cómo conectar una pregunta con una selección temporal.

4. Reducir dimensiones: de un campo a una serie¶

Una de las ventajas de trabajar con dimensiones etiquetadas es que podemos decirle a xarray qué dimensiones queremos reducir por nombre.

Calculemos la anomalía media sobre todo el dominio:

In [14]:
ssta_mean = ds.ssta.mean(
    dim=("lat", "lon"),
    skipna=True
)
In [15]:
ssta_mean.plot(figsize=(10, 3))
plt.ylabel("SSTA (°C)")
plt.title("SSTA media en el dominio")
plt.show()
No description has been provided for this image

Compáremos las dimensiones antes y después:

In [16]:
ds.ssta.dims
Out[16]:
('time', 'lat', 'lon')
In [17]:
ssta_mean.dims
Out[17]:
('time',)

Pasamos de:

(time, lat, lon)

a:

(time,)

porque promediamos lat y lon. Ésta es una operación muy común en análisis oceanográfico: reducir un campo espacial para construir una serie de tiempo.

skipna=True indica que el promedio debe ignorar valores faltantes, por ejemplo los puntos en tierra.

5. Seleccionar una región geográfica¶

El dominio completo no necesariamente corresponde a la región física que queremos estudiar. Podemos seleccionar una caja usando las coordenadas de latitud y longitud.

Usaremos una región de ejemplo al norte de la península:

In [18]:
baja_norte = ds.ssta.sel(
    lat=slice(28, 33),
    lon=slice(-120, -115)
)

baja_norte
Out[18]:
<xarray.DataArray 'ssta' (time: 540, lat: 20, lon: 20)> Size: 864kB
array([[[-1.050777, -0.928625, ..., -0.545403, -0.608767],
        [-0.830544, -0.698408, ..., -0.568657, -0.788465],
        ...,
        [-1.60812 , -1.423265, ...,       nan,       nan],
        [-1.275999, -1.140823, ...,       nan,       nan]],

       [[-0.895227, -0.816608, ..., -1.253399, -1.431128],
        [-0.853249, -0.721119, ..., -1.365362, -1.658245],
        ...,
        [-1.708464, -1.767599, ...,       nan,       nan],
        [-1.361904, -1.368387, ...,       nan,       nan]],

       ...,

       [[ 1.492355,  1.580235, ...,  1.319828,  1.373085],
        [ 1.526945,  1.60129 , ...,  1.364742,  1.339455],
        ...,
        [ 1.297667,  1.237612, ...,       nan,       nan],
        [ 1.119097,  0.969063, ...,       nan,       nan]],

       [[ 1.567741,  1.662237, ...,  2.600471,  2.729153],
        [ 1.525042,  1.563175, ...,  2.469732,  2.469902],
        ...,
        [ 1.946615,  2.153955, ...,       nan,       nan],
        [ 1.932486,  2.19413 , ...,       nan,       nan]]],
      shape=(540, 20, 20), dtype=float32)
Coordinates:
  * time     (time) datetime64[ns] 4kB 1981-09-01 1981-10-01 ... 2026-08-01
  * lat      (lat) float32 80B 28.12 28.38 28.62 28.88 ... 32.38 32.62 32.88
  * lon      (lon) float32 80B -119.9 -119.6 -119.4 ... -115.6 -115.4 -115.1
Attributes:
    long_name:     Monthly sea surface temperature anomaly
    units:         degree_Celsius
    source:        NOAA OISST V2.1
    cell_methods:  time: mean
    comment:       Anomaly field retained from the original course dataset. T...
xarray.DataArray
'ssta'
  • time: 540
  • lat: 20
  • lon: 20
  • -1.051 -0.9286 -0.7301 -0.4302 -0.2732 -0.2102 ... nan nan nan nan nan
    array([[[-1.050777, -0.928625, ..., -0.545403, -0.608767],
            [-0.830544, -0.698408, ..., -0.568657, -0.788465],
            ...,
            [-1.60812 , -1.423265, ...,       nan,       nan],
            [-1.275999, -1.140823, ...,       nan,       nan]],
    
           [[-0.895227, -0.816608, ..., -1.253399, -1.431128],
            [-0.853249, -0.721119, ..., -1.365362, -1.658245],
            ...,
            [-1.708464, -1.767599, ...,       nan,       nan],
            [-1.361904, -1.368387, ...,       nan,       nan]],
    
           ...,
    
           [[ 1.492355,  1.580235, ...,  1.319828,  1.373085],
            [ 1.526945,  1.60129 , ...,  1.364742,  1.339455],
            ...,
            [ 1.297667,  1.237612, ...,       nan,       nan],
            [ 1.119097,  0.969063, ...,       nan,       nan]],
    
           [[ 1.567741,  1.662237, ...,  2.600471,  2.729153],
            [ 1.525042,  1.563175, ...,  2.469732,  2.469902],
            ...,
            [ 1.946615,  2.153955, ...,       nan,       nan],
            [ 1.932486,  2.19413 , ...,       nan,       nan]]],
          shape=(540, 20, 20), dtype=float32)
    • time
      (time)
      datetime64[ns]
      1981-09-01 ... 2026-08-01
      standard_name :
      time
      long_name :
      time
      axis :
      T
      array(['1981-09-01T00:00:00.000000000', '1981-10-01T00:00:00.000000000',
             '1981-11-01T00:00:00.000000000', ..., '2026-06-01T00:00:00.000000000',
             '2026-07-01T00:00:00.000000000', '2026-08-01T00:00:00.000000000'],
            shape=(540,), dtype='datetime64[ns]')
    • lat
      (lat)
      float32
      28.12 28.38 28.62 ... 32.62 32.88
      standard_name :
      latitude
      long_name :
      latitude
      units :
      degrees_north
      axis :
      Y
      array([28.125, 28.375, 28.625, 28.875, 29.125, 29.375, 29.625, 29.875, 30.125,
             30.375, 30.625, 30.875, 31.125, 31.375, 31.625, 31.875, 32.125, 32.375,
             32.625, 32.875], dtype=float32)
    • lon
      (lon)
      float32
      -119.9 -119.6 ... -115.4 -115.1
      standard_name :
      longitude
      long_name :
      longitude
      units :
      degrees_east
      axis :
      X
      array([-119.875, -119.625, -119.375, -119.125, -118.875, -118.625, -118.375,
             -118.125, -117.875, -117.625, -117.375, -117.125, -116.875, -116.625,
             -116.375, -116.125, -115.875, -115.625, -115.375, -115.125],
            dtype=float32)
  • long_name :
    Monthly sea surface temperature anomaly
    units :
    degree_Celsius
    source :
    NOAA OISST V2.1
    cell_methods :
    time: mean
    comment :
    Anomaly field retained from the original course dataset. The climatological reference period is not documented in the source file.

Visualicemos primero qué seleccionamos:

In [19]:
baja_norte.isel(time=0).plot()
plt.title("Región seleccionada")
plt.show()
No description has been provided for this image

Y después calculemos su media espacial:

In [20]:
baja_norte.mean(("lat", "lon"), skipna=True).plot(figsize=(10, 3))
plt.ylabel("SSTA (°C)")
plt.title("SSTA media - región Baja Norte")
plt.show()
No description has been provided for this image

La selección de la caja es una decisión científica, no una decisión de Python.

El código nos permite seleccionar cualquier región, pero debemos justificar por qué esa región responde a nuestra pregunta.

Esta libertad será importante para el proyecto final.

6. El puente entre xarray y pandas¶

Hasta ahora tenemos dos tipos de datos:

NetCDF → xarray → campo multidimensional de SST/SSTA
CSV    → pandas → series ONI y precipitación

Para compararlos podemos reducir el campo de SSTA a una serie y convertirla a un objeto de pandas.

In [21]:
sst_series = baja_norte.mean(
    dim=("lat", "lon"),
    skipna=True
)

sst_series
Out[21]:
<xarray.DataArray 'ssta' (time: 540)> Size: 2kB
array([ 1.74564160e-02, -8.01558197e-01, -5.48438787e-01, -2.10056201e-01,
       -3.00844878e-01, -3.91321152e-01, -3.51450324e-01,  2.18306631e-01,
        2.36718208e-01, -8.96373689e-01, -8.26660514e-01, -7.74362922e-01,
       -8.32917541e-02,  2.16435254e-01,  5.55345178e-01,  5.58817208e-01,
        7.67568827e-01,  8.17435920e-01,  5.92509389e-01,  3.92195582e-01,
       -8.96406993e-02, -4.53420550e-01, -4.91134137e-01,  4.46056247e-01,
        1.47094560e+00,  1.31415939e+00,  1.16202915e+00,  3.07478607e-01,
        1.01089582e-01,  2.49819666e-01,  9.59187672e-02, -7.72193447e-02,
       -3.22561711e-01, -2.27311581e-01,  1.01847410e+00,  8.41802597e-01,
        1.46675611e+00,  4.73688900e-01, -5.48028052e-01, -7.17218101e-01,
       -8.69918585e-01, -8.71838868e-01, -9.00755703e-01, -7.64285564e-01,
       -8.32358479e-01, -5.95172942e-01,  7.39920318e-01,  1.02240585e-01,
        1.37973234e-01,  2.48553261e-01, -5.81110537e-01, -8.74161959e-01,
       -5.98022267e-02,  1.37858000e-02,  1.45766810e-01,  1.06481969e-01,
        1.07414640e-01, -3.40419650e-01, -3.23842049e-01, -4.98340487e-01,
       -1.37972903e+00, -8.75574112e-01, -4.53465253e-01,  3.95062685e-01,
       -4.71810848e-02, -4.68279608e-02, -2.77384013e-01, -1.24894202e-01,
       -6.47406606e-03, -6.33568466e-01, -1.23914349e+00, -7.22683966e-01,
       -5.93612790e-01,  3.84082384e-02,  4.53453034e-01, -1.12486862e-01,
       -9.98680532e-01, -6.73011720e-01, -2.05730632e-01,  5.48982501e-01,
...
        9.45368707e-01,  1.02782607e+00,  1.04618251e+00,  8.91335964e-01,
        1.31337094e+00,  1.97421849e+00,  9.42699850e-01,  7.82236397e-01,
        2.48114347e-01, -1.15848340e-01, -2.80837715e-01, -1.76299915e-01,
       -2.12682039e-01,  8.36434774e-03,  4.68366623e-01,  1.26603842e+00,
        9.86229181e-01,  4.25968647e-01, -1.18900582e-01,  2.46720091e-02,
       -3.01908590e-02,  4.35215198e-02,  2.52360944e-02,  1.03148624e-01,
       -4.52163190e-01,  5.62318325e-01,  6.03779495e-01,  1.29020143e+00,
        1.66140044e+00,  9.86985683e-01,  3.67624372e-01,  3.21864456e-01,
        1.28840417e-01, -2.97220111e-01, -4.33409423e-01, -1.06257606e+00,
       -6.39319062e-01, -2.51341134e-01, -7.72708714e-01,  2.68216729e-01,
        5.86270988e-01,  1.81181297e-01,  6.09207451e-01,  9.90591884e-01,
        8.24549317e-01,  6.18465662e-01,  4.67631251e-01,  1.33606359e-01,
       -4.22421023e-02, -2.48348311e-01,  9.76257995e-02,  3.48739564e-01,
       -7.58259296e-02, -3.32571298e-01, -5.47031045e-01, -1.91240966e-01,
       -2.62534738e-01, -5.87881923e-01, -6.90237880e-01,  1.99982464e-01,
        1.95412084e-01,  2.33372405e-01, -7.88965728e-04, -3.45169932e-01,
        7.07026303e-01,  7.82383800e-01,  8.04951608e-01,  1.49029386e+00,
        1.71222687e+00,  1.74768174e+00,  2.01705718e+00,  2.44491124e+00,
        1.82997346e+00,  1.98151922e+00,  1.38823926e+00,  1.72411776e+00],
      dtype=float32)
Coordinates:
  * time     (time) datetime64[ns] 4kB 1981-09-01 1981-10-01 ... 2026-08-01
Attributes:
    long_name:     Monthly sea surface temperature anomaly
    units:         degree_Celsius
    source:        NOAA OISST V2.1
    cell_methods:  time: mean
    comment:       Anomaly field retained from the original course dataset. T...
xarray.DataArray
'ssta'
  • time: 540
  • 0.01746 -0.8016 -0.5484 -0.2101 -0.3008 ... 1.83 1.982 1.388 1.724
    array([ 1.74564160e-02, -8.01558197e-01, -5.48438787e-01, -2.10056201e-01,
           -3.00844878e-01, -3.91321152e-01, -3.51450324e-01,  2.18306631e-01,
            2.36718208e-01, -8.96373689e-01, -8.26660514e-01, -7.74362922e-01,
           -8.32917541e-02,  2.16435254e-01,  5.55345178e-01,  5.58817208e-01,
            7.67568827e-01,  8.17435920e-01,  5.92509389e-01,  3.92195582e-01,
           -8.96406993e-02, -4.53420550e-01, -4.91134137e-01,  4.46056247e-01,
            1.47094560e+00,  1.31415939e+00,  1.16202915e+00,  3.07478607e-01,
            1.01089582e-01,  2.49819666e-01,  9.59187672e-02, -7.72193447e-02,
           -3.22561711e-01, -2.27311581e-01,  1.01847410e+00,  8.41802597e-01,
            1.46675611e+00,  4.73688900e-01, -5.48028052e-01, -7.17218101e-01,
           -8.69918585e-01, -8.71838868e-01, -9.00755703e-01, -7.64285564e-01,
           -8.32358479e-01, -5.95172942e-01,  7.39920318e-01,  1.02240585e-01,
            1.37973234e-01,  2.48553261e-01, -5.81110537e-01, -8.74161959e-01,
           -5.98022267e-02,  1.37858000e-02,  1.45766810e-01,  1.06481969e-01,
            1.07414640e-01, -3.40419650e-01, -3.23842049e-01, -4.98340487e-01,
           -1.37972903e+00, -8.75574112e-01, -4.53465253e-01,  3.95062685e-01,
           -4.71810848e-02, -4.68279608e-02, -2.77384013e-01, -1.24894202e-01,
           -6.47406606e-03, -6.33568466e-01, -1.23914349e+00, -7.22683966e-01,
           -5.93612790e-01,  3.84082384e-02,  4.53453034e-01, -1.12486862e-01,
           -9.98680532e-01, -6.73011720e-01, -2.05730632e-01,  5.48982501e-01,
    ...
            9.45368707e-01,  1.02782607e+00,  1.04618251e+00,  8.91335964e-01,
            1.31337094e+00,  1.97421849e+00,  9.42699850e-01,  7.82236397e-01,
            2.48114347e-01, -1.15848340e-01, -2.80837715e-01, -1.76299915e-01,
           -2.12682039e-01,  8.36434774e-03,  4.68366623e-01,  1.26603842e+00,
            9.86229181e-01,  4.25968647e-01, -1.18900582e-01,  2.46720091e-02,
           -3.01908590e-02,  4.35215198e-02,  2.52360944e-02,  1.03148624e-01,
           -4.52163190e-01,  5.62318325e-01,  6.03779495e-01,  1.29020143e+00,
            1.66140044e+00,  9.86985683e-01,  3.67624372e-01,  3.21864456e-01,
            1.28840417e-01, -2.97220111e-01, -4.33409423e-01, -1.06257606e+00,
           -6.39319062e-01, -2.51341134e-01, -7.72708714e-01,  2.68216729e-01,
            5.86270988e-01,  1.81181297e-01,  6.09207451e-01,  9.90591884e-01,
            8.24549317e-01,  6.18465662e-01,  4.67631251e-01,  1.33606359e-01,
           -4.22421023e-02, -2.48348311e-01,  9.76257995e-02,  3.48739564e-01,
           -7.58259296e-02, -3.32571298e-01, -5.47031045e-01, -1.91240966e-01,
           -2.62534738e-01, -5.87881923e-01, -6.90237880e-01,  1.99982464e-01,
            1.95412084e-01,  2.33372405e-01, -7.88965728e-04, -3.45169932e-01,
            7.07026303e-01,  7.82383800e-01,  8.04951608e-01,  1.49029386e+00,
            1.71222687e+00,  1.74768174e+00,  2.01705718e+00,  2.44491124e+00,
            1.82997346e+00,  1.98151922e+00,  1.38823926e+00,  1.72411776e+00],
          dtype=float32)
    • time
      (time)
      datetime64[ns]
      1981-09-01 ... 2026-08-01
      standard_name :
      time
      long_name :
      time
      axis :
      T
      array(['1981-09-01T00:00:00.000000000', '1981-10-01T00:00:00.000000000',
             '1981-11-01T00:00:00.000000000', ..., '2026-06-01T00:00:00.000000000',
             '2026-07-01T00:00:00.000000000', '2026-08-01T00:00:00.000000000'],
            shape=(540,), dtype='datetime64[ns]')
  • long_name :
    Monthly sea surface temperature anomaly
    units :
    degree_Celsius
    source :
    NOAA OISST V2.1
    cell_methods :
    time: mean
    comment :
    Anomaly field retained from the original course dataset. The climatological reference period is not documented in the source file.
In [22]:
sst_series = sst_series.to_series()

type(sst_series)
Out[22]:
pandas.Series

Ahora sst_series es una pandas.Series cuyo índice es el tiempo.

Para que este notebook pueda ejecutarse de principio a fin de manera independiente, volvemos a leer aquí los dos CSV:

In [23]:
oni = pd.read_csv(
    "../data/oni_mensual_1981_2026.csv",
    parse_dates=["time"]
)

rain = pd.read_csv(
    "../data/precipitacion_ensenada_1981_2026.csv",
    parse_dates=["time"]
)

Podemos convertir time en el índice de cada tabla. Esto facilita alinear las tres series usando sus fechas:

In [24]:
oni = oni.set_index("time")
rain = rain.set_index("time")
In [25]:
analysis = pd.concat(
    [
        sst_series.rename("ssta_degC"),
        oni["oni_degC"],
        rain["precip_mm"]
    ],
    axis=1
)

analysis.head()
Out[25]:
ssta_degC oni_degC precip_mm
time
1981-09-01 0.017456 -0.25 2.0
1981-10-01 -0.801558 -0.16 5.0
1981-11-01 -0.548439 -0.13 68.5
1981-12-01 -0.210056 -0.15 20.0
1982-01-01 -0.300845 -0.08 54.8

pd.concat(..., axis=1) coloca las series como columnas y las alinea usando el índice temporal.

Conceptualmente acabamos de hacer:

NetCDF ──xarray──┐
                 ├── pandas DataFrame → análisis conjunto
CSV ────pandas───┘

Éste es un patrón muy común: distintas herramientas son convenientes para distintas estructuras de datos, y podemos movernos entre ellas cuando la pregunta lo requiere.

In [26]:
analysis.info()
<class 'pandas.DataFrame'>
DatetimeIndex: 540 entries, 1981-09-01 to 2026-08-01
Data columns (total 3 columns):
 #   Column     Non-Null Count  Dtype  
---  ------     --------------  -----  
 0   ssta_degC  540 non-null    float32
 1   oni_degC   540 non-null    float64
 2   precip_mm  511 non-null    float64
dtypes: float32(1), float64(2)
memory usage: 14.8 KB

Observa que la columna de precipitación tiene menos valores válidos. concat no "rellena" las observaciones que faltan: conserva NaN, como debe ser.

7. Una primera comparación¶

Podemos empezar con una comparación visual entre ONI y la SSTA regional:

In [27]:
analysis[["ssta_degC", "oni_degC"]].plot(figsize=(10, 4))
plt.ylabel("Anomalía de temperatura (°C)")
plt.xlabel("Tiempo")
plt.title("SSTA regional y ONI")
plt.show()
No description has been provided for this image

Otra forma de explorar la relación es mediante un diagrama de dispersión:

In [28]:
analysis.plot.scatter(
    x="oni_degC",
    y="ssta_degC",
    figsize=(5, 5)
)
plt.xlabel("ONI (°C)")
plt.ylabel("SSTA regional (°C)")
plt.show()
No description has been provided for this image

Y podemos calcular una correlación lineal:

In [29]:
analysis[["oni_degC", "ssta_degC"]].corr()
Out[29]:
oni_degC ssta_degC
oni_degC 1.000000 0.390292
ssta_degC 0.390292 1.000000

Esto es exploración, no un análisis estadístico completo. Estas series tienen estructura temporal, persistencia y posibles desfases; además, correlación no implica causalidad. El objetivo aquí es aprender a preparar y combinar los datos de manera transparente. En el proyecto final podrán decidir qué comparación tiene sentido investigar y qué limitaciones deben mencionar.

Pon en práctica lo que aprendiste¶

Elige una de estas exploraciones:

  1. Grafica ONI y precipitación para un intervalo de tiempo que te interese.
  2. Cambia la región de SSTA y repite la comparación con ONI.
  3. Calcula el promedio de precipitación para las fases "El Niño", "La Niña" y "Neutral" usando la función enso_phase() del notebook 1.
  4. Explora visualmente si parece existir un desfase entre ONI y SSTA regional.

No es necesario llegar a una conclusión definitiva. Escribe debajo de tu figura dos o tres frases separando claramente lo que observas de lo que interpretas.

8. El problema con un notebook largo¶

Hasta aquí nuestra exploración funciona, pero imaginemos que mañana queremos repetir el mismo análisis para otra región.

Podríamos buscar todas las celdas donde escribimos:

lat=slice(28, 33)
lon=slice(-120, -115)

y cambiarlas a mano. Eso tiene varios problemas:

  • repetimos código;
  • podemos olvidar modificar una celda;
  • es difícil mantener versiones consistentes;
  • reutilizar el análisis en otro proyecto requiere copiar y pegar.

Como vimos en el notebook 1, una solución es empaquetar una tarea con una función.

9. Convertir una operación en una función¶

Primero, recordemos la operación que queremos reutilizar:

In [30]:
region = ds.ssta.sel(
    lat=slice(28, 33),
    lon=slice(-120, -115)
)

regional_mean_example = region.mean(
    dim=("lat", "lon"),
    skipna=True
)

Ahora generalizamos la idea. En vez de fijar la variable y las coordenadas dentro del código, las recibimos como parámetros:

In [31]:
def regional_mean(da, lat_range, lon_range):
    """Calcula la media espacial de un DataArray en una región rectangular.

    Parameters
    ----------
    da : xarray.DataArray
        Campo con dimensiones lat y lon.
    lat_range : tuple
        (lat_min, lat_max)
    lon_range : tuple
        (lon_min, lon_max)

    Returns
    -------
    xarray.DataArray
        Serie resultante después de promediar lat y lon.
    """

    region = da.sel(
        lat=slice(*lat_range),
        lon=slice(*lon_range)
    )

    return region.mean(
        dim=("lat", "lon"),
        skipna=True
    )

El operador * en slice(*lat_range) "desempaca" la tupla. Por ejemplo:

lat_range = (28, 33)
slice(*lat_range)

equivale a:

slice(28, 33)

Ahora la misma función sirve para muchas regiones:

In [32]:
baja_series = regional_mean(ds.ssta, (28, 33), (-120, -115))

baja_series
Out[32]:
<xarray.DataArray 'ssta' (time: 540)> Size: 2kB
array([ 1.74564160e-02, -8.01558197e-01, -5.48438787e-01, -2.10056201e-01,
       -3.00844878e-01, -3.91321152e-01, -3.51450324e-01,  2.18306631e-01,
        2.36718208e-01, -8.96373689e-01, -8.26660514e-01, -7.74362922e-01,
       -8.32917541e-02,  2.16435254e-01,  5.55345178e-01,  5.58817208e-01,
        7.67568827e-01,  8.17435920e-01,  5.92509389e-01,  3.92195582e-01,
       -8.96406993e-02, -4.53420550e-01, -4.91134137e-01,  4.46056247e-01,
        1.47094560e+00,  1.31415939e+00,  1.16202915e+00,  3.07478607e-01,
        1.01089582e-01,  2.49819666e-01,  9.59187672e-02, -7.72193447e-02,
       -3.22561711e-01, -2.27311581e-01,  1.01847410e+00,  8.41802597e-01,
        1.46675611e+00,  4.73688900e-01, -5.48028052e-01, -7.17218101e-01,
       -8.69918585e-01, -8.71838868e-01, -9.00755703e-01, -7.64285564e-01,
       -8.32358479e-01, -5.95172942e-01,  7.39920318e-01,  1.02240585e-01,
        1.37973234e-01,  2.48553261e-01, -5.81110537e-01, -8.74161959e-01,
       -5.98022267e-02,  1.37858000e-02,  1.45766810e-01,  1.06481969e-01,
        1.07414640e-01, -3.40419650e-01, -3.23842049e-01, -4.98340487e-01,
       -1.37972903e+00, -8.75574112e-01, -4.53465253e-01,  3.95062685e-01,
       -4.71810848e-02, -4.68279608e-02, -2.77384013e-01, -1.24894202e-01,
       -6.47406606e-03, -6.33568466e-01, -1.23914349e+00, -7.22683966e-01,
       -5.93612790e-01,  3.84082384e-02,  4.53453034e-01, -1.12486862e-01,
       -9.98680532e-01, -6.73011720e-01, -2.05730632e-01,  5.48982501e-01,
...
        9.45368707e-01,  1.02782607e+00,  1.04618251e+00,  8.91335964e-01,
        1.31337094e+00,  1.97421849e+00,  9.42699850e-01,  7.82236397e-01,
        2.48114347e-01, -1.15848340e-01, -2.80837715e-01, -1.76299915e-01,
       -2.12682039e-01,  8.36434774e-03,  4.68366623e-01,  1.26603842e+00,
        9.86229181e-01,  4.25968647e-01, -1.18900582e-01,  2.46720091e-02,
       -3.01908590e-02,  4.35215198e-02,  2.52360944e-02,  1.03148624e-01,
       -4.52163190e-01,  5.62318325e-01,  6.03779495e-01,  1.29020143e+00,
        1.66140044e+00,  9.86985683e-01,  3.67624372e-01,  3.21864456e-01,
        1.28840417e-01, -2.97220111e-01, -4.33409423e-01, -1.06257606e+00,
       -6.39319062e-01, -2.51341134e-01, -7.72708714e-01,  2.68216729e-01,
        5.86270988e-01,  1.81181297e-01,  6.09207451e-01,  9.90591884e-01,
        8.24549317e-01,  6.18465662e-01,  4.67631251e-01,  1.33606359e-01,
       -4.22421023e-02, -2.48348311e-01,  9.76257995e-02,  3.48739564e-01,
       -7.58259296e-02, -3.32571298e-01, -5.47031045e-01, -1.91240966e-01,
       -2.62534738e-01, -5.87881923e-01, -6.90237880e-01,  1.99982464e-01,
        1.95412084e-01,  2.33372405e-01, -7.88965728e-04, -3.45169932e-01,
        7.07026303e-01,  7.82383800e-01,  8.04951608e-01,  1.49029386e+00,
        1.71222687e+00,  1.74768174e+00,  2.01705718e+00,  2.44491124e+00,
        1.82997346e+00,  1.98151922e+00,  1.38823926e+00,  1.72411776e+00],
      dtype=float32)
Coordinates:
  * time     (time) datetime64[ns] 4kB 1981-09-01 1981-10-01 ... 2026-08-01
Attributes:
    long_name:     Monthly sea surface temperature anomaly
    units:         degree_Celsius
    source:        NOAA OISST V2.1
    cell_methods:  time: mean
    comment:       Anomaly field retained from the original course dataset. T...
xarray.DataArray
'ssta'
  • time: 540
  • 0.01746 -0.8016 -0.5484 -0.2101 -0.3008 ... 1.83 1.982 1.388 1.724
    array([ 1.74564160e-02, -8.01558197e-01, -5.48438787e-01, -2.10056201e-01,
           -3.00844878e-01, -3.91321152e-01, -3.51450324e-01,  2.18306631e-01,
            2.36718208e-01, -8.96373689e-01, -8.26660514e-01, -7.74362922e-01,
           -8.32917541e-02,  2.16435254e-01,  5.55345178e-01,  5.58817208e-01,
            7.67568827e-01,  8.17435920e-01,  5.92509389e-01,  3.92195582e-01,
           -8.96406993e-02, -4.53420550e-01, -4.91134137e-01,  4.46056247e-01,
            1.47094560e+00,  1.31415939e+00,  1.16202915e+00,  3.07478607e-01,
            1.01089582e-01,  2.49819666e-01,  9.59187672e-02, -7.72193447e-02,
           -3.22561711e-01, -2.27311581e-01,  1.01847410e+00,  8.41802597e-01,
            1.46675611e+00,  4.73688900e-01, -5.48028052e-01, -7.17218101e-01,
           -8.69918585e-01, -8.71838868e-01, -9.00755703e-01, -7.64285564e-01,
           -8.32358479e-01, -5.95172942e-01,  7.39920318e-01,  1.02240585e-01,
            1.37973234e-01,  2.48553261e-01, -5.81110537e-01, -8.74161959e-01,
           -5.98022267e-02,  1.37858000e-02,  1.45766810e-01,  1.06481969e-01,
            1.07414640e-01, -3.40419650e-01, -3.23842049e-01, -4.98340487e-01,
           -1.37972903e+00, -8.75574112e-01, -4.53465253e-01,  3.95062685e-01,
           -4.71810848e-02, -4.68279608e-02, -2.77384013e-01, -1.24894202e-01,
           -6.47406606e-03, -6.33568466e-01, -1.23914349e+00, -7.22683966e-01,
           -5.93612790e-01,  3.84082384e-02,  4.53453034e-01, -1.12486862e-01,
           -9.98680532e-01, -6.73011720e-01, -2.05730632e-01,  5.48982501e-01,
    ...
            9.45368707e-01,  1.02782607e+00,  1.04618251e+00,  8.91335964e-01,
            1.31337094e+00,  1.97421849e+00,  9.42699850e-01,  7.82236397e-01,
            2.48114347e-01, -1.15848340e-01, -2.80837715e-01, -1.76299915e-01,
           -2.12682039e-01,  8.36434774e-03,  4.68366623e-01,  1.26603842e+00,
            9.86229181e-01,  4.25968647e-01, -1.18900582e-01,  2.46720091e-02,
           -3.01908590e-02,  4.35215198e-02,  2.52360944e-02,  1.03148624e-01,
           -4.52163190e-01,  5.62318325e-01,  6.03779495e-01,  1.29020143e+00,
            1.66140044e+00,  9.86985683e-01,  3.67624372e-01,  3.21864456e-01,
            1.28840417e-01, -2.97220111e-01, -4.33409423e-01, -1.06257606e+00,
           -6.39319062e-01, -2.51341134e-01, -7.72708714e-01,  2.68216729e-01,
            5.86270988e-01,  1.81181297e-01,  6.09207451e-01,  9.90591884e-01,
            8.24549317e-01,  6.18465662e-01,  4.67631251e-01,  1.33606359e-01,
           -4.22421023e-02, -2.48348311e-01,  9.76257995e-02,  3.48739564e-01,
           -7.58259296e-02, -3.32571298e-01, -5.47031045e-01, -1.91240966e-01,
           -2.62534738e-01, -5.87881923e-01, -6.90237880e-01,  1.99982464e-01,
            1.95412084e-01,  2.33372405e-01, -7.88965728e-04, -3.45169932e-01,
            7.07026303e-01,  7.82383800e-01,  8.04951608e-01,  1.49029386e+00,
            1.71222687e+00,  1.74768174e+00,  2.01705718e+00,  2.44491124e+00,
            1.82997346e+00,  1.98151922e+00,  1.38823926e+00,  1.72411776e+00],
          dtype=float32)
    • time
      (time)
      datetime64[ns]
      1981-09-01 ... 2026-08-01
      standard_name :
      time
      long_name :
      time
      axis :
      T
      array(['1981-09-01T00:00:00.000000000', '1981-10-01T00:00:00.000000000',
             '1981-11-01T00:00:00.000000000', ..., '2026-06-01T00:00:00.000000000',
             '2026-07-01T00:00:00.000000000', '2026-08-01T00:00:00.000000000'],
            shape=(540,), dtype='datetime64[ns]')
  • long_name :
    Monthly sea surface temperature anomaly
    units :
    degree_Celsius
    source :
    NOAA OISST V2.1
    cell_methods :
    time: mean
    comment :
    Anomaly field retained from the original course dataset. The climatological reference period is not documented in the source file.
In [33]:
ensenada_region = regional_mean(
    ds.ssta,
    (30, 33),
    (-118, -115)
)

ensenada_region
Out[33]:
<xarray.DataArray 'ssta' (time: 540)> Size: 2kB
array([ 3.62946577e-02, -1.18687034e+00, -8.29025805e-01, -2.63829947e-01,
       -6.14233315e-01, -4.53669995e-01, -3.65795195e-01, -1.95580438e-01,
        3.03255720e-03, -1.08906245e+00, -4.80314136e-01, -3.65696073e-01,
        3.73925269e-02,  2.40376964e-01,  3.90374213e-01,  5.25056124e-01,
        7.56639898e-01,  5.97896636e-01,  5.39421976e-01,  8.96685570e-02,
       -1.39169604e-01, -5.67946732e-01, -2.86197990e-01,  5.50798476e-01,
        1.31498802e+00,  1.07315552e+00,  1.13743627e+00,  5.64195998e-02,
        4.49751914e-02,  3.80425066e-01,  5.66140294e-01,  1.58508494e-01,
        1.61651731e-01,  3.13639730e-01,  1.56470954e+00,  1.39395130e+00,
        1.90803671e+00,  6.26028538e-01, -6.39585853e-01, -9.62161064e-01,
       -9.88233328e-01, -1.00671279e+00, -1.13069832e+00, -8.40362608e-01,
       -6.94967628e-01, -7.48120248e-01,  5.78929007e-01,  6.28157035e-02,
       -1.84798717e-01,  2.59546906e-01, -5.99936903e-01, -1.08487940e+00,
       -9.07322690e-02,  8.78633782e-02,  1.91181138e-01, -5.46693690e-02,
        1.80477574e-01, -3.66395891e-01, -5.42671025e-01, -7.76732564e-01,
       -1.59355414e+00, -9.48474705e-01, -3.37785810e-01,  5.67610860e-01,
        1.04841895e-01, -1.40318554e-02, -1.85128435e-01,  2.13046208e-01,
        5.84474578e-02, -6.95351243e-01, -1.53275263e+00, -1.05274558e+00,
       -6.88185334e-01, -2.81184256e-01,  3.84503007e-01, -3.54109436e-01,
       -1.31950200e+00, -8.33082914e-01,  6.34780377e-02,  9.08944011e-01,
...
        1.22340226e+00,  8.16568673e-01,  1.08476114e+00,  8.58179212e-01,
        1.38246810e+00,  2.30985236e+00,  1.21432519e+00,  1.10700023e+00,
        5.14360249e-01,  1.27596632e-01,  3.53533030e-02,  1.60148427e-01,
        1.54361382e-01,  4.25053149e-01,  1.13496768e+00,  1.11815774e+00,
        1.02193010e+00,  6.82282388e-01, -1.41372532e-01, -3.92025011e-03,
        5.13838306e-02,  2.96215773e-01,  2.63938159e-01,  7.80684799e-02,
       -4.89832044e-01,  8.86821926e-01,  5.01251400e-01,  1.07486308e+00,
        1.68024135e+00,  8.64592075e-01,  1.35845184e-01,  3.72110233e-02,
        2.24161148e-02, -3.63212854e-01, -5.41945577e-01, -1.08120263e+00,
       -8.59260023e-01, -4.89191324e-01, -8.57845247e-01, -3.16693872e-01,
        4.38857526e-02, -1.74302474e-01,  3.28329742e-01,  9.31417465e-01,
        6.15147293e-01,  4.01500970e-01,  3.16131830e-01, -2.70038217e-01,
       -1.34662330e-01, -3.93551320e-01, -1.70417383e-01,  1.54962003e-01,
       -3.46843123e-01, -6.64969206e-01, -9.71608102e-01, -1.20594180e+00,
       -9.34022546e-01, -7.57179499e-01, -8.82939160e-01, -1.67060375e-01,
        4.83270139e-02, -8.95957723e-02, -3.67742926e-02, -3.92212301e-01,
        3.48263562e-01,  2.09312444e-03,  1.92956388e-01,  1.36520243e+00,
        1.57076025e+00,  1.70194900e+00,  2.09488440e+00,  2.50352168e+00,
        1.67587101e+00,  1.76184404e+00,  1.15060616e+00,  1.51067805e+00],
      dtype=float32)
Coordinates:
  * time     (time) datetime64[ns] 4kB 1981-09-01 1981-10-01 ... 2026-08-01
Attributes:
    long_name:     Monthly sea surface temperature anomaly
    units:         degree_Celsius
    source:        NOAA OISST V2.1
    cell_methods:  time: mean
    comment:       Anomaly field retained from the original course dataset. T...
xarray.DataArray
'ssta'
  • time: 540
  • 0.03629 -1.187 -0.829 -0.2638 -0.6142 ... 1.676 1.762 1.151 1.511
    array([ 3.62946577e-02, -1.18687034e+00, -8.29025805e-01, -2.63829947e-01,
           -6.14233315e-01, -4.53669995e-01, -3.65795195e-01, -1.95580438e-01,
            3.03255720e-03, -1.08906245e+00, -4.80314136e-01, -3.65696073e-01,
            3.73925269e-02,  2.40376964e-01,  3.90374213e-01,  5.25056124e-01,
            7.56639898e-01,  5.97896636e-01,  5.39421976e-01,  8.96685570e-02,
           -1.39169604e-01, -5.67946732e-01, -2.86197990e-01,  5.50798476e-01,
            1.31498802e+00,  1.07315552e+00,  1.13743627e+00,  5.64195998e-02,
            4.49751914e-02,  3.80425066e-01,  5.66140294e-01,  1.58508494e-01,
            1.61651731e-01,  3.13639730e-01,  1.56470954e+00,  1.39395130e+00,
            1.90803671e+00,  6.26028538e-01, -6.39585853e-01, -9.62161064e-01,
           -9.88233328e-01, -1.00671279e+00, -1.13069832e+00, -8.40362608e-01,
           -6.94967628e-01, -7.48120248e-01,  5.78929007e-01,  6.28157035e-02,
           -1.84798717e-01,  2.59546906e-01, -5.99936903e-01, -1.08487940e+00,
           -9.07322690e-02,  8.78633782e-02,  1.91181138e-01, -5.46693690e-02,
            1.80477574e-01, -3.66395891e-01, -5.42671025e-01, -7.76732564e-01,
           -1.59355414e+00, -9.48474705e-01, -3.37785810e-01,  5.67610860e-01,
            1.04841895e-01, -1.40318554e-02, -1.85128435e-01,  2.13046208e-01,
            5.84474578e-02, -6.95351243e-01, -1.53275263e+00, -1.05274558e+00,
           -6.88185334e-01, -2.81184256e-01,  3.84503007e-01, -3.54109436e-01,
           -1.31950200e+00, -8.33082914e-01,  6.34780377e-02,  9.08944011e-01,
    ...
            1.22340226e+00,  8.16568673e-01,  1.08476114e+00,  8.58179212e-01,
            1.38246810e+00,  2.30985236e+00,  1.21432519e+00,  1.10700023e+00,
            5.14360249e-01,  1.27596632e-01,  3.53533030e-02,  1.60148427e-01,
            1.54361382e-01,  4.25053149e-01,  1.13496768e+00,  1.11815774e+00,
            1.02193010e+00,  6.82282388e-01, -1.41372532e-01, -3.92025011e-03,
            5.13838306e-02,  2.96215773e-01,  2.63938159e-01,  7.80684799e-02,
           -4.89832044e-01,  8.86821926e-01,  5.01251400e-01,  1.07486308e+00,
            1.68024135e+00,  8.64592075e-01,  1.35845184e-01,  3.72110233e-02,
            2.24161148e-02, -3.63212854e-01, -5.41945577e-01, -1.08120263e+00,
           -8.59260023e-01, -4.89191324e-01, -8.57845247e-01, -3.16693872e-01,
            4.38857526e-02, -1.74302474e-01,  3.28329742e-01,  9.31417465e-01,
            6.15147293e-01,  4.01500970e-01,  3.16131830e-01, -2.70038217e-01,
           -1.34662330e-01, -3.93551320e-01, -1.70417383e-01,  1.54962003e-01,
           -3.46843123e-01, -6.64969206e-01, -9.71608102e-01, -1.20594180e+00,
           -9.34022546e-01, -7.57179499e-01, -8.82939160e-01, -1.67060375e-01,
            4.83270139e-02, -8.95957723e-02, -3.67742926e-02, -3.92212301e-01,
            3.48263562e-01,  2.09312444e-03,  1.92956388e-01,  1.36520243e+00,
            1.57076025e+00,  1.70194900e+00,  2.09488440e+00,  2.50352168e+00,
            1.67587101e+00,  1.76184404e+00,  1.15060616e+00,  1.51067805e+00],
          dtype=float32)
    • time
      (time)
      datetime64[ns]
      1981-09-01 ... 2026-08-01
      standard_name :
      time
      long_name :
      time
      axis :
      T
      array(['1981-09-01T00:00:00.000000000', '1981-10-01T00:00:00.000000000',
             '1981-11-01T00:00:00.000000000', ..., '2026-06-01T00:00:00.000000000',
             '2026-07-01T00:00:00.000000000', '2026-08-01T00:00:00.000000000'],
            shape=(540,), dtype='datetime64[ns]')
  • long_name :
    Monthly sea surface temperature anomaly
    units :
    degree_Celsius
    source :
    NOAA OISST V2.1
    cell_methods :
    time: mean
    comment :
    Anomaly field retained from the original course dataset. The climatological reference period is not documented in the source file.

Mismo código, distinta región. Ésa es una de las razones principales para escribir funciones: separar la lógica general de los valores particulares de un análisis.

En programas más grandes construimos funciones pequeñas, con propósitos específicos, y las combinamos para formar tareas más complejas.

10. Del notebook a código reutilizable¶

Un notebook es muy útil para:

  • explorar datos;
  • probar ideas;
  • combinar explicación, figuras y código;
  • documentar decisiones.

Pero no queremos que una función útil exista únicamente en una celda de un notebook. Podemos moverla a un archivo de Python que otros programas puedan importar.

En JupyterLab crea:

scripts/
└── analysis_tools.py

y guarda ahí, por ejemplo:

def regional_mean(da, lat_range, lon_range):
    """Calcula la media espacial de un DataArray en una región rectangular."""

    region = da.sel(
        lat=slice(*lat_range),
        lon=slice(*lon_range)
    )

    return region.mean(
        dim=("lat", "lon"),
        skipna=True
    )


def enso_phase(oni_value):
    """Clasifica de manera simplificada un valor individual de ONI."""

    if oni_value >= 0.5:
        return "El Niño"
    elif oni_value <= -0.5:
        return "La Niña"
    else:
        return "Neutral"

Ahora tenemos un módulo con herramientas que podemos reutilizar desde más de un análisis.

11. Del notebook a un script reproducible¶

El siguiente paso es separar una tarea completa de exploración del notebook.

Crea:

scripts/
└── make_summary_figure.py

con el siguiente contenido:

import xarray as xr
import pandas as pd
import matplotlib.pyplot as plt

from analysis_tools import regional_mean


# 1. Leer datos
ds = xr.open_dataset("data/sst_baja_1981_2026.nc")

oni = pd.read_csv(
    "data/oni_mensual_1981_2026.csv",
    parse_dates=["time"]
).set_index("time")


# 2. Calcular una serie regional de SSTA
regional_ssta = regional_mean(
    ds.ssta,
    (28, 33),
    (-120, -115)
).to_series()


# 3. Alinear las series
analysis = pd.concat(
    [
        regional_ssta.rename("ssta_degC"),
        oni["oni_degC"]
    ],
    axis=1
)


# 4. Crear la figura
fig, axes = plt.subplots(
    nrows=2,
    figsize=(10, 6),
    sharex=True
)

analysis["ssta_degC"].plot(ax=axes[0])
axes[0].set_ylabel("SSTA (°C)")
axes[0].set_title("SSTA regional")

analysis["oni_degC"].plot(ax=axes[1])
axes[1].set_ylabel("ONI (°C)")
axes[1].set_xlabel("Tiempo")
axes[1].set_title("Oceanic Niño Index")

fig.tight_layout()
fig.savefig("figures/ssta_oni_summary.png", dpi=150)

Vuelve a la raíz del proyecto en la terminal y ejecuta:

python scripts/make_summary_figure.py

Si todo funciona, el script debe generar:

figures/
└── ssta_oni_summary.png

¿Qué ganamos?¶

Notebook
↓
explorar, probar, explicar

Función
↓
encapsular una tarea para reutilizarla

Script
↓
repetir un análisis completo

El notebook sigue siendo valioso: documenta la exploración y el razonamiento. El script nos permite volver a producir el resultado de forma consistente.

Reproducibilidad no significa sólo conservar una figura final: significa conservar los pasos necesarios para volver a crearla.

12. En resumen¶

En este notebook:

  • abrimos un NetCDF con xr.open_dataset();
  • distinguimos Dataset y DataArray;
  • usamos dimensiones, coordenadas y atributos;
  • seleccionamos por posición con .isel() y por coordenada con .sel();
  • redujimos dimensiones con .mean();
  • seleccionamos una región geográfica;
  • convertimos una serie de xarray a pandas;
  • alineamos datos provenientes de NetCDF y CSV;
  • hicimos una exploración gráfica y una correlación sencilla;
  • convertimos una operación repetida en la función regional_mean();
  • separamos funciones reutilizables en analysis_tools.py;
  • diseñamos un script que puede regenerar una figura.

Siguiente paso¶

Ahora tenemos archivos que queremos conservar, modificar y mejorar:

notebooks/
scripts/
figures/
README.md
environment.yml

En la sesión 3 usaremos Git para registrar la historia de esos cambios y GitHub para respaldar y compartir el proyecto.

Recursos para continuar¶

  • Software Carpentry: Programming with Python
  • xarray tutorial
  • xarray documentation
  • pandas documentation
  • Jupyter