{"cells":[{"metadata":{"trusted":true},"cell_type":"code","source":"import os\nimport numpy as np\nimport pandas as pd\nimport statsmodels.api as sm\nimport matplotlib.pyplot as plt\nimport seaborn as sns\nfrom sklearn.linear_model import LogisticRegression\nfrom sklearn.ensemble import RandomForestClassifier\nfrom sklearn.neural_network import MLPClassifier\nfrom sklearn.metrics import log_loss\nfrom sklearn.preprocessing import scale\n\nfor dirname, _, filenames in os.walk('/kaggle/input/'):\n    for filename in filenames:\n        print(os.path.join(dirname, filename))","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"e38835708d1b268a5aa1a291572b8de51a07e9b6"},"cell_type":"markdown","source":"Team power ranking idea and code is due to @raddar https://www.kaggle.com/raddar/team-power-rankings\nKenPom data is from @paulorzp https://www.kaggle.com/paulorzp/kenpom-scraper-2020"},{"metadata":{"_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","trusted":true},"cell_type":"code","source":"input_dir = 'google-cloud-ncaa-march-madness-2020-division-1-mens-tournament'\n\nseeds = pd.read_csv('../input/{}/MDataFiles_Stage1/MNCAATourneySeeds.csv'.format(input_dir))\ntourney_results = pd.read_csv('../input/{}/MDataFiles_Stage1/MNCAATourneyCompactResults.csv'.format(input_dir))\nregular_results = pd.read_csv('../input/{}/MDataFiles_Stage1/MRegularSeasonCompactResults.csv'.format(input_dir))\nregular_results_deets = pd.read_csv('../input/{}/MDataFiles_Stage1/MRegularSeasonDetailedResults.csv'.format(input_dir))\nteams = pd.read_csv('../input/{}/MDataFiles_Stage1/MTeams.csv'.format(input_dir))\nkenpom = pd.read_csv('/kaggle/input/kenpom-2020/NCAA2020_Kenpom.csv')","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"177d18adcc1743fd9b493981ffda26574aa4354f"},"cell_type":"markdown","source":"We are going to use the function which allows us to duplicate the data by swapping the order of teams in given tables:"},{"metadata":{"_cell_guid":"79c7e3d0-c299-4dcb-8224-4455121ee9b0","_uuid":"d629ff2d2480ee46fbb7e2d37f6b5fab8052498a","trusted":true},"cell_type":"code","source":"def prepare_data(df):\n    dfswap = df[['Season', 'DayNum', 'LTeamID', 'LScore', 'WTeamID', 'WScore', 'WLoc', 'NumOT']]\n\n    dfswap.loc[df['WLoc'] == 'H', 'WLoc'] = 'A'\n    dfswap.loc[df['WLoc'] == 'A', 'WLoc'] = 'H'\n    df.columns.values[6] = 'location'\n    dfswap.columns.values[6] = 'location'         \n    df.columns = [x.replace('W','T1_').replace('L','T2_') for x in list(df.columns)]\n    dfswap.columns = [x.replace('L','T1_').replace('W','T2_') for x in list(dfswap.columns)]\n    output = pd.concat([df, dfswap]).sort_index().reset_index(drop=True)\n    \n    return output","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"0a3c9bc650994b4ca298503b29e56f14702bf9c0","trusted":true},"cell_type":"code","source":"tourney_results = prepare_data(tourney_results)\nregular_results = prepare_data(regular_results)","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"7e40d0a8fc5294d49e78664a53c35ac127e4fe18","trusted":true},"cell_type":"code","source":"# convert to str, so the model would treat TeamID them as factors\nregular_results['T1_TeamID'] = regular_results['T1_TeamID'].astype(str)\nregular_results['T2_TeamID'] = regular_results['T2_TeamID'].astype(str)\n\n# make it a binary task\nregular_results['win'] = np.where(regular_results['T1_Score']>regular_results['T2_Score'], 1, 0)\n\ndef team_quality(season):\n    \"\"\"\n    Calculate team quality for each season seperately. \n    Team strength changes from season to season (students playing change!)\n    So pooling everything would be bad approach!\n    \"\"\"\n    formula = 'win~-1+T1_TeamID+T2_TeamID'\n    glm = sm.GLM.from_formula(formula=formula, \n                              data=regular_results.loc[regular_results.Season==season,:], \n                              family=sm.families.Binomial()).fit()\n    \n    # extracting parameters from glm\n    quality = pd.DataFrame(glm.params).reset_index()\n    quality.columns = ['TeamID','beta']\n    quality['Season'] = season\n    # taking exp due to binomial model being used\n    quality['quality'] = np.exp(quality['beta'])\n    # only interested in glm parameters with T1_, as T2_ should be mirroring T1_ ones\n    quality = quality.loc[quality.TeamID.str.contains('T1_')].reset_index(drop=True)\n    quality['TeamID'] = quality['TeamID'].apply(lambda x: x[10:14]).astype(int)\n    return quality","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"This is the team_quality feature, which is essentially a glm fit on the number of wins. Again, idea and code is from @raddar. This takes a while to run."},{"metadata":{"_uuid":"837a53b417200ee689f5a6ddf6dfd0102f85b1cf","trusted":true},"cell_type":"code","source":"import time\nstart = time.time()\nteam_qual = pd.concat([team_quality(2010),\n                       team_quality(2011),\n                       team_quality(2012),\n                       team_quality(2013),\n                       team_quality(2014),\n                       team_quality(2015),\n                       team_quality(2016),\n                       team_quality(2017),\n                       team_quality(2018),\n                       team_quality(2019)]).reset_index(drop=True)\nend = time.time()\nprint(\"time elapsed:\",end - start)","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"978666a8d4831c6d7b14790681eae5b58c7b1062","trusted":true},"cell_type":"code","source":"team_quality_T1 = team_qual[['TeamID','Season','quality']]\nteam_quality_T1.columns = ['T1_TeamID','Season','T1_quality']\nteam_quality_T2 = team_qual[['TeamID','Season','quality']]\nteam_quality_T2.columns = ['T2_TeamID','Season','T2_quality']\n\ntourney_results['T1_TeamID'] = tourney_results['T1_TeamID'].astype(int)\ntourney_results['T2_TeamID'] = tourney_results['T2_TeamID'].astype(int)\ntourney_results = tourney_results.merge(team_quality_T1, on = ['T1_TeamID','Season'], how = 'left')\ntourney_results = tourney_results.merge(team_quality_T2, on = ['T2_TeamID','Season'], how = 'left')","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"45a0e7f596ed0cd8f52f23344e4bfa741e9d08b2","trusted":true},"cell_type":"code","source":"# we only have tourney results since year 2010\ntourney_results = tourney_results.loc[tourney_results['Season'] >= 2010].reset_index(drop=True)\n\n# not interested in pre-selection matches\ntourney_results = tourney_results.loc[tourney_results['DayNum'] >= 136].reset_index(drop=True)","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"5ca8d86ad11d25d138e0bcee87e45e18dea6b770","trusted":true},"cell_type":"code","source":"seeds['seed'] = seeds['Seed'].apply(lambda x: int(x[1:3]))\nseeds['division'] = seeds['Seed'].apply(lambda x: x[0])\n\nseeds_T1 = seeds[['Season','TeamID','seed','division']].copy()\nseeds_T2 = seeds[['Season','TeamID','seed','division']].copy()\nseeds_T1.columns = ['Season','T1_TeamID','T1_seed','T1_division']\nseeds_T2.columns = ['Season','T2_TeamID','T2_seed','T2_division']\n\ntourney_results = tourney_results.merge(seeds_T1, on = ['Season', 'T1_TeamID'], how = 'left')\ntourney_results = tourney_results.merge(seeds_T2, on = ['Season', 'T2_TeamID'], how = 'left')","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"Here we convert quality to powerrank, by grouping quality by division so we get a number similar to seed."},{"metadata":{"_uuid":"cdf6ff0388e664fdcdd9cc31a784e940703841c5","trusted":true},"cell_type":"code","source":"tourney_results['T1_powerrank'] = tourney_results.groupby(['Season','T1_division'])['T1_quality'].rank(method='dense', ascending=False).astype(int)\ntourney_results['T2_powerrank'] = tourney_results.groupby(['Season','T2_division'])['T2_quality'].rank(method='dense', ascending=False).astype(int)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"This is where the KenPom data gets applied"},{"metadata":{"trusted":true},"cell_type":"code","source":"kpcols = list(kenpom.columns)\na, b = kpcols.index('Season'), kpcols.index('TeamName')\nkpcols[b], kpcols[a] = kpcols[a], kpcols[b]\nkenpom = kenpom[kpcols]\n\nkenpom_T1 = kenpom.copy()\nkenpom_T2 = kenpom.copy()\n\nkpT1cols = []; kpT2cols = [];\nkpT1cols.append('Season');kpT2cols.append('Season')\nfor k in kenpom.columns:\n    if k!='Season':\n        kpT1cols.append('T1_{}'.format(k))\n        kpT2cols.append('T2_{}'.format(k))\n    \nkenpom_T1.columns = kpT1cols\nkenpom_T2.columns = kpT2cols\n\ntourney_results = tourney_results.merge(kenpom_T1, on = ['Season', 'T1_TeamID'], how = 'left')\ntourney_results = tourney_results.merge(kenpom_T2, on = ['Season', 'T2_TeamID'], how = 'left')","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Offensive and defensive efficiency"},{"metadata":{"trusted":true},"cell_type":"code","source":"tourney_results.head()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"def prepare_data_deets(df):\n    dfswap = df.copy()\n\n    dfswap.loc[df['WLoc'] == 'H', 'WLoc'] = 'A'\n    dfswap.loc[df['WLoc'] == 'A', 'WLoc'] = 'H'\n    df.columns.values[6] = 'location'\n    dfswap.columns.values[6] = 'location'         \n    df.columns = [x.replace('W','T1_').replace('L','T2_') for x in list(df.columns)]\n    dfswap.columns = [x.replace('L','T1_').replace('W','T2_') for x in list(dfswap.columns)]\n    output = pd.concat([df, dfswap]).sort_index().reset_index(drop=True)\n    \n    return output","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"regular_results_deets = prepare_data_deets(regular_results_deets)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"regular_results_deets['T1_FGeff'] = regular_results_deets['T1_FGM']/regular_results_deets['T1_FGA']\nregular_results_deets['T2_FGeff'] = regular_results_deets['T2_FGM']/regular_results_deets['T2_FGA']","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"T1_FGeffM_S = regular_results_deets.groupby(['T1_TeamID','Season']).agg({'T1_FGeff':['mean','std']})\nT2_FGeffM_S = regular_results_deets.groupby(['T2_TeamID','Season']).agg({'T2_FGeff':['mean','std']})","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"tourney_results = tourney_results.merge(T1_FGeffM_S, on = ['Season', 'T1_TeamID'], how = 'left')\ntourney_results = tourney_results.merge(T2_FGeffM_S, on = ['Season', 'T2_TeamID'], how = 'left')","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"3e906b31f7e40784e0343acaae7b37bd8a5bce63","trusted":true},"cell_type":"code","source":"tourney_results['win'] = np.where(tourney_results['T1_Score'] > tourney_results['T2_Score'], 1, 0)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Model"},{"metadata":{"trusted":true},"cell_type":"code","source":"T1_conf_dum = pd.get_dummies(tourney_results['T1_conference'])\nT2_conf_dum = pd.get_dummies(tourney_results['T2_conference'])\n\nT1_conf_dum.columns = ['T1_{}'.format(i) for i in T1_conf_dum.columns]\nT2_conf_dum.columns = ['T2_{}'.format(i) for i in T2_conf_dum.columns]","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"feats_to_use = ['Season','T1_powerrank','T2_powerrank',\\\n                'T1_seed','T2_seed','T1_rank','T2_rank',\\\n                ('T1_FGeff', 'mean'),('T2_FGeff', 'mean'),\\\n                ('T1_FGeff', 'std'),('T2_FGeff', 'std')]\nmodel_df = tourney_results[feats_to_use+['win']]","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"model_df = pd.concat([model_df.loc[:, model_df.columns != 'win'],T1_conf_dum,T2_conf_dum,model_df['win']],axis=1)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"Scaling the continuous variables helps the ML algorithms converge."},{"metadata":{"trusted":true},"cell_type":"code","source":"model_df[['T1_powerrank','T2_powerrank','T1_seed','T2_seed','T1_rank','T2_rank']] = scale(model_df[['T1_powerrank','T2_powerrank','T1_seed','T2_seed','T1_rank','T2_rank']])","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"scores_lr = []\nscores_rf = []\nscores_nn = []\nscores_ens = []\n\nfor i in np.arange(2010,2020):\n    X_train = model_df[model_df.Season!=i].iloc[:,1:-1]\n    y_train = model_df[model_df.Season!=i].iloc[:,-1]\n    X_test = model_df[model_df.Season==i].iloc[:,1:-1]\n    y_test = model_df[model_df.Season==i].iloc[:,-1]\n    \n    lr = LogisticRegression(random_state=4351)\n    lr.fit(X_train,y_train)\n\n    rf = RandomForestClassifier(n_estimators=1000,random_state=342)\n    rf.fit(X_train,y_train)\n    \n    nn = MLPClassifier(hidden_layer_sizes=(5,7,),random_state=222)\n    nn.fit(X_train,y_train)\n    \n    lr_yhat_prob = lr.predict_proba(X_test)[:,1]\n    lr_yhat = lr.predict(X_test)\n\n    rf_yhat_prob = rf.predict_proba(X_test)[:,1]\n    rf_yhat = rf.predict(X_test)\n    \n    nn_yhat_prob = nn.predict_proba(X_test)[:,1]\n    nn_yhat = nn.predict(X_test)\n    \n    ens_yhat_prob = 0.33*lr_yhat_prob + 0.33*rf_yhat_prob + 0.33*nn_yhat_prob\n    \n    scores_lr.append(log_loss(y_test.values,lr_yhat_prob))\n    scores_rf.append(log_loss(y_test.values,rf_yhat_prob))\n    scores_nn.append(log_loss(y_test.values,nn_yhat_prob))\n    scores_ens.append(log_loss(y_test.values,ens_yhat_prob))","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"We can examine the logistic regression coefficients for clues on relationships. A positive value means it correlated positively with a win for team 1, a negative value means that variable correlated negatively with a win for team 1."},{"metadata":{"trusted":true},"cell_type":"code","source":"plt.figure(figsize=(10,15))\ny_pos = np.arange(len(X_train.columns))\nplt.barh(y_pos, lr.coef_[0])\n \n# Create names on the y-axis\nplt.yticks(y_pos, X_train.columns)\nplt.show()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"We can also show feature importances from the Random Forest model."},{"metadata":{"trusted":true},"cell_type":"code","source":"plt.figure(figsize=(10,15))\ny_pos = np.arange(len(X_train.columns))\nplt.barh(y_pos, rf.feature_importances_)\n \n# Create names on the y-axis\nplt.yticks(y_pos, X_train.columns)\nplt.show()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"plt.figure(figsize=(15,8))\nplt.plot(np.arange(2010,2020),scores_lr,'b.')\nplt.plot(np.arange(2010,2020),scores_rf,'r.')\nplt.plot(np.arange(2010,2020),scores_nn,'g.')\nplt.plot(np.arange(2010,2020),scores_ens,'m.')","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"print(np.mean(scores_lr),np.std(scores_lr))\nprint(np.mean(scores_rf),np.std(scores_rf))\nprint(np.mean(scores_nn),np.std(scores_nn))\nprint(np.mean(scores_ens),np.std(scores_ens))","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"As you can see, depending on which year you test, a different model performs better. With these features, random forest wins."},{"metadata":{},"cell_type":"markdown","source":"# Submission Stage 1"},{"metadata":{"trusted":true},"cell_type":"code","source":"test_df = pd.read_csv('../input/{}/MSampleSubmissionStage1_2020.csv'.format(input_dir))","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"test_df['Season'] = test_df['ID'].map(lambda x: int(x[:4]))\ntest_df['T1_TeamID'] = test_df['ID'].map(lambda x: int(x[5:9]))\ntest_df['T2_TeamID'] = test_df['ID'].map(lambda x: int(x[10:14]))","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"test_dfT1 = test_df.merge(team_quality_T1, on = ['Season','T1_TeamID'], how = 'left')\ntest_dfT1 = test_dfT1.merge(seeds_T1, on = ['Season','T1_TeamID'], how = 'left')\ntest_dfT2 = test_df.merge(team_quality_T2, on = ['Season','T2_TeamID'], how = 'left')\ntest_dfT2 = test_dfT2.merge(seeds_T2, on = ['Season','T2_TeamID'], how = 'left')\n#test_df = test_df[['Season','T1_powerrank','T2_powerrank','T1_seed','T2_seed','win']]","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"test_dfT1['T1_powerrank'] = test_dfT1.groupby(['Season','T1_division'])['T1_quality'].rank(method='dense', ascending=False).astype(int)\ntest_dfT2['T2_powerrank'] = test_dfT2.groupby(['Season','T2_division'])['T2_quality'].rank(method='dense', ascending=False).astype(int)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"kenpom_T1['Season'] = kenpom_T1['Season'].astype('int64')\nkenpom_T2['Season'] = kenpom_T2['Season'].astype('int64')\n\ntest_dfT1 = test_dfT1.merge(kenpom_T1, on = ['Season', 'T1_TeamID'], how = 'left')\ntest_dfT2 = test_dfT2.merge(kenpom_T2, on = ['Season', 'T2_TeamID'], how = 'left')","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"test_df = pd.concat([test_dfT1[['Season','T1_TeamID','T1_powerrank','T1_seed','T1_rank']],test_dfT2[['T2_TeamID','T2_powerrank','T2_seed','T2_rank']]],axis=1)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"test_dfFGeffM_ST1 = test_df.merge(T1_FGeffM_S, on = ['Season', 'T1_TeamID'], how = 'left')\ntest_dfFGeffM_ST2 = test_df.merge(T2_FGeffM_S, on = ['Season', 'T2_TeamID'], how = 'left')","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"test_df = pd.concat([test_dfFGeffM_ST1[['Season','T1_TeamID','T1_powerrank','T1_seed','T1_rank',('T1_FGeff','mean'),('T1_FGeff','std')]],\\\n                     test_dfFGeffM_ST2[['T2_TeamID','T2_powerrank','T2_seed','T2_rank',('T2_FGeff','mean'),('T2_FGeff','std')]]],axis=1)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"T1_conf_dum = pd.get_dummies(test_dfT1['T1_conference'])\nT2_conf_dum = pd.get_dummies(test_dfT2['T2_conference'])\n\nT1_conf_dum.columns = ['T1_{}'.format(i) for i in T1_conf_dum.columns]\nT2_conf_dum.columns = ['T2_{}'.format(i) for i in T2_conf_dum.columns]\n\n# No Pac 10 in this data\nT1_conf_dum.insert(loc=22,column='T1_P10',value = 0)\nT2_conf_dum.insert(loc=22,column='T2_P10',value = 0)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"test_df.shape,T1_conf_dum.shape","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"test_df = pd.concat([test_df['Season'],test_df[['T1_powerrank','T2_powerrank',\\\n                                                'T1_seed','T2_seed',\\\n                                                'T1_rank','T2_rank',\n                                               ('T1_FGeff', 'mean'),('T1_FGeff', 'std'),\n                                               ('T2_FGeff', 'mean'),('T2_FGeff', 'std')]],\\\n                     T1_conf_dum,T2_conf_dum],axis=1)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"test_df[['T1_powerrank','T2_powerrank','T1_seed','T2_seed','T1_rank','T2_rank']] = \\\n    scale(test_df[['T1_powerrank','T2_powerrank','T1_seed','T2_seed','T1_rank','T2_rank']])","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"yhat = 0.3*nn.predict_proba(test_df.drop('Season', axis=1))+\\\n        0.3*rf.predict_proba(test_df.drop('Season', axis=1))+\\\n        0.3*lr.predict_proba(test_df.drop('Season', axis=1))","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"yhat","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"submit = pd.read_csv('../input/{}/MSampleSubmissionStage1_2020.csv'.format(input_dir))","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"submit['Pred'] = yhat[:,1]","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"submit.to_csv('SampleSubmissionStage1_Latimer.csv',index=False)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"Version 3 LB - 0.5917\nVersion 4 LB - 0.51550 (lr)"}],"metadata":{"kernelspec":{"display_name":"Python 3","language":"python","name":"python3"},"language_info":{"codemirror_mode":{"name":"ipython","version":3},"file_extension":".py","mimetype":"text/x-python","name":"python","nbconvert_exporter":"python","pygments_lexer":"ipython3","version":"3.6.6"}},"nbformat":4,"nbformat_minor":1}