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.

02. Anatomía del dataset

Suyana

Lo que devuelve argopy por defecto está en formato “point-cloud”: una sola dimensión N_POINTS con todos los puntos (cada perfil = varios puntos consecutivos). Es eficiente pero no es el formato clásico de Argo.

Para hacer ciencia conviene reformatearlo a la estructura N_PROF × N_LEVELS:

  • N_PROF: número de perfiles (= ciclos).

  • N_LEVELS: niveles de presión dentro de cada perfil.

argopy te lo hace con .argo.point2profile().

%run _style.py
from argopy import DataFetcher
import argopy

ds_point = DataFetcher(src='erddap', mode='expert').float(5905141).to_xarray()
print('point-cloud:', dict(ds_point.dims))
point-cloud: {'N_POINTS': 176145}
/var/folders/8j/y_l8frxs2n19mq92k5pv4y100000gn/T/ipykernel_26108/3254288223.py:6: FutureWarning: The return type of `Dataset.dims` will be changed to return a set of dimension names in future, in order to be more consistent with `DataArray.dims`. To access a mapping from dimension names to lengths, please use `Dataset.sizes`.
  print('point-cloud:', dict(ds_point.dims))
ds = ds_point.argo.point2profile()
print('profile format:', dict(ds.dims))
ds
profile format: {'N_PROF': 314, 'N_LEVELS': 562}
/var/folders/8j/y_l8frxs2n19mq92k5pv4y100000gn/T/ipykernel_26108/2047636976.py:2: FutureWarning: The return type of `Dataset.dims` will be changed to return a set of dimension names in future, in order to be more consistent with `DataArray.dims`. To access a mapping from dimension names to lengths, please use `Dataset.sizes`.
  print('profile format:', dict(ds.dims))
Loading...

Variables clave

El dataset trae, además de las coordenadas (LATITUDE, LONGITUDE, TIME, PRES), las siguientes familias de variables:

FamiliaQué es
TEMP, PSAL, PRESVariables crudas (real-time).
TEMP_ADJUSTED, PSAL_ADJUSTED, PRES_ADJUSTEDVariables con ajustes aplicados (calibración delayed-mode si está disponible).
*_QC, *_ADJUSTED_QCFlags de calidad (string, valores ‘1’-‘9’).
*_ERRORError estimado del valor ajustado (solo delayed-mode).
DATA_MODEPor perfil: ‘R’ (real-time), ‘A’ (real-time + adjustments), ‘D’ (delayed-mode).

Modos de datos: R / A / D

  • R (real-time): lo que la boya transmite directo, con QC automático. Disponible en horas.

  • A (adjusted): igual a R pero con un ajuste preliminar automático en *_ADJUSTED.

  • D (delayed-mode): un PI lo revisó, calibró el sensor con CTDs cercanas y aplicó ajustes finos. Disponible 6 a 12 meses después.

Para ciencia rigurosa usá siempre *_ADJUSTED si DATA_MODE es ‘D’ o ‘A’, y la variable cruda solo cuando es ‘R’.

import numpy as np

modes = np.array([str(m) for m in ds.DATA_MODE.values])
unique, counts = np.unique(modes, return_counts=True)
for m, c in zip(unique, counts):
    print(f'  {m}: {c} perfiles')
  D: 314 perfiles

QC flags

Cada valor (TEMP, PSAL, PRES) tiene asociado un QC flag. Tabla canónica:

FlagSignificado
'1'Good. Dato bueno.
'2'Probably good.
'3'Probably bad. Usar con cuidado.
'4'Bad. Descartar.
'5'Changed (corrigió un valor).
'8'Estimado (interpolado).
'9'Missing value.

Para análisis científico, lo estándar es quedarse con flags ‘1’ y ‘2’ (good + probably good).

Vamos a usar la variable ajustada y enmascarar lo que no es bueno:

import xarray as xr

# usar ADJUSTED si DATA_MODE != 'R', sino la cruda
def merge_adjusted(ds, var):
    """Devuelve var con valores ADJUSTED donde DATA_MODE != R."""
    mode_is_R = (ds.DATA_MODE.astype(str) == 'R')
    return xr.where(mode_is_R, ds[var], ds[f'{var}_ADJUSTED'])

temp = merge_adjusted(ds, 'TEMP')
psal = merge_adjusted(ds, 'PSAL')
pres = merge_adjusted(ds, 'PRES')

def mask_qc(var, qc, good=('1', '2')):
    qc_str = qc.astype(str)
    mask = xr.zeros_like(qc_str, dtype=bool)
    for g in good:
        mask = mask | (qc_str == g)
    return var.where(mask)

mode_is_R = (ds.DATA_MODE.astype(str) == 'R')
temp_qc = xr.where(mode_is_R, ds.TEMP_QC, ds.TEMP_ADJUSTED_QC)
psal_qc = xr.where(mode_is_R, ds.PSAL_QC, ds.PSAL_ADJUSTED_QC)
pres_qc = xr.where(mode_is_R, ds.PRES_QC, ds.PRES_ADJUSTED_QC)

temp_clean = mask_qc(temp, temp_qc)
psal_clean = mask_qc(psal, psal_qc)
pres_clean = mask_qc(pres, pres_qc)

print('temp shape:', temp_clean.shape)
print('% válidos temperatura:', f'{float(temp_clean.notnull().mean())*100:.1f}%')
print('% válidos salinidad: ', f'{float(psal_clean.notnull().mean())*100:.1f}%')
temp shape: (314, 562)
% válidos temperatura: 99.6%
% válidos salinidad:  99.6%

Esta dupla (merge_adjusted + mask_qc) es el patrón que vas a usar todo el tiempo. Conviene tenerlo a mano.

Para que el código que sigue quede limpio, armamos un Dataset “clean” con todo esto y lo guardamos:

ds_clean = xr.Dataset({
    'TEMP': temp_clean,
    'PSAL': psal_clean,
    'PRES': pres_clean,
}, coords={
    'LATITUDE': ds.LATITUDE,
    'LONGITUDE': ds.LONGITUDE,
    'TIME': ds.TIME,
    'CYCLE_NUMBER': ds.CYCLE_NUMBER,
})
ds_clean
Loading...

Resumen

  • argopy te devuelve formato N_POINTS por defecto. Usá .argo.point2profile() para pasar a N_PROF × N_LEVELS.

  • Decidí siempre por perfil si usás la variable cruda o _ADJUSTED según DATA_MODE.

  • Enmascará por QC: quedate con flags '1' y '2'.

  • Encapsulá esto en funciones (merge_adjusted, mask_qc) y guardá el dataset limpio una vez.

Con el dataset limpio podemos arrancar a hacer ciencia. En el próximo capítulo: perfiles T/S y diagramas T-S.