πŸ˜Άβ€πŸŒ«οΈ Utah Air Quality#

Demonstrates the following:

  • Getting timeseries and latest data

  • Plotting with custom color scale

  • Plotting with Polars built-in plotting (Altair)

  • Making a summary table with Great Tables

[2]:
from datetime import datetime

import matplotlib.pyplot as plt
import polars as pl
import seaborn as sns
from great_tables import GT
from herbie.toolbox import EasyMap
from matplotlib.colors import BoundaryNorm, LinearSegmentedColormap, ListedColormap

import synoptic

Ozone map#

Create a custom colormap for ozone

[3]:
colors = [
    "#00e400",  # Green   - Good
    "#ffff00",  # Yellow  - Moderate
    "#ff7e00",  # Orange  - Sensitive Groups
    "#ff0000",  # Red     - Unhealthy
    "#99004c",  # Purple  - Hazardous
    "#4c0026",  # Maroon  - Very Unhealthy
]
bounds = [0, 55, 71, 86, 106, 201, 300]
ozone_cmap = LinearSegmentedColormap.from_list("ozone", colors, N=len(colors))
norm = BoundaryNorm(bounds, ozone_cmap.N)
ozone_cmap

[3]:
ozone
ozone colormap
under
bad
over

Get Ozone air quality data at a specific time

