100% Free Forever
AI-Powered Learning
Industry Expert Content
Certificates & Badges
Learn At Your Own Pace
Machine Learning with Scikit-learn
40 minintermediate

Phase 3 — Clustering: Player Segmentation

Phase 3 — Clustering: Player Segmentation

The head scout needs the 300 shortlisted IPL players segmented into performance archetypes before the mega-auction. Your task: apply K-Means and agglomerative clustering to the player dataset, choose the best K using Elbow and Silhouette methods, visualise with PCA, name each archetype with a descriptive cricket label, and flag statistically unusual players with Isolation Forest.

Analogy🏏Cricket
🏏 Think of it like cricket: every auction table has scouts arguing about categories — 'Is this player a genuine all-rounder or a batting all-rounder who bowls a bit?' Clustering answers that argument with data, not intuition. The dendrogram and silhouette scores replace subjective opinion with quantitative evidence about where the natural boundaries between player types lie.

Step 0 — Load Player Data and Scale

python
# ── Paste the shared data generator from Lesson 31 here first ────────────────
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.preprocessing import StandardScaler
from sklearn.cluster import KMeans, AgglomerativeClustering, DBSCAN
from sklearn.decomposition import PCA
from sklearn.ensemble import IsolationForest
from sklearn.metrics import silhouette_score
from scipy.cluster.hierarchy import dendrogram, linkage

# Select clustering features (exclude player_id and matches_played)
cluster_features = ['batting_avg','strike_rate','bowling_economy',
                    'wickets_per_game','catches_per_game',
                    'sixes_per_innings','dot_ball_pct']

X_raw = players[cluster_features].values
scaler = StandardScaler()
X = scaler.fit_transform(X_raw)

print(f"Player matrix: {X.shape}")
print("Feature correlation (absolute, top pairs):")
corr = pd.DataFrame(X, columns=cluster_features).corr().abs()
pairs = [(corr.columns[i], corr.columns[j], corr.iloc[i,j])
         for i in range(len(corr)) for j in range(i+1, len(corr))]
for a, b, r in sorted(pairs, key=lambda x: -x[2])[:5]:
    print(f"  {a} — {b}: {r:.3f}")

Step 1 — Elbow + Silhouette to Choose K

python
inertias, sil_scores = [], []
K_range = range(2, 9)

for k in K_range:
    km = KMeans(n_clusters=k, n_init=10, random_state=42)
    lbl = km.fit_predict(X)
    inertias.append(km.inertia_)
    sil_scores.append(silhouette_score(X, lbl))

fig, axes = plt.subplots(1, 2, figsize=(12, 4))
axes[0].plot(K_range, inertias, 'bo-'); axes[0].set(xlabel='K', ylabel='Inertia', title='Elbow Method')
axes[1].plot(K_range, sil_scores, 'rs-'); axes[1].set(xlabel='K', ylabel='Silhouette', title='Silhouette Scores')
plt.suptitle('Player Segmentation — K Selection'); plt.tight_layout(); plt.show()

best_k = K_range[sil_scores.index(max(sil_scores))]
print(f"Best K by silhouette: {best_k}  (score={max(sil_scores):.4f})")

Step 2 — Agglomerative Dendrogram (60-sample subset)

python
np.random.seed(7)
subset_idx = np.random.choice(len(X), 60, replace=False)
Z = linkage(X[subset_idx], method='ward')

plt.figure(figsize=(16, 5))
dendrogram(Z, leaf_rotation=90, leaf_font_size=8, color_threshold=6)
plt.axhline(6, color='red', ls='--', label='Suggested cut')
plt.title('Player Archetype Dendrogram (60-sample subset)')
plt.legend(); plt.tight_layout(); plt.show()
print("Inspect the dendrogram to confirm or revise K")

Step 3 — Fit K-Means and Agglomerative, Compare

python
# Fit both with chosen K
km  = KMeans(n_clusters=best_k, n_init=10, random_state=42)
agg = AgglomerativeClustering(n_clusters=best_k, linkage='ward')

players['km_label']  = km.fit_predict(X)
players['agg_label'] = agg.fit_predict(X)

km_sil  = silhouette_score(X, players['km_label'])
agg_sil = silhouette_score(X, players['agg_label'])
print(f"K-Means silhouette      : {km_sil:.4f}")
print(f"Agglomerative silhouette: {agg_sil:.4f}")

# Use the better algorithm going forward
best_cluster_col = 'km_label' if km_sil >= agg_sil else 'agg_label'
print(f"Selected: {best_cluster_col}")

