rinklytics / src /data /archive /statsapi /visualization /evaluation_teamSuccess.py
jjerlee's picture
Deploy Rinklytics Streamlit dashboard and datasets (part 2)
1f62eb2 verified
Raw History Blame Contribute Delete
8.58 kB
# Process to evaluate team season succcess,
# over all available play-level data (2011-current)
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import matplotlib.pyplot as plt
from matplotlib.ticker import MultipleLocator, FixedFormatter, FixedLocator
plt.rcParams.update({'font.size': 16})
# Directories - locate your data location from the scraping exercise
# https://github.com/justinjoliver/NHL-Analytics/tree/main/src/data
str_dirc = '__DATA DIR__';
# import relevant functions and import process data
str_dir_sourceCode = "__SOURE DIR__"
exec(open(str_dir_sourceCode + "/measurement_teamSuccess.py").read())
# Last year season progress
idx_yr_last = 2022
list_years = np.arange(2011, idx_yr_last)
for iter_yr in list_years:
# Pull the latest data for process
df_box = pd.read_csv(str_dirc + f"/cleaned/{iter_yr}_02_box.csv",
parse_dates = ['gameDate'],
index_col = 'gameIdx')
# Unpack data
df_box_exnt = df_unpack(df_box)
#df_box_exnt.columns
# Calculate metrics
df_box_exnt = df_metrics(df_box_exnt)
# Aggregate up for team-level statistics
dfteams = df_box_exnt.groupby(['team_tri_for'])\
.agg(
{
"fenwick_lvl":"sum",
"corsi_lvl":"sum",
"rpe":"last",
"rwin":"last",
"rgame":"last"
}
)
dfteams.sort_values(by = ["rwin","fenwick_lvl", "corsi_lvl"], inplace = True)
# Join with season-pythagorean expectation
#dfteams = dfteams.join(df_pe_season)
# Normalize KPIs for measurement comparison
dfteams['wp'] = dfteams.rwin / dfteams.rgame
dfteams['kpi_corsi'] = kpinorm(dfteams.corsi_lvl)
dfteams['kpi_fenwick'] = kpinorm(dfteams.fenwick_lvl)
dfteams['kpi_pe'] = kpinorm(dfteams.rpe)
# Coefficient estimates from 2010-2020, calculate fitted win percentage
beta_pyth_old = 0.00809623 * 100
alpah_pyth_old = 0.042901
x_starting = 0.3
x_end = x_starting + 0.45
y_starting = (x_starting) * beta_pyth_old + alpah_pyth_old
y_end = (x_end) * beta_pyth_old + alpah_pyth_old
dfteams['rwin_fittedPE'] = (dfteams['rpe'] * beta_pyth_old + alpah_pyth_old) * 82
# drop the first game,
df_box_exnt.loc[df_box_exnt.gameDate_del_against.notnull(), :].head(3)
time_games_all = np.sort(df_box_exnt.gameDate_for.unique()); # by date existing
# Capture the last date.
iter_time = time_games_all[-1];
# NOTE: while it may be exciting to compute the measure in real time, it takes
# some time to compute it all. I focus on looking at upto the latest available
# data
# Head to head statistics
dff_h2h_com = df_gen_h2h_common(df_box_exnt, iter_time)
# Game pairwise - RPI statistics
dfteams = dfteams.join(pairwise_h2h(df_box_exnt, dff_h2h_com))
# More head-to-head statistics
dff_h2h_com_win = dff_h2h_com\
.groupby(['team_tri_for_ow', 'team_tri_for_oow']) \
.agg(
{
'game_won_h2h_ow':'sum',
'game_played_h2h_ow':'sum',
'game_won_h2h_oow':'sum',
'game_played_h2h_oow':'sum'
}
)
# for each opponent, compare who has winning record as common opponent
# I use win ratio
dff_h2h_com_win['h2h_win'] = (\
(dff_h2h_com_win.game_won_h2h_ow / dff_h2h_com_win.game_played_h2h_ow) > \
(dff_h2h_com_win.game_won_h2h_oow / dff_h2h_com_win.game_played_h2h_oow)
) * 1
# For each team, count how many opponents the team has winning record against
rank_pairwise_win = dff_h2h_com_win.groupby(['team_tri_for_ow']) \
.agg({'h2h_win':'sum'}) \
.sort_values(by = 'h2h_win', ascending = False)
rank_pairwise_win.columns = ['pairwise_win']
# Here the rank can even be more continuous (how much they win instead of count in games)
# Join with the existing team stats
dfteams = dfteams.join(rank_pairwise_win)
# Normalize
dfteams['kpi_pairwise'] = kpinorm(dfteams.pairwise_win)
dfteams['kpi_rpi'] = kpinorm(dfteams.rpi)
dfteams['kpi_wp'] = kpinorm(dfteams.wp)
dfteams['Final'] = dfteams\
.sort_values(by = 'kpi_wp', ascending = False) \
.reset_index().index + 1
dfteams['Final'] = kpinorm(dfteams.Final)
# Data append
# Add season statistics for and against
df_box_exnt = df_box_exnt.merge(dfteams.reset_index(),
on = ['team_tri_for'], suffixes=('', '_for')) \
.merge(dfteams.reset_index(),
left_on = ['team_tri_against'],
right_on = ['team_tri_for'],
suffixes=('', '_against'))
# Check wins against better opponent
# Is the team-for facing eventual better opponent?
# Using pairwise here - alternatively can use RPI
# ...........................................................................
# Observing win streaks
# Is opponent better than average?
df_box_exnt['idx_OppoGood'] = df_box_exnt.rpi_against > \
np.quantile(df_box_exnt.rpi_against, 0.75)
# Playing against worse team, thus leading to expected outcome?
df_box_exnt['idx_OppoBetter'] = df_box_exnt.pairwise_win_against < \
df_box_exnt.pairwise_win
df_box_exnt['idx_OppoBetter'] = df_box_exnt.rpi_against < \
df_box_exnt.rpi
df_box_exnt['win_for_better'] = df_box_exnt.win_for * df_box_exnt.idx_OppoGood
df_box_exnt['win_for_expected'] = df_box_exnt.win_for * df_box_exnt.idx_OppoBetter
pivot_teamcumwin = df_box_exnt \
.groupby(['team_tri_for', 'rgame']) \
.agg(
win = ("win_for", "sum"),
win_better = ("win_for_better", "sum"),
win_expected = ("win_for_expected", "sum")
) \
.reset_index()
for iter_lag in np.arange(1,3):
pivot_teamcumwin[f'win_lag{iter_lag}'] = pivot_teamcumwin \
.groupby(['team_tri_for']).win.shift(iter_lag)
pivot_teamcumwin[f'win_better_lag{iter_lag}'] = pivot_teamcumwin \
.groupby(['team_tri_for']).win_better.shift(iter_lag)
pivot_teamcumwin[f'win_expected_lag{iter_lag}'] = pivot_teamcumwin \
.groupby(['team_tri_for']).win_expected.shift(iter_lag)
# Identify all win streak
pivot_teamcumwin['idx_WinStreak'] = 0
pivot_teamcumwin.loc[(np.sum(pivot_teamcumwin[['win','win_lag1', 'win_lag2']], axis = 1) == 3), 'idx_WinStreak'] = 1
# Identify win streak with games all expected to win
pivot_teamcumwin['idx_WinStreak_expected'] = 0
pivot_teamcumwin.loc[\
(np.sum(pivot_teamcumwin[['win_expected','win_expected_lag1', 'win_expected_lag2']], axis = 1) >= 2) \
& pivot_teamcumwin['idx_WinStreak'] == 1 \
, 'idx_WinStreak_expected'] = 1
# Identify win streak against good team: 75%tile and above RPI
pivot_teamcumwin['idx_HotStreak'] = 0
pivot_teamcumwin.loc[(pivot_teamcumwin.idx_WinStreak == 1) & \
(np.sum(pivot_teamcumwin[['win_better', 'win_better_lag1', 'win_better_lag2']], axis = 1) >= 1), \
'idx_HotStreak'] = 1
df_streak = pivot_teamcumwin[['team_tri_for',
'idx_WinStreak',
'idx_WinStreak_expected',
'idx_HotStreak']] \
.groupby('team_tri_for').sum()
df_streak['ratio'] = df_streak.idx_HotStreak / df_streak.idx_WinStreak
df_streak['ratio_viz'] = np.int0(df_streak['ratio']*100)
df_streak['ratio_viz'] = [str(iter) + '%' for iter in df_streak['ratio_viz']]
# Sort value for future ease of use
# Finally join with data
dfteams = dfteams.join(df_streak)
# ===========================================================================
# Some plotting
exec(open(str_dir_sourceCode + "/evaluation_winStreak_plot.py").read())
# ...........................................................................
# output data
# (a) game-level stats
df_box_exnt['yr_season'] = iter_yr
df_box_exnt.to_csv(str_dirc + f"/processed/{iter_yr}_02_box.csv")
# (b) team-level stats for each season
dfteams['yr_season'] = iter_yr
dfteams.to_csv(str_dirc + f"/processed/{iter_yr}_02_team.csv")
# (c) team-level head-to-head statistics
dff_h2h_com_win['yr_season'] = iter_yr