⛴️ Winds in Chesapeake Bay#

Demonstrates pivoting, rolling averages, and wind vectors

[1]:
from datetime import datetime

import matplotlib.pyplot as plt
import polars as pl
import seaborn as sns
from herbie.toolbox import EasyMap

import synoptic
/home/blaylock/miniconda3/envs/synoptic2/lib/python3.12/site-packages/pyproj/__init__.py:89: UserWarning: pyproj unable to set database path.
  _pyproj_global_context_initialize()
[2]:
df = synoptic.TimeSeries(
    start=datetime(2017, 8, 1),
    end=datetime(2017, 8, 4),
    vars=["wind_speed", "wind_direction", "wind_gust"],
    stid=["YKRV2", "44072", "WDSV2", "CBBV2"],
).df()
df
🚚💨 Speedy delivery from Synoptic timeseries service.
📦 Received data from 4 stations.
[2]:
shape: (6_666, 20)
date_timevariablesensor_indexis_derivedvalueunitsidstidnameelevationlatitudelongitudemnet_idstatetimezoneelev_demperiod_of_record_startperiod_of_record_endis_restrictedis_active
datetime[μs, UTC]stru32boolf64stru32strstrf64f64f64u32strstrf64datetime[μs, UTC]datetime[μs, UTC]boolbool
2017-08-01 00:00:00 UTC"wind_gust"1false4.02"m/s"23426"YKRV2""York River East Rear Range Lig…0.037.25-76.3333121"VA""America/New_York"0.02016-12-14 16:49:00 UTC2023-08-16 12:54:00 UTCfalsefalse
2017-08-01 00:06:00 UTC"wind_gust"1false4.47"m/s"23426"YKRV2""York River East Rear Range Lig…0.037.25-76.3333121"VA""America/New_York"0.02016-12-14 16:49:00 UTC2023-08-16 12:54:00 UTCfalsefalse
2017-08-01 00:12:00 UTC"wind_gust"1false3.58"m/s"23426"YKRV2""York River East Rear Range Lig…0.037.25-76.3333121"VA""America/New_York"0.02016-12-14 16:49:00 UTC2023-08-16 12:54:00 UTCfalsefalse
2017-08-01 00:18:00 UTC"wind_gust"1false4.47"m/s"23426"YKRV2""York River East Rear Range Lig…0.037.25-76.3333121"VA""America/New_York"0.02016-12-14 16:49:00 UTC2023-08-16 12:54:00 UTCfalsefalse
2017-08-01 00:24:00 UTC"wind_gust"1false4.47"m/s"23426"YKRV2""York River East Rear Range Lig…0.037.25-76.3333121"VA""America/New_York"0.02016-12-14 16:49:00 UTC2023-08-16 12:54:00 UTCfalsefalse
2017-08-03 23:20:00 UTC"wind_gust"1false9.99"m/s"60116"44072""York Spit"0.037.2-76.396"VA""America/New_York"0.02016-07-22 17:08:00 UTC2024-11-10 04:54:00 UTCfalsetrue
2017-08-03 23:30:00 UTC"wind_gust"1false8.99"m/s"60116"44072""York Spit"0.037.2-76.396"VA""America/New_York"0.02016-07-22 17:08:00 UTC2024-11-10 04:54:00 UTCfalsetrue
2017-08-03 23:40:00 UTC"wind_gust"1false8.99"m/s"60116"44072""York Spit"0.037.2-76.396"VA""America/New_York"0.02016-07-22 17:08:00 UTC2024-11-10 04:54:00 UTCfalsetrue
2017-08-03 23:50:00 UTC"wind_gust"1false8.99"m/s"60116"44072""York Spit"0.037.2-76.396"VA""America/New_York"0.02016-07-22 17:08:00 UTC2024-11-10 04:54:00 UTCfalsetrue
2017-08-04 00:00:00 UTC"wind_gust"1false8.99"m/s"60116"44072""York Spit"0.037.2-76.396"VA""America/New_York"0.02016-07-22 17:08:00 UTC2024-11-10 04:54:00 UTCfalsetrue
[3]:
df.select("stid", "latitude", "longitude").unique()
[3]:
shape: (4, 3)
stidlatitudelongitude
strf64f64
"CBBV2"36.96667-76.11333
"WDSV2"36.9817-76.3217
"44072"37.2-76.3
"YKRV2"37.25-76.3333
[4]:
ax = EasyMap(figsize=(8, 8)).OSM(zoom=10).ax

