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))
dsprofile 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))
Variables clave¶
El dataset trae, además de las coordenadas (LATITUDE, LONGITUDE, TIME, PRES), las siguientes familias de variables:
| Familia | Qué es |
|---|---|
TEMP, PSAL, PRES | Variables crudas (real-time). |
TEMP_ADJUSTED, PSAL_ADJUSTED, PRES_ADJUSTED | Variables con ajustes aplicados (calibración delayed-mode si está disponible). |
*_QC, *_ADJUSTED_QC | Flags de calidad (string, valores ‘1’-‘9’). |
*_ERROR | Error estimado del valor ajustado (solo delayed-mode). |
DATA_MODE | Por 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:
| Flag | Significado |
|---|---|
'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_cleanResumen¶
argopyte devuelve formatoN_POINTSpor defecto. Usá.argo.point2profile()para pasar aN_PROF × N_LEVELS.Decidí siempre por perfil si usás la variable cruda o
_ADJUSTEDsegúnDATA_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.