Data AnalyticsPortfolio projects with Indian public data

Noida vs Ghaziabad PM2.5: a CPCB station data portfolio project

PK
Pankit Kumar
Sr. Data Scientist at Parexel (a Goldman Sachs–backed company) · 27 September 2026 · 5 min read
In this article (7 sections)

Related programme: the Data Analytics with Generative AI course (3–4 months; ₹90,000 (inclusive of GST) · EMI from ₹4,000/month).

The short answer

This project asks a concrete local question: across a full year, do Noida's and Ghaziabad's continuous monitoring stations record different PM2.5 levels, and does the gap hold in every season? You pull station data from CPCB's Central Control Room portal, clean it, and deliver a monthly comparison and an "exceedance days" table. It is a good first public-data project because the source is official, the cleaning problems are real, and every NCR interviewer already has an opinion about air quality.

Getting the station data out of CCR

Go to airquality.cpcb.gov.in/ccr. The home screen has cards including "All India CAQMS Data", "AQI Data Repository" and "CAAQMS Stations List Delhi NCR". Use the stations list first so you know which Noida and Ghaziabad stations exist and what they are called.

Then open "All India CAQMS Data". Its Advance Search asks for:

  • a date range
  • state (Uttar Pradesh for both cities) and city
  • one or more stations
  • parameters (choose PM2.5, and PM10 if you want a second view)
  • an averaging criterion: 15M, 30M, 1H, 4H, 8H or 24H

Choose 24H for this project. Results appear in Table and Charts views behind a captcha, so expect to download in chunks rather than in one pull. The exact column names in the export can change, so check the downloaded file's header before writing your loader. Save every raw file untouched in a raw/ folder and record the date you downloaded it.

What an employer sees in this project

Environmental consultancies, CSR teams, real-estate research desks and smart-city vendors all work with CPCB data. What they want to know is whether you can take a messy official export and say something defensible. The comparison itself is simple. The value is in showing that you handled missing days, did not compare a station with 200 valid days against one with 340, and wrote your limits down.

Cleaning problems you should expect

ProblemWhat it looks likeWhat to do
Missing daysBlank or "None" values for a dateKeep the row, mark it missing, count coverage per station-month
Non-numeric textValues such as "NA" in a numeric columnCoerce to numeric and count how many you coerced
Station renamesThe same site under two labelsBuild a station lookup table by hand and document it
Mixed timestampsDate plus time strings for 24H averagesParse to date only for daily work
Implausible spikesA single extreme value between normal daysFlag it; do not delete it without a stated rule

The most important step is coverage. A monthly mean built from nine valid days is not comparable with one built from thirty.

A runnable analysis on synthetic data

The numbers below are synthetic, generated to exercise the code. They are not CPCB readings and say nothing about real air in either city.

python
import pandas as pd
import numpy as np

rng = np.random.default_rng(7)
days = pd.date_range("2025-01-01", "2025-12-31", freq="D")
rows = []
for station, city, shift in [("Station N1", "Noida", 0), ("Station G1", "Ghaziabad", 12)]:
    seasonal = 90 + 70 * np.cos((np.asarray(days.dayofyear) - 10) / 365 * 2 * np.pi)
    values = seasonal + shift + rng.normal(0, 18, len(days))
    values = values.round(1).astype(object)
    missing = rng.choice(len(days), size=40, replace=False)
    values[missing] = "NA"
    rows.append(pd.DataFrame({"date": days, "station": station, "city": city, "pm25": values}))

df = pd.concat(rows, ignore_index=True)
df["pm25"] = pd.to_numeric(df["pm25"], errors="coerce")
print("coerced to missing:", int(df["pm25"].isna().sum()))

df["month"] = df["date"].dt.to_period("M")
monthly = (df.groupby(["city", "month"])
             .agg(valid_days=("pm25", "count"), mean_pm25=("pm25", "mean"))
             .reset_index())
