Air Quality Data Analysis Project with Python

Data Science project 16

Air Quality Data Analysis Project with Python

Build a complete air quality analysis workflow with reproducible Python code, traceable calculations, validation checks, and honest limitations.

Explore Data Science training in VizagView all project ideas

Analysis question

How can gaps be handled transparently and monthly level and upper-tail summaries be compared?

Dataset

A seeded year of synthetic daily PM2.5-like and NO2-like sensor measurements with deliberately missing PM2.5 values.

Requirements

  • Python 3.10 or later
  • A terminal or command prompt
  • python -m pip install numpy pandas
  • About 45-60 minutes to build and review

Method and data checks

Sort by date, interpolate the demonstration PM2.5 series in both directions, derive calendar month, and calculate monthly mean, 90th percentile, and NO2 mean.

  • Missing PM2.5 is introduced and then explicitly handled
  • Monthly means and 90th percentiles use the cleaned daily series
  • The demonstration threshold is labelled non-health guidance
  • Row count is retained for reconciliation

Complete Python code

Save the program as ds_air_quality_analysis.py. The dataset generator or loader, analysis functions, output, and reproducibility controls are included.

"""Clean and summarise synthetic air-sensor measurements."""

from __future__ import annotations

import numpy as np
import pandas as pd


def make_demo_air_quality(days: int = 365, seed: int = 42) -> pd.DataFrame:
    rng = np.random.default_rng(seed)
    dates = pd.date_range("2025-01-01", periods=days)
    x = np.arange(days)
    pm25 = 34 + 11 * np.sin(2 * np.pi * x / 90) + rng.normal(0, 6, days)
    no2 = 26 + 5 * np.sin(2 * np.pi * x / 7) + rng.normal(0, 3, days)
    data = pd.DataFrame({"date": dates, "pm25": np.clip(pm25, 1, None), "no2": np.clip(no2, 1, None)})
    data.loc[rng.choice(days, 18, replace=False), "pm25"] = np.nan
    return data


def analyse_air_quality(data: pd.DataFrame, demo_pm25_threshold: float = 40.0) -> tuple[pd.DataFrame, dict[str, float]]:
    air = data.copy().sort_values("date")
    air["pm25"] = air["pm25"].interpolate(limit_direction="both")
    air["month"] = pd.to_datetime(air["date"]).dt.to_period("M").astype(str)
    monthly = air.groupby("month", as_index=False).agg(mean_pm25=("pm25", "mean"), p90_pm25=("pm25", lambda s: s.quantile(0.9)), mean_no2=("no2", "mean"))
    kpis = {"mean_pm25": float(air["pm25"].mean()), "days_above_demo_threshold": float((air["pm25"] > demo_pm25_threshold).sum()), "rows": float(len(air))}
    return monthly, kpis


def main() -> None:
    monthly, kpis = analyse_air_quality(make_demo_air_quality())
    print({k: round(v, 2) for k, v in kpis.items()})
    print(monthly.round(2).to_string(index=False))
    print("Synthetic sensor data; the 40-unit threshold is illustrative, not health guidance.")


if __name__ == "__main__":
    main()

Run the project

  1. Create and activate a virtual environment.
  2. Install dependencies with python -m pip install numpy pandas.
  3. Run python ds_air_quality_analysis.py.
  4. Reconcile row counts and totals before interpreting patterns.
  5. Read the limitations before substituting any real dataset.

Expected analytical output

Overall demonstration KPIs and a 12-row monthly air-quality table.

Interpretation and responsible-use limits

Synthetic sensor values and the example 40-unit threshold are not health guidance. Real analysis needs measurement units, calibration, quality flags, siting metadata, local standards, and environmental experts.

Ways to extend the project

Load authoritative station data, preserve quality codes, compare stations, quantify missingness, avoid unjustified interpolation, and cite the exact standard and averaging period.

Continue learning Data Science

Try the next project, return to the Softenant project library, or explore the Data Science course in Vizag for guided data cleaning, analysis, visualisation, and portfolio feedback.