Skip to main content

airqoair

airqoair is a Python library for air-quality analysis, plotting, reporting, and interactive maps.

It is designed for environmental monitoring workflows where you want:

  • direct input from pandas.DataFrame, .csv, .json, .jsonl, or .ndjson
  • temporal diagnostics such as diurnal, weekday, monthly, and grouped site comparisons
  • directional analysis such as wind roses, pollution roses, and polar plots
  • interactive map outputs with folium
  • no machine learning or forecasting in the package itself

Install

From PyPI:

pip install airqoair

Documentation:

https://airqoair-project.github.io/book

From this monorepo for development:

cd packages/airqoair
pip install -e ".[dev]"

Quick Start

import airqoair as aq

daily = aq.time_average(
    "kampala.csv",
    avg="D",
    pollutant_cols=["pm2_5", "pm10"],
)

variation = aq.time_variation("kampala.csv", pollutant="pm2_5")
monthly = aq.monthly_plot("kampala.csv", pollutant="pm2_5")
rose = aq.wind_rose("kampala.csv")
site_map = aq.station_map("kampala.csv", value="pm2_5")

print(daily.head())

variation.save("outputs/time_variation.png")
monthly.save("outputs/monthly_profile.png")
rose.save("outputs/wind_rose.png")
site_map.save("outputs/stations.html")

Grouped Conditioning

One of the most useful analysis patterns is conditioning the same workflow by site, year, season, or other categories.

airqoair supports this in the temporal workflow through:

  • group_by="site_name" for an existing column in your data
  • type="year", type="monthyear", type="season", type="weekday", or type="weekend" for derived date-based conditioning

Monthly bars by site:

import airqoair as aq

monthly_by_site = aq.monthly_plot(
    "kampala.csv",
    pollutant="pm2_5",
    group_by="site_name",
)

monthly_by_site.save("outputs/monthly_by_site.png")

Diurnal profile by season:

import airqoair as aq

diurnal_by_season = aq.diurnal_plot(
    "kampala.csv",
    pollutant="pm2_5",
    type="season",
)

diurnal_by_season.save("outputs/diurnal_by_season.png")

Monthly profile table by site:

import airqoair as aq

monthly = aq.monthly_profile(
    "kampala.csv",
    pollutant="pm2_5",
    group_by="site_name",
)

print(monthly.head())

Accepted Inputs

Most functions accept either a DataFrame or a file path.

Supported file formats:

  • .csv
  • .json
  • .jsonl
  • .ndjson

Examples:

import airqoair as aq

df = aq.load_data("kampala.jsonl")
daily = aq.time_average(df, avg="D", pollutant_cols=["pm2_5"])
import airqoair as aq

variation = aq.time_variation("kampala.json", pollutant="pm2_5")

Expected Columns

Defaults assume these names:

Purpose Default column(s)
datetime date
pollutant examples pm2_5, pm10, no2, o3
wind speed ws
wind direction wd
latitude latitude
longitude longitude

You can override them in function calls, for example:

import airqoair as aq

aq.wind_rose("kampala.csv", ws="wind_speed", wd="wind_direction")
aq.station_map("kampala.csv", latitude="lat", longitude="lon", value="pm2_5")
aq.time_average("kampala.csv", date_col="timestamp", pollutant_cols=["pm2_5"])

Feature Coverage

Directional Analysis

  • wind_rose()
  • pollution_rose()
  • percentile_rose()
  • polar_frequency()
  • polar_plot()
  • polar_annulus()

Time Series, Profiles, and Reports

  • time_average()
  • time_variation()
  • diurnal_profile()
  • weekday_profile()
  • monthly_profile()
  • diurnal_plot()
  • weekday_plot()
  • monthly_plot()
  • variation_report()

Visualization and Trends

  • time_series_plot()
  • distribution_plot()
  • calendar_plot()
  • time_proportion_plot()
  • trend_level()
  • run_regression()
  • theil_sen_trend()
  • smooth_trend()
  • dilution_plot()
  • pollutant_ratio_plot()
  • trend_heatmap()

Model Evaluation

  • model_evaluation()
  • taylor_diagram()
  • conditional_quantiles()

Interactive Maps

  • station_map()
  • map_primer()
  • network_visualization()
  • directional_map()
  • polar_map()
  • trajectory_analysis()

Common Workflows

Diurnal, Weekday, and Monthly Summaries

import airqoair as aq

diurnal = aq.diurnal_profile("kampala.csv", pollutant="pm2_5")
weekday = aq.weekday_profile("kampala.csv", pollutant="pm2_5")
monthly = aq.monthly_profile("kampala.csv", pollutant="pm2_5")

Grouped Site Comparison

import airqoair as aq

variation = aq.time_variation(
    "kampala.csv",
    pollutant="pm2_5",
    group_by="site_name",
)

report = aq.variation_report(
    "kampala.csv",
    pollutant="pm2_5",
    group_by="site_name",
)

print(report.sections["summary"].head())
variation.save("outputs/site_time_variation.png")

Directional Analysis

import airqoair as aq

aq.wind_rose("kampala.csv").save("outputs/wind_rose.png")
aq.percentile_rose("kampala.csv", pollutant="pm2_5").save("outputs/percentile_rose.png")
aq.polar_plot("kampala.csv", pollutant="pm2_5").save("outputs/polar_plot.png")

Time Trends

import airqoair as aq

aq.calendar_plot("kampala.csv", pollutant="pm2_5").save("outputs/calendar.png")
aq.smooth_trend("kampala.csv", pollutant="pm2_5", deseason=True, n_boot=200).save("outputs/smooth_trend.png")
aq.trend_heatmap("kampala.csv", pollutant="pm2_5").save("outputs/trend_heatmap.png")

