ipl-analytics / src /features.py
anshumanSingh-tech
Update dataset with complete IPL 2026 season, fix dashboard bugs, improve ML features
1c7a005
Raw
History Blame Contribute Delete
7.73 kB
import pandas as pd
import numpy as np
def build_batting_features(deliveries: pd.DataFrame) -> pd.DataFrame:
d = deliveries[deliveries["is_wide"] == 0].copy()
agg = d.groupby("batsman").agg(
total_runs = ("batsman_runs", "sum"),
balls_faced = ("is_legal_delivery", "sum"),
matches_batted = ("match_id", "nunique"),
innings = ("inning", "count"),
fours = ("is_four", "sum"),
sixes = ("is_six", "sum"),
dot_balls = ("is_dot_ball", "sum"),
dismissals = ("is_wicket", "sum"),
).reset_index()
agg["strike_rate"] = (agg["total_runs"] / agg["balls_faced"].replace(0, np.nan) * 100).round(2)
agg["batting_average"] = (agg["total_runs"] / agg["dismissals"].replace(0, np.nan)).round(2)
agg["boundary_rate"] = ((agg["fours"] + agg["sixes"]) / agg["balls_faced"].replace(0, np.nan) * 100).round(2)
agg["dot_ball_pct"] = (agg["dot_balls"] / agg["balls_faced"].replace(0, np.nan) * 100).round(2)
agg["runs_per_match"] = (agg["total_runs"] / agg["matches_batted"].replace(0, np.nan)).round(2)
for phase in ["powerplay", "middle", "death"]:
phase_df = d[d["over_phase"] == phase].groupby("batsman").agg(
_runs = ("batsman_runs", "sum"),
_balls = ("is_legal_delivery", "sum"),
).reset_index()
phase_df[f"sr_{phase}"] = (phase_df["_runs"] / phase_df["_balls"].replace(0, np.nan) * 100).round(2)
agg = agg.merge(phase_df[["batsman", f"sr_{phase}"]], on="batsman", how="left")
inning_scores = d.groupby(["batsman", "match_id", "inning"])["batsman_runs"].sum().reset_index()
inning_scores.columns = ["batsman", "match_id", "inning", "innings_runs"]
fifties = inning_scores[inning_scores["innings_runs"].between(50, 99)].groupby("batsman").size().rename("fifties")
hundreds = inning_scores[inning_scores["innings_runs"] >= 100].groupby("batsman").size().rename("hundreds")
agg = agg.merge(fifties, on="batsman", how="left")
agg = agg.merge(hundreds, on="batsman", how="left")
agg["fifties"] = agg["fifties"].fillna(0).astype(int)
agg["hundreds"] = agg["hundreds"].fillna(0).astype(int)
agg = agg[agg["balls_faced"] >= 30].copy()
print(f"build_batting_features: {len(agg)} batsmans with 30+ balls faced")
return agg.sort_values("total_runs", ascending=False).reset_index(drop=True)
def build_bowling_features(deliveries: pd.DataFrame) -> pd.DataFrame:
d = deliveries.copy()
agg = d.groupby("bowler").agg(
balls_bowled = ("is_legal_delivery", "sum"),
runs_conceded = ("total_runs", "sum"),
wickets = ("is_wicket", "sum"),
dot_balls = ("is_dot_ball", "sum"),
wides = ("is_wide", "sum"),
no_balls = ("is_noball", "sum"),
matches_bowled = ("match_id", "nunique"),
fours_conceded = ("is_four", "sum"),
sixes_conceded = ("is_six", "sum"),
).reset_index()
overs = agg["balls_bowled"] / 6
agg["economy_rate"] = (agg["runs_conceded"] / overs.replace(0, np.nan)).round(2)
agg["bowling_average"] = (agg["runs_conceded"] / agg["wickets"].replace(0, np.nan)).round(2)
agg["bowling_sr"] = (agg["balls_bowled"] / agg["wickets"].replace(0, np.nan)).round(2)
agg["dot_ball_pct"] = (agg["dot_balls"] / agg["balls_bowled"].replace(0, np.nan) * 100).round(2)
agg["wickets_per_match"] = (agg["wickets"] / agg["matches_bowled"].replace(0, np.nan)).round(2)
for phase in ["powerplay", "middle", 'death']:
phase_df = d[d["over_phase"] == phase].groupby("bowler").agg(
_runs = ("total_runs", "sum"),
_balls = ("is_legal_delivery", "sum"),
).reset_index()
phase_df[f"economy_{phase}"] = (phase_df["_runs"] / (phase_df["_balls"] / 6).replace(0, np.nan)).round(2)
agg = agg.merge(phase_df[["bowler", f"economy_{phase}"]], on="bowler", how="left")
inning_wkt = d.groupby(["bowler", "match_id", "inning"])["is_wicket"].sum().reset_index()
inning_wkt.columns = ["bowler", "match_id", "inning", "wickets_in_inning"]
three_wkt = inning_wkt[inning_wkt["wickets_in_inning"] >= 3].groupby("bowler").size().rename("three_wicket_haul")
five_wkt = inning_wkt[inning_wkt["wickets_in_inning"] >= 5].groupby("bowler").size().rename("five_wicket_haul")
agg = agg.merge(three_wkt, on="bowler", how="left")
agg = agg.merge(five_wkt, on="bowler", how="left")
agg["three_wicket_haul"] = agg["three_wicket_haul"].fillna(0).astype(int)
agg["five_wicket_haul"] = agg["five_wicket_haul"].fillna(0).astype(int)
agg = agg[agg["balls_bowled"] >= 60].copy()
print(f"build_bowling_features: {len(agg)} bowlers with 60+ balls bowled")
return agg.sort_values("wickets", ascending=False).reset_index(drop=True)
def build_match_summary(matches: pd.DataFrame, deliveries: pd.DataFrame) -> pd.DataFrame:
innings_total = deliveries.groupby(["match_id", "inning", "batting_team"]).agg(
total_runs = ("total_runs", "sum"),
total_wickets = ("is_wicket", "sum"),
total_balls = ("is_legal_delivery", "sum"),
total_fours = ("is_four", "sum"),
total_sixes = ("is_six", "sum"),
dot_balls = ("is_dot_ball", "sum"),
).reset_index()
innings_total["run_rate"] = (
innings_total["total_runs"] / (innings_total["total_balls"] / 6).replace(0, np.nan)
).round(2)
inn1 = innings_total[innings_total["inning"] == 1].add_prefix("inn1_").rename(columns={"inn1_match_id": "match_id"})
inn2 = innings_total[innings_total["inning"] == 2].add_prefix("inn2_").rename(columns={"inn2_match_id": "match_id"})
summary = matches.merge(inn1.drop(columns=["inn1_inning"]), on="match_id", how="left")
summary = summary.merge(inn2.drop(columns=["inn2_inning"]), on="match_id", how="left")
summary["toss_winner_won"] = (summary["toss_winner"] == summary["winner"]).astype(int)
summary["batting_first_team"] = summary["inn1_batting_team"]
summary["batting_first_won"] = (
summary["inn1_batting_team"] == summary["winner"]
).astype(int)
summary["score_diff"] = summary["inn1_total_runs"] - summary["inn2_total_runs"]
min_season = summary["season"].min()
summary["season_num"] = summary["season"] - min_season + 1
print(f"build_match_summary: {summary.shape}")
return summary.reset_index(drop=True)
def build_team_season_stats(matches: pd.DataFrame) -> pd.DataFrame:
records = []
for season in sorted(matches["season"].unique()):
season_df = matches[matches["season"] == season]
all_teams = pd.concat([season_df["team1"], season_df["team2"]]).unique()
for team in all_teams:
played = season_df[(season_df["team1"] == team) | (season_df["team2"] == team)]
won = season_df[season_df["winner"] == team]
bat_first = played[played["inn1_batting_team"] == team] if "inn1_batting_team" in played.columns else pd.DataFrame()
toss_won = played[played["toss_winner"] == team]
toss_bat = toss_won[toss_won["toss_decision"] == "bat"]
records.append({
"season": season,
"team": team,
"matches_played": len(played),
"matches_won": len(won),
"win_pct": round(len(won) / len(played) * 100, 1) if len(played) > 0 else 0,
"toss_wins": len(toss_won),
"toss_bat_choice": len(toss_bat),
})
df = pd.DataFrame(records)
print(f"build_team_season_stats: {df.shape}")
return df