{"cells":[{"metadata":{},"cell_type":"markdown","source":"### 概要\n  * 作成日 : 2020/3/9（月）\n  * 作成者 : 藤根\n\n### アプローチ内容\n  * チームに関する以下データを整形・学習し、トーナメントの勝敗予測アルゴリズムを構築する\n    1. 所属カンファレンス名\n    1. コーチ名\n    1. NCAA出場年数\n    1. シード\n    1. ~~各シーズンにおける累計得点~~ 今回は時間の都合でスキップ\n \n### 結果\n  * スコアは0.538 -> 0.445台まで向上。\n\n### 次のアプローチ\n  * レギュラーシーズンの累積得点数をデータに加える（失点も加える？）\n  * ~~トーナメントの組合せだけでなく、submissionの全組合せで学習してみる。~~ 全組合せの勝敗データが無いため、パス\n  * いくつかのアルゴリズムを適用し、グリッドサーチでパラメータ探索\n  * 複数の予測モデルを構築し、アンサンブル学習で予測結果を出力"},{"metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true},"cell_type":"code","source":"import os\n\nimport numpy as np\nimport pandas as pd\nfrom sklearn.preprocessing import MinMaxScaler\nfrom sklearn.model_selection import cross_validate\nfrom sklearn.pipeline import make_pipeline\nfrom sklearn.ensemble import RandomForestClassifier\nfrom sklearn.svm import SVC","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# 提出用ファイルを取得\nsub = pd.read_csv('/kaggle/input/google-cloud-ncaa-march-madness-2020-division-1-mens-tournament/MSampleSubmissionStage1_2020.csv')\nid_splited = sub['ID'].str.split('_', expand=True).astype(int).rename(columns={0: 'Season', 1: 'Team1', 2: 'Team2'})\nsub = pd.concat([sub, id_splited], axis=1).set_index(['Season', 'Team1', 'Team2']).sort_index()\nsub","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# シーズン毎の出場チームを抽出\ntourney_teams = {}\ntourney_teams_all = set()\nfor season in sub.index.get_level_values('Season').drop_duplicates():\n    tourney_teams[season] = set()\n    tourney_teams[season].update(sub.loc[season].index.get_level_values('Team1'))\n    tourney_teams[season].update(sub.loc[season].index.get_level_values('Team2'))\n    tourney_teams_all.update(tourney_teams[season])\n{k: len(v) for k, v in tourney_teams.items()}","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# 所属カンファレンス情報を取得\nconferences = pd.read_csv('/kaggle/input/google-cloud-ncaa-march-madness-2020-division-1-mens-tournament/MDataFiles_Stage1/MTeamConferences.csv')\nconferences = pd.concat([conferences.query('Season == @season and TeamID in @teams') for season, teams in tourney_teams.items()])\nconferences = conferences.set_index(['Season', 'TeamID']).sort_index()\nconferences","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# コーチ名を取得\n# シーズン期間中にコーチを変更したチームがあるため、今回は「トーナメント開始時点でコーチだった\n# 人物が、トーナメント結果に強い影響を与える」と仮定し、LastDayNumが154のコーチを使用する。\ncoaches = pd.read_csv('/kaggle/input/google-cloud-ncaa-march-madness-2020-division-1-mens-tournament/MDataFiles_Stage1/MTeamCoaches.csv')\ncoaches = pd.concat([coaches.query('Season == @season and TeamID in @team') for season, team in tourney_teams.items()])\n#coaches[(coaches['FirstDayNum'] != 0) | (coaches['LastDayNum'] != 154)] \ncoaches = coaches[coaches['LastDayNum'] == 154].set_index(['Season', 'TeamID']).sort_index().loc[:, ['CoachName']]\ncoaches","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# NCAAの初回出場年を取得し、初回出場年から現在までの年数を計算\nteams = pd.read_csv('/kaggle/input/google-cloud-ncaa-march-madness-2020-division-1-mens-tournament/MDataFiles_Stage1/MTeams.csv', usecols=['TeamID', 'FirstD1Season'])\nteams['First'] = 2020 - teams.pop('FirstD1Season')\nteams = pd.concat([teams.query('TeamID in @team').assign(Season=season) for season, team in tourney_teams.items()])\nteams = teams.set_index(['Season', 'TeamID']).sort_index()\nteams","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# 各シーズンのシードを取得\nseeds = pd.read_csv('/kaggle/input/google-cloud-ncaa-march-madness-2020-division-1-mens-tournament/MDataFiles_Stage1/MNCAATourneySeeds.csv')\nseeds = pd.concat([seeds.query('Season == @season and TeamID in @teams') for season, teams in tourney_teams.items()])\nseeds = seeds.set_index(['Season', 'TeamID']).sort_index()\nseeds['Region'] = seeds['Seed'].str[0]\nseeds['Number'] = seeds['Seed'].str[1:3].astype(int)\ndel seeds['Seed']\nseeds","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# 上記取得データを結合\nctcs = pd.concat([coaches, teams, conferences, seeds], axis=1)\nctcs","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# NCAAMトーナメントの勝敗結果を取得\nresult = pd.read_csv('/kaggle/input/google-cloud-ncaa-march-madness-2020-division-1-mens-tournament/MDataFiles_Stage1/MNCAATourneyCompactResults.csv')\nresult = result[result['Season'] >= 2015].set_index(['Season', 'WTeamID', 'LTeamID'])\nresult","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# 各種データと勝敗結果を結合\nmerged_teams = pd.concat([ctcs.loc[[(season, wteam), (season, lteam)], :] for season, wteam, lteam, in result.index])\n\nteam1 = merged_teams.iloc[::2, :].reset_index('TeamID') # teams winned\nteam2 = merged_teams.iloc[1::2, :].reset_index('TeamID') # teams losed\n\nmerged_teams = pd.concat([\n    pd.concat([team1.add_suffix('1'), team2.add_suffix('2')], axis=1).assign(Res=1),\n    pd.concat([team2.add_suffix('1'), team1.add_suffix('2')], axis=1).assign(Res=0),\n]).reset_index().set_index(['Season', 'TeamID1', 'TeamID2'])\nmerged_teams","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# データを、説明変数Xと目的変数yに分離\ncolumns = merged_teams.columns\nX = merged_teams[columns[columns != 'Res']]\ny = merged_teams['Res']","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"X","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"y","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# Xを数値化、正規化\nfor c in X.select_dtypes(include='number').columns:\n    X[c] = MinMaxScaler().fit_transform(X[c].to_numpy().reshape(-1,1))\nX = pd.get_dummies(X, columns=X.columns[X.dtypes == 'object'])\nX","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# RandomForestで学習＆予測精度を出力\nclf = RandomForestClassifier(n_estimators=200)\nscore = cross_validate(clf, X, y, cv=3, return_estimator=True)\nscore['test_score']","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# 各変数の重要度を可視化\nimportances = pd.Series(score['estimator'][1].feature_importances_, index=X.columns)\n_ = importances.sort_values(ascending=False).head(30).plot(kind='bar', grid=True, figsize=(20, 5))","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# 次にSVCで学習＆予測精度を出力\nclf = SVC(kernel='rbf', probability=True)\nscore = cross_validate(clf, X, y, cv=3, return_estimator=True)\nscore['test_score']","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# SVCのkernelを'linear'にすれば、以下コードで各説明変数の寄与率を出力できる\n#importances = pd.Series(score['estimator'][2].coef_.flatten(), index=X.columns)\n#_ = importances.abs().sort_values(ascending=False).head(30).plot(kind='bar', grid=True, figsize=(20, 5))","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# SVCで作成した予測モデルから予測結果を出力し、DataFrame型に加工\nbest_estimator = score['estimator'][score['test_score'].argmax()]\npred = pd.DataFrame(best_estimator.predict_proba(X), index=X.index, columns=best_estimator.classes_)\npred","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# 予測結果を提出用データに上書き\nsub['Pred'] = 0.5\nfor idx in sub.index:\n    if idx in X.index:\n        sub.loc[idx, 'Pred'] = pred.loc[idx, 1]\n        \n_ = sub['Pred'].plot(kind='hist', grid=True, bins=50, logy=True)\n_ = sub[sub['Pred'] != 0.5]['Pred'].plot(kind='hist', bins=50, grid=True)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# 実際のトーナメント組合せだけで学習すると、全組合せの6%しか無いんだな。\n# まあ、トーナメントに無い組合せはどんな数値を入れても実際のスコアには影響しないんだろうけど\nlen(X) / len(sub) * 100","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# 提出用データをファイル出力\nsub.to_csv('submissoin.csv', index=False)\npd.read_csv('submissoin.csv')","execution_count":null,"outputs":[]}],"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}