Calendar plot with daily value labels and discrete bands:

import airqoair as aq

aq.calendar_plot(
    "kampala.csv",
    pollutant="pm2_5",
    year=2025,
    annotate="value",
    breaks=[0, 15, 35, 55, 150],
    labels=["Good", "Moderate", "Unhealthy", "Very Unhealthy"],
    lim=35,
).save("outputs/calendar_2025.png")

Time proportion plot split by site or wind sector over time:

import airqoair as aq

aq.time_proportion_plot(
    "kampala.csv",
    pollutant="pm2_5",
    proportion="site_name",
    avg_time="7D",
).save("outputs/time_proportion_by_site.png")

aq.time_proportion_plot(
    "kampala.csv",
    pollutant="pm2_5",
    proportion="wd",
    avg_time="1M",
    sector_count=8,
).save("outputs/time_proportion_by_wind_sector.png")

Trend-level heat map across month and hour:

import airqoair as aq

aq.trend_level(
    "kampala.csv",
    pollutant="pm2_5",
    x="month",
    y="hour",
).save("outputs/trend_level_month_hour.png")

Rolling regression diagnostics:

import airqoair as aq

aq.run_regression(
    "kampala.csv",
    x="ws",
    y="pm2_5",
    avg_time="MS",
    window=12,
).save("outputs/run_regression.png")

Theil-Sen trend with deseasonalising and confidence intervals:

import airqoair as aq

aq.theil_sen_trend(
    "kampala.csv",
    pollutant="pm2_5",
    deseason=True,
    n_boot=200,
).save("outputs/theil_sen_trend.png")

Smooth trend with deseasonalising and grouped site panels:

import airqoair as aq

aq.smooth_trend(
    "kampala.csv",
    pollutant="pm2_5",
    group_by="site_name",
    deseason=True,
    n_boot=200,
).save("outputs/smooth_trend_by_site.png")

Maps

import airqoair as aq

aq.station_map("kampala.csv", value="pm2_5").save("outputs/stations.html")
aq.directional_map("kampala.csv").save("outputs/directional_map.html")
aq.polar_map("kampala.csv", site_col="site_name", pollutant="pm2_5", kind="polar_plot").save("outputs/polar_map.html")

Grouped interactive map with clustering, marker scaling, and a heat layer:

import airqoair as aq

aq.station_map(
    "kampala.csv",
    value="pm2_5",
    group_by="site_name",
    radius_col="device_count",
    cluster=True,
    heatmap=True,
    tiles=["CartoDB positron", "OpenStreetMap"],
).save("outputs/stations_grouped.html")

Returned Objects

Plotting functions return AirQoFigure.

  • .data contains the processed table behind the plot
  • .figure is the underlying Matplotlib figure
  • .ax returns the first Matplotlib axis for single-axis convenience
  • .axes returns all Matplotlib axes on the figure
  • .show() displays the plot
  • .save(path) writes the plot to disk

Map functions return AirQoMap.

  • .data contains the mapped rows
  • .map is the underlying folium.Map
  • .save(path) writes the HTML map

variation_report() returns AirQoReport.

  • .sections contains summary and profile tables
  • .save_markdown(path) writes a markdown report
  • .save_figure(path) writes the report figure

Current Scope

airqoair is designed for practical air-quality diagnostics in Python, but it is not yet a full feature-for-feature replacement for every established toolkit in this space.

This package currently focuses on:

  • practical temporal profiles and grouped conditioning
  • directional analysis and mapping
  • trend and evaluation utilities that are straightforward to use from Python data workflows

Development

Run tests from the package root:

cd packages/airqoair
python -m pytest tests

If you are working from the repo without installation, point PYTHONPATH at packages/airqoair, not the repository root.

Documentation site

A book-style documentation site scaffold is included under:

packages/airqoair/docs/
packages/airqoair/mkdocs.yml

To build it locally:

cd packages/airqoair
pip install -e ".[docs]"
python -m mkdocs serve

Images for the docs can be stored in:

packages/airqoair/docs/assets/images/

.gitignore

The package-local .gitignore excludes:

  • Python caches and bytecode
  • virtual environments
  • pytest, coverage, mypy, ruff, tox, and nox caches
  • build artifacts and editable-install metadata
  • generated plots, HTML maps, markdown reports, and temporary outputs

License

MIT

Metadata

Release files for airqoair 0.1.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for airqoair 0.1.1
File Size Uploaded
airqoair-0.1.1.tar.gz 40.5 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for airqoair 0.1.1
File Interpreter ABI Platform
airqoair-0.1.1-py3-none-any.whl Python 3 none any Details

Total release size: 77.9 kB

Release files / airqoair-0.1.1.tar.gz

Download URL airqoair-0.1.1.tar.gz
Size 40.5 kB
Tags Source
SHA-256 checksum
How to use checksums
6e8d2b4aea0cee4c484f4e24871fb4d0968de0bd9f453bb045d24e3aa77758d1
BLAKE2b-256 checksum
How to use checksums
57d23fd2b97f66fb3a64593350c0153588d420779b079b61d223c13ee8096674
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.10.11

Release files / airqoair-0.1.1-py3-none-any.whl

Download URL airqoair-0.1.1-py3-none-any.whl
Size 37.4 kB
Tags Python 3
SHA-256 checksum
How to use checksums
677672e6bd4a424ddea9ed53606a3223d8df18a61506d33615343c6a11ddfd45
BLAKE2b-256 checksum
How to use checksums
a1cb48165495588a04dbc48b3d4c837ebbb64bcf7dd95799cf33255891378ecd
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.10.11

Release history Release notifications | RSS feed

This release

0.1.1 This release

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page