{"cells":[{"metadata":{},"cell_type":"markdown","source":"The goal of this notebook is to explore the data of both the Men's and Women's competitions and answering the following questions:\n\n* How did the game evolved over the years?\n* What stats are most useful to predict the outcome of a game?\n* What are the differences between the Men's and Women's competitions?\n\nTo do so, we need to produce some aggregated statistics. The next few hidden cells have all the functions to do just that."},{"metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"import numpy as np \nimport pandas as pd \n\nimport seaborn as sns\nimport matplotlib.pyplot as plt\nimport matplotlib.lines as mlines\n%matplotlib inline\n\nfrom sklearn.model_selection import KFold\nfrom sklearn.metrics import roc_auc_score\nfrom sklearn.preprocessing import StandardScaler\nfrom sklearn.pipeline import Pipeline\nfrom sklearn.ensemble import RandomForestClassifier\nfrom sklearn.manifold import TSNE\n\nimport explore_data as exp\nimport df_pipeline as df_p\n\npd.set_option(\"max_columns\", 300)","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"d629ff2d2480ee46fbb7e2d37f6b5fab8052498a","_cell_guid":"79c7e3d0-c299-4dcb-8224-4455121ee9b0","trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"def process_details(data):\n    df = data.copy()\n    stats = ['Score', 'FGM', 'FGA', 'FGM3', 'FGA3', 'FTM', \n             'FTA', 'OR', 'DR', 'Ast', 'TO', 'Stl', 'Blk', \n             'PF', 'FGM2', 'FGA2', 'Tot_Reb', 'FGM_no_ast', \n             'Def_effort', 'Reb_opp', 'possessions', \n             'off_rating', 'def_rating', 'scoring_opp', \n             'TO_perposs', 'impact'] \n        \n    for prefix in ['W', 'L']:\n        df[prefix+'FG_perc'] = df[prefix+'FGM'] / df[prefix+'FGA']\n        df[prefix+'FGM2'] = df[prefix+'FGM'] - df[prefix+'FGM3']\n        df[prefix+'FGA2'] = df[prefix+'FGA'] - df[prefix+'FGA3']\n        df[prefix+'FG2_perc'] = df[prefix+'FGM2'] / df[prefix+'FGA2']\n        df[prefix+'FG3_perc'] = df[prefix+'FGM3'] / df[prefix+'FGA3']\n        df[prefix+'FT_perc'] = df[prefix+'FTM'] / df[prefix+'FTA']\n        df[prefix+'Tot_Reb'] = df[prefix+'OR'] + df[prefix+'DR']\n        df[prefix+'FGM_no_ast'] = df[prefix+'FGM'] - df[prefix+'Ast']\n        df[prefix+'FGM_no_ast_perc'] = df[prefix+'FGM_no_ast'] / df[prefix+'FGM']\n        df[prefix+'possessions'] = df[prefix+'FGA'] - df[prefix+'OR'] + df[prefix+'TO'] + 0.475*df[prefix+'FTA']\n        df[prefix+'off_rating'] = df[prefix+'Score'] / df[prefix+'possessions'] * 100\n        df[prefix+'shtg_opportunity'] = 1 + (df[prefix+'OR'] - df[prefix+'TO']) / df[prefix+'possessions']\n        df[prefix+'TO_perposs'] = df[prefix+'TO'] / df[prefix+'possessions']\n        df[prefix+'IE_temp'] = df[prefix+'Score'] + df[prefix+'FTM'] + df[prefix+'FGM'] + \\\n                                df[prefix+'DR'] + 0.5*df[prefix+'OR'] - df[prefix+'FTA'] - df[prefix+'FGA'] + \\\n                                df[prefix+'Ast'] + df[prefix+'Stl'] + 0.5*df[prefix+'Blk'] - df[prefix+'PF']\n\n    df['Wdef_rating'] = df['Loff_rating']\n    df['Ldef_rating'] = df['Woff_rating']\n\n    df['Wimpact'] = df['WIE_temp'] / (df['WIE_temp'] + df['LIE_temp'])\n    df['Limpact'] = df['LIE_temp'] / (df['WIE_temp'] + df['LIE_temp'])\n\n    del df['WIE_temp']\n    del df['LIE_temp']\n\n    df[[col for col in df.columns if 'perc' in col]] = df[[col for col in df.columns if 'perc' in col]].fillna(0)\n\n    df['WReb_opp'] = df['WDR'] / (df['LFGA'] - df['LFGM'])\n    df['LReb_opp'] = df['LDR'] / (df['WFGA'] - df['WFGM'])\n    \n    return df\n\n\ndef full_stats(data):\n    df = data.copy()\n    \n    to_select = [col for col in df.columns if 'W' in col and '_perc' not in col]\n    df_W = df[['Season', 'DayNum', 'NumOT'] + to_select].copy()\n    df_W.columns = df_W.columns.str.replace('W','')\n    df_W['N_wins'] = 1\n    \n    to_select = [col for col in df.columns if 'L' in col and '_perc' not in col]\n    df_L = df[['Season', 'DayNum', 'NumOT'] + to_select].copy()\n    df_L.columns = df_L.columns.str.replace('L','')\n    df_L = df_L.rename(columns={'Woc': 'Loc'})\n    df_L['N_wins'] = 0\n    \n    df = pd.concat([df_W, df_L])\n    \n    del df['DayNum']\n    del df['Loc']\n    \n    to_use = [col for col in df.columns if col != 'NumOT']\n    \n    means = df[to_use].groupby(['Season','TeamID'], as_index=False).mean()\n    \n    sums = df[to_use].groupby(['Season','TeamID'], as_index=False).sum()\n    sums['FGM_perc'] = sums.FGM / sums.FGA\n    sums['FGM2_perc'] = sums.FGM2 / sums.FGA2\n    sums['FGM3_perc'] = sums.FGM3 / sums.FGA3\n    sums['FT_perc'] = sums.FTM / sums.FTA\n    sums['FGM_no_ast_perc'] = sums.FGM_no_ast / sums.FGM\n    to_use = ['Season', 'TeamID', 'FGM_perc',\n              'FGM2_perc', 'FGM3_perc', 'FT_perc', \n              'FGM_no_ast_perc']\n    \n    sums = sums[to_use].fillna(0)\n    \n    stats_tot = pd.merge(means, sums, on=['Season', 'TeamID'])\n  \n    return stats_tot\n\n\ndef add_seed(seed_location, total):\n    seed_data = pd.read_csv(seed_location)\n    seed_data['Seed'] = seed_data['Seed'].apply(lambda x: int(x[1:3]))\n    total = pd.merge(total, seed_data, how='left', on=['TeamID', 'Season'])\n    return total\n\n\ndef make_teams_target(data, league):\n    if league == 'men':\n        limit = 2003\n    else:\n        limit = 2010\n\n    df = data[data.Season >= limit].copy()\n\n    df['Team1'] = np.where((df.WTeamID < df.LTeamID), df.WTeamID, df.LTeamID)\n    df['Team2'] = np.where((df.WTeamID > df.LTeamID), df.WTeamID, df.LTeamID)\n    df['target'] = np.where((df['WTeamID'] < df['LTeamID']),1,0)\n    df['target_points'] = np.where((df['WTeamID'] < df['LTeamID']),df.WScore - df.LScore,df.LScore - df.WScore)\n    df.loc[df.WLoc == 'N', 'LLoc'] = 'N'\n    df.loc[df.WLoc == 'H', 'LLoc'] = 'A'\n    df.loc[df.WLoc == 'A', 'LLoc'] = 'H'\n    df['T1_Loc'] = np.where((df.WTeamID < df.LTeamID), df.WLoc, df.LLoc)\n    df['T2_Loc'] = np.where((df.WTeamID > df.LTeamID), df.WLoc, df.LLoc)\n    df['T1_Loc'] = df['T1_Loc'].map({'H': 1, 'A': -1, 'N': 0})\n    df['T2_Loc'] = df['T2_Loc'].map({'H': 1, 'A': -1, 'N': 0})\n\n    reverse = data[data.Season >= limit].copy()\n    reverse['Team1'] = np.where((reverse.WTeamID > reverse.LTeamID), reverse.WTeamID, reverse.LTeamID)\n    reverse['Team2'] = np.where((reverse.WTeamID < reverse.LTeamID), reverse.WTeamID, reverse.LTeamID)\n    reverse['target'] = np.where((reverse['WTeamID'] > reverse['LTeamID']),1,0)\n    reverse['target_points'] = np.where((reverse['WTeamID'] > reverse['LTeamID']),\n                                        reverse.WScore - reverse.LScore,\n                                        reverse.LScore - reverse.WScore)\n    reverse.loc[reverse.WLoc == 'N', 'LLoc'] = 'N'\n    reverse.loc[reverse.WLoc == 'H', 'LLoc'] = 'A'\n    reverse.loc[reverse.WLoc == 'A', 'LLoc'] = 'H'\n    reverse['T1_Loc'] = np.where((reverse.WTeamID > reverse.LTeamID), reverse.WLoc, reverse.LLoc)\n    reverse['T2_Loc'] = np.where((reverse.WTeamID < reverse.LTeamID), reverse.WLoc, reverse.LLoc)\n    reverse['T1_Loc'] = reverse['T1_Loc'].map({'H': 1, 'A': -1, 'N': 0})\n    reverse['T2_Loc'] = reverse['T2_Loc'].map({'H': 1, 'A': -1, 'N': 0})\n    \n    df = pd.concat([df, reverse], ignore_index=True)\n\n    to_drop = ['WScore','WTeamID', 'LTeamID', 'LScore', 'WLoc', 'LLoc', 'NumOT']\n    for col in to_drop:\n        del df[col]\n    \n    df.loc[:,'ID'] = df.Season.astype(str) + '_' + df.Team1.astype(str) + '_' + df.Team2.astype(str)\n    return df\n\n\ndef make_training_data(details, targets):\n    tmp = details.copy()\n    tmp.columns = ['Season', 'Team1'] + \\\n                ['T1_'+col for col in tmp.columns if col not in ['Season', 'TeamID']]\n    total = pd.merge(targets, tmp, on=['Season', 'Team1'], how='left')\n\n    tmp = details.copy()\n    tmp.columns = ['Season', 'Team2'] + \\\n                ['T2_'+col for col in tmp.columns if col not in ['Season', 'TeamID']]\n    total = pd.merge(total, tmp, on=['Season', 'Team2'], how='left')\n    \n    if total.isnull().any().any():\n        raise ValueError('Something went wrong')\n        \n    stats = [col[3:] for col in total.columns if 'T1_' in col]\n\n    for stat in stats:\n        total['delta_'+stat] = total['T1_'+stat] - total['T2_'+stat]\n        \n    return total\n\n\ndef prepare_data(league):\n    save_loc = 'processed_data/' + league + '/'\n\n    if league == 'women':\n        main_loc = '../input/google-cloud-ncaa-march-madness-2020-division-1-womens-tournament/'\n        regular_season = main_loc + 'WDataFiles_Stage1/WRegularSeasonDetailedResults.csv'\n        playoff = main_loc + 'WDataFiles_Stage1/WNCAATourneyDetailedResults.csv'\n        playoff_compact = main_loc + 'WDataFiles_Stage1/WNCAATourneyCompactResults.csv'\n        seed = main_loc + 'WDataFiles_Stage1/WNCAATourneySeeds.csv'\n    else:\n        main_loc = '../input/google-cloud-ncaa-march-madness-2020-division-1-mens-tournament/'\n        regular_season = main_loc + 'MDataFiles_Stage1/MRegularSeasonDetailedResults.csv'\n        playoff = main_loc + 'MDataFiles_Stage1/MNCAATourneyDetailedResults.csv'\n        playoff_compact = main_loc + 'MDataFiles_Stage1/MNCAATourneyCompactResults.csv'\n        seed = main_loc + 'MDataFiles_Stage1/MNCAATourneySeeds.csv'\n    \n    # Season stats\n    reg = pd.read_csv(regular_season)\n    reg = process_details(reg)\n    regular_stats = full_stats(reg)\n    \n    regular_stats = add_seed(seed, regular_stats)\n    \n    # Playoff stats\n    play = pd.read_csv(playoff)\n    play = process_details(play)\n    playoff_stats = full_stats(play)\n    \n    playoff_stats = add_seed(seed, playoff_stats)\n    \n    # Target data generation\n    target_data = pd.read_csv(playoff_compact)\n    target_data = make_teams_target(target_data, league)\n    \n    all_reg = make_training_data(regular_stats, target_data)\n    \n    return all_reg, regular_stats, playoff_stats\n\n\ndef get_coef(pipe):\n    '''\n    Get dataframe with coefficients of a model in Pipeline\n    The step before the model has to have a get_feature_name method\n    '''\n    imp = pipe.steps[-1][1].coef_.ravel().tolist()\n    feats = pipe.steps[-2][1].get_feature_names()\n    result = pd.DataFrame({'feat':feats,'score':imp})\n    result['abs_res'] = abs(result['score'])\n    result = result.sort_values(by=['abs_res'],ascending=False)\n    del result['abs_res']\n    return result\n\n\ndef get_feature_importance(pipe):\n    '''\n    Get dataframe with the feature importance of a model in Pipeline\n    The step before the model has to have a get_feature_name method\n    '''\n    imp = pipe.steps[-1][1].feature_importances_.tolist() #it's a pipeline\n    feats = pipe.steps[-2][1].get_feature_names()\n    result = pd.DataFrame({'feat':feats,'score':imp})\n    result = result.sort_values(by=['score'],ascending=False)\n    return result\n\n\ndef cv_score(df_train, y_train, kfolds, pipeline, imp_coef=False, predict_proba=False):\n    '''\n    Train and test a pipeline in kfold cross validation\n    Returns the oof predictions for the entire train set and a dataframe with the\n    coefficients or feature importances, averaged across the folds, with standard deviation\n    '''\n    oof = np.zeros(len(df_train))\n    train = df_train.copy()\n    \n    feat_df = pd.DataFrame()\n    \n    for n_fold, (train_index, test_index) in enumerate(kfolds.split(train.values)):\n            \n        trn_data = train.iloc[train_index][:]\n        val_data = train.iloc[test_index][:]\n        \n        trn_target = y_train.iloc[train_index].values.ravel()\n        val_target = y_train.iloc[test_index].values.ravel()\n        \n        pipeline.fit(trn_data, trn_target)\n        \n        if predict_proba:\n            oof[test_index] = pipeline.predict_proba(val_data)[:,1]\n        else:\n            oof[test_index] = pipeline.predict(val_data).ravel()\n\n        if imp_coef:\n            try:\n                fold_df = get_coef(pipeline)\n            except AttributeError:\n                fold_df = get_feature_importance(pipeline)\n                \n            fold_df['fold'] = n_fold + 1\n            feat_df = pd.concat([feat_df, fold_df], axis=0)\n       \n    if imp_coef:\n        feat_df = feat_df.groupby('feat')['score'].agg(['mean', 'std'])\n        feat_df['abs_sco'] = (abs(feat_df['mean']))\n        feat_df = feat_df.sort_values(by=['abs_sco'],ascending=False)\n        del feat_df['abs_sco']\n        return oof, feat_df\n    else:    \n        return oof","execution_count":null,"outputs":[]},{"metadata":{"_cell_guid":"","_uuid":"","trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"men_train, men_reg, men_play = prepare_data('men')\n\nmen_reg.head()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"women_train, women_reg, women_play = prepare_data('women')\n\nwomen_reg.head()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Teams over the years\n\nIt must first be noticed that both competitions saw an increase in the number of teams participating, showing a somewhat similar pattern in the period from 2010 to last year."},{"metadata":{"trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"fig, ax = plt.subplots(1,2, figsize=(15,6), sharey=True)\n\nfig.suptitle('Number of teams competing', fontsize=18)\nmen_reg.groupby('Season').TeamID.nunique().plot(ax=ax[0])\nwomen_reg.groupby('Season').TeamID.nunique().plot(ax=ax[1])\n\nax[0].set_title(\"Men's competition\", fontsize=14)\nax[1].set_title(\"Women's competition\", fontsize=14)\n\nax[0].axvline(2010, color='r', linestyle='--')\n\nplt.show()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"We can then look at how the game has changed over the years by looking at how, on average, the stats are changing season by season"},{"metadata":{"trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"stats = ['Score', 'FGA', 'FGM', 'FGM_perc', 'FGA3', 'FGM3', 'FGM3_perc', 'FT_perc', \n         'DR', 'OR', 'Ast', 'TO', 'Stl', 'Blk', 'possessions', 'off_rating']\n\nfor col in stats:\n\n    fig, ax = plt.subplots(1,2, figsize=(15,6), sharey=True)\n\n    fig.suptitle(col, fontsize=18)\n    men_reg.groupby('Season')[col].mean().plot(ax=ax[0], label='Men')\n    women_reg.groupby('Season')[col].mean().plot(ax=ax[0], label='Women')\n\n    men_play.groupby('Season')[col].mean().plot(ax=ax[1], label='Men')\n    women_play.groupby('Season')[col].mean().plot(ax=ax[1], label='Women')\n\n    ax[0].set_title(\"Regular Season\", fontsize=14)\n    ax[1].set_title(\"NCAA Tourney\", fontsize=14)\n\n    ax[0].axvline(2010, color='r', linestyle='--')\n    ax[1].axvline(2010, color='r', linestyle='--')\n    \n    ax[0].legend()\n    ax[1].legend()\n\n    plt.show()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"* Men tend to score more than Women, although the Men score less during the playoff while the Women have scored a similar amount of points both during regular season and playoffs.\n* Both Men and Women are attempting more and more shots. In particular, the number of shots attempted in a Men's game increase quite dramatically in 2016.\n* Men are more accurate when shooting and the accuracy remained stable over the years. We again see a drop in shooting accuracy during the playoff, probably due to a more fierce defense.\n* The increased number of shot attempted seems to be coming from behind the 3 points line. We can also see that this did not affect accuracy. This is probably telling us that the game, very much as in the NBA and WNBA, has changed towards, focusing its action along the perimeter more and more.\n* The free throw percentage of Men and Women is very similar and it has been slightly increasing\n* Women used to get way more defensive rebounds, but since 2016 Men have caught them up and the difference, both in the regular season and in the playoffs, is minimal. This might be related to the larger number of shots taken from the perimeter, which are low percentage shots and thus increase the opportunities for a defensive rebound.\n* Similarly, the offensive rebounds have been consistently decreasing, with the Women having the upper hand.\n* If in the regular season the number of assists is staying the same for Men and Women (with the now usual jump in 2016 for the Men), we observe opposite trends in the NCAA Tourney. Here, Women are getting more and more assists while Men show a more individualistic game.\n* Women have more turnover but the number, for both Men and Women, is decreasing. Almost as a mirror image, Women steal the ball more.\n* The number of blocks is very similar between Men and Women and it is remaining reasonably stable.\n* Women have more possessions per game but Men are catching up since 2016\n\nThe main takeaway, once again, is that something seems to have changed in the Men's game since 2016.\n\n# Turning up the heat\n\nIt is no mystery that teams want to bring their A-game in the playoff. This section will explore if this is always the case. We aggregate by seed to see if the team quality is somewhat influencing any pattern when we compare regular season and playoff statistics. The lines in the corresponding colors is the mean value of the given statistic in the regular season and in the playoffs."},{"metadata":{"_kg_hide-input":true,"trusted":true},"cell_type":"code","source":"def newline(ax, p1, p2, color='black'):\n    l = mlines.Line2D([p1[0],p2[0]], [p1[1],p2[1]], color=color)\n    ax.add_line(l)\n    return ax\n\n\nmen_tot = pd.merge(men_reg, men_play, on=['Season', 'TeamID', 'Seed'], how='inner')\nwomen_tot = pd.merge(women_reg, women_play, on=['Season', 'TeamID', 'Seed'], how='inner')\n\nstats = ['Score', 'FGA', 'FGM', 'FGM_perc', 'FGA3', 'FGM3', 'FGM3_perc', 'FT_perc', \n         'DR', 'OR', 'Ast', 'TO', 'Stl', 'Blk', 'possessions', 'off_rating']\n\nfor stat in stats:\n    \n    fig, ax = plt.subplots(1,2, figsize=(15,8), sharex=True)\n    \n    fig.suptitle(stat, fontsize=18)\n    \n    men = men_tot[['Seed', f'{stat}_x', f'{stat}_y']].copy()\n    men.rename(columns={f'{stat}_x': 'Regular', f'{stat}_y': 'Playoff'}, inplace=True)\n    mean_reg = men['Regular'].mean()\n    mean_play = men['Playoff'].mean()\n    ax[0].axvline(mean_reg, color='#0e668b', linestyle='--')\n    ax[0].axvline(mean_play, color='#ff0000', linestyle='--')\n    men = men.groupby('Seed').mean().sort_values('Seed', ascending=True).reset_index()\n    ax[0].scatter(y=men['Seed'], x=men['Regular'], s=80, color='#0e668b', alpha=0.5, label='Regular')\n    ax[0].scatter(y=men['Seed'], x=men['Playoff'], s=80, color='#ff0000', alpha=0.6, label='Playoff')\n    ax[0].legend()\n    ax[0].set_ylabel('Seed', fontsize=12)\n    ax[0].set_yticks(np.arange(1, 17, 1))\n    \n    women = women_tot[['Seed', f'{stat}_x', f'{stat}_y']].copy()\n    women.rename(columns={f'{stat}_x': 'Regular', f'{stat}_y': 'Playoff'}, inplace=True)\n    mean_reg = women['Regular'].mean()\n    mean_play = women['Playoff'].mean()\n    ax[1].axvline(mean_reg, color='#0e668b', linestyle='--')\n    ax[1].axvline(mean_play, color='#ff0000', linestyle='--')\n    women = women.groupby('Seed').mean().sort_values('Seed', ascending=True).reset_index()\n    ax[1].scatter(y=women['Seed'], x=women['Regular'], s=80, color='#0e668b', alpha=0.5, label='Regular')\n    ax[1].scatter(y=women['Seed'], x=women['Playoff'], s=80, color='#ff0000', alpha=0.6, label='Playoff')\n    ax[1].legend()\n    ax[1].set_ylabel('Seed', fontsize=12)\n    ax[1].set_yticks(np.arange(1, 17, 1))\n    \n    for i, p1, p2 in zip(men['Seed'], men['Regular'], men['Playoff']):\n        ax[0] = newline(ax[0], [p1, i], [p2, i])\n    for i, p1, p2 in zip(women['Seed'], women['Regular'], women['Playoff']):\n        ax[1] = newline(ax[1], [p1, i], [p2, i])\n        \n    ax[0].set_title(\"Men's Competition\", fontsize=14)\n    ax[1].set_title(\"Women's Competition\", fontsize=14)\n    ax[0].set_ylim(ax[0].get_ylim()[::-1])\n    ax[1].set_ylim(ax[1].get_ylim()[::-1])\n    \n    plt.show()\n    ","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"Nothing particularly surprising, but we can call out a few things:\n\n* All the team generally score fewer points during the playoff, but this is more evident for lower seed teams. In particular, in the Women's competition, the drop is very large for low ranked teams. This is very understandable, considering that Seed 1 and Seed 16 teams generally meet in the very first round.\n* The Field Goals Attempted, however, tend to go higher during the playoffs\n* In particular, lower ranked teams tend to shot the ball from 3 more often, with less success.\n* The Free Throws percentage is not changing much during the playoffs, but higher-ranked teams are stepping up their game\n* Similarly to scoring, defensive rebounds are a key statistic for higher ranking teams. Moreover, top teams tend to have on average the same amount of rebounds they had in the regular season.\n* Both Men and Women tend to share the ball less during the playoffs but the gap between high and low seeds is more evident in the Women's tournament\n* The number of turnovers stays roughly the same but the number of steals decreases for both Men and Women\n* The same can be said for the number of Blocked shots, except for Seed 1 Women's teams that stay consistent with their regular season performance\n* The offensive rating of the Number 1 seed in the Women's tournament is much higher than the rest of the pack and it even goes up during the playoff.\n\n# What makes a winner?\n\nWe turn our attention to the Turney games and on how the regular season statistics can help us to predict their outcome.\n\nFirst, let's see what features most correlate with the point difference for the **Men's competition**"},{"metadata":{"trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"men_corr = high_corr = exp.plot_correlations(men_train, target='target_points', limit=12, annot=True)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"Ignoring the feature `target` as it is obviously very correlated, we see how the Seed of the competing teams is the most correlated feature, either as a difference between the two teams or simply as individual entry. This is very correlated with pretty much all the remaining top features. We can see this correlation even further if we plot these features against the point difference."},{"metadata":{"trusted":true},"cell_type":"code","source":"exp.corr_target(men_train, 'target_points', list(men_corr[2:].index), x_estimator=None)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"Which is suggesting that rather than the individual statistics of Team 1 and 2, it might be a good idea to focus on the differences between Team 1 and Team 2.\n\nSimilarly, for the **Women's competition** we get"},{"metadata":{"_kg_hide-input":true,"trusted":true},"cell_type":"code","source":"women_corr = high_corr = exp.plot_correlations(women_train, target='target_points', limit=12, annot=True)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"We notice that **the correlations are much stronger**, confirming the importance of the Seed in predicting the outcome of the games.\n\nFurther investigation confirms this insight"},{"metadata":{"_kg_hide-input":true,"trusted":true},"cell_type":"code","source":"exp.corr_target(women_train, 'target_points', list(women_corr[2:].index), x_estimator=None)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"Showing a much sharper trend than in the Men's counterpart. \n\nThis invites a further investigation: **how frequently an advantage in a statistic translates into a victory?** \n\n*Note: the statistics are considering the performance during the regular season as a whole, we are not looking at game statistics vs outcome of the same game*"},{"metadata":{"_kg_hide-input":true,"trusted":true},"cell_type":"code","source":"men_delta = men_train[['Season', 'target', 'target_points'] + [col for col in men_train if 'delta_' in col and 'Loc' not in col]].copy()\nwomen_delta = women_train[['Season', 'target', 'target_points'] + [col for col in women_train if 'delta_' in col and 'Loc' not in col]].copy()\n\nmen_scores = []\nmen_feats = []\nwomen_scores = []\nwomen_feats = []\n\nfor col in [col for col in men_delta if 'delta_' in col]:\n    men_delta[col] = np.sign(men_delta[col])\n    women_delta[col] = np.sign(women_delta[col])\n    if 'Seed' in col or col=='delta_TO':\n        men_delta[col] = - men_delta[col]\n        women_delta[col] = - women_delta[col]\n    try:\n        men_scores.append(men_delta.groupby(col)['target'].mean()[1])\n        men_feats.append(col)\n    except KeyError:\n        pass\n    try:\n        women_scores.append(women_delta.groupby(col)['target'].mean()[1])\n        women_feats.append(col)\n    except KeyError:\n        pass\n    \nmen_prob = pd.DataFrame({'feat': men_feats, 'Men': men_scores})\nwomen_prob = pd.DataFrame({'feat': women_feats, 'Women': women_scores})\n\ntot_prob = pd.merge(men_prob, women_prob, on='feat').sort_values('Men', ascending=False)\n\ntot_prob['feat'] = tot_prob.feat.str.replace('delta_', '')\n\nfig, ax = plt.subplots(1, figsize=(8,15))\n\nax.scatter(y=tot_prob['feat'], x=tot_prob['Men'], s=80, color='g', alpha=0.6, label='Men')\nax.scatter(y=tot_prob['feat'], x=tot_prob['Women'], s=80, color='r', alpha=0.6, label='Women')\nax.legend()\n\nax.axvline(0.5, color='k', linestyle='--', alpha=0.3)\nax.set_ylabel('')\nax.set_xlim((0,1))\n\nax.set_title('Percentage of wins given the stat advantage', fontsize=16)\nax.grid(axis='x')\nax.set_xticklabels(['{:,.0%}'.format(x) for x in ax.get_xticks()])\n\nfor i, p1, p2 in zip(tot_prob['feat'], tot_prob['Men'], tot_prob['Women']):\n        ax = newline(ax, [p1, i], [p2, i])\n\nplt.show()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"We thus see how, with very few exceptions, dominance in one statistics during the regular season tends to translate to a victory in the Tourney more often for Women than for Men. In general, **having a better seed than your opponent translates to a victory more than 70% of the time**.\n\nIf we look at the distribution of score differences, we see how on average a team in the Men's tournament with a better seed wins by 6-7 points"},{"metadata":{"trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"exp.segm_target(men_delta, cat='delta_Seed', target='target_points')","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"While in the Women's tournament the team with the better seed wins on average by 13 points"},{"metadata":{"trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"exp.segm_target(women_delta, cat='delta_Seed', target='target_points')","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"Breaking this trend down by season, we see that we go from the very predictable Women's tourney of 2012 to the very unpredictable Men's tourney of 2014"},{"metadata":{"_kg_hide-input":true,"trusted":true},"cell_type":"code","source":"def plot_perc_Season(men, women, feature):\n    mean_men = men.groupby(feature).target.mean()[1]\n    mean_women = women.groupby(feature).target.mean()[1]\n    \n    fig, ax = plt.subplots(1, figsize=(15, 6))\n    \n    tmp = men.groupby(['Season'] +[feature], as_index=False).target.mean().rename(columns={'target': 'Men'})\n    tmp = tmp[tmp[feature] == 1]\n    tmp.plot(x='Season', y='Men', ax=ax, color='g')\n    ax.axhline(mean_men, color='g', linestyle='--', alpha=0.5)\n    \n    tmp = women.groupby(['Season'] +[feature], as_index=False).target.mean().rename(columns={'target': 'Women'})\n    tmp = tmp[tmp[feature] == 1]\n    tmp.plot(x='Season', y='Women', ax=ax, color='r')\n    ax.axhline(mean_women, color='r', linestyle='--', alpha=0.5)\n    \n    ax.set_xlim((2003,2019))\n    ax.set_yticklabels(['{:,.0%}'.format(x) for x in ax.get_yticks()])\n    \n    ax.set_title(f'Percentage of wins given the advantage in {feature.replace(\"delta_\", \"\")}', fontsize=18)\n    \n    plt.show()","execution_count":null,"outputs":[]},{"metadata":{"_kg_hide-input":true,"trusted":true},"cell_type":"code","source":"plot_perc_Season(men_delta, women_delta, 'delta_Seed')","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"On this line of exploration, we notice that having the advantage in Field Goals Made during the regular season is becoming less and less important for the Men's tourney"},{"metadata":{"_kg_hide-input":true,"trusted":true},"cell_type":"code","source":"plot_perc_Season(men_delta, women_delta, 'delta_FGM')","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"While precision on the perimeter during the regular season is becoming more and more relevant in the Women's Tourney"},{"metadata":{"_kg_hide-input":true,"trusted":true},"cell_type":"code","source":"plot_perc_Season(men_delta, women_delta, 'delta_FGM3_perc')","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"Another surprising trend is how having an advantage in Free Throws made during the regular season is becoming less and less important for the victory in the Men's Tourney"},{"metadata":{"_kg_hide-input":true,"trusted":true},"cell_type":"code","source":"plot_perc_Season(men_delta, women_delta, 'delta_FTM')","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"Furthermore, we notice how winning teams in the Women's tournament are taking better and better care of the ball during the regular season \n\n(*TO, as well as Seed, is a statistic that you want to be as low as possible, but here we reversed the sign to make it more similar to the other stats*)"},{"metadata":{"_kg_hide-input":true,"trusted":true},"cell_type":"code","source":"plot_perc_Season(men_delta, women_delta, 'delta_TO')","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Adversarial validation\n\nHere the idea is to train a model to distinguish between the Men's and the Women's tournaments and further identify important features."},{"metadata":{"trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"kfolds = KFold(n_splits=7, shuffle=True, random_state=345)\n\nmen = men_train.copy()\nwomen = women_train.copy()\n# target variable we want to predict\nmen['tourney'] = 1\nwomen['tourney'] = 0\ntot_adv = pd.concat([men, women], ignore_index=True)\ntot_adv.drop(['ID', 'DayNum', 'Team1', 'Team2', 'Season'], axis=1, inplace=True)\n\ntrain = tot_adv.drop(['tourney'] + \n                     [col for col in tot_adv if '_rating' in col or 'possessions' in col], axis=1) # dropping some very obvious features\ny_train = tot_adv['tourney']\n\npipe = Pipeline([('scl', df_p.df_scaler())] + [('forest', RandomForestClassifier(n_estimators=500, \n                                                                                min_samples_split=40, \n                                                                                min_samples_leaf=20, \n                                                                                max_features='sqrt', \n                                                                                n_jobs=4))])\noof, imp_coef = cv_score(train, y_train, kfolds, pipe, imp_coef=True)\nprint(f'ROC AUC score: {round(roc_auc_score(y_true=y_train, y_score=oof),4)}')\nplt.figure(figsize=(14, 12))\nsns.barplot(x=\"mean\", y=\"feat\", \n            data=imp_coef.head(50).reset_index(), \n            xerr=imp_coef.head(50)['std'])\nplt.show()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"We see that the model is very good in finding out if a game has been played in the Women's tournament or in the Men's one. The most important features for this prediction are the number of rebounds, of field goals attempted, the precision on these field goals, and the number of tournovers.\n\nMoreover, we can also see how different the 2 tournaments are by using t-SNE, which shows how only a few games can cross the border betweem the Men's and Women's tournaments."},{"metadata":{"trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"tsne = TSNE(n_components=2, init='pca', random_state=51, perplexity=100, learning_rate=100)\n\ngreen = tot_adv['tourney'] == 1\nred = tot_adv['tourney'] == 0\n\ny_total = tsne.fit_transform(tot_adv.drop(['target_points', 'tourney', 'target'], axis=1))                         \n                           \nfig, ax = plt.subplots(1, figsize=(15,8))\n\nax.scatter(y_total[red, 0], y_total[red, 1], c=\"r\", alpha=0.7, label='Women')\nax.scatter(y_total[green, 0], y_total[green, 1], c=\"g\", alpha=0.7, label='Men')\nax.legend()\nplt.show()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"At last, comparing the embeddings for the Men's and Women's tournaments separately, we notice once more how the two tournaments differ with a more net split between wins and losses in the Women's tournament."},{"metadata":{"_kg_hide-input":true,"trusted":true},"cell_type":"code","source":"tsne = TSNE(n_components=2, init='pca', random_state=51, perplexity=50, learning_rate=300)\n\nred_m = men_train['target'] == 1\ngreen_m = men_train['target'] == 0\nred_w = women_train['target'] == 1\ngreen_w = women_train['target'] == 0\n\ny_men = tsne.fit_transform(men_train.drop(['ID', 'DayNum', 'Team1', 'Team2', 'Season'] + \n                                          ['target'], axis=1))\ny_women = tsne.fit_transform(women_train.drop(['ID', 'DayNum', 'Team1', 'Team2', 'Season'] + \n                                          ['target'], axis=1))\n                           \nfig, ax = plt.subplots(1,2, figsize=(15,7))\n\nax[0].scatter(y_men[red_m, 0], y_men[red_m, 1], c=\"orange\", alpha=0.8, label='Win')\nax[0].scatter(y_men[green_m, 0], y_men[green_m, 1], c=\"b\", alpha=0.5, label='Loss')\nax[1].scatter(y_women[red_w, 0], y_women[red_w, 1], c=\"orange\", alpha=0.8, label='Win')\nax[1].scatter(y_women[green_w, 0], y_women[green_w, 1], c=\"b\", alpha=0.5, label='Loss')\nax[0].legend()\nax[1].legend()\nax[0].set_title(\"Men's Tournament\", fontsize=16)\nax[1].set_title(\"Women's Tournament\", fontsize=16)\nplt.show()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"Given these results, it might not be a good idea to train one model for both competitions."}],"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"pygments_lexer":"ipython3","nbconvert_exporter":"python","version":"3.6.4","file_extension":".py","codemirror_mode":{"name":"ipython","version":3},"name":"python","mimetype":"text/x-python"}},"nbformat":4,"nbformat_minor":4}