{"cells":[{"metadata":{},"cell_type":"markdown","source":"# Data Cleaning\n\nHere I make the training data used for the model.\n\nPart of the beauty of the model is in its simplicity, taking only scoring margin and home and away as features. As such, there is only some filtering (to 2020), name changing, and light feature creation to be done on the training set."},{"metadata":{"trusted":true},"cell_type":"code","source":"# Load needed packages\nimport numpy as np\nimport pandas as pd\nimport pystan\nimport matplotlib.pyplot as plt\nimport random\n\n# Import data\ndat = pd.read_csv(\"/kaggle/input/march-madness-analytics-2020/MDataFiles_Stage2/MRegularSeasonCompactResults.csv\") \nteam_key = pd.read_csv(\"/kaggle/input/march-madness-analytics-2020/MDataFiles_Stage2/MTeams.csv\")[[\"TeamID\", \"TeamName\"]]\n\n# Filter to 2019\ndat = dat[dat.Season == 2020].reset_index(drop = True)\n\n# Make home\ndat['homei'] = np.where(dat.WLoc == \"H\", 1, 0)\ndat['homej'] = np.where(dat.WLoc == \"A\", 1, 0)\n\n# Create margin\ndat['margin'] = dat.WScore - dat.LScore\n\n# Filter to needed columns and rename\ndat = dat[[\"Season\", \"DayNum\", \"WTeamID\", \"WScore\", \"LTeamID\", \"LScore\", \"margin\", \"homei\", \"homej\"]]\ndat = dat.rename(columns={'WTeamID' : 'teami',\n                          'WScore'  : 'scorei', \n                          'LTeamID' : 'teamj', \n                          'LScore'  : 'scorej',\n                          'DayNum'  : 'daynum',\n                          'Season'  : 'season'})\n\n# Create a game id\ndat[\"gameid\"] = np.where(dat['teami'] < dat['teamj'], \n                         dat['teami'].astype(str) + \"_\" + dat['teamj'].astype(str), \n                         dat['teamj'].astype(str) + \"_\" + dat['teami'].astype(str))\n\n# Set up team id mapping\nteam_key[\"id\"] = range(1, len(team_key.index) + 1)\n\n# Recoding ids to be between 1 and 366\ndat = dat.merge(team_key, left_on=\"teami\" , right_on=\"TeamID\")\ndat = dat.drop(columns=[\"TeamName\", \"teami\", \"TeamID\"])\ndat = dat.rename(index = str, columns = {\"id\" : \"teami\"})\ndat = dat.merge(team_key, left_on=\"teamj\" , right_on=\"TeamID\")\ndat = dat.drop(columns=[\"TeamName\", \"teamj\", \"TeamID\"])\ndat = dat.rename(index = str, columns = {\"id\" : \"teamj\"})\n\n# Final dataset for modeling\nnames = [\"N\", \"y\", \"h_i\", \"h_j\", \"team_i\", \"team_j\", \"N_g\"]\nvalues = [len(dat.index), dat.margin, dat.homei, dat.homej, dat.teami, dat.teamj, 367]\n\ntrain = dict(zip(names, values))","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Building the Model\n\nHere the model is trained on the 2020 regular season results"},{"metadata":{"trusted":true},"cell_type":"code","source":"model = \"\"\"\ndata {\n    int N;\n    vector[N] y;\n    int team_i[N];\n    int team_j[N];\n    int h_i[N];\n    int h_j[N];\n    int N_g;\n}\nparameters {\n    vector[N_g] alpha_raw;\n    vector[N_g] theta_raw;\n    real eta;\n    real<lower=0> tau_theta;\n    real<lower=0> tau_alpha;\n    real<lower=0> sigma;\n}\ntransformed parameters {\n    vector[N_g] alpha;\n    vector[N_g] theta;\n    alpha = eta + alpha_raw*tau_alpha;\n    theta = theta_raw*tau_theta;\n}\nmodel {\n    // vector for conditional mean storage\n    vector[N] mu;\n\n    // priors\n    tau_theta ~ cauchy(0,1)T[0,];\n    tau_alpha ~ cauchy(0,.25)T[0,];\n    sigma ~ cauchy(0,1)T[0,];\n    eta ~ normal(4,1);\n    theta_raw ~ normal(0,1);\n    alpha_raw ~ normal(0,1);\n\n    // define mu for the Gaussian\n    for( t in 1:N ) {\n    mu[t] = (theta[team_i[t]] + alpha[team_i[t]]*h_i[t]) - \n    (theta[team_j[t]] + alpha[team_j[t]]*h_j[t]);\n}\n\n    // the likelihood\n    y ~ normal(mu,sigma);\n}\n\"\"\"\n\nsm = pystan.StanModel(model_code = model)\nfit = sm.sampling(data = train, \n                  iter = 1500, \n                  warmup = 750,\n                  refresh = 100,\n                  control = dict(adapt_delta = 0.9))","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Model Visualizations\n\nLet's take a look at who the teams to beat would have been according to the model. Here are boxplots of the top 25 team's skill, sorted by their median values. One criticism of this model is that it doesn't adjust for strength of schedule which means a blowout against a weaker team could inflate a team's ability."},{"metadata":{"trusted":true},"cell_type":"code","source":"# Extracting team skill levels\ntheta = pd.DataFrame(fit.extract()[\"theta\"])\nalpha = pd.DataFrame(fit.extract()[\"alpha\"])\nsigma = fit.extract()[\"sigma\"]\nalpha.columns = team_key.TeamName\ntheta.columns = team_key.TeamName\n\n# Filtering to top 25 teams\ntheta25 = theta[theta.median().nlargest(25).index]\ntheta25 = theta25[theta25.columns[::-1]]\n\n# Creating the plot\ntheta25.boxplot(grid = False, vert = False, showfliers = False, figsize=(12, 8))\nplt.title('Team Power Rankings')\nplt.xlabel('Skill Level')\nplt.ylabel('Teams')","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"This kernal is still in development. It uses our silver medal model from last year's tournament (obviously no way to validate it against this year's tournament). If there is a visualization that you would like to see, feel free to mention it in the comments. I'm planning on comparing these power rankings to other metrics, showing how it works, and simulating a tournament. I hope you found this interesting, thanks! "}],"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}