Openair-inspired Python tools for air quality analysis, visualisation, and mapping
Project description
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 datatype="year",type="monthyear",type="season",type="weekday", ortype="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.
.datacontains the processed table behind the plot.figureis the underlying Matplotlib figure.axreturns the first Matplotlib axis for single-axis convenience.axesreturns all Matplotlib axes on the figure.show()displays the plot.save(path)writes the plot to disk
Map functions return AirQoMap.
.datacontains the mapped rows.mapis the underlyingfolium.Map.save(path)writes the HTML map
variation_report() returns AirQoReport.
.sectionscontains 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
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file airqoair-0.1.1.tar.gz.
File metadata
- Download URL: airqoair-0.1.1.tar.gz
- Upload date:
- Size: 40.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.10.11
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
6e8d2b4aea0cee4c484f4e24871fb4d0968de0bd9f453bb045d24e3aa77758d1
|
|
| MD5 |
b64324f74c64f443b2b25d617fb7d5fc
|
|
| BLAKE2b-256 |
57d23fd2b97f66fb3a64593350c0153588d420779b079b61d223c13ee8096674
|
File details
Details for the file airqoair-0.1.1-py3-none-any.whl.
File metadata
- Download URL: airqoair-0.1.1-py3-none-any.whl
- Upload date:
- Size: 37.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.10.11
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
677672e6bd4a424ddea9ed53606a3223d8df18a61506d33615343c6a11ddfd45
|
|
| MD5 |
c1b28e7915db560ff8e0ff22889a8f53
|
|
| BLAKE2b-256 |
a1cb48165495588a04dbc48b3d4c837ebbb64bcf7dd95799cf33255891378ecd
|