2025-11-23

Introduction

Background

Delhi experience one of the poorest air qualty levels across the globe. This is due to vehicular emission, industrial activities, and seasonal agricultural burning among other factors. The Air Quality Index (AQI) is influenced by polutants such as PM2.5, PM10, NO2, SO2, CO, O3, etc.

Dataset

For out analasys, we will use the dataset city_day.csv from Kaggle(“Air Quality Data in India (2015 - 2020)”), which contains daily polutant concentration and AQI data of cities across India in the years 2015 - 2020

Problem

The goal of this project is to identify which pollutants are most strongly associated with AQI levels in Delhi :

  • How do pollutant concentrations relate to delhi AQI
  • Which pollutants show the strongrest association witht the AQI
  • How consistent is the relation between the strongest contributing pollutent with the AQI across years.

libraries used

library(tidyverse)
library(knitr)
library(ggplot2)
library(reshape2)
library(gridExtra)
library(reshape2)

Data Loading

  • Loading all the data from the city_day.csv into a variable df. Then looking at all the columns of the data to see which all variables we have to play with for our analasys
df = read.csv("city_day.csv")
colnames(df)
##  [1] "City"       "Date"       "PM2.5"      "PM10"       "NO"        
##  [6] "NO2"        "NOx"        "NH3"        "CO"         "SO2"       
## [11] "O3"         "Benzene"    "Toluene"    "Xylene"     "AQI"       
## [16] "AQI_Bucket"
  • now since there is a City column, that tells us that there are multiple cities in this data, so creating a variable data_delhi which only has data for delhi
data_delhi = filter(df, City == "Delhi")

Data Cleaning

We take a look at the structure of the data to determine what all action we need to perform for cleaning

str(data_delhi)
## 'data.frame':    2009 obs. of  16 variables:
##  $ City      : chr  "Delhi" "Delhi" "Delhi" "Delhi" ...
##  $ Date      : chr  "2015-01-01" "2015-01-02" "2015-01-03" "2015-01-04" ...
##  $ PM2.5     : num  313.2 186.2 87.2 151.8 146.6 ...
##  $ PM10      : num  608 270 132 242 219 ...
##  $ NO        : num  69.2 62.1 25.7 25 14 ...
##  $ NO2       : num  36.4 32.9 30.3 36.9 34.9 ...
##  $ NOx       : num  110.6 88.1 48 48.6 38.2 ...
##  $ NH3       : num  33.9 31.8 69.5 130.4 122.9 ...
##  $ CO        : num  15.2 9.54 10.61 11.54 9.2 ...
##  $ SO2       : num  9.25 6.65 2.65 4.63 3.33 3.66 5.82 3.31 3.48 5.91 ...
##  $ O3        : num  41.7 30 19.7 25.4 23.2 ...
##  $ Benzene   : num  14.36 10.55 3.91 4.26 2.8 ...
##  $ Toluene   : num  24.86 20.09 10.23 9.71 6.21 ...
##  $ Xylene    : num  9.84 4.29 1.99 3.34 2.96 3.47 5.21 4.83 5.26 4.76 ...
##  $ AQI       : num  472 454 143 319 325 318 353 383 375 376 ...
##  $ AQI_Bucket: chr  "Severe" "Severe" "Moderate" "Very Poor" ...
  • The current data set has dates in char data type, so we need to convert them for a time series later.
data_delhi$Date = as.Date(data_delhi$Date)

Data Cleaning

  • checking for Not Available(NA) values for AQI
na_table = data.frame(
  Columns = colnames(data_delhi),
  Na_count = colSums(is.na(data_delhi)))

na_table
##               Columns Na_count
## City             City        0
## Date             Date        0
## PM2.5           PM2.5        2
## PM10             PM10       77
## NO                 NO        2
## NO2               NO2        2
## NOx               NOx        0
## NH3               NH3        9
## CO                 CO        0
## SO2               SO2      110
## O3                 O3       84
## Benzene       Benzene        0
## Toluene       Toluene        0
## Xylene         Xylene      781
## AQI               AQI       10
## AQI_Bucket AQI_Bucket        0

This tell us that columns PM2.5, PM10, NO, NO2, SO3, O3, AQI, Xylene , have missing values

Data Cleaning

Since removeing all the na will cause us to loose 30-60% of out data. Also since we only focus on finding associations with AQI, we only remove rows which have missing AQI data.

clean_data_delhi = filter(data_delhi, !is.na(AQI))

AQI trends over time

Findings

  • The seasonal trends are clearly visible
  • Winters(October - January), experience worse AQI in comparison to summers. Because of cooler tempratures, and crop-burning events.
  • The year 2020 experienced better AQI, which could be derived from the covid lockdown(reduced transportation, and industrial emissions)

Takeaway: Air quality in Delhi fluctuates seasonally, with consistent winter deterioration and occasional year-specific events influencing pollution levels.

Pollutant Distribution