monthly["days_in_month"] = monthly["month"].dt.days_in_month
monthly["coverage"] = monthly["valid_days"] / monthly["days_in_month"]
reliable = monthly[monthly["coverage"] >= 0.75]

wide = reliable.pivot(index="month", columns="city", values="mean_pm25").round(1)
wide["gap_ghaziabad_minus_noida"] = (wide["Ghaziabad"] - wide["Noida"]).round(1)
print(wide.head(6))

# Exceedance days against the 24-hour PM2.5 standard of 60 ug/m3
exceed = df.assign(over=df["pm25"] > 60).groupby("city")["over"].sum()
print(exceed)

The 75% coverage threshold is a choice, not a rule from CPCB. State it in your README and show that your conclusion does not flip at 60% or 90%.

The 60 µg/m³ figure is the 24-hour PM2.5 limit in India's National Ambient Air Quality Standards (2009). Cite the CPCB notification rather than a news article if you use it.

Analysis plan, in order

  1. 1Station inventory. List every Noida and Ghaziabad station with its first and last valid date.
  2. 2Coverage matrix. Build a station × month grid of valid-day share. Colour it, and publish it before any averages.
  3. 3City series. Decide how to combine stations into a city figure (median of stations is harder to distort than mean) and say why.
  4. 4Monthly gap. Plot both cities by month with the gap line underneath.
  5. 5Exceedance days. Count days above the 24-hour standard per city and season, only on reliable months.
  6. 6Sensitivity. Rerun with a different coverage threshold and with PM10 instead.

What the finished deliverable looks like

  • Chart 1: a two-line monthly PM2.5 plot. The x-axis is month and the y-axis is µg/m³. Months that fail coverage are left as gaps, not interpolated.
  • Chart 2: a heatmap of coverage by station and month.
  • Table: exceedance days by city and season (winter, pre-monsoon, monsoon, post-monsoon), with valid-day counts beside them.
  • README outline:
  • question
  • source and download date
  • station list
  • cleaning rules
  • coverage threshold
  • results
  • what this cannot tell you (the source, the cause, health effects)

A good README also says plainly that a few stations cannot represent a whole city.

Questions this project will attract

  • Why did you use the median of stations rather than the mean?
  • What would change if one station moved or was recalibrated mid-year?
  • How did you decide a spike was a sensor fault rather than a real event?
  • Can you say the gap is caused by industry in Ghaziabad? (The honest answer is no, not from this data alone.)
  • How would you automate the monthly refresh given the captcha?

A companion project, auditing monitor gaps before averaging, takes the coverage step further. The cleaning and grouping skills here are the core of our Data Analyst programme. If you want to see where you stand first, try the skill check.

Frequently asked questions

Where do I download station-level air quality data for Noida?

Use the Central Control Room for Air Quality Management portal at airquality.cpcb.gov.in/ccr. The "All India CAQMS Data" card opens an Advance Search where you pick Uttar Pradesh, the city Noida, one or more stations, the parameter and an averaging interval such as 1H or 24H.

Is one year of data enough for this project?

One full year is the minimum, because PM2.5 in NCR is strongly seasonal and a partial year will mostly measure which months you happened to download. Two years let you check whether the pattern you describe repeats.

PK
Pankit Kumar
Lead Instructor, NeuraPath Academy

Pankit Kumar has 10 years in Data Science & AI, building and shipping production systems in regulated pharma and clinical environments. He is a freelance trainer at Boston Institute of Analytics, AnalytixLabs and Scaler, and has taught this material to thousands of working professionals.

This article is part of our Data Analytics with Generative AI programme — 3–4 months. The full analyst stack — Excel, SQL, Power BI and Python pipelines — then a generative-AI layer you can prove is right.

Explore Data Analytics with Generative AI
Counselling is free · no obligation

Not sure which programme fits?

Tell us your background and we will map it to the right entry point — including saying so when a cheaper programme is the better fit. A counsellor replies within one working day.