| import pandas as pd
|
| import numpy as np
|
| from sklearn.svm import SVR
|
|
|
| SEASON_MAP = {
|
| "2007/08": 1, "2009": 2, "2009/10": 3, "2011": 4, "2012": 5, "2013": 6, "2014": 7, "2015": 8, "2016": 9, "2017": 10,
|
| "2018": 11, "2019": 12, "2020/21": 13, "2021": 14, "2022": 15, "2023": 16, "2024": 17, "2025": 18, "2026": 19,
|
| }
|
|
|
| VENUE_NORM = {
|
| "ma chidambaram stadium": "MA Chidambaram Stadium","ma chidambaram stadium, chepauk": "MA Chidambaram Stadium", "ma chidambaram stadium, chepauk, chennai": "MA Chidambaram Stadium",
|
| "m chinnaswamy stadium": "M Chinnaswamy Stadium","m.chinnaswamy stadium": "M Chinnaswamy Stadium","m chinnaswamy stadium, bengaluru": "M Chinnaswamy Stadium",
|
| "wankhede stadium": "Wankhede Stadium","wankhede stadium, mumbai": "Wankhede Stadium",
|
| "narendra modi stadium": "Narendra Modi Stadium, Ahmedabad","narendra modi stadium, ahmedabad": "Narendra Modi Stadium, Ahmedabad",
|
| "sardar patel stadium, motera": "Narendra Modi Stadium, Ahmedabad","sardar patel stadium": "Narendra Modi Stadium, Ahmedabad","motera stadium": "Narendra Modi Stadium, Ahmedabad",
|
| "eden gardens": "Eden Gardens","eden gardens, kolkata": "Eden Gardens",
|
| "sawai mansingh stadium": "Sawai Mansingh Stadium","sawai mansingh stadium, jaipur": "Sawai Mansingh Stadium",
|
| "barsapara cricket stadium": "Barsapara Cricket Stadium, Guwahati","barsapara stadium, guwahati": "Barsapara Cricket Stadium, Guwahati","barsapara cricket stadium, guwahati": "Barsapara Cricket Stadium, Guwahati",
|
| "arun jaitley stadium": "Arun Jaitley Stadium","arun jaitley stadium, delhi": "Arun Jaitley Stadium",
|
| "feroz shah kotla": "Arun Jaitley Stadium","feroz shah kotla ground": "Arun Jaitley Stadium",
|
| "dr. y.s. rajasekhara reddy aca-vdca cricket stadium": "Dr. Y.S. Rajasekhara Reddy ACA-VDCA Cricket Stadium","dr. y.s. rajasekhara reddy aca-vdca cricket stadium, visakhapatnam": "Dr. Y.S. Rajasekhara Reddy ACA-VDCA Cricket Stadium",
|
| "punjab cricket association is bindra stadium": "Punjab Cricket Association IS Bindra Stadium","punjab cricket association is bindra stadium, mohali": "Punjab Cricket Association IS Bindra Stadium","punjab cricket association is bindra stadium, mohali, chandigarh": "Punjab Cricket Association IS Bindra Stadium","punjab cricket association stadium, mohali": "Punjab Cricket Association IS Bindra Stadium",
|
| "himachal pradesh cricket association stadium": "Himachal Pradesh Cricket Association Stadium", "himachal pradesh cricket association stadium, dharamsala": "Himachal Pradesh Cricket Association Stadium",
|
| "rajiv gandhi international stadium": "Rajiv Gandhi International Stadium","rajiv gandhi international stadium, uppal": "Rajiv Gandhi International Stadium","rajiv gandhi international stadium, uppal, hyderabad": "Rajiv Gandhi International Stadium","rajiv gandhi international cricket stadium": "Rajiv Gandhi International Stadium",
|
| "bharat ratna shri atal bihari vajpayee ekana cricket stadium": "Bharat Ratna Shri Atal Bihari Vajpayee Ekana Cricket Stadium","bharat ratna shri atal bihari vajpayee ekana cricket stadium, lucknow": "Bharat Ratna Shri Atal Bihari Vajpayee Ekana Cricket Stadium",
|
| "brabourne stadium": "Brabourne Stadium","brabourne stadium, mumbai": "Brabourne Stadium",
|
| "dr dy patil sports academy": "Dr DY Patil Sports Academy","dr dy patil sports academy, mumbai": "Dr DY Patil Sports Academy",
|
| "maharashtra cricket association stadium": "Maharashtra Cricket Association Stadium","maharashtra cricket association stadium, pune": "Maharashtra Cricket Association Stadium",
|
| "maharaja yadavindra singh international cricket stadium, mullanpur": "Maharaja Yadavindra Singh International Cricket Stadium","maharaja yadavindra singh international cricket stadium, new chandigarh": "Maharaja Yadavindra Singh International Cricket Stadium",
|
| }
|
|
|
| def _norm_venue(name: str) -> str:
|
| key = str(name).strip().lower()
|
| return VENUE_NORM.get(key, str(name).strip())
|
|
|
| ACTIVE_TEAMS = {
|
| "Chennai Super Kings", "Delhi Capitals", "Gujarat Titans", "Kolkata Knight Riders",
|
| "Lucknow Super Giants", "Mumbai Indians", "Punjab Kings", "Rajasthan Royals",
|
| "Royal Challengers Bengaluru", "Sunrisers Hyderabad",
|
| }
|
|
|
| TEAM_NORM = {
|
| "royal challengers bangalore": "Royal Challengers Bengaluru","royal challengers bengaluru": "Royal Challengers Bengaluru",
|
| "delhi daredevils": "Delhi Capitals","delhi capitals": "Delhi Capitals",
|
| "kings xi punjab": "Punjab Kings","punjab kings": "Punjab Kings",
|
| "rising pune supergiant": "Rising Pune Supergiants","rising pune supergiants": "Rising Pune Supergiants",
|
| "sunrisers hyderabad": "Sunrisers Hyderabad",
|
| }
|
|
|
| def _norm_team(name: str) -> str:
|
| key = str(name).strip().lower()
|
| canonical = TEAM_NORM.get(key, str(name).strip())
|
| return canonical if canonical in ACTIVE_TEAMS else "Other"
|
|
|
|
|
| def _parse_wickets(player_id_str) -> int:
|
| if pd.isna(player_id_str) or str(player_id_str).strip() == "":
|
| return 0
|
| ids = [p.strip() for p in str(player_id_str).split(",") if p.strip()]
|
| return int(np.clip(len(ids) - 2, 0, 10))
|
|
|
|
|
| def _ceil_to_5(x: float) -> int:
|
| x = int(x)
|
| return x if x % 5 == 0 else int(np.ceil(x / 5.0) * 5)
|
|
|
|
|
| class MyModel:
|
|
|
| def __init__(self):
|
| self.model = None
|
| self.global_mean = 50
|
| self.venue_map = {}
|
| self.team_columns = []
|
|
|
| self._v_wkt_mean = {}
|
| self._v_inn_mean = {}
|
| self._v_six_mean = {}
|
| self._v_six_season_mean = {}
|
| self._v_season_mean = {}
|
| self._h2h_venue_mean = {}
|
| self._bat_venue_mean = {}
|
| self._bat_season_mean = {}
|
| self._bat_form = {}
|
| self._v_trend = {}
|
| self._global_v_wkt = 50.0
|
| self._global_v_inn = 50.0
|
| self._global_six_mean = 3.0
|
|
|
| def _build_features(self, agg: pd.DataFrame) -> pd.DataFrame:
|
| gm = self._global_v_wkt
|
| last_s = max(self._v_season_mean.keys(), key=lambda x: x[1], default=(0, 0))[1]
|
|
|
|
|
| agg["v_wkt_mean"] = [
|
| self._v_wkt_mean.get((v, w),
|
| self._v_inn_mean.get((v, i),
|
| gm))
|
| for v, w, i in zip(agg["v_enc"], agg["wickets"], agg["inning"])
|
| ]
|
|
|
|
|
| agg["v_inn_mean"] = [
|
| self._v_inn_mean.get((v, i), gm)
|
| for v, i in zip(agg["v_enc"], agg["inning"])
|
| ]
|
|
|
|
|
| agg["v_six_mean"] = [
|
| self._v_six_mean.get(v, self._global_six_mean)
|
| for v in agg["v_enc"]
|
| ]
|
| agg["v_six_season_mean"] = [
|
| self._v_six_season_mean.get((v, s),
|
| self._v_six_mean.get(v, self._global_six_mean))
|
| for v, s in zip(agg["v_enc"], agg["season"])
|
| ]
|
|
|
|
|
| agg["v_season_mean"] = [
|
| self._v_season_mean.get((v, s),
|
| self._v_season_mean.get((v, last_s),
|
| self._v_inn_mean.get((v, i),
|
| gm)))
|
| for v, s, i in zip(agg["v_enc"], agg["season"], agg["inning"])
|
| ]
|
|
|
|
|
| agg["h2h_venue_mean"] = [
|
| self._h2h_venue_mean.get((v, bt, bw),
|
| self._bat_venue_mean.get((v, bt),
|
| self._v_inn_mean.get((v, i),
|
| gm)))
|
| for v, bt, bw, i in zip(agg["v_enc"], agg["batting_team"], agg["bowling_team"], agg["inning"])
|
| ]
|
|
|
|
|
| agg["bat_form_3"] = [
|
| self._bat_form.get(bt,
|
| self._bat_season_mean.get((bt, last_s),
|
| self._v_inn_mean.get((v, i),
|
| gm)))
|
| for bt, v, i in zip(agg["batting_team"], agg["v_enc"], agg["inning"])
|
| ]
|
|
|
| agg["v_trend"] = [
|
| self._v_trend.get(v, 0.0) for v in agg["v_enc"]
|
| ]
|
| return agg
|
|
|
| def fit(self, deliveries_df, players_df=None, matches_df=None):
|
|
|
| df = deliveries_df.copy()
|
| df["over"] = pd.to_numeric(df["over"], errors="coerce")
|
| df["inning"] = pd.to_numeric(df["inning"], errors="coerce").astype("int32")
|
|
|
| run_cols = ["batsman_runs", "extras", "isWide", "isNoBall", "Byes", "LegByes"]
|
| run_cols = [c for c in run_cols if c in df.columns]
|
| df[run_cols] = df[run_cols].apply(pd.to_numeric, errors="coerce").fillna(0)
|
|
|
| df = df[df["over"] < 6].copy()
|
| df["ball_runs"] = df[run_cols].sum(axis=1)
|
| df["is_wicket"] = df.get("player_dismissed", pd.Series()).notna().astype(int)
|
| df["is_six"] = (
|
| (df["batsman_runs"] == 6) &
|
| (df.get("isWide", pd.Series(0, index=df.index)).fillna(0) == 0) &
|
| (df.get("isNoBall", pd.Series(0, index=df.index)).fillna(0) == 0)
|
| ).astype(int)
|
|
|
| agg = (
|
| df.groupby(["matchId", "inning"])
|
| .agg(
|
| runs=("ball_runs", "sum"),
|
| wickets=("is_wicket", "sum"),
|
| sixes=("is_six", "sum"),
|
| batting_team=("batting_team", "first"),
|
| bowling_team=("bowling_team", "first"),
|
| )
|
| .reset_index()
|
| )
|
|
|
| agg["batting_team"] = agg["batting_team"].apply(_norm_team)
|
| agg["bowling_team"] = agg["bowling_team"].apply(_norm_team)
|
|
|
| meta_cols = ["matchId", "venue", "season"]
|
| for opt in ["toss_winner", "toss_decision"]:
|
| if opt in matches_df.columns:
|
| meta_cols.append(opt)
|
|
|
| meta = matches_df[meta_cols].drop_duplicates("matchId").copy()
|
| meta["venue"] = meta["venue"].astype(str).apply(_norm_venue)
|
| agg = agg.merge(meta, on="matchId", how="left")
|
|
|
| agg["venue"] = agg["venue"].fillna("Unknown")
|
| agg["season"] = (
|
| agg["season"].fillna("Unknown").astype(str)
|
| .map(lambda x: SEASON_MAP.get(x.strip(), 0)).astype("int16")
|
| )
|
|
|
| if "toss_winner" in agg.columns and "toss_decision" in agg.columns:
|
| agg["toss_bat"] = (
|
| (agg["batting_team"] == agg["toss_winner"]) &
|
| (agg["toss_decision"].str.lower().str.strip() == "bat")
|
| ).astype(int)
|
| else:
|
| agg["toss_bat"] = 0
|
|
|
| self.venue_map = {v: i for i, v in enumerate(sorted(agg["venue"].unique()))}
|
| agg["v_enc"] = agg["venue"].map(self.venue_map).fillna(-1).astype(int)
|
|
|
| gm = float(agg["runs"].mean())
|
| self._global_v_wkt = gm
|
| self._global_v_inn = gm
|
| self._global_six_mean = float(agg["sixes"].mean())
|
| self.global_mean = int(round(gm))
|
|
|
| self._v_wkt_mean = agg.groupby(["v_enc", "wickets"])["runs"].mean().to_dict()
|
| self._v_inn_mean = agg.groupby(["v_enc", "inning"]) ["runs"].mean().to_dict()
|
| self._v_six_mean = agg.groupby("v_enc")["sixes"].mean().to_dict()
|
| self._v_six_season_mean = agg.groupby(["v_enc", "season"])["sixes"].mean().to_dict()
|
| self._v_season_mean = agg.groupby(["v_enc", "season"])["runs"].mean().to_dict()
|
| self._h2h_venue_mean = agg.groupby(["v_enc", "batting_team", "bowling_team"])["runs"].mean().to_dict()
|
| self._bat_venue_mean = agg.groupby(["v_enc", "batting_team"])["runs"].mean().to_dict()
|
| self._bat_season_mean = agg.groupby(["batting_team", "season"])["runs"].mean().to_dict()
|
|
|
| agg_sorted = agg.sort_values(["season", "matchId"])
|
| bat_form_series = (
|
| agg_sorted.groupby("batting_team")["runs"]
|
| .transform(lambda x: x.shift(1).rolling(3, min_periods=1).mean())
|
| )
|
| form_df = agg_sorted.copy()
|
| form_df["bat_form_3"] = bat_form_series.reindex(agg_sorted.index).fillna(gm)
|
| self._bat_form = form_df.groupby("batting_team")["bat_form_3"].last().to_dict()
|
|
|
| vsm = agg.groupby(["v_enc", "season"])["runs"].mean().reset_index()
|
| def _slope(grp):
|
| if len(grp) < 2:
|
| return 0.0
|
| x = grp["season"].values.astype(float)
|
| return float(np.polyfit(x, grp["runs"].values, 1)[0])
|
| self._v_trend = vsm.groupby("v_enc").apply(_slope).to_dict()
|
|
|
| agg = self._build_features(agg)
|
|
|
| bat_ohe = pd.get_dummies(agg["batting_team"], prefix="bat")
|
| bowl_ohe = pd.get_dummies(agg["bowling_team"], prefix="bowl")
|
| self.team_columns = list(bat_ohe.columns) + list(bowl_ohe.columns)
|
|
|
| base_features = [
|
| "v_enc", "inning", "wickets", "season",
|
| "v_wkt_mean", "v_inn_mean",
|
| "v_six_mean", "v_six_season_mean",
|
| "h2h_venue_mean",
|
| "v_season_mean",
|
| "bat_form_3",
|
| "v_trend",
|
| "toss_bat",
|
| ]
|
| X = pd.concat([
|
| agg[base_features].reset_index(drop=True),
|
| bat_ohe.reset_index(drop=True),
|
| bowl_ohe.reset_index(drop=True),
|
| ], axis=1)
|
| y = agg["runs"].values.astype("float32")
|
|
|
| self.model = SVR(kernel='rbf', gamma='scale', C=113.084, epsilon=3.418)
|
| self.model.fit(X.values.astype("float32"), y)
|
| self._feature_names = list(X.columns)
|
|
|
| return self
|
|
|
| def predict(self, test_df):
|
|
|
| records = []
|
|
|
| for i, row in test_df.iterrows():
|
| inning = int(row.get("innings", 1))
|
| venue = _norm_venue(str(row.get("venue", "Unknown")))
|
| season = int(SEASON_MAP.get(str(row.get("season", "2026")), 19))
|
| wickets = _parse_wickets(row.get("Batsman's Player Id", ""))
|
| v_enc = self.venue_map.get(venue, -1)
|
| batting_team = _norm_team(str(row.get("batting_team", "Unknown")))
|
| bowling_team = _norm_team(str(row.get("bowling_team", "Unknown")))
|
|
|
| toss_winner = str(row.get("toss_winner", ""))
|
| toss_decision = str(row.get("toss_decision", "")).strip().lower()
|
| toss_bat = int(batting_team == toss_winner and toss_decision == "bat")
|
| row_df = pd.DataFrame([{
|
| "v_enc": v_enc,
|
| "inning": inning,
|
| "wickets": wickets,
|
| "season": season,
|
| "batting_team": batting_team,
|
| "bowling_team": bowling_team,
|
| "toss_bat": toss_bat,
|
| }])
|
| row_df = self._build_features(row_df)
|
|
|
| base_features = [
|
| "v_enc", "inning", "wickets", "season",
|
| "v_wkt_mean", "v_inn_mean",
|
| "v_six_mean", "v_six_season_mean",
|
| "v_season_mean", "h2h_venue_mean", "bat_form_3", "v_trend", "toss_bat",
|
| ]
|
| base = row_df[base_features].reset_index(drop=True)
|
|
|
| team_row = pd.DataFrame([[0] * len(self.team_columns)], columns=self.team_columns)
|
| bat_col = f"bat_{batting_team}"
|
| bowl_col = f"bowl_{bowling_team}"
|
| if bat_col in team_row.columns: team_row[bat_col] = 1
|
| if bowl_col in team_row.columns: team_row[bowl_col] = 1
|
|
|
| x = pd.concat([base, team_row], axis=1).values.astype("float32")
|
|
|
| if self.model:
|
| raw = float(self.model.predict(x)[0])
|
| pred = _ceil_to_5(raw)
|
| else:
|
| pred = self.global_mean
|
|
|
| records.append({"id": row.get("id", i), "predicted_score": pred})
|
|
|
| return pd.DataFrame(records) |