🦾 Pivot from long to wide format#

Demonstrates the following:

  • Different Polars pivot manuevers to change long form to wide form DataFrames.

Data is returned as a long format, dataframe. This means there is one unique observation (air temperature, humidity, wind speed, etc.) per row. It’s often useful to pivot variables into columns. This can be done with Polars. I encourage users of SynopticPy to become familiar with Polars for DataFrame manipulation.

[1]:
from datetime import datetime, timedelta

import matplotlib.pyplot as plt
import polars as pl
import seaborn as sns

import synoptic
[2]:
s = synoptic.Latest(stid="ukbkb,wbb", vars="air_temp,dew_point_temperature")
df = s.df()
df
🚚💨 Speedy delivery from Synoptic's latest service.
📦 Received data from 2 stations (0.15 seconds).
[2]:
shape: (4, 21)
stidvariablesensor_indexis_derivedvaluedate_timeunitsidnameelevationlatitudelongitudemnet_idstatetimezoneelev_demperiod_of_record_startperiod_of_record_endqc_flaggedis_restrictedis_active
strstru32boolf64datetime[μs, UTC]stru32strf64f64f64u32strstrf64datetime[μs, UTC]datetime[μs, UTC]boolboolbool
"WBB""air_temp"1false0.9722024-12-03 05:30:00 UTC"Celsius"1"U of U William Browning Buildi…4806.040.76623-111.84755153"UT""America/Denver"4727.71997-01-01 00:00:00 UTC2024-12-03 05:05:00 UTCfalsefalsetrue
"UKBKB""air_temp"1false0.02024-12-03 05:30:00 UTC"Celsius"37032"EW2355 Spanish Fork"4734.040.09867-111.6276765"UT""America/Denver"4740.82013-03-13 00:00:00 UTC2024-12-03 05:00:00 UTCfalsefalsetrue
"WBB""dew_point_temperature"1true-3.492024-12-03 05:30:00 UTC"Celsius"1"U of U William Browning Buildi…4806.040.76623-111.84755153"UT""America/Denver"4727.71997-01-01 00:00:00 UTC2024-12-03 05:05:00 UTCfalsefalsetrue
"UKBKB""dew_point_temperature"1true-3.62024-12-03 05:30:00 UTC"Celsius"37032"EW2355 Spanish Fork"4734.040.09867-111.6276765"UT""America/Denver"4740.82013-03-13 00:00:00 UTC2024-12-03 05:00:00 UTCfalsefalsetrue

SynopticPy provides a basic pivot method that likely gets the job done in most cases.

[3]:
df.synoptic.pivot()
[3]:
shape: (2, 7)
date_timestidlatitudelongitudeelevationair_tempdew_point_temperature
datetime[μs, UTC]strf64f64f64f64f64
2024-12-03 05:30:00 UTC"WBB"40.76623-111.847554806.00.972-3.49
2024-12-03 05:30:00 UTC"UKBKB"40.09867-111.627674734.00.0-3.6

With a pivotted DataFrame, you can use the with_wind_uv method.

