Saltar a contenido

Rosas de viento y diagramas polares

Las rosas de dirección y los diagramas polares son las figuras más características del análisis oceanográfico. Se usan para visualizar la distribución de velocidades y direcciones del viento, corrientes y oleaje.

Rosa de viento con windrose

La librería windrose genera rosas directamente desde arrays de velocidad y dirección:

from windrose import WindroseAxes
import matplotlib.pyplot as plt
import numpy as np

fig = plt.figure(figsize=(7, 7))
ax = WindroseAxes.from_ax(fig=fig)

ax.bar(
    direccion,           # array de direcciones (°, convención meteorológica: FROM)
    velocidad,           # array de velocidades (m/s)
    normed=True,         # mostrar como porcentaje
    opening=0.9,
    edgecolor='white',
    bins=[0, 2, 4, 6, 8, 10],   # intervalos de velocidad
    cmap=plt.cm.YlOrRd
)

ax.set_legend(title='Velocidad (m/s)', loc='lower right')
ax.set_title('Rosa de viento — Los Vilos')

fig.savefig('rosa_viento.png', dpi=150, bbox_inches='tight')
plt.close(fig)

Rosa de corrientes (sin windrose)

Para corrientes se suele hacer una rosa manual usando proyección polar de Matplotlib, con más control sobre el diseño:

import matplotlib.pyplot as plt
import numpy as np

def rosa_corrientes(direcciones, velocidades, titulo='', ax=None):
    """Rosa de corrientes en 8 octantes."""
    octantes = np.arange(0, 360, 45)
    etiquetas = ['N', 'NE', 'E', 'SE', 'S', 'SO', 'O', 'NO']
    colores_vel = plt.cm.Blues(np.linspace(0.3, 1.0, 4))
    bins_vel = [0, 0.05, 0.1, 0.2, np.inf]
    labels_vel = ['0–0.05', '0.05–0.1', '0.1–0.2', '>0.2']

    if ax is None:
        fig, ax = plt.subplots(subplot_kw={'projection': 'polar'}, figsize=(6, 6))

    # Orientar norte arriba, sentido horario
    ax.set_theta_zero_location('N')
    ax.set_theta_direction(-1)

    ancho = np.radians(45) * 0.85
    bottom = np.zeros(8)

    for i, (vmin, vmax) in enumerate(zip(bins_vel[:-1], bins_vel[1:])):
        mask = (velocidades >= vmin) & (velocidades < vmax)
        conteos = np.zeros(8)
        for j, oct in enumerate(octantes):
            mask_dir = (direcciones >= oct - 22.5) & (direcciones < oct + 22.5)
            conteos[j] = np.sum(mask & mask_dir)

        pct = conteos / len(velocidades) * 100
        theta = np.radians(octantes)
        ax.bar(theta, pct, width=ancho, bottom=bottom,
               color=colores_vel[i], label=labels_vel[i], edgecolor='white', linewidth=0.5)
        bottom += pct

    ax.set_xticks(np.radians(octantes))
    ax.set_xticklabels(etiquetas)
    ax.set_title(titulo, pad=15)
    ax.legend(title='Vel (m/s)', loc='lower right', bbox_to_anchor=(1.3, -0.1))

    return ax

Diagrama polar de dispersión

Muestra la distribución conjunta de velocidad y dirección, con cada punto representando un registro:

fig, ax = plt.subplots(subplot_kw={'projection': 'polar'}, figsize=(7, 7))
ax.set_theta_zero_location('N')
ax.set_theta_direction(-1)

sc = ax.scatter(
    np.radians(direccion),
    velocidad,
    c=velocidad,           # color según velocidad
    cmap='YlOrRd',
    s=3,
    alpha=0.4
)

# Marcar el máximo
idx_max = np.argmax(velocidad)
ax.scatter(np.radians(direccion[idx_max]), velocidad[idx_max],
           c='red', s=80, zorder=5, label=f'Máx: {velocidad[idx_max]:.2f} m/s')

fig.colorbar(sc, ax=ax, label='Velocidad (m/s)', shrink=0.7)
ax.set_title('Diagrama polar de dispersión')
ax.legend(loc='lower right')

Vector progresivo

El vector progresivo acumula el desplazamiento (U, V) a lo largo del tiempo, mostrando la trayectoria resultante:

def vector_progresivo(velocidad, direccion, dt_horas=0.167):
    """
    Calcula el vector progresivo.
    dt_horas: intervalo entre registros en horas (10 min = 0.167 h)
    """
    u = velocidad * np.sin(np.radians(direccion))
    v = velocidad * np.cos(np.radians(direccion))

    # Desplazamiento acumulado en km
    x = np.cumsum(u * dt_horas * 3.6)   # m/s × h × 3.6 → km
    y = np.cumsum(v * dt_horas * 3.6)

    return x, y

# Graficar
x, y = vector_progresivo(vel_prof, dir_prof)

fig, ax = plt.subplots(figsize=(6, 6))
ax.plot(x, y, color='steelblue', linewidth=0.8)
ax.plot(0, 0, 'go', markersize=8, label='Inicio')
ax.plot(x[-1], y[-1], 'r*', markersize=12, label=f'Final: {np.sqrt(x[-1]**2+y[-1]**2):.1f} km')
ax.axhline(0, color='gray', linewidth=0.5)
ax.axvline(0, color='gray', linewidth=0.5)
ax.set_xlabel('Desplazamiento E–O (km)')
ax.set_ylabel('Desplazamiento N–S (km)')
ax.set_aspect('equal')
ax.legend()
ax.set_title('Vector progresivo')

Patrón diurno

Gráfico de líneas por mes mostrando el ciclo horario de la velocidad:

import pandas as pd

# df tiene índice DatetimeIndex
patron = df.groupby([df.index.month, df.index.hour])['velocidad'].mean().unstack(level=0)
# patron: filas=hora (0–23), columnas=mes (1–12)

nombres_meses = {9:'Sep', 10:'Oct', 11:'Nov', 12:'Dic', 1:'Ene', 2:'Feb', 3:'Mar'}
colores_meses = plt.cm.tab10(np.linspace(0, 1, 12))

fig, ax = plt.subplots(figsize=(10, 5))

for mes, col in zip(patron.columns, colores_meses):
    ax.plot(patron.index, patron[mes], label=nombres_meses.get(mes, mes),
            color=col, linewidth=1.2)

# Promedio global
ax.plot(patron.index, patron.mean(axis=1),
        color='black', linewidth=2.5, linestyle='--', label='Promedio global')

ax.set_xlabel('Hora del día (UTC-3)')
ax.set_ylabel('Velocidad media (m/s)')
ax.set_title('Patrón diurno de velocidad del viento')
ax.set_xticks(range(0, 24, 2))
ax.set_xticklabels([f'{h:02d}:00' for h in range(0, 24, 2)], rotation=45)
ax.legend(ncol=4, fontsize=8)
ax.grid(True, alpha=0.3)
fig.tight_layout()

Convención de dirección

En meteorología la dirección indica desde dónde viene el viento (FROM). En oceanografía de corrientes, la convención es hacia dónde va (TO). Al graficar rosas, hay que tener claro cuál convención se está usando para que la rosa tenga sentido físico.