Unsupervised learning: k-means clustering & PCA

Not every dataset comes with a target column. A saree retailer in Surat may have lakhs of billing records but no label saying which customers are "loyal" or "bargain hunters". Unsupervised learning finds that structure for you.

K-means clustering

K-means groups points into k clusters so that each point is close to its cluster's centre (centroid):

  1. Place k centroids (scikit-learn uses the smart k-means++ initialisation).
  2. Assign every point to its nearest centroid.
  3. Move each centroid to the mean of its assigned points.
  4. Repeat until assignments stop changing.

Because it uses distances, scaling is mandatory: otherwise "annual spend in INR" swamps "visits per month".

import pandas as pd
from sklearn.cluster import KMeans
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import make_pipeline
from sklearn.metrics import silhouette_score

rfm = pd.read_csv("customers_rfm.csv")      # recency_days, frequency, monetary_inr
X = rfm[["recency_days", "frequency", "monetary_inr"]]

for k in range(2, 8):
    pipe = make_pipeline(StandardScaler(), KMeans(n_clusters=k, n_init=10, random_state=0))
    labels = pipe.fit_predict(X)
    scaled = pipe[:-1].transform(X)
    print(k, round(pipe[-1].inertia_), round(silhouette_score(scaled, labels), 3))

Choosing k

Method What to look for
Elbow (inertia) The point where adding clusters stops reducing inertia much
Silhouette score Higher is better (range -1 to 1)
Business sense Can marketing actually act on 4 segments? 12 is rarely usable

After choosing k, profile the clusters with rfm.groupby(labels).mean() and give them names such as "Champions", "At risk" or "New & curious". That table is what the business actually uses.

PCA: principal component analysis

PCA rotates your data to new axes (principal components) ordered by how much variance they capture. Keeping the first few components compresses correlated features into fewer columns. This is useful for visualisation, speeding up models, and reducing noise.

from sklearn.decomposition import PCA

pca = make_pipeline(StandardScaler(), PCA(n_components=0.90))   # keep 90% of variance
Z = pca.fit_transform(features)
print(Z.shape, pca[-1].explained_variance_ratio_.round(3))

Passing a float between 0 and 1 to n_components tells PCA to keep as many components as needed to explain that share of variance. For a 2-D scatter plot of segments, use n_components=2 and colour points by cluster label.

Common mistakes

  • Running k-means or PCA on unscaled data.
  • Expecting k-means to find odd-shaped or very uneven clusters. It prefers round, similar-sized blobs, so try DBSCAN or GaussianMixture for other shapes.
  • Treating cluster numbers as stable. Label 2 today may be label 0 after retraining, so map clusters by their profile.
  • Interpreting principal components as original features. Each is a mix; inspect components_ to see the loadings.
  • Fitting PCA on the full dataset before a train/test split in a supervised pipeline (data leakage).

Practice

Create RFM features (recency, frequency, monetary) from any sales CSV, cluster customers with k in 2..6, pick k using silhouette plus business sense, then profile and name each segment. Finally project the data to two PCA components and plot it coloured by segment.

B

Written by Bablu Kumar Jha

Founder, BeeProton AI · Mumbai, Maharashtra, India