
Forecasting Air Quality, one cell at a time
Most AQI dashboards tell you how bad the air is. This project asks how bad it will be: a supervised regression model that predicts the Air Quality Index from pollutant concentrations, built on real CPCB (Central Pollution Control Board) readings for Patiala.
This case study is laid out like the original Jupyter notebook. Code is condensed from the project; the outputs, errors and figures are the real ones from the project deck.
[ ] import pandas as pd import seaborn as sns import matplotlib.pyplot as mlt import numpy as np from sklearn.linear_model import LinearRegression from sklearn.tree import DecisionTreeRegressor from sklearn.ensemble import RandomForestRegressor
The data
31 days of daily readings for 1–31 October 2023 from the CPCB monitoring station at Model Town, Patiala: PM2.5, PM10, NO₂, NH₃, SO₂, CO, O₃ and the resulting AQI. First attempt at loading it:
[ ] reg = pd.read_excel("C:\Users\jaisw\OneDrive\Documents\regaqi.xlsx")
Bug #1. In a normal Python string, \U starts a Unicode escape, so the Windows path C:\Users\… blows up before pandas ever runs. The fix is a raw string (r"...") or forward slashes.
[ ] reg = pd.read_excel(r"C:\Users\jaisw\OneDrive\Documents\regaqi.xlsx") reg.head()
| DATE | pm2.5 | pm10 | NO2 | NH3 | SO2 | CO | O3 | AQI | |
|---|---|---|---|---|---|---|---|---|---|
| 0 | 01-10-2023 | 72 | 116 | 29 | 5 | 9 | 35 | 49 | 116 |
| 1 | 02-10-2023 | 58 | 100 | 29 | 5 | 8 | 30 | 52 | 100 |
| 2 | 03-10-2023 | 67 | 107 | 28 | 5 | 9 | 35 | 51 | 107 |
| 3 | 04-10-2023 | 66 | 117 | 30 | 5 | 9 | 34 | 55 | 117 |
| 4 | 05-10-2023 | 66 | 119 | 29 | 5 | 11 | 31 | 61 | 119 |
Exploratory analysis
Before modelling, a correlation heatmap shows which pollutants move with AQI. That's the first hint of which features will matter most.
[ ] sns.heatmap(reg.corr())<Axes: >

Modelling
AQI is a continuous number, so this is a regression problem. Three models were trained and compared, from simple to ensemble:
- Linear Regression, the baseline. Easy to interpret, but air quality isn't linear.
- Decision Tree Regressor captures non-linear interactions, but overfits on 31 days of data.
- Random Forest Regressor averages many trees, which cuts overfitting and generalises best.
[ ] X = reg.drop(columns=["DATE", "AQI"]).values y = reg["AQI"].values models = { "Linear Regression": LinearRegression(), "Decision Tree": DecisionTreeRegressor(), "Random Forest": RandomForestRegressor(), } for name, model in models.items(): model.fit(X, y)
Result: Random Forest came out on top with 94% prediction accuracy, a clear jump over the linear baseline.
The model then produced a forecast for the next day, 1 November 2023:
[ ] forecast # predicted pollutant levels and AQI for 01-11-2023
| DATE | pm2.5 | pm10 | NO2 | NH3 | SO2 | CO | O3 | AQI |
|---|---|---|---|---|---|---|---|---|
| 01-11-2023 | 135.21 | 100.00 | 27.52 | 5.86 | 16.11 | 37.52 | 41.13 | 143.10 |
An AQI of ~143 is in the "moderate" band, edging towards "poor". That's the kind of early warning the project set out to give.
One more bug on the way, when wrapping the plotting code into a script:
[ ] if _name_ == "_main_": main()
Bug #2. Python's module guard uses double underscores, __name__ == "__main__". With single underscores, _name_ is just an undefined variable.
Impact
On 24 November 2023 the project was presented to Sh. Harjot Singh Bains, Cabinet Minister for Higher Education, Technical Education & School Education, Government of Punjab.
What's next
- Live forecasting from real-time sensor feeds and weather APIs
- LSTM models for proper time-series forecasting
- SHAP explanations of which pollutant drives each prediction
- A deployed dashboard with a map of pollution trends