Skip to content

Data Cleaning — IPL Sports Analytics

Setup

import pandas as pd
import numpy as np

matches = pd.read_csv("matches.csv")
deliveries = pd.read_csv("deliveries.csv")

print(f"Matches: {matches.shape}, Deliveries: {deliveries.shape}")
print(f"\nMatches missing:\n{matches.isnull().sum()[matches.isnull().sum() > 0]}")
print(f"\nDeliveries missing:\n{deliveries.isnull().sum()[deliveries.isnull().sum() > 0]}")

Step 1 — Standardise Team Names

Teams renamed over the years. Map old names to current ones for consistent multi-season analysis.

team_name_map = {
    "Delhi Daredevils": "Delhi Capitals",
    "Kings XI Punjab": "Punjab Kings",
    "Rising Pune Supergiant": "Rising Pune Supergiants",
    "Deccan Chargers": "Sunrisers Hyderabad",   # franchise lineage (optional)
}

for col in ["team1", "team2", "toss_winner", "winner"]:
    matches[col] = matches[col].replace(team_name_map)

for col in ["batting_team", "bowling_team"]:
    deliveries[col] = deliveries[col].replace(team_name_map)

Step 2 — Handle Match Results with No Winner

# Matches with no result (rain, tie) have NULL winner
print(f"Matches with no winner: {matches['winner'].isna().sum()}")

# For win-rate analysis, exclude no-result matches
matches_with_result = matches[matches["winner"].notna()].copy()

Step 3 — Parse Dates

matches["date"] = pd.to_datetime(matches["date"], errors="coerce")
matches["year"] = matches["date"].dt.year

Step 4 — Create Batting Analysis Base

The key insight: exclude wides and no-balls from "balls faced."

# A ball "faced" by the batsman excludes wides and no-balls
deliveries["is_ball_faced"] = ~deliveries["extras_type"].isin(["wides", "noballs"])
deliveries["is_ball_faced"] = deliveries["is_ball_faced"].astype(int)

# Legal deliveries for bowler (wides and no-balls don't count as a ball bowled)
deliveries["is_legal_ball"] = ~deliveries["extras_type"].isin(["wides", "noballs"])
deliveries["is_legal_ball"] = deliveries["is_legal_ball"].astype(int)

# Boundary flags
deliveries["is_four"] = (deliveries["batsman_runs"] == 4).astype(int)
deliveries["is_six"] = (deliveries["batsman_runs"] == 6).astype(int)

Step 5 — Add Phase of Innings

def innings_phase(over):
    if over < 6:
        return "Powerplay (1-6)"
    elif over < 15:
        return "Middle (7-15)"
    else:
        return "Death (16-20)"

deliveries["phase"] = deliveries["over"].apply(innings_phase)

Step 6 — Build a Batsman Summary Table

batsman_stats = (
    deliveries.groupby("batter")
    .agg(
        runs=("batsman_runs", "sum"),
        balls_faced=("is_ball_faced", "sum"),
        fours=("is_four", "sum"),
        sixes=("is_six", "sum"),
        innings=("match_id", "nunique"),
    )
    .reset_index()
)

# Strike rate (runs per 100 balls)
batsman_stats["strike_rate"] = (batsman_stats["runs"] / batsman_stats["balls_faced"] * 100).round(1)

# Filter to meaningful sample size
batsman_stats = batsman_stats[batsman_stats["balls_faced"] >= 200]

print(batsman_stats.sort_values("runs", ascending=False).head(10))

Step 7 — Build a Bowler Summary Table

# Wickets credited to bowler (exclude run outs — not the bowler's wicket)
bowler_wicket_kinds = ["bowled", "caught", "lbw", "stumped", "caught and bowled", "hit wicket"]
deliveries["is_bowler_wicket"] = (
    deliveries["is_wicket"] &
    deliveries["dismissal_kind"].isin(bowler_wicket_kinds)
).astype(int)

# Runs conceded by bowler = batsman_runs + wides + noballs (NOT byes/leg-byes)
deliveries["bowler_runs"] = deliveries["batsman_runs"] + deliveries["extra_runs"].where(
    deliveries["extras_type"].isin(["wides", "noballs"]), 0
)

bowler_stats = (
    deliveries.groupby("bowler")
    .agg(
        runs_conceded=("bowler_runs", "sum"),
        balls_bowled=("is_legal_ball", "sum"),
        wickets=("is_bowler_wicket", "sum"),
    )
    .reset_index()
)

bowler_stats["overs"] = bowler_stats["balls_bowled"] / 6
bowler_stats["economy"] = (bowler_stats["runs_conceded"] / bowler_stats["overs"]).round(2)
bowler_stats["bowling_sr"] = (bowler_stats["balls_bowled"] / bowler_stats["wickets"]).round(1)
bowler_stats = bowler_stats[bowler_stats["balls_bowled"] >= 500]

print(bowler_stats.sort_values("wickets", ascending=False).head(10))

Step 8 — Export

matches.to_csv("matches_clean.csv", index=False)
deliveries.to_csv("deliveries_clean.csv", index=False)
batsman_stats.to_csv("batsman_stats.csv", index=False)
bowler_stats.to_csv("bowler_stats.csv", index=False)
print("Cleaned files exported.")

← Dataset Guide · Next: SQL Analysis →