[4]:
synoptic.TimeSeries(stid="WBB", vars="wind_speed,wind_speed", recent="12h").df()
🚚💨 Speedy delivery from Synoptic's timeseries service.
📦 Received data from 1 stations (0.16 seconds).
[4]:
shape: (713, 21)
stiddate_timevariablesensor_indexis_derivedvalueunitsidnameelevationlatitudelongitudemnet_idstatetimezoneelev_demperiod_of_record_startperiod_of_record_endqc_flaggedis_restrictedis_active
strdatetime[μs, UTC]stru32boolf64stru32strf64f64f64u32strstrf64datetime[μs, UTC]datetime[μs, UTC]boolboolbool
"WBB"2024-12-02 17:39:00 UTC"wind_speed"1false0.962"m/s"1"U of U William Browning Buildi…4806.040.76623-111.84755153"UT""America/Denver"4727.71997-01-01 00:00:00 UTC2024-12-03 05:05:00 UTCfalsefalsetrue
"WBB"2024-12-02 17:40:00 UTC"wind_speed"1false0.561"m/s"1"U of U William Browning Buildi…4806.040.76623-111.84755153"UT""America/Denver"4727.71997-01-01 00:00:00 UTC2024-12-03 05:05:00 UTCfalsefalsetrue
"WBB"2024-12-02 17:41:00 UTC"wind_speed"1false0.386"m/s"1"U of U William Browning Buildi…4806.040.76623-111.84755153"UT""America/Denver"4727.71997-01-01 00:00:00 UTC2024-12-03 05:05:00 UTCfalsefalsetrue
"WBB"2024-12-02 17:42:00 UTC"wind_speed"1false0.427"m/s"1"U of U William Browning Buildi…4806.040.76623-111.84755153"UT""America/Denver"4727.71997-01-01 00:00:00 UTC2024-12-03 05:05:00 UTCfalsefalsetrue
"WBB"2024-12-02 17:43:00 UTC"wind_speed"1false0.571"m/s"1"U of U William Browning Buildi…4806.040.76623-111.84755153"UT""America/Denver"4727.71997-01-01 00:00:00 UTC2024-12-03 05:05:00 UTCfalsefalsetrue
"WBB"2024-12-03 05:27:00 UTC"wind_speed"1false0.509"m/s"1"U of U William Browning Buildi…4806.040.76623-111.84755153"UT""America/Denver"4727.71997-01-01 00:00:00 UTC2024-12-03 05:05:00 UTCfalsefalsetrue
"WBB"2024-12-03 05:28:00 UTC"wind_speed"1false0.458"m/s"1"U of U William Browning Buildi…4806.040.76623-111.84755153"UT""America/Denver"4727.71997-01-01 00:00:00 UTC2024-12-03 05:05:00 UTCfalsefalsetrue
"WBB"2024-12-03 05:29:00 UTC"wind_speed"1false0.319"m/s"1"U of U William Browning Buildi…4806.040.76623-111.84755153"UT""America/Denver"4727.71997-01-01 00:00:00 UTC2024-12-03 05:05:00 UTCfalsefalsetrue
"WBB"2024-12-03 05:30:00 UTC"wind_speed"1false0.0"m/s"1"U of U William Browning Buildi…4806.040.76623-111.84755153"UT""America/Denver"4727.71997-01-01 00:00:00 UTC2024-12-03 05:05:00 UTCfalsefalsetrue
"WBB"2024-12-03 05:35:00 UTC"wind_speed"1falsenull"m/s"1"U of U William Browning Buildi…4806.040.76623-111.84755153"UT""America/Denver"4727.71997-01-01 00:00:00 UTC2024-12-03 05:05:00 UTCfalsefalsetrue
[5]:
df = (
    synoptic.TimeSeries(stid="WBB", vars="wind_speed,wind_direction", recent="12h")
    .df()
    .synoptic.pivot()
    .synoptic.with_wind_uv()
)
df
🚚💨 Speedy delivery from Synoptic's timeseries service.
📦 Received data from 1 stations (0.18 seconds).
[5]:
shape: (713, 9)
date_timestidlatitudelongitudeelevationwind_speedwind_directionwind_uwind_v
datetime[μs, UTC]strf64f64f64f64f64f64f64
2024-12-02 17:39:00 UTC"WBB"40.76623-111.847554806.00.962262.20.9530990.130558
2024-12-02 17:40:00 UTC"WBB"40.76623-111.847554806.00.561304.10.464542-0.314518
2024-12-02 17:41:00 UTC"WBB"40.76623-111.847554806.00.386343.80.107691-0.370673
2024-12-02 17:42:00 UTC"WBB"40.76623-111.847554806.00.427301.70.363296-0.224376
2024-12-02 17:43:00 UTC"WBB"40.76623-111.847554806.00.571270.40.570986-0.003986
2024-12-03 05:27:00 UTC"WBB"40.76623-111.847554806.00.509204.90.2143070.461685
2024-12-03 05:28:00 UTC"WBB"40.76623-111.847554806.00.458218.40.2844860.358932
2024-12-03 05:29:00 UTC"WBB"40.76623-111.847554806.00.319216.90.1915340.255099
2024-12-03 05:30:00 UTC"WBB"40.76623-111.847554806.00.0nullnullnull
2024-12-03 05:35:00 UTC"WBB"40.76623-111.847554806.0nullnullnullnull
[ ]:

Customized pivots#

[6]:
df = synoptic.TimeSeries(stid="WBB", vars="wind_speed,wind_speed", recent="12h").df()

