{"cells":[{"metadata":{"_uuid":"d629ff2d2480ee46fbb7e2d37f6b5fab8052498a","collapsed":true,"_cell_guid":"79c7e3d0-c299-4dcb-8224-4455121ee9b0","trusted":false},"cell_type":"markdown","source":"# **Does The Best Team Win?**\nGoogle Cloud & NCAA March Madness Analytics Report by Bethany Connolly"},{"metadata":{},"cell_type":"markdown","source":"![](https://images.pexels.com/photos/1293265/pexels-photo-1293265.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=750&w=1260)\nStock Image from Pexels.com"},{"metadata":{},"cell_type":"markdown","source":"## Contents\n\n**1.** Summary\n\n**2.** Introduction\n\n**3.** Data Loading\n\n**4.** Measuring Success: Winning\n\n**5.** Examining The Data Spread \n\n**6.** Regular Season Win-Count vs. March Madness Results \n\n**7.** A Different Measure of Success: Score\n\n**8.** Regular Season Cumulative Score vs. March Madness Results \n\n**9.** Does The Most Successfull Team Win\n\n**10.** Conclusions"},{"metadata":{},"cell_type":"markdown","source":"## 1. Summary\n\nThe NCAA Division I Men's Basketball Tournament is one of the most famous and popular sport tournaments in the United States. \nThis single-elimination competition sees 68 of the country’s top college basketball teams compete for the national title. But to what extent does the outcome of this fast paced, drop out system reflect the overall rankings of the teams that compete?\nIn this report I compare the success of teams throughout the regular season to their subsequent success in March Madness. Success is defined as the total matches won as well as the cumulative point score achieved over the regular season. By ranking teams in this way, their likelihood of progressing to the later stages is predicted. Based on this data analysis, it can be argued that a team's cumulative score over the regular season is a more consistent metric for ranking it than either its number of wins or its ranking in March Madness."},{"metadata":{},"cell_type":"markdown","source":"## 2. Introduction\n\nWhat makes a great basketball team? Is it the team that wins the national championship? This would certainly be one measure of success, but how reliable is it? \n\nMarch Madness is a brutal tournament: over just three weeks, 68 of the country’s top teams compete in single elimination games until a champion team is crowned. Is this team the most consistently successful, or is a team’s likelihood of progressing through the competition determined by other factors? Cinderella teams are well known low seeded teams which make it further through the competition that expected. The madness of this tournament offers underdogs a chance of glory at the expense of otherwise successful opponents. Imagine that your favourite team gets paired against one of the top teams in the country which causes them to drop out in the first round. Does that mean they are one of the worst teams or were they just unlucky?\n\nThe point of a tournament is to separate out teams, the assumption being that the further you progress in the tournament, the better your team is. \n\n**In the case of March Madness, is this true?**\n\nMarch Madness isn’t the only chance for teams to compete each year. In fact, this short 20-day competition is preceded by an extensive regular season (132 days long). This longer regular season may offer a much more thorough evaluation of the different team’s performance consistency. It isn’t single elimination; win or lose, teams can compete over and over again. The ultimate result is more combinations of different teams competing, more data from many more games and the chance to better understand a team’s long-term performance over many months. \n\nIn this report I use the regular season results to evaluate a different metric for identifying the 'best' teams. Through exploratory data analysis of the 1985 - 2019 competitions, I compare the results of the regular season to those of the tournament with the aim of determining to what extent a team’s success in the regular season is reflected by its performance in the NCAA tournament."},{"metadata":{},"cell_type":"markdown","source":"## 3. Data Loading\n\nFor this report I explored the men’s regular season and tournament results for the years 1985 - 2019. These different competitions can be easily identified by the day of the competition calendar they fall on: \n\n`DayNum 0 - 132 => Regular Season`\n\n`DayNum 134 - 154 => NCAA Tournament`"},{"metadata":{"trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"import pandas as pd\nimport matplotlib.pyplot as plt\nimport matplotlib.cm as cm\nimport numpy as np  \n\nfrom scipy import stats  \nfrom operator import itemgetter \nfrom pathlib import Path\n\n# Load the datafiles\ndata_directory = Path('../input/march-madness-analytics-2020/2020DataFiles/2020DataFiles/2020-Mens-Data/MDataFiles_Stage1/')\nregular_season = pd.read_csv(data_directory / \"MRegularSeasonCompactResults.csv\")\ntourney_results = pd.read_csv(data_directory / \"MNCAATourneyCompactResults.csv\")\ntourney_seeds = pd.read_csv(data_directory / \"MNCAATourneySeeds.csv\")\n","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## 4. Measuring Success: Winning\n\n**How can a team’s success through the regular season be measured? **\n\nOne obvious answer to this is the frequency of its wins - the more times a team competes and wins, surely the better the team.\nThis metric is easily calculated by recording each instance of a team winning over the course of the pre-season and summing the wins to obtain a win-count.\n\nBut instead of just identifying the team with the most wins at the end of the regular season, let's first consider how a team’s cumulative win-count changes over this 4-month season. For example, the graph below shows the change in win-count for each of the 68 teams in the 2019 regular season.\n\n**What can we learn from this data?**\n\nAt the early stages of the season, the lines of the different teams are all bunched together - there isn’t a wide spread of wins/losses.\nAs more games take place, and the teams progress through the regular season, their total win-counts continue to rise. What differs amongst them is the rate of increase of their win-counts i.e. the slope of each team’s line.  Over the four months of the game season, the lines continue to diverge and by end of the regular season we see a wide spread of cumulative win-count. \n\nIf the purpose of a tournament is to separate teams out and rank them, then this looks like a good metric.\n\nLet’s now examine the spread of this data further...\n\n\n"},{"metadata":{"trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"# Drop the year 2020 (cancelled)\nregular_season = regular_season.drop(regular_season[regular_season.Season == 2020].index)\n\n# Get the unique team IDs for each competition year\ntourney_teams = tourney_seeds.groupby('Season').TeamID.unique()\n\n# Make a dictionary of years containing how many times each team won a game in the regular season\nyear_team_dict = {}\nfor year, year_group in regular_season.groupby('Season'):\n    year_team_dict[year] = {}\n    for win_team, win_team_group in year_group.groupby('WTeamID'):\n        if win_team in tourney_teams[year]:\n            year_team_dict[year][win_team] = win_team_group.DayNum.values\n\n# Plot the results of the cumulative win count for each team in the 2019 season\n%config InlineBackend.figure_format = 'svg'\nplt.figure(figsize = (10, 5))\nfor team, days in year_team_dict[2019].items():\n    plt.plot(days, range(len(days)))\n    plt.xlabel(\"Regular Season (Days)\", fontsize = 12)\n    plt.ylabel(\"Cumulative Wins\", fontsize = 12)\n    plt.title(\"Cumulative Wins Per Team Over Regular Season: 2019\", fontsize = 14, fontweight = 'bold')  \nplt.show()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## 5. Examining The Data Spread  \n\nWe saw in the above graph that totalling a team’s wins over the entire pre-season seems to be a good way of separating teams. But how well separated are these teams? Below are a series of histograms for each regular season from 1985 - 2019. These charts show the frequency of total win-counts achieved by the teams each year.\n\nThis looks pretty promising - there is a wide spread of results over the years but in most cases a reasonably normal distribution is achieved (black curve over the histogram).\nA normal distribution means that the majority of teams get a certain total win-count - these are the average teams. They aren’t bad, but they're not amazing either.\nBy comparison, a smaller group of teams perform quite badly, winning very few games over the entire season. Based on their performance, we probably wouldn't expect these teams to do too well in the tournament.\nLikewise, another small subset of teams fares much better than the rest, they win loads of their matches! The shaded blue area on each histogram shows the upper quartile - the teams that fall in this region represent the top 25% of all teams that year. Since these teams win so much, we might expect them to win more often in the tournament too.\n\nNext, let’s examine if this is what really happens!\n"},{"metadata":{"trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"# Obtain the final number of matches won per team, per year\nyear_team_cumulative = {}\nfor year in year_team_dict:\n    year_team_cumulative[year] = {}\n    for team, days in year_team_dict[year].items():\n        year_team_cumulative[year][team] = len(days) \n\n\n# Figure of histograms for each year 1985 - 2019        \nplt.figure(figsize = (14, 16))\nfor num, year in enumerate(year_team_cumulative):\n\n    # Color map for histogram\n    cm = plt.cm.get_cmap('plasma')\n    ytc = list(year_team_cumulative[year].values())\n    \n    # Make the hisogram\n    plt.subplot(7,5,(num + 1))\n    n, bins, patches = plt.hist(ytc, density = True, alpha = 0.9, edgecolor='black')\n    bin_centers = 0.5 * (bins[:-1] + bins[1:])\n    \n    # Set the bin color, based on bin value\n    col = bin_centers - min(bin_centers)\n    col /= max(col)\n    for c, p in zip(col, patches):\n        plt.setp(p, 'facecolor', cm(c))\n        \n    # Labels and title\n    plt.xlabel(\"Regular Season Win Count\", fontsize = 9)\n    plt.ylabel(\"Frequency Normalized\", fontsize = 9)\n    plt.title(year, fontsize = 12, fontweight = 'bold')\n    \n    # Set the xticks \n    xt = plt.xticks()[0]  \n    xmin, xmax = min(xt), max(xt)  \n    lnspc = np.linspace(xmin, xmax, len(ytc))\n    \n    # Fit the normal curve\n    m, s = stats.norm.fit(ytc)   \n    pdf_g = stats.norm.pdf(lnspc, m, s) \n    plt.plot(lnspc, pdf_g, color = 'black')\n\n    # Show top 25th percent\n    q3 = np.percentile(ytc, 75)\n    maxq = np.percentile(ytc, 100)\n    plt.axvspan(q3, maxq, color = 'blue', alpha = 0.2)\n   \n    plt.subplots_adjust(hspace = 1, wspace=0.45)   \n    \nplt.show()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"markdown","source":"## 6. Regular Season Win-Count vs. March Madness Results \n\nIf both the regular season and March Madness are good ways of identifying top performing teams, we might reasonably expect the same teams to come out on top in each. Let’s consider a set of top teams in the tournament, the 'sweet sixteen'. These teams make up the semi-finalists of the competition. By comparing the top 16 winning teams from the regular season to those of the 'sweet sixteen' we can determine to what extend consistently winning teams make it to the later stages of March Madness. For each year, the top 16 teams in each competition were identified and the number of teams which appeared in each list were counted. 'Wins Prediction Accuracy' was calculated as a fraction of correctly identified teams i.e. ‘sweet sixteen’ teams correctly predicted by regular season win-count divided by sixteen. The chart below shows this prediction accuracy plotted for each year of the competition. \n\nFor reference, lets imagine that the semi-final teams were not selected by competing but instead chosen completely by chance from all 68 of the original teams. Each team has a 1/68 chance of being chosen so we would now expect only 24% of the top 16 teams from the regular season to make it to the semi-finals.\n\nBy using our metric, cumulative win-count over the regular season, on average half of the sweet 16 teams can be correctly predicted (Win Count Prediction Accuracy': mean = 0.50, standard deviation = 0.09). \n\n**What can we conclude from this?**\n* Progression through the competition isn’t random, teams which win more games in the regular season do have a better chance of getting to the later stages of the NCAA tournament.\n* Simply considering this one variable - how many times a team wins over the regular season - is a reasonably good way of predicting how far it will progress through the tournament - its about twice as good as random.\n* However... not all top performing teams make it to the later stages of the competition! Although these top teams have proven themselves through the whole regular season, for some reason when March Madness comes around, they can’t keep their winning streak going.\nSo why might this be?\n\nThe nature of the tournament is single elimination - you lose once and you're out. \nThis is a pretty brutal way of thinning the herd and prevents unlucky teams from redeeming themselves later on. If two top performing teams are paired in early stages of the tournament, only one can continue through. This also means that teams which perform less consistently in the regular season can make a lucky win to jump ahead of higher ranked teams. These so called ‘Cinderella teams’ take the place of teams which would be expected to progress further.  \n\n**Is there a different measure of success which would allow us to better predict the tournament results?**\n"},{"metadata":{"trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"# Get a dictionary of top 16 teams from the preseason based on win count\nregular_topteams = {}\nn = 16\nfor year in year_team_cumulative:\n    top_n = dict(sorted(year_team_cumulative[year].items(), key = itemgetter(1), reverse = True)[:n])\n    regular_topteams[year] = {}\n    for team, score in year_team_cumulative[year].items():\n        regular_topteams[year] = list(top_n.keys()) \n\n# Get a dictionary of 'sweet 16' teams each year based on teams which win on days 138/139\ntournament_top_teams = {}\nfor year, year_group in tourney_results.groupby('Season'):\n    top_teams = year_group[(year_group['DayNum'] == 138) | (year_group['DayNum'] == 139)] \n    tournament_top_teams[year] = list(top_teams.WTeamID)\n    \n# Calculate accuracy score of regular season top 16 predictions from tournament matches\nscore = {}\nfor year in tournament_top_teams:\n    correct_teams = []\n    for team in tournament_top_teams[year]:\n        if team in regular_topteams[year]:\n            correct_teams.append(team)\n    team_predict_score = len(correct_teams)/ n    \n    score[year] = team_predict_score   \nscores = list(score.values())\n\n# Calcuate average prediction across all years\nmean_score = sum(scores) / len(scores)\n\n# Calculate mean error in the accuracy score\nstd = np.std(scores)\n\n# Baseline calculated as random chance that any 16 teams make it to the semi-final\nbaseline = (16/68)\n\n# Scatterplot of accuracy each year\nplt.figure(figsize = (10, 5))\nax = plt.subplot()\ncolors = scores\nplt.scatter(score.keys(), score.values(), cmap = 'plasma', c = colors, edgecolors = 'black', marker='o', s = 75)\nplt.title(\"Semi-Finalists Prediction Accuracy: Win Count\", fontsize = 14, fontweight = 'bold')\nplt.xlabel(\"Year\", fontsize=12)\nplt.ylabel(\"Win Count Prediction Accuracy\", fontsize = 12)\nplt.hlines(mean_score, xmin = 1985, xmax = 2019, color = 'red', label = 'Mean Prediction Accuracy', linestyles='dashed', alpha = 0.7)\nplt.text(2009,0.95,'- Mean Prediction Accuracy',rotation=0, color = 'red', alpha = 0.8, fontsize = 11)\nplt.text(2009,0.9,'- Random Prediction Accuracy',rotation=0, color = 'blue', alpha = 0.8, fontsize = 11)\nplt.hlines(baseline, xmin = 1985, xmax = 2019, color = 'blue', label = 'Random Prediction Accuracy', linestyles='dashed', alpha = 0.7)\nax.set_ylim(ymin=0, ymax = 1)\nplt.show()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## 7. A Different Measure of Success: Score\n\nSo, using our win-count metric , we predict 50% of the 'sweet sixteen' teams. But is there a better way of measuring a team’s potential?\n\nAlthough a team winning a game is an excellent way of measuring success, it offers no indication of the magnitude of the win. Imagine a team that just barely wins its game by scraping an extra point in the last few seconds of play. Now imagine another team which dominates the opposition and wins by a huge margin. By our previous metric both teams had won one game and the victories were recorded as equal. Likewise, a team which just barely lost was ranked as low as a team which suffered a huge defeat. There must be a better way of comparing these results!\n\n**Let’s re-examine our data in a different way...**\n\nInstead of ranking the different team’s success through the regular season by the number of games they win, lets now rank them by the score they get per game. The cumulative score can be defined by the total score over all the games a team plays through the season. Now teams which consistently win by large margins will get higher cumulative scores by the end of the season than those which win the same number of games by tiny margins.\n\nThe cumulative win score was therefore calculated by summing the score for each team (win or lose) per year over the entire regular season. The spread of this data is presented again as a series of histograms which show the frequency of total scores obtained amongst the teams.\n\nJust like the total win-count histograms we saw earlier; the cumulative score histograms often show normal distributions (black curve over histogram). We can see that there are many average scoring teams as well as a handful or poor and great ones. \n\n**Cumulative score also seems to be a good way of separating the teams based on performance!**"},{"metadata":{"trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"# List of scores (win and loose games) for each team in the regular season\nyear_team_dict2 = {}\nfor year, year_group in regular_season.groupby('Season'):\n    year_team_dict2[year] = {}\n    for team, team_group in year_group.groupby('WTeamID'):\n        if team in tourney_teams[year]:\n            year_team_dict2[year][team] = list(team_group.WScore.values)\n    for team, team_group in year_group.groupby('LTeamID'):\n        if team in tourney_teams[year]:\n            year_team_dict2[year][team].extend(team_group.LScore)\n\n# Calculate each teams cumulative win score each year \nyear_team_cumulative_score = {}\nfor year in year_team_dict2:\n    year_team_cumulative_score[year] = {}\n    for team, score in year_team_dict2[year].items():\n        year_team_cumulative_score[year][team] = sum(score)\n        \n# Figure of histograms for each year 1985 - 2019   \nplt.figure(figsize = (14, 16))\nfor num, year in enumerate(year_team_cumulative):\n     \n    # Color map for histogram\n    cm = plt.cm.get_cmap('plasma') #color map is plasma\n    ytc = list(year_team_cumulative_score[year].values()) # list of scores\n    \n    # make the hisogram\n    plt.subplot(7,5,(num+1))\n    n, bins, patches = plt.hist(ytc, density = True, alpha = 0.9, edgecolor='black')#plot each histogram\n    \n    # Set the bin color, based on bin value\n    bin_centers = 0.5 * (bins[:-1] + bins[1:])\n    col = bin_centers - min(bin_centers)\n    col /= max(col)\n    for c, p in zip(col, patches):\n        plt.setp(p, 'facecolor', cm(c))\n     \n    # Labels and title\n    plt.xlabel(\"Cumulative Score\", fontsize = 9)\n    plt.ylabel(\"Frequency Normalized\", fontsize = 9)\n    plt.title(year, fontsize = 12, fontweight = 'bold')\n    \n    # Set the xticks \n    #xmin, xmax = 0, 40 \n    xt = plt.xticks()[0]  \n    xmin, xmax = min(xt), max(xt)  \n    lnspc = np.linspace(xmin, xmax, len(ytc))\n    \n    # Fit the normal curve.\n    m, s = stats.norm.fit(ytc)   \n    pdf_g = stats.norm.pdf(lnspc, m, s) \n    plt.plot(lnspc, pdf_g, color = 'black')\n\n    # Show top 25th percent\n    q3 = np.percentile(ytc, 75)\n    maxq = np.percentile(ytc, 100)\n    plt.axvspan(q3, maxq, color = 'blue', alpha = 0.2)\n   \n    plt.subplots_adjust(hspace = 1, wspace=0.6)   \n    \nplt.show()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## 8. Regular Season Cumulative Score vs. March Madness Results \n\nNow that we have a new metric for determining success of the teams, lets again compare our calculated top teams to the tournament semi-finalists.\n\nThe graph below shows the 'Total Score Prediction Accuracy' per year i.e. the ratio of top 16 scoring teams in the regular season found in the 'sweet sixteen'. Although this result ('Total Score Prediction Accuracy': mean = 0.42, standard deviation = 0.07) is again significantly better than guessing at random (prediction accuracy = 0.24), considering its mean error, it shows basically the same prediction accuracy as the earlier metric, win-count."},{"metadata":{"trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"# Dictionary of top 16 teams in the regular season based on cumulative score\nregular_topteams_score = {}\nn = 16\nfor year in year_team_cumulative_score:\n    top_n = dict(sorted(year_team_cumulative_score[year].items(), key = itemgetter(1), reverse = True)[:n])\n    regular_topteams_score[year] = {}\n    for team, score in year_team_cumulative_score[year].items():\n        regular_topteams_score[year] = list(top_n.keys()) \n\n# Teams in the tournament semi_final \ntournament_top_teams_score = {}\nfor year, year_group in tourney_results.groupby('Season'):\n    top_teams = year_group[(year_group['DayNum'] == 138) | (year_group['DayNum'] == 139)] \n    tournament_top_teams_score[year] = list(top_teams.WTeamID)\n\n# Calculate accuracy score of cumulative score top 16 predictions from tournament matches\nfor year in tournament_top_teams_score:\n    correct_teams = []\n    for team in tournament_top_teams_score[year]:\n        if team in regular_topteams_score[year]:\n            correct_teams.append(team)\n    team_predict_score = len(correct_teams)/16\n    score2[year] = team_predict_score   \n\n# Calcuate average prediction across all years\nscores2 = list(score2.values())\nmean_score2 = sum(scores2) / len(scores2)\n\n# Calculate mean error in the accuracy score\nstd2 = np.std(scores)\n\n# Baseline calculated as random chance that any 16 teams make it to the semi-final\nbaseline2 = (16/68)\n\n# Scatterplot of cumulative score prediction accuracy each year\nplt.figure(figsize = (10, 5))\nax = plt.subplot()\ncolors = scores2\nplt.scatter(score2.keys(), score2.values(), cmap = 'plasma', c = colors, edgecolors = 'black', marker='o', s = 75)\nplt.title(\"Semi-Finalists Prediction Accuracy: Cumulative Score\", fontsize = 14, fontweight = 'bold')\nplt.xlabel(\"Year\", fontsize=12)\nplt.ylabel(\"Total Score Prediction Accuracy\", fontsize=12)\nplt.hlines(mean_score2, xmin = 1985, xmax = 2019, color = 'red', label = 'Mean Prediction Accuracy', linestyles='dashed', alpha = 0.7)\nplt.text(2009,0.95,'- Mean Prediction Accuracy',rotation=0, color = 'red', alpha = 0.9, fontsize = 11)\nplt.text(2009,0.9,'- Random Prediction Accuracy',rotation=0, color = 'blue', alpha = 0.9, fontsize = 11)\nplt.hlines(baseline2, xmin = 1985, xmax = 2019, color = 'blue', label = 'Random Prediction Accuracy', linestyles='dashed', alpha = 0.7)\nax.set_ylim(ymin=0, ymax = 1)\n\nplt.show()\n","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"This is further highlighted by the two histograms below, which compare the frequency of prediction accuracy of each of the two metrics. \n\nIn each case a spread of prediction accuracies is seen around the mean value of around 0.5."},{"metadata":{"trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"# Histograms figure comparing frequency of prediction accuracy for each metric\nfig = plt.subplots(1,2, figsize = (14, 6))\n\n# Histogram for Semi-Finalists Prediction Frequency (Win Count)\ncm = plt.cm.get_cmap('plasma')\nax = plt.subplot(1,2,1)\nn, bins, patches = plt.hist(scores, bins = 5, density = True, edgecolor='black', alpha = 0.9)\nbin_centers = 0.5 * (bins[:-1] + bins[1:])\ncol = bin_centers - min(bin_centers)\ncol /= max(col)\nfor c, p in zip(col, patches):\n    plt.setp(p, 'facecolor', cm(c))\nplt.xlabel(\"Prediction Accuracy\", fontsize = 12)\nplt.ylabel(\"Frequency\", fontsize = 12)\nplt.title(\"Semi-Finalists Prediction Frequency: Win Count\", fontsize = 13, fontweight = 'bold')\nax.set_xlim(xmin=0, xmax = 1)\nplt.axvline(mean_score, color = 'black', label = 'Mean Prediction Accuracy', linestyle='dashed')\nplt.text(0.62,3.85,'Mean Prediction Accuracy',rotation=0, color = 'black', fontsize = 9.5)\nplt.text(0.62,3.68,'Random Prediction Accuracy',rotation=0, color = 'blue', fontsize = 9.5)\nplt.axvline(baseline, color = 'blue', label = 'Random Prediction Accuracy', linestyle='dashed')\nax.set_xlim(xmin=0, xmax = 1)\n\n# Histogram for Semi-Finalists Prediction Frequency (Cumulative Score)\ncm = plt.cm.get_cmap('plasma')\nax2 = plt.subplot(1,2,2)\nn, bins, patches = plt.hist(scores2, bins = 5, density = True, edgecolor='black', alpha = 0.9)\nbin_centers = 0.5 * (bins[:-1] + bins[1:])\ncol = bin_centers - min(bin_centers)\ncol /= max(col)\nfor c, p in zip(col, patches):\n    plt.setp(p, 'facecolor', cm(c))\nplt.xlabel(\"Prediction Accuracy\", fontsize = 14)\nplt.ylabel(\"Frequency\", fontsize = 14)\nplt.title(\"Semi-Finalists Prediction Frequency: Cumulative Score\", fontsize = 13, fontweight = 'bold')\nax2.set_xlim(xmin=0, xmax = 1)\nplt.axvline(mean_score2, color = 'black', label = 'Mean Prediction Accuracy', linestyle='dashed')\nplt.text(0.6,9.15,'Mean Prediction Accuracy',rotation=0, color = 'black', fontsize = 9.5)\nplt.text(0.6,8.75,'Random Prediction Accuracy',rotation=0, color = 'blue', fontsize = 9.5)\nplt.axvline(baseline2, color = 'blue', label = 'Random Prediction Accuracy', linestyle='dashed')\nax2.set_xlim(xmin=0, xmax = 1)\n\nplt.subplots_adjust(wspace=0.15)   \n\nplt.show()\n","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## 9. Does The Best Team Win?\n\nTo wrap up, let’s take one final look at the ability of our metrics to record success. \nEven if we can’t predict all the top teams in the competition, maybe we can predict the champion team. Surely the team which wins the most games or scores the most points over the regular season would be expected to do well in the championship!\n\nThe top performing team in the regular season was identified by both win-count and cumulative score for each year. The years where these teams went on to be the top performing team are listed below.\n\nUsing win-count, the national champion team was identified three times over the last 35 years, an 8.7% success rate.\n\n**Using cumulative score, the national champion team was identified 7 times over the last 35 years, a 20.0% success rate.**\nFor a single easy to calculate metric that’s pretty good!\n\nLet’s compare these results to standard team ranking systems:\n\nSince 1985, the AP number 1 ranked team has won the championship only 4 times - an 11% success rate. \n\n**This means that our easy metric of cumulative score is about twice as good at predicting the winning team.**\n\nIf we were to choose a team at random, we would be correct only 1.5% of the time, so this new metric is much better than guessing. Win-count on the other hand is significantly worse than cumulative score which demonstrates the benefit of considering the magnitude of wins rather than just absolute win or lose values."},{"metadata":{"trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"# List of annual tournament champions \ntournament_number1team = {}\nfor year, year_group in tourney_results.groupby('Season'):\n    number1team = year_group[(year_group['DayNum'] == 154)]\n    tournament_number1team[year] = list(number1team.WTeamID)\n\n# Win Count: top team in the regular season each year\nregular_number1team_count = {}\nn = 1\nfor year in year_team_cumulative:\n    top_n = dict(sorted(year_team_cumulative[year].items(), key = itemgetter(1), reverse = True)[:n])\n    regular_number1team_count[year] = {}\n    for team, score in year_team_cumulative[year].items():\n        regular_number1team_count[year] = list(top_n.keys()) \n\n# List of teams and years where win count top team went on to win the championship\nreg_tournament_match = {}\nfor year in regular_number1team_count:\n    if tournament_number1team[year] == regular_number1team_count[year]:\n        reg_tournament_match[year] = tournament_number1team[year]\n\n# Calculate percentage of years with matching topteam\ntotal_years = len(tournament_number1team)\npredict_number1_percent_count = len(reg_tournament_match)/total_years * 100\n\n\n# Cumulative Score: top team in the regular season each year\nregular_number1team_score = {}\nn = 1\nfor year in year_team_cumulative_score:\n    top_n_score = dict(sorted(year_team_cumulative_score[year].items(), key = itemgetter(1), reverse = True)[:n])\n    regular_number1team_score[year] = {}\n    for team, score in year_team_cumulative_score[year].items():\n        regular_number1team_score[year] = list(top_n_score.keys()) \n\n# List of teams and years where cumulative score top team went on to win the championship\nreg_tournament_match_score = {}\nfor year in regular_number1team_score:\n    if tournament_number1team[year] == regular_number1team_score[year]:\n        reg_tournament_match_score[year] = tournament_number1team[year]\n\n# Calculate percentage of years with matching topteam\npredict_number1_percent_score = len(reg_tournament_match_score)/total_years * 100\n\nprint(\"Years where the team with the most number of wins in the regular season became the national champion:\")\nprint(list(reg_tournament_match.keys()))\nprint()\nprint(\"Years where the team with the highest cumulative score over the regular season became the national champion:\")\nprint(list(reg_tournament_match_score.keys()))","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## 10. Conclusions\n\nBy looking at historical data, we compared to what extent the results of the regular season are reflected by the results of March Madness.\n\nUsing the number of matches won over the preseason as a metric for success, we found that 50% of the top 16 teams in the national championship could be identified.\n\nUsing cumulative match score over the preseason instead as a metric, a comparable success rate in identifying the top team was identified.\n\nThese results show that while top teams are likely to do better in the tournament, it doesn’t guarantee them comparable success. There is probably some element of luck due to the unforgiving single-elimination nature of the tournament. March Madness doesn’t identify the teams which win consistently over many months, but rather the teams that perform well on a particular day or over a three-week period.\n\nTo further demonstrate this, the top performing team over the regular season was predicted using these metrics for success. The team that won the most matches was found to be the national champion only 8.7% of the time while the team which scored the most overall went on to win 20% of the time. This metric of success is more than twice as reliable as the current AP metric which has only been correct in predicting the champion team 11% of the time.  \n\nThese results demonstrate that even if your favourite team doesn't win the national championships, it doesn't mean they aren't the best team that year... it just means they weren't the best team that day."}],"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}