clean_data_delhi %>%
  select(PM2.5, PM10, NO2, SO2, CO, O3, AQI) %>%pivot_longer(cols = everything(),names_to = "Pollutant",values_to = "Value") %>%ggplot(aes(x = Pollutant, y = Value)) +geom_boxplot(outlier.color="red", fill="lightgray") +
  labs(title = "Distribution of Pollutants in Delhi")

Findings

Boxplots reveal that PM2.5 and PM10 have:

  • Higher median concentrations compared to other pollutants
  • Very wide ranges
  • Many high outliers
  • By contrast, gaseous pollutants have lower variability and fewer extreme values.

Takeaway: This suggests that PM2.5/PM10 dominate the AQI fluctuation

Pollutant Trends(PM2.5)

p2 = ggplot(clean_data_delhi, aes(Date, PM2.5)) +geom_line(color = "darkred") +
  labs(title = "PM2.5 Trend in Delhi",x = "Date",y = "PM2.5 (µg/m³)")
p2

Pollutant Trends(PM10)

p3=ggplot(clean_data_delhi, aes(Date, PM10)) +geom_line(color = "gold") +
  labs(title = "PM10 Trend in Delhi",x = "Date",y = "PM10 (µg/m³)")
p3

Pollutant Trends(CO)

p4=ggplot(clean_data_delhi, aes(Date, CO)) +geom_line(color = "darkgreen") +
  labs(title = "CO Trend in Delhi",x = "Date",y = "PM10 (µg/m³)")
p4

Pollutant Trends(O3)

p5=ggplot(clean_data_delhi, aes(Date, O3)) +geom_line(color = "blue") +
  labs(title = "O3 Trend in Delhi",x = "Date",y = "PM10 (µg/m³)")
p5

Comparing the pollutants

Findings

  • PM2.5 and PM10 show patterns nearly identical to AQI — high in winter, lower in summer/monsoon, and abrupt drops during 2020 lockdown.
  • Gaseous pollutants such CO also show winter elevation but with less dramatic swings.
  • O₃ behaves differently: it sometimes increases when particulate matter decreases.

Takeaway : particulatre pollutanta such as PM2.5 and PM10 mirror AQI, showing strong correlation between them

The corelation matrix

  • Making a corelation matrix to find further evidence
cor_matrix <- clean_data_delhi %>% select(PM2.5, PM10, NO2, SO2, CO, O3, AQI) %>%
cor(use = "pairwise.complete.obs")
cor_matrix
##           PM2.5      PM10        NO2        SO2          CO          O3
## PM2.5 1.0000000 0.8586986 0.65071647  0.3685593  0.16846092  0.25393294
## PM10  0.8586986 1.0000000 0.71591542  0.4619593  0.12535568  0.30975911
## NO2   0.6507165 0.7159154 1.00000000  0.6194672  0.03121322  0.43830188
## SO2   0.3685593 0.4619593 0.61946724  1.0000000 -0.16073543  0.31719942
## CO    0.1684609 0.1253557 0.03121322 -0.1607354  1.00000000 -0.02750262
## O3    0.2539329 0.3097591 0.43830188  0.3171994 -0.02750262  1.00000000
## AQI   0.8821317 0.8848638 0.66575585  0.4147024  0.27983643  0.33022383
##             AQI
## PM2.5 0.8821317
## PM10  0.8848638
## NO2   0.6657558
## SO2   0.4147024
## CO    0.2798364
## O3    0.3302238
## AQI   1.0000000

Heat map of the corelation matrix

melt(cor_matrix) %>% ggplot(aes(Var1, Var2, fill = value)) +geom_tile(color = "white") +
scale_fill_gradient2(low = "darkgreen", mid = "yellow",high = "red", limits = c(-1,1)) +
labs(title = "Correlation Heatmap")

Findings

  • PM2.5 → AQI: very strong positive correlation (typically 0.75–0.90)
  • PM10 → AQI: strong positive correlation (typically 0.70–0.85)
  • NO₂ → AQI: moderate correlation (0.40–0.60)
  • CO → AQI: moderate correlation (0.40–0.55)
  • SO₂ → AQI: weak-to-moderate correlation
  • O₃ → AQI: weak or negative correlation, depending on season

Takeaway: PM2.5 and PM10 are the primary pollutants that determine daily AQI levels in Delhi. Gaseous pollutants influence AQI but to a lesser extent.

Conclusion

  • The explorations show that PM2.5 and PM10 are the strongest contributors to Delhi’s AQI levels from 2015–2020.
  • These pollutants closely match AQI trends, especially during winter months, when air dispersion is low and emissions accumulate.
  • Gaseous pollutants (NO2, SO2, CO, etc) show moderate associations, suggesting they influence air quality but do not dominate AQI behavior.
  • Ozone behaves differently from other pollutants and often shows weaker correlation with AQI.
  • Overall, particulate matter remains the primary driver of air-quality deterioration in Delhi.