Chris Parmer — home

Wind rose

Iowa Mesonet ASOS, station DSM, full year 2023

Example from the compendium of canonical charts

Wind rose — Iowa Mesonet ASOS, station DSM, full year 2023

Python Code

"""Wind rose — Iowa Mesonet ASOS, station DSM, full year 2023."""


import numpy as np
import plotly.graph_objects as go

URL = (
    "https://mesonet.agron.iastate.edu/cgi-bin/request/asos.py"
    "?station=DSM&data=drct&data=sknt"
    "&year1=2023&month1=1&day1=1"
    "&year2=2024&month2=1&day2=1"
    "&tz=Etc/UTC&format=onlycomma&missing=M&trace=T"
)

SPEED_BINS = [
    # (label,  lo_m/s, hi_m/s, color)  — solid (opaque) violet shades light→dark,
    # so the polar grid lines don't show through the bars.
    ("Calm",   0,      0.5,   "rgb(237, 231, 252)"),
    ("0–5",    0.5,    5,     "rgb(212, 199, 249)"),
    ("5–10",   5,      10,    "rgb(184, 162, 245)"),
    ("10–15",  10,     15,    "rgb(157, 126, 241)"),
    (">15",    15,     999,   VIOLET),
]

COMPASS_16 = ["N","NNE","NE","ENE","E","ESE","SE","SSE",
              "S","SSW","SW","WSW","W","WNW","NW","NNW"]


def generate():
    print(f"fetching Iowa Mesonet DSM 2023 …")
    df = fetch_csv(URL, na_values=["M", "T"])
    df = df.dropna(subset=["drct", "sknt"])
    df["ws_ms"] = df["sknt"] * 0.5144  # knots → m/s

    n_total = len(df)
    n_calm = (df["ws_ms"] < 0.5).sum()
    width = 360 / 16
    centers = np.arange(0, 360, width)  # 0°=N, clockwise

    traces = []
    for label, lo, hi, color in SPEED_BINS:
        sub = df[(df["ws_ms"] >= lo) & (df["ws_ms"] < hi)]
        rs = [
            ((sub["drct"] - c + 180) % 360 - 180).abs().lt(width / 2).sum() / n_total * 100
            for c in centers
        ]
        traces.append(go.Barpolar(
            r=rs,
            theta=centers,
            width=width,
            name=label,
            marker_color=color,
            hovertemplate="%{theta:.0f}°: %{r:.2f}%<extra>" + label + " m/s</extra>",
        ))

    fig = go.Figure(traces)
    fig.update_layout(
        title=dict(text="Wind Rose — Des Moines (DSM), 2023", x=0.5),
        polar=dict(
            angularaxis=dict(
                tickvals=list(centers),
                ticktext=COMPASS_16,
                direction="clockwise",
                rotation=90,
            ),
            radialaxis=dict(ticksuffix="%"),
        ),
        legend=dict(title="Speed (m/s)"),
        margin=dict(t=60, b=50, l=40, r=40),
        height=500,
        annotations=[dict(
            x=0, y=-0.1, xref="paper", yref="paper", showarrow=False,
            text=f"Calm ({n_calm / n_total * 100:.1f}% of obs) excluded from sectors.",
            font=dict(size=11, color="#666"),
        )],
    )
    return fig


if __name__ == "__main__":
    generate()

Made with Plotly