df.pivot(
    on="variable",
    index=["date_time", "stid", "latitude", "longitude", "elevation"],
    values="value",
)
🚚💨 Speedy delivery from Synoptic's timeseries service.
📦 Received data from 1 stations (0.17 seconds).
[6]:
shape: (713, 6)
date_timestidlatitudelongitudeelevationwind_speed
datetime[μs, UTC]strf64f64f64f64
2024-12-02 17:39:00 UTC"WBB"40.76623-111.847554806.00.962
2024-12-02 17:40:00 UTC"WBB"40.76623-111.847554806.00.561
2024-12-02 17:41:00 UTC"WBB"40.76623-111.847554806.00.386
2024-12-02 17:42:00 UTC"WBB"40.76623-111.847554806.00.427
2024-12-02 17:43:00 UTC"WBB"40.76623-111.847554806.00.571
2024-12-03 05:27:00 UTC"WBB"40.76623-111.847554806.00.509
2024-12-03 05:28:00 UTC"WBB"40.76623-111.847554806.00.458
2024-12-03 05:29:00 UTC"WBB"40.76623-111.847554806.00.319
2024-12-03 05:30:00 UTC"WBB"40.76623-111.847554806.00.0
2024-12-03 05:35:00 UTC"WBB"40.76623-111.847554806.0null

Special Case: Multiple Sensors#

If your set of stations has multiple sensors the measure the same variable (e.g., NAA measures outdoor air temperature and greenhouse air temperature) you will need to take care with how you perform your pivot. Either filter the rows for a single sensor ID, or pivot on both the variable and sensor columns.

[7]:
# Notice that NAA has two different reports for air temperature
df = synoptic.Latest(stid="naa", vars="air_temp,wind_speed").df()
df
🚚💨 Speedy delivery from Synoptic's latest service.
📦 Received data from 1 stations (0.15 seconds).
[7]:
shape: (3, 21)
stidvariablesensor_indexis_derivedvaluedate_timeunitsidnameelevationlatitudelongitudemnet_idstatetimezoneelev_demperiod_of_record_startperiod_of_record_endqc_flaggedis_restrictedis_active
strstru32boolf64datetime[μs, UTC]stru32strf64f64f64u32strstrf64datetime[μs, UTC]datetime[μs, UTC]boolboolbool
"NAA""air_temp"1false-2.0172024-12-03 05:30:00 UTC"Celsius"43136"Neil Armstrong Academy"4260.040.71152-112.01448153"UT""America/Denver"4252.02014-05-27 00:00:00 UTC2024-12-03 05:00:00 UTCfalsefalsetrue
"NAA""air_temp"2false2.5562024-12-03 05:30:00 UTC"Celsius"43136"Neil Armstrong Academy"4260.040.71152-112.01448153"UT""America/Denver"4252.02014-05-27 00:00:00 UTC2024-12-03 05:00:00 UTCfalsefalsetrue
"NAA""wind_speed"1false0.192024-12-03 05:30:00 UTC"m/s"43136"Neil Armstrong Academy"4260.040.71152-112.01448153"UT""America/Denver"4252.02014-05-27 00:00:00 UTC2024-12-03 05:00:00 UTCfalsefalsetrue
[8]:
# Filter sensor ID, then pivot
df.filter(sensor_index=1).pivot(
    "variable", index=["stid", "date_time", "latitude", "longitude"], values="value"
)
[8]:
shape: (1, 6)
stiddate_timelatitudelongitudeair_tempwind_speed
strdatetime[μs, UTC]f64f64f64f64
"NAA"2024-12-03 05:30:00 UTC40.71152-112.01448-2.0170.19
[9]:
# Pivot on both variable and sensor column
df.pivot(
    ["variable", "sensor_index"],
    index=["stid", "date_time", "latitude", "longitude"],
    values="value",
)
[9]:
shape: (1, 7)
stiddate_timelatitudelongitude{"air_temp",1}{"air_temp",2}{"wind_speed",1}
strdatetime[μs, UTC]f64f64f64f64f64
"NAA"2024-12-03 05:30:00 UTC40.71152-112.01448-2.0172.5560.19
[10]:
df.pivot(
    ["variable", "sensor_index"],
    index=["stid", "date_time", "latitude", "longitude"],
    values="value",
).columns

[10]:
['stid',
 'date_time',
 'latitude',
 'longitude',
 '{"air_temp",1}',
 '{"air_temp",2}',
 '{"wind_speed",1}']

Special Case: Measured and derived values#

Some stations have both measured and derived variables, like dew point temperature. Your pivot options are

  1. Filter to use only the measured value

  2. Pivot on both variable and derived columns.

  3. Use an aggregattion function to take mean or first of the two values.