sns.scatterplot(
    df.select("stid", "latitude", "longitude").unique(),
    x="longitude",
    y="latitude",
    style="stid",
    hue="stid",
    s=200,
)
[4]:
<GeoAxes: xlabel='longitude', ylabel='latitude'>
../../_images/user_guide_gallery_winds-chesapeake-bay_4_1.png
[5]:
sns.lineplot(
    df.filter(variable="wind_speed"),
    x="date_time",
    y="value",
    hue="stid",
)
[5]:
<Axes: xlabel='date_time', ylabel='value'>
../../_images/user_guide_gallery_winds-chesapeake-bay_5_1.png
[6]:
# Compute 60-min average wind speed

df_smoothed = (
    df.filter(variable="wind_speed", sensor_index=1)
    .sort("date_time")
    .rolling("date_time", period="60m", group_by="stid")
    .agg(pl.col("value").mean())
)

sns.lineplot(
    df_smoothed,
    x="date_time",
    y="value",
    hue="stid",
)
[6]:
<Axes: xlabel='date_time', ylabel='value'>
../../_images/user_guide_gallery_winds-chesapeake-bay_6_1.png

Pivot#

Let’s focus on one station and pivot the data to get variables in columns

[7]:
stid = "CBBV2"

a = (
    df.filter(stid=stid)  # Only focus on one station
    .synoptic.pivot()  # Pivot variable values into columns
    .synoptic.with_wind_uv()  # compute u and v wind component
)
a
[7]:
shape: (613, 10)
date_timestidlatitudelongitudeelevationwind_directionwind_speedwind_gustwind_uwind_v
datetime[μs, UTC]strf64f64f64f64f64f64f64f64
2017-08-01 00:00:00 UTC"CBBV2"36.96667-76.113330.0139.02.684.91-1.7582382.022622
2017-08-01 00:06:00 UTC"CBBV2"36.96667-76.113330.0133.03.584.91-2.6182462.441554
2017-08-01 00:12:00 UTC"CBBV2"36.96667-76.113330.0138.03.134.47-2.0943792.326043
2017-08-01 00:18:00 UTC"CBBV2"36.96667-76.113330.0140.02.234.02-1.4334161.708279
2017-08-01 00:24:00 UTC"CBBV2"36.96667-76.113330.0146.02.233.58-1.2471.848754
2017-08-03 23:30:00 UTC"CBBV2"36.96667-76.113330.0166.05.817.15-1.4055665.637418
2017-08-03 23:36:00 UTC"CBBV2"36.96667-76.113330.0166.06.267.59-1.5144316.074051
2017-08-03 23:42:00 UTC"CBBV2"36.96667-76.113330.0164.05.816.7-1.6014535.58493
2017-08-03 23:48:00 UTC"CBBV2"36.96667-76.113330.0166.05.817.15-1.4055665.637418
2017-08-04 00:00:00 UTC"CBBV2"36.96667-76.113330.0173.05.367.15-0.653225.320047
[ ]:
fig, ax = plt.subplots(1, 1, figsize=(10, 4))

ax.fill_between(a["date_time"], a["wind_speed"], a["wind_gust"], color=".8")
ax.plot(a["date_time"], a["wind_speed"], color="tab:blue")

ax.quiver(
    a["date_time"],
    a["wind_speed"] * 0 + 11,
    a["wind_u"],
    a["wind_v"],
    a["wind_speed"],
    cmap="Greens",
)

ax.set_ylim(ymin=0)
ax.set_xlim(a["date_time"].min(), a["date_time"].max())
ax.set_ylabel("Wind Speed (m/s)")
Text(0, 0.5, 'Wind Speed (m/s)')
../../_images/user_guide_gallery_winds-chesapeake-bay_9_1.png
[12]:
# Rolling
rolling_60m = a.rolling("date_time", period="60m", group_by="stid").agg(
    pl.col("wind_speed", "wind_u", "wind_v").mean(), pl.len()
)
rolling_60m
[12]:
shape: (613, 6)
stiddate_timewind_speedwind_uwind_vlen
strdatetime[μs, UTC]f64f64f64u32
"CBBV2"2017-08-01 00:00:00 UTC2.68-1.7582382.0226221
"CBBV2"2017-08-01 00:06:00 UTC3.13-2.1882422.2320882
"CBBV2"2017-08-01 00:12:00 UTC3.13-2.1569542.2634063
"CBBV2"2017-08-01 00:18:00 UTC2.905-1.976072.1246254
"CBBV2"2017-08-01 00:24:00 UTC2.77-1.8302562.069455
"CBBV2"2017-08-03 23:30:00 UTC5.363-1.4102435.11295310
"CBBV2"2017-08-03 23:36:00 UTC5.721-1.3755185.52757510
"CBBV2"2017-08-03 23:42:00 UTC5.944-1.3566635.77603110
"CBBV2"2017-08-03 23:48:00 UTC5.899-1.2934145.74787810
"CBBV2"2017-08-04 00:00:00 UTC5.858889-1.1870415.7299939
[ ]:
# Group_by_dynamic
grouped_60m = a.group_by_dynamic(
    "date_time",
    every="60m",
    group_by="stid",
    include_boundaries=True,
).agg(pl.col("wind_speed", "wind_u", "wind_v").mean(), pl.len())