Step 4 — PCA Visualisation of Player Archetypes

python
pca = PCA(n_components=2)
X_2d = pca.fit_transform(X)
ev   = pca.explained_variance_ratio_

colours = plt.cm.Set1(np.linspace(0, 0.85, best_k))
plt.figure(figsize=(10, 6))
for k_idx, col in zip(range(best_k), colours):
    m = players[best_cluster_col] == k_idx
    plt.scatter(X_2d[m, 0], X_2d[m, 1], c=[col],
                label=f'Archetype {k_idx}', s=30, alpha=0.7)

plt.xlabel(f'PC1 ({ev[0]*100:.1f}%)')
plt.ylabel(f'PC2 ({ev[1]*100:.1f}%)')
plt.title(f'Player Archetypes in PCA Space  (2 PCs explain {sum(ev)*100:.1f}% variance)')
plt.legend(fontsize=9); plt.tight_layout(); plt.show()

Step 5 — Name the Archetypes

python
# Profile each cluster on original (unscaled) features
profile = players.groupby(best_cluster_col)[cluster_features].mean().round(2)
print("\n=== Archetype Profiles (mean feature values) ===")
print(profile.to_string())

# After inspecting the profile, assign cricket labels
# The exact mapping will depend on which cluster is which — adjust based on your output
# Heuristic rules applied to means:
def assign_archetype(row):
    if row['batting_avg'] > 32 and row['bowling_economy'] > 9:
        return 'Power Batter'
    elif row['batting_avg'] < 18 and row['wickets_per_game'] > 1.0:
        return 'Specialist Bowler'
    elif row['batting_avg'] > 24 and row['wickets_per_game'] > 0.6:
        return 'All-Rounder'
    elif row['catches_per_game'] > 0.6 and row['batting_avg'] < 22:
        return 'Fielding Specialist'
    else:
        return 'Middle-Order Batter'

archetype_map = {idx: assign_archetype(profile.loc[idx]) for idx in profile.index}
players['archetype'] = players[best_cluster_col].map(archetype_map)
print("\nArchetype assignments:", archetype_map)
print("\nArchetype sizes:")
print(players['archetype'].value_counts())

Step 6 — Anomaly Detection on Player Stats

python
# Isolation Forest to flag statistically unusual players
iso = IsolationForest(n_estimators=200, contamination=0.05, random_state=42)
players['anomaly'] = iso.fit_predict(X)         # -1 = anomalous
players['anom_score'] = -iso.score_samples(X)

n_flagged = (players['anomaly'] == -1).sum()
print(f"Anomalous players flagged: {n_flagged}")
print("\nTop 10 most anomalous players:")
print(players.nlargest(10, 'anom_score')[
    cluster_features + ['archetype','anom_score']
].round(2).to_string())

Step 7 — Auction-Ready Archetype Report

python
print("=" * 65)
print("    IPL MEGA-AUCTION — PLAYER ARCHETYPE REPORT")
print("    Sri Hayavadhana Info-Tech Analytics Division")
print("=" * 65)

auction_report = players.groupby('archetype').agg(
    count          = ('batting_avg', 'count'),
    batting_avg    = ('batting_avg', 'mean'),
    strike_rate    = ('strike_rate', 'mean'),
    bowling_econ   = ('bowling_economy', 'mean'),
    wickets_pg     = ('wickets_per_game', 'mean'),
    anomalies      = ('anomaly', lambda x: (x==-1).sum())
).round(2)

print(auction_report.to_string())
print(f"\nClustering method    : {best_cluster_col.replace('_label','').title()}")
print(f"Silhouette score     : {max(km_sil, agg_sil):.4f}")
print(f"Total players        : {len(players)}")
print(f"Anomalies flagged    : {n_flagged} (for scout review)")
print("\n✅ Phase 3 — Player Segmentation complete!")
  • Scale all features before clustering and anomaly detection; StandardScaler is the standard choice for player stat distributions.
  • Compare K-Means and agglomerative clustering silhouette scores — always report which algorithm was chosen and why.
  • Use a 60-sample dendrogram subset for visual inspection — plotting all 300 nodes makes the dendrogram unreadable.
  • Profile clusters on original (unscaled) feature means to assign interpretable cricket archetype names.
  • Isolation Forest on player data flags unusual players who may be exceptional talents, data errors, or match-fixing risks — flag for manual scout review.
  • Always report explained variance in PCA visualisations — low values warn readers that the 2D plot captures only a fraction of the true cluster separation.
Lesson 34 of 35
0% complete