[4]:
df = synoptic.NearestTime(
    attime=datetime(2024, 7, 23, 18),
    within="30m",
    network=9,  # Utah's Division of Air Quality
    vars="ozone_concentration",
).df()
df = df.filter(sensor_index=1).sort("value", descending=True)
df
πŸššπŸ’¨ Speedy delivery from Synoptic's nearesttime service.
πŸ“¦ Received data from 21 stations.
[4]:
shape: (19, 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
2024-07-23 18:00:00 UTC"ozone_concentration"1false77.0"ppb"220329"QUURB""UofU Red Butte"4996.040.76652-111.828369"UT""America/Denver"null2023-09-01 18:00:00 UTC2024-11-14 03:00:00 UTCfalsetrue
2024-07-23 18:00:00 UTC"ozone_concentration"1false75.0"ppb"31846"QRP""Rose Park"4232.040.7955-111.93099"UT""America/Denver"4225.72016-12-07 16:10:00 UTC2024-11-14 03:00:00 UTCfalsetrue
2024-07-23 18:00:00 UTC"ozone_concentration"1false73.0"ppb"10330"QBV""Bountiful - Viewmont"4304.040.9029-111.88449"UT""America/Denver"4297.92004-06-09 00:00:00 UTC2024-11-14 03:00:00 UTCfalsetrue
2024-07-23 18:00:00 UTC"ozone_concentration"1false73.0"ppb"31843"QP2""Price"5682.039.5958-110.779"UT""America/Denver"5695.52011-11-01 00:00:00 UTC2024-11-14 03:00:00 UTCfalsetrue
2024-07-23 18:00:00 UTC"ozone_concentration"1false70.0"ppb"152942"QUTTC""Utah Technical Center"4232.040.7771-111.9459"UT""America/Denver"4232.32020-06-29 19:12:00 UTC2024-11-14 03:00:00 UTCfalsetrue
2024-07-23 18:00:00 UTC"ozone_concentration"1false57.0"ppb"47907"QSM""Smithfield"4508.041.84278-111.851949"UT""America/Denver"4507.92015-12-04 19:29:00 UTC2024-11-14 03:00:00 UTCfalsetrue
2024-07-23 18:00:00 UTC"ozone_concentration"1false57.0"ppb"70743"QCV""Copperview"4409.040.59806-111.894179"UT""America/Denver"4409.42018-05-28 02:33:00 UTC2024-11-14 03:00:00 UTCfalsetrue
2024-07-23 18:00:00 UTC"ozone_concentration"1false54.0"ppb"584"QEN""Enoch"5558.037.74745-113.055529"UT""America/Denver"5551.22018-01-24 20:05:00 UTC2024-11-14 03:00:00 UTCfalsetrue
2024-07-23 18:00:00 UTC"ozone_concentration"1false53.0"ppb"34586"QHU""Hurricane"3245.037.179-113.30529"UT""America/Denver"3261.22012-09-07 00:00:00 UTC2024-11-14 03:00:00 UTCfalsetrue
2024-07-23 18:00:00 UTC"ozone_concentration"1false53.0"ppb"87682"QNR""Near Road"4282.040.66284-111.90189"UT""America/Denver"4278.22019-01-07 22:17:00 UTC2024-11-14 03:00:00 UTCfalsetrue
[5]:
ax = EasyMap(figsize=(8, 8)).COUNTIES().STATES(linewidth=2, alpha=1).ROADS().ax

art = ax.scatter(
    df["longitude"],
    df["latitude"],
    c=df["value"],
    s=df["value"] ** 1.3,  # scale the sizes a little bigger
    cmap=ozone_cmap,
    norm=norm,
    ec="k",
    lw=0.5,
    zorder=10,
)

plt.colorbar(
    art, ax=ax, label="Ozone Concentration (ppb)", extend="max", pad=0.01, shrink=0.5
)

ax.set_title("Utah Ozone Concentration", loc="left", fontweight="bold")
ax.set_title(f'{df['date_time'].max():%H:%M UTC %d %b %Y}', loc="right")

ax.set_extent((-114.25, -108.75, 36.75, 42.25))
../../_images/user_guide_gallery_utah-air-quality_6_0.png

Ozone timeseries#

[6]:
df = synoptic.TimeSeries(
    start=datetime(2024, 7, 23, 21),
    end=datetime(2024, 7, 25, 21),
    network=9,  # Utah's Division of Air Quality
    vars="ozone_concentration",
).df()

# Datetimes as local timezone
df = df.synoptic.with_local_timezone()

# 3h rolling mean
df = df.rolling(
    "date_time", period="3h", group_by=pl.col("stid") + " - " + pl.col("name")
).agg(pl.col("value").mean())

# Plot
df.plot.line(x="date_time", y="value", color="stid").properties(
    title="Utah Air Quality", width=600
)
πŸššπŸ’¨ Speedy delivery from Synoptic's timeseries service.
πŸ“¦ Received data from 21 stations.
[6]:

PM 2.5 table#

[32]:
df = synoptic.NearestTime(
    attime=datetime(2023, 12, 19, 21),
    within="30m",
    network=9,  # Utah's Division of Air Quality
    vars="PM_25_concentration",
).df()
df = df.filter(sensor_index=1).sort("value", descending=True)
df.head()
πŸššπŸ’¨ Speedy delivery from Synoptic's nearesttime service.
πŸ“¦ Received data from 22 stations.
[32]:
shape: (5, 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
2023-12-19 21:00:00 UTC"PM_25_concentration"1false42.1"ug/m3"87682"QNR""Near Road"4282.040.66284-111.90189"UT""America/Denver"4278.22019-01-07 22:17:00 UTC2024-11-14 03:00:00 UTCfalsetrue
2023-12-19 21:00:00 UTC"PM_25_concentration"1false31.6"ug/m3"589"QHW""Hawthorne"4301.040.7335-111.87179"UT""America/Denver"4294.61997-03-28 00:00:00 UTC2024-11-14 03:00:00 UTCfalsetrue
2023-12-19 21:00:00 UTC"PM_25_concentration"1false29.5"ug/m3"46466"QED""Erda"4334.040.60056-112.3559"UT""America/Denver"4327.42015-06-24 20:34:00 UTC2024-11-14 03:00:00 UTCfalsetrue
2023-12-19 21:00:00 UTC"PM_25_concentration"1false29.5"ug/m3"152942"QUTTC""Utah Technical Center"4232.040.7771-111.9459"UT""America/Denver"4232.32020-06-29 19:12:00 UTC2024-11-14 03:00:00 UTCfalsetrue
2023-12-19 21:00:00 UTC"PM_25_concentration"1false28.4"ug/m3"164230"QLP""Lake Park"4262.040.70987-112.008669"UT""America/Denver"4252.02021-05-07 18:26:00 UTC2024-11-14 03:00:00 UTCfalsetrue
[31]:
(
    GT(
        df.select("value", "stid", "name", "elevation")
        .sort("value", descending=True)
        .head(10)
    )
    .tab_header(
        title="Utah PM 2.5 Concentration (10 Highest)",
        subtitle=f"{df['date_time'].max():%H:%M UTC %d %b %Y}",
    )
    .fmt_number(columns="elevation", compact=True)
    .tab_spanner(label="Location", columns=["stid", "name", "elevation"])
    .cols_label(value="ug/m3", stid="ID", name="Name", elevation="Elevation (ft)")
)

[31]:
Utah PM 2.5 Concentration (10 Highest)
21:00 UTC 19 Dec 2023
ug/m3 Location
ID Name Elevation (ft)
42.1 QNR Near Road 4.28K
31.6 QHW Hawthorne 4.30K
29.5 QED Erda 4.33K
29.5 QUTTC Utah Technical Center 4.23K
28.4 QLP Lake Park 4.26K
25.1 QBV Bountiful - Viewmont 4.30K
24.4 QIP Inland Port 4.23K
22.2 QBR Brigham City 4.38K
22.1 QRP Rose Park 4.23K
21.4 QSM Smithfield 4.51K