· ~5 min read

Madrid air quality: clustering, classification and transfer learning

A team project on Madrid air-quality data: K-Means against DBSCAN, SVM against Naive Bayes, and a small neural network that behaved better with transfer learning.

Goal

This was the final project of the Data Analytics and Machine Learning course in my MSc, done as a team project with two classmates. We took the public "Air Quality Madrid" dataset (hourly pollutant readings from the city's monitoring stations) and asked three questions: are there hidden pollution profiles, can a model predict the air-quality class, and can it predict the exact index value?

Preparing the data

The exploratory plots shaped the rest of the work. The correlation matrix showed strong positive correlation between the nitrogen oxides and PM10. The histograms were heavily right-skewed: most hours are clean, and a long tail is not. The box plots, on a log scale, showed many extreme days. We kept them, because they are real pollution episodes rather than sensor errors, and they are exactly what a useful model has to handle.

Clustering: distance or density?

We clustered on CO, NO2 and PM10, standardised so that no pollutant dominates through its units, with Euclidean distance.

AlgorithmSettings triedBestSilhouette
K-Meansk = 3, 4, 5k = 4about 0.41
DBSCANmin_samples = 20, eps = 0.5, 0.6, 0.8eps = 0.6above 0.60

K-Means was faster and easier to explain, but it forces the data into round clusters of similar size, and pollution does not come in balls. DBSCAN found one dense core of normal conditions and marked the sparse high-pollution hours as noise, which for this problem is the useful answer: it separates ordinary pollution from episodes.

Classification: predicting the CAQI class

ModelAccuracyWhere it went wrong
SVM, RBF kernel98%Almost nowhere; the confusion matrix is nearly diagonal
Gaussian Naive Bayes74%The middle classes 1-3, e.g. a true "high" predicted as "medium" or "very high"

Naive Bayes assumes every pollutant is independent of the others, and here they clearly are not: when NO2 rises, PM10 tends to rise with it. The SVM can learn those joint patterns.

Regression: predicting the CAQI value

We built a small feed-forward network in PyTorch: the scaled pollutant features in, two hidden layers of 64 and 32 ReLU units, one output, trained with Adam on mean squared error. Then we compared two ways of training it. The training data was split into a "source" part and a "target" part:

Model (test set, 20%)R²MAE
Custom0.8821.29
Transfer learning0.9018.10

The headline numbers are close, but the plots tell the story. The transfer model's training loss starts low, because it already knows the shape of the problem. And on the real-versus-predicted plot, the custom model loses control at the high end: for true values around 400 it predicted up to 1,700, while the transfer model stayed near the ideal line.

What I learned

The take-home points we wrote were: skewed data with real outliers is the normal case for environmental data; density-based methods suit it better than distance-based ones; models that capture correlations beat models that assume independence; and pre-training made the network far more stable on extreme values.

Looking at it again with a more suspicious eye, these are the checks I would add before trusting the numbers:

A team course project for the MSc in Cybersecurity & Data Science at the University of Piraeus, on a public dataset.

Want the configs or code from this lab, or have a question? Email me at kostaspapas1023@gmail.com

← all posts