Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

05. Variabilidad estacional

Suyana

Con muchos perfiles de una región podemos armar una climatología y ver cómo se modifica la columna de agua a lo largo del año. Descargamos todas las boyas activas en una caja durante varios años y agrupamos por mes.

%run _style.py
import numpy as np
import xarray as xr
import pandas as pd
import matplotlib.pyplot as plt
from argopy import DataFetcher
from _style import PALETTE

box = [-45, -30, -38, -30, 0, 2000, '2018-01-01', '2020-12-31']
ds_point = DataFetcher(src='erddap', mode='expert').region(box).to_xarray()
ds = ds_point.argo.point2profile()
print('perfiles:', ds.sizes['N_PROF'], '· niveles máx:', ds.sizes['N_LEVELS'])
perfiles: 1897 · niveles máx: 1006
def clean(ds):
    mode_is_R = (ds.DATA_MODE.astype(str) == 'R')
    def merge(v):
        return xr.where(mode_is_R, ds[v], ds[f'{v}_ADJUSTED'])
    def merge_qc(v):
        return xr.where(mode_is_R, ds[f'{v}_QC'], ds[f'{v}_ADJUSTED_QC'])
    def mask(var, qc):
        qc_str = qc.astype(str)
        return var.where((qc_str == '1') | (qc_str == '2'))
    return xr.Dataset({
        'TEMP': mask(merge('TEMP'), merge_qc('TEMP')),
        'PSAL': mask(merge('PSAL'), merge_qc('PSAL')),
        'PRES': mask(merge('PRES'), merge_qc('PRES')),
    }, coords={'LATITUDE': ds.LATITUDE, 'LONGITUDE': ds.LONGITUDE,
               'TIME': ds.TIME, 'CYCLE_NUMBER': ds.CYCLE_NUMBER})

dsc = clean(ds)

Interpolar a niveles fijos

Cada perfil viene con N_LEVELS propios (la boya muestrea a distintas presiones). Para promediar entre perfiles necesitamos interpolar todos a una grilla común de presión.

from scipy.interpolate import interp1d

p_grid = np.arange(0, 2001, 10)

def interp_profile(values, pres):
    mask = ~np.isnan(values) & ~np.isnan(pres)
    if mask.sum() < 5:
        return np.full_like(p_grid, np.nan, dtype=float)
    p_valid, v_valid = pres[mask], values[mask]
    return interp1d(p_valid, v_valid, bounds_error=False, fill_value=np.nan)(p_grid)

n_prof = dsc.sizes['N_PROF']
temp_grid = np.empty((n_prof, len(p_grid)))
psal_grid = np.empty((n_prof, len(p_grid)))
for i in range(n_prof):
    temp_grid[i] = interp_profile(dsc.TEMP.isel(N_PROF=i).values, dsc.PRES.isel(N_PROF=i).values)
    psal_grid[i] = interp_profile(dsc.PSAL.isel(N_PROF=i).values, dsc.PRES.isel(N_PROF=i).values)

grid = xr.Dataset({
    'TEMP': (['N_PROF', 'PRES'], temp_grid),
    'PSAL': (['N_PROF', 'PRES'], psal_grid),
}, coords={'PRES': p_grid, 'TIME': dsc.TIME,
           'LATITUDE': dsc.LATITUDE, 'LONGITUDE': dsc.LONGITUDE})
grid
Loading...

Climatología mensual

month = grid.TIME.dt.month
grid = grid.assign_coords(month=('N_PROF', month.values))

clim_temp = grid.TEMP.groupby('month').mean(skipna=True)
clim_psal = grid.PSAL.groupby('month').mean(skipna=True)
print(clim_temp.dims, clim_temp.shape)
('month', 'PRES') (12, 201)
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(13, 7), sharey=True)

im1 = ax1.contourf(clim_temp.month, clim_temp.PRES, clim_temp.T,
                   levels=20, cmap='RdYlBu_r')
ax1.invert_yaxis()
ax1.set_xlabel('Mes')
ax1.set_ylabel('Presión (dbar)')
ax1.set_title('Temperatura climatológica (°C)', loc='left')
ax1.set_xticks(range(1, 13))
ax1.set_ylim(500, 0)
plt.colorbar(im1, ax=ax1, label='°C')

im2 = ax2.contourf(clim_psal.month, clim_psal.PRES, clim_psal.T,
                   levels=20, cmap='viridis')
ax2.set_xlabel('Mes')
ax2.set_title('Salinidad climatológica (PSU)', loc='left')
ax2.set_xticks(range(1, 13))
ax2.set_ylim(500, 0)
plt.colorbar(im2, ax=ax2, label='PSU')

plt.tight_layout()
plt.show()
<Figure size 1430x770 with 4 Axes>

Lo que se ve clarito es la termoclina estacional: el ciclo de calentamiento en verano austral (enero a marzo) mete agua tibia hasta ~50 a 100 dbar, mientras que en invierno (julio a septiembre) la columna de mezcla baja hasta los 200 a 300 dbar y la termoclina queda más profunda.

Perfiles promedio por estación

verano = grid.where(grid.month.isin([1, 2, 3]), drop=True).mean('N_PROF', skipna=True)
invierno = grid.where(grid.month.isin([7, 8, 9]), drop=True).mean('N_PROF', skipna=True)

fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(10, 8), sharey=True)
ax1.plot(verano.TEMP, verano.PRES, color=PALETTE['orange'], lw=2, label='verano (EFM)')
ax1.plot(invierno.TEMP, invierno.PRES, color=PALETTE['blue'], lw=2, label='invierno (JAS)')
ax1.invert_yaxis()
ax1.set_xlabel('Temperatura (°C)')
ax1.set_ylabel('Presión (dbar)')
ax1.set_ylim(500, 0)
ax1.set_title('Perfiles estacionales. Atlántico Sur abierto (2018 a 2020)', loc='left')
ax1.legend(frameon=False)

ax2.plot(verano.PSAL, verano.PRES, color=PALETTE['orange'], lw=2, label='verano')
ax2.plot(invierno.PSAL, invierno.PRES, color=PALETTE['blue'], lw=2, label='invierno')
ax2.set_xlabel('Salinidad (PSU)')
ax2.set_ylim(500, 0)
ax2.set_title(' ', loc='left')
ax2.legend(frameon=False)

plt.tight_layout()
plt.show()
<Figure size 1100x880 with 2 Axes>

Resumen

  • Para climatologías hay que interpolar todos los perfiles a una grilla de presión común.

  • groupby('month').mean() te da la climatología mensual.

  • En el Atlántico Sur el ciclo estacional se ve clarito en los primeros ~300 dbar.

El próximo paso natural es calcular la profundidad de la capa de mezcla (MLD) y mapearla.