grouped_60m
shape: (73, 8)
stid_lower_boundary_upper_boundarydate_timewind_speedwind_uwind_vlen
strdatetime[μs, UTC]datetime[μs, UTC]datetime[μs, UTC]f64f64f64u32
"CBBV2"2017-08-01 00:00:00 UTC2017-08-01 01:00:00 UTC2017-08-01 00:00:00 UTC3.038-1.7241222.46074310
"CBBV2"2017-08-01 01:00:00 UTC2017-08-01 02:00:00 UTC2017-08-01 01:00:00 UTC4.11-0.2196244.06587210
"CBBV2"2017-08-01 02:00:00 UTC2017-08-01 03:00:00 UTC2017-08-01 02:00:00 UTC3.8890.9456653.72492310
"CBBV2"2017-08-01 03:00:00 UTC2017-08-01 04:00:00 UTC2017-08-01 03:00:00 UTC3.8881.8430563.40302410
"CBBV2"2017-08-01 04:00:00 UTC2017-08-01 05:00:00 UTC2017-08-01 04:00:00 UTC4.13252.3765483.3635128
"CBBV2"2017-08-03 20:00:00 UTC2017-08-03 21:00:00 UTC2017-08-03 20:00:00 UTC3.888-2.6524062.80227410
"CBBV2"2017-08-03 21:00:00 UTC2017-08-03 22:00:00 UTC2017-08-03 21:00:00 UTC4.023-2.7384622.93450810
"CBBV2"2017-08-03 22:00:00 UTC2017-08-03 23:00:00 UTC2017-08-03 22:00:00 UTC3.889-2.1306293.16159110
"CBBV2"2017-08-03 23:00:00 UTC2017-08-04 00:00:00 UTC2017-08-03 23:00:00 UTC5.908889-1.2376395.7725719
"CBBV2"2017-08-04 00:00:00 UTC2017-08-04 01:00:00 UTC2017-08-04 00:00:00 UTC5.36-0.653225.3200471
[ ]:
fig, ax = plt.subplots(1, 1, figsize=(10, 4))

ax.fill_between(a["date_time"], a["wind_speed"], color=".9", label="raw")

ax.plot(
    rolling_60m["date_time"],
    rolling_60m["wind_speed"],
    color="tab:blue",
    label="Rolling 60m Mean",
)

# I'm using `_upper_boundary` as the time value which says "at this
# time, the average wind speed for the previous hour was 'x'".
ax.plot(
    grouped_60m["_upper_boundary"],
    grouped_60m["wind_speed"],
    color="tab:orange",
    label="Grouped 60m Mean",
    marker="s",
    markersize=3,
)

ax.set_ylim(ymin=0)
ax.set_xlim(a["date_time"].min(), a["date_time"].max())
ax.set_ylabel("Wind Speed (m/s)")

ax.legend()
<matplotlib.legend.Legend at 0x7ff511d3a600>
../../_images/user_guide_gallery_winds-chesapeake-bay_12_1.png
[47]:
# Plot quiver

fig, ax = plt.subplots(1, 1, figsize=(10, 4))

ax.quiver(
    grouped_60m["date_time"],
    grouped_60m["wind_speed"] * 0,
    grouped_60m["wind_u"],
    grouped_60m["wind_v"],
    grouped_60m["wind_speed"],
    cmap="Greens",
)

ax.set_ylim(-1, 1)
ax.set_title("60m Mean Wind Vector, colored by Speed")

ax.set_yticks([])
[47]:
[]
../../_images/user_guide_gallery_winds-chesapeake-bay_13_1.png