[11]:
df = synoptic.Latest(stid="KU69", vars="wind_speed,dew_point_temperature").df()
df
🚚💨 Speedy delivery from Synoptic's latest service.
📦 Received data from 1 stations (0.23 seconds).
[11]:
shape: (3, 21)
stidvariablesensor_indexis_derivedvaluedate_timeunitsidnameelevationlatitudelongitudemnet_idstatetimezoneelev_demperiod_of_record_startperiod_of_record_endqc_flaggedis_restrictedis_active
strstru32boolf64datetime[μs, UTC]stru32strf64f64f64u32strstrf64datetime[μs, UTC]datetime[μs, UTC]boolboolbool
"KU69""dew_point_temperature"1false-8.02024-12-03 05:15:00 UTC"Celsius"6470"DUCHESNE"5826.040.1919-110.380991"UT""America/Denver"5810.42007-10-11 00:00:00 UTC2024-12-03 04:35:00 UTCfalsefalsetrue
"KU69""wind_speed"1false2.5722024-12-03 05:15:00 UTC"m/s"6470"DUCHESNE"5826.040.1919-110.380991"UT""America/Denver"5810.42007-10-11 00:00:00 UTC2024-12-03 04:35:00 UTCfalsefalsetrue
"KU69""dew_point_temperature"1true-8.092024-12-03 05:15:00 UTC"Celsius"6470"DUCHESNE"5826.040.1919-110.380991"UT""America/Denver"5810.42007-10-11 00:00:00 UTC2024-12-03 04:35:00 UTCfalsefalsetrue
[12]:
# Pivot, calculate the mean if more than one value per variable
df.pivot("variable", index="stid", values="value", aggregate_function="mean")
[12]:
shape: (1, 3)
stiddew_point_temperaturewind_speed
strf64f64
"KU69"-8.0452.572
[13]:
# Pivot, use the first value if more than one value per variable
df.pivot("variable", index="stid", values="value", aggregate_function="first")
[13]:
shape: (1, 3)
stiddew_point_temperaturewind_speed
strf64f64
"KU69"-8.02.572
[14]:
# Pivot on both variable and derived columns
df.sort("variable").pivot(
    ["variable", "is_derived"],
    index=["stid", "latitude", "longitude"],
    values="value",
)
[14]:
shape: (1, 6)
stidlatitudelongitude{"dew_point_temperature",false}{"dew_point_temperature",true}{"wind_speed",false}
strf64f64f64f64f64
"KU69"40.1919-110.38099-8.0-8.092.572

Pivot many station variables#

Let’s pivot requests with more station data…

[15]:
df = synoptic.Latest(
    state="UT",
    network=[1, 2],
    vars="air_temp,dew_point_temperature",
    units="english",
    complete=1,  # to get mesonet shortname
).df()

# Pivot
df = df.pivot(
    "variable",
    index=["stid", "latitude", "longitude", "shortname"],
    values="value",
    aggregate_function="first",
)
df
🚚💨 Speedy delivery from Synoptic's latest service.
📦 Received data from 130 stations (0.22 seconds).
[15]:
shape: (130, 6)
stidlatitudelongitudeshortnameair_tempdew_point_temperature
strf64f64strf64f64
"KSLC"40.77069-111.96503"ASOS/AWOS"33.9827.95
"KU42"40.6196-111.99016"ASOS/AWOS"30.7425.99
"KHIF"41.11112-111.96229"ASOS/AWOS"33.825.57
"KOGD"41.19406-112.01681"ASOS/AWOS"33.824.67
"KBMC"41.5464-112.0601"ASOS/AWOS"28.5824.01
"TT509"37.32322-112.18486"RAWS"32.015.26
"KSPK"40.145-111.6677"ASOS/AWOS"37.422.8
"TT773"38.62606-111.9419"RAWS"22.0-3.57
"KU64"37.93243-109.34122"ASOS/AWOS"27.1417.82
"K40U"40.9833-109.6833"ASOS/AWOS"18.3215.75
[16]:
# Plot on a map

from herbie.toolbox import EasyMap, ccrs, pc

ax = EasyMap("10m", figsize=(8, 8)).STATES().LAKES().ROADS().ax
art = sns.scatterplot(
    df.filter(pl.col("longitude") < -60),  # One station has bad longitude metadata
    ax=ax,
    x="longitude",
    y="latitude",
    hue="air_temp",
    style="shortname",
    edgecolor="k",
    palette="Spectral_r",
)
/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()
../../_images/user_guide_gallery_how-to-pivot_23_1.png
[ ]: