Who’s the Best NCAA Tournament Coach?

With the NCAA Men’s basketball tournament and my bracket officially busted I want to start gathering information for next year. I unfortunately chose Virginia to win it all over Villanova as I thought they were the best teams all year long. I was right about one team but woefully wrong about the other. Congrats to UMBC but I want to win my bracket challenge next year.

One aspect I have thought to include in my choices next year is the coach of the team. It seems to me that the same coaches are in the sweet sixteen and beyond each year (Bill Self, Coach K, Jim Boeheim, etc). Also it seems certain coaches get their teams to overacheive (Beilein, Brad Stevens, etc.). My goal is to find current coaches that win more than they are supposed to in the tournament.

To do this I have taken the data from the Kaggle NCAA 2018 ML Contest and will analyze it to find the average amount of wins for each seed and compare that to how the coaches do.

Import, Combine, and Clean the Data

To start I import the data for the conferences, teams, and coaches. Then I merge (AKA SQL join) and sort them by team to get data for each team, their coach, and the current conference on a yearly basis.

%matplotlib inline
import matplotlib as plt
import pandas as pd
import numpy as np
import seaborn as sns
import scipy as spy
from sklearn.model_selection import train_test_split

conf = pd.read_csv("TeamConferences.csv")
teams = pd.read_csv("Teams.csv")
coaches = pd.read_csv("TeamCoaches.csv")

allTeams = coaches.merge(teams[["TeamID", "TeamName"]], on="TeamID").merge(conf, on=["Season", "TeamID"])
allTeams.drop(["FirstDayNum","LastDayNum"], axis=1, inplace=True)
allTeams.drop_duplicates(inplace=True)
allTeams.head(25)

Season TeamID CoachName TeamName ConfAbbrev
0 1985 1102 reggie_minton Air Force wac
1 1986 1102 reggie_minton Air Force wac
2 1987 1102 reggie_minton Air Force wac
3 1988 1102 reggie_minton Air Force wac
4 1989 1102 reggie_minton Air Force wac
5 1990 1102 reggie_minton Air Force wac
6 1991 1102 reggie_minton Air Force wac
7 1992 1102 reggie_minton Air Force wac
8 1993 1102 reggie_minton Air Force wac
9 1994 1102 reggie_minton Air Force wac
10 1995 1102 reggie_minton Air Force wac
11 1996 1102 reggie_minton Air Force wac
12 1997 1102 reggie_minton Air Force wac
13 1998 1102 reggie_minton Air Force wac
14 1999 1102 reggie_minton Air Force wac
15 2000 1102 reggie_minton Air Force mwc
16 2001 1102 joe_scott Air Force mwc
17 2002 1102 joe_scott Air Force mwc
18 2003 1102 joe_scott Air Force mwc
19 2004 1102 joe_scott Air Force mwc
20 2005 1102 chris_mooney Air Force mwc
21 2006 1102 jeff_bzdelik Air Force mwc
22 2007 1102 jeff_bzdelik Air Force mwc
23 2008 1102 jeff_reynolds Air Force mwc
24 2009 1102 jeff_reynolds Air Force mwc

Next I will import the compact results as well as the tournament seeds files. Then merge the results with the seeds to create a data frame with all of the results as well as the winning seed and losing seed. I also had to clean the seed values as the seeds included a letter denoting which region the seed was in. All I cared about was the number of the seed, for more explanation and description of the function used see the Basic Starter Kernel - NCAA Men’s Dataset.

results = pd.read_csv("NCAATourneyCompactResults.csv")
seeds = pd.read_csv("NCAATourneySeeds.csv")

# convert and remove string characters from seed values
# borrowed from the Kaggle NCAA 2018 Men's competition tutorial notebook.
def seed_to_int(seed):
    #Get just the digits from the seeding. Return as int
    s_int = int(seed[1:3])
    return s_int

seeds['seed_int'] = seeds.Seed.apply(seed_to_int)
seeds.drop(labels=['Seed'], inplace=True, axis=1) # This is the string label

seedResults =  results.merge(seeds, right_on=["TeamID","Season"], left_on= ["WTeamID","Season"]).merge(seeds, right_on=["TeamID","Season"], left_on= ["LTeamID","Season"])
seedResults.drop(["TeamID_x", "TeamID_y"],axis = 1, inplace=True)
seedResults.rename(index=str, columns={"seed_int_x":"WSeed", "seed_int_y":"LSeed"}, inplace=True)
seedResults.head(10)

Season DayNum WTeamID WScore LTeamID LScore WLoc NumOT WSeed LSeed
0 1985 136 1116 63 1234 54 N 0 9 8
1 1985 136 1120 59 1345 58 N 0 11 6
2 1985 138 1120 66 1242 64 N 0 11 3
3 1985 136 1207 68 1250 43 N 0 1 16
4 1985 138 1207 63 1396 46 N 0 1 8
5 1985 143 1207 65 1260 53 N 0 1 4
6 1985 145 1207 60 1210 54 N 0 1 2
7 1985 152 1207 77 1385 59 N 0 1 1
8 1985 136 1229 58 1425 55 N 0 9 8
9 1985 136 1242 49 1325 38 N 0 3 14

Next I needed to merge the seed results with the team information

allResults =  seedResults.merge(allTeams, right_on=["TeamID","Season"], left_on= ["WTeamID","Season"]).merge(allTeams, right_on=["TeamID","Season"], left_on= ["LTeamID","Season"])

def xyToWL(colNames):
    """
    Convert suffix _x and _y to prefix W and L respectively.

    Parameters
    ----------
    colNames : list
        List of column names to convert

    Returns
    -------
    list
        New column names converted to WColumnName from ColumnName_x

    """
    newCols = []
    for col in colNames:
        if col.endswith("_x"):
            newCol = col.replace("_x", "")
            newCol = "W" + newCol
            newCols.append(newCol)
        elif col.endswith("_y"):
            newCol = col.replace("_y", "")
            newCol = "L" + newCol
            newCols.append(newCol)
        else:
            newCols.append(col)
    return newCols

# drop repeated w team id and l team id
allResults.drop(["TeamID_x", "TeamID_y"], axis=1, inplace=True)
allResults.drop_duplicates(inplace=True)

# Convert suffixed columns
allResults.columns = xyToWL(allResults.columns)
allResults = allResults[~((allResults.WSeed ==16) & (allResults.LSeed == 16))]
allResults.sort_values(["Season","DayNum"], inplace=True)
allResults.head(15)
Season DayNum WTeamID WScore LTeamID LScore WLoc NumOT WSeed LSeed WCoachName WTeamName WConfAbbrev LCoachName LTeamName LConfAbbrev
0 1985 136 1116 63 1234 54 N 0 9 8 eddie_sutton Arkansas swc george_raveling Iowa big_ten
1 1985 136 1120 59 1345 58 N 0 11 6 sonny_smith Auburn sec gene_keady Purdue big_ten
3 1985 136 1207 68 1250 43 N 0 1 16 john_thompson_jr Georgetown big_east tom_schneider Lehigh ecc
8 1985 136 1229 58 1425 55 N 0 9 8 bob_donewald Illinois St mvc stan_morrison USC pac_ten
9 1985 136 1242 49 1325 38 N 0 3 14 larry_brown Kansas big_eight danny_nee Ohio mac
10 1985 136 1246 66 1449 58 N 0 12 5 joe_b_hall Kentucky sec marv_harshman Washington pac_ten
12 1985 136 1256 78 1338 54 N 0 5 12 andy_russo Louisiana Tech southland roy_chipman Pittsburgh big_east
14 1985 136 1260 59 1233 58 N 0 4 13 gene_sullivan Loyola-Chicago mw_city pat_kennedy Iona maac
16 1985 136 1314 76 1292 57 N 0 2 15 dean_smith North Carolina acc bruce_stewart MTSU ovc
19 1985 136 1323 79 1333 70 N 0 7 10 digger_phelps Notre Dame ind ralph_miller Oregon St pac_ten
20 1985 136 1326 75 1235 64 N 0 4 13 eldon_miller Ohio St big_ten johnny_orr Iowa St big_eight
21 1985 136 1328 96 1299 83 N 0 1 16 billy_tubbs Oklahoma big_eight don_corbett NC A&T meac
24 1985 136 1374 85 1330 68 N 0 5 12 dave_bliss SMU swc paul_webb Old Dominion sun_belt
25 1985 136 1385 83 1380 59 N 0 1 16 lou_carnesecca St John's big_east robert_hopkins Southern Univ swac
29 1985 136 1396 60 1439 57 N 0 8 9 john_chaney Temple a_ten charles_moir Virginia Tech metro

Next I needed to calculate the expected wins (EWins) for each seed. To do this I grouped and merged the allResults dataframe on the seeds to give the totalWins, totalLosses, and WPct for each seed. I then calculated the EWins using the following formula:

\[EWins = \frac{totalWins}{4 \cdot numberOfSeasons}\]

Note: The 4 in the denominator is due to the fact that each seed has 4 teams per year.

bySeed = mergeWL(allResults, "Seed")
# EWins is yearly expected wins per team by their seed. --> totalWins / 4 * number of seasons   (4 due to 4 different seeds every year)
bySeed["EWins"] = bySeed.totalWins/(4*seedResults.Season.nunique())
bySeed = bySeed.round(decimals=3)
bySeed.head(16)

Seed totalWins totalLosses WPct EWins
0 1 446.0 115 0.795 3.379
1 2 320.0 130 0.711 2.424
2 3 245.0 131 0.652 1.856
3 4 207.0 131 0.612 1.568
4 5 145.0 133 0.522 1.098
5 6 149.0 133 0.528 1.129
6 7 123.0 132 0.482 0.932
7 8 97.0 132 0.424 0.735
8 9 74.0 132 0.359 0.561
9 10 83.0 134 0.382 0.629
10 11 89.0 144 0.382 0.674
11 12 71.0 135 0.345 0.538
12 13 33.0 133 0.199 0.250
13 14 24.0 134 0.152 0.182
14 15 9.0 133 0.063 0.068
15 16 0.0 133 0.000 0.000
# helper function mergeWL
def mergeWL(resDF, onCol):
    '''
    Group resDF by onCol and count totalWins, total Losses and WPct

    Parameters
    ----------
    resDF : pandas.dataframe
        Dataframe of results to filter and group wins and losses

    onCol: str
        String of column to group by

    Returns
    -------
    pandas.dataframe
        New dataframe with wins, losses, and WPct calculated.

    '''
    wins = resDF.groupby("W"+onCol, as_index=False)["WTeamID"].agg(['count']).rename(columns={'count': 'totalWins'})
    loss = resDF.groupby("L"+onCol, as_index=False)["LTeamID"].agg(['count']).rename(columns={'count': 'totalLosses'})
    comb = wins.merge(loss, how="right", left_index = True, right_index = True)
    comb.reset_index(inplace=True)
    comb.rename(columns={comb.columns[0]:onCol}, inplace=True)
    comb["WPct"] = comb.totalWins/(comb.totalWins + comb.totalLosses)
    comb.fillna(0, inplace=True)
    return comb

Finally on to calculating coach wins, losses, and the average of their wins added based on their seed (AvgWinsAdded). To start I grouped and merged allResults by CoachName.

byCoach = mergeWL(allResults, "CoachName")
byCoach.head(10)
CoachName totalWins totalLosses WPct
0 al_brown 0.0 1 0.000000
1 al_skinner 8.0 9 0.470588
2 alan_leforce 1.0 2 0.333333
3 andrew_toole 0.0 1 0.000000
4 andy_enfield 4.0 3 0.571429
5 andy_kennedy 2.0 2 0.500000
6 andy_russo 2.0 2 0.500000
7 andy_stoglin 0.0 2 0.000000
8 anthony_evans 1.0 1 0.500000
9 anthony_grant 1.0 3 0.250000

Next I calculated the average seed (avgSeed) and number of appearances (Appearances) for each coach.

avgSeed = byCoachSeed.groupby("CoachName", as_index=False).agg({'Seed': "mean", 'Season': 'count'})
avgSeed.rename(columns={"Seed":"AvgSeed", "Season":"Appearances"}, inplace=True)
byCoach = byCoach.merge(avgSeed,on="CoachName").round(3)
byCoach["totalWins"] = byCoach["totalWins"].astype(int)
byCoach.head(10)
CoachName totalWins totalLosses WPct AvgSeed Appearances
0 al_brown 0 1 0.000 14.000 1
1 al_skinner 8 9 0.471 6.556 9
2 alan_leforce 1 2 0.333 12.000 2
3 andrew_toole 0 1 0.000 16.000 1
4 andy_enfield 4 3 0.571 11.333 3
5 andy_kennedy 2 2 0.500 11.500 2
6 andy_russo 2 2 0.500 8.500 2
7 andy_stoglin 0 2 0.000 16.000 2
8 anthony_evans 1 1 0.500 15.000 1
9 anthony_grant 1 3 0.250 10.333 3

Finally I needed to calculate the number of wins added per year by each coach. To do this I created another dataframe called byCoachSeed to hold all of the wins and losses grouped by coach and season. Using this dataframe I was able to count the number of wins each year by each team (coach) to get their wins in the tournament for that year. Also I calculated the difference between average wins by seed and the actual wins that year for that coach(Diff).

winCoachSeed = allResults.groupby(["WCoachName", "Season"], as_index=False)["WSeed"].mean()
lossCoachSeed = allResults.groupby(["LCoachName", "Season"], as_index=False)["LSeed"].mean()
winCoachSeed.rename(columns={"WCoachName":"CoachName","WSeed":"Seed"}, inplace=True)
lossCoachSeed.rename(columns={"LCoachName":"CoachName","LSeed":"Seed"}, inplace=True)
byCoachSeed = winCoachSeed.append(lossCoachSeed,ignore_index=True).drop_duplicates().sort_values(["CoachName", "Season"])
byCoachSeed.reset_index(drop=True, inplace=True)

coachWins = allResults.groupby(["WCoachName", "Season"], as_index=False)["WSeed"].count().rename(columns={"WSeed":"TourneyWins","WCoachName":"CoachName"})
byCoachSeed = byCoachSeed.merge(coachWins, how="left",on=["CoachName", "Season"]).fillna(0)
byCoachSeed["TourneyWins"] = byCoachSeed["TourneyWins"].astype(int)

byCoachSeed = byCoachSeed.merge(bySeed[["Seed", "EWins"]], how="left", on="Seed").fillna(0)
byCoachSeed["Diff"] = byCoachSeed.TourneyWins - byCoachSeed.EWins
byCoachSeed.head(15)


CoachName Season Seed TourneyWins EWins Diff
0 al_brown 1986 14 0 0.182 -0.182
1 al_skinner 1993 8 1 0.735 0.265
2 al_skinner 1997 9 0 0.561 -0.561
3 al_skinner 2001 3 1 1.856 -0.856
4 al_skinner 2002 11 0 0.674 -0.674
5 al_skinner 2004 6 2 1.129 0.871
6 al_skinner 2005 4 1 1.568 -0.568
7 al_skinner 2006 4 2 1.568 0.432
8 al_skinner 2007 7 1 0.932 0.068
9 al_skinner 2009 7 0 0.932 -0.932
10 alan_leforce 1991 10 0 0.629 -0.629
11 alan_leforce 1992 14 1 0.182 0.818
12 andrew_toole 2015 16 0 0.000 0.000
13 andy_enfield 2013 15 2 0.068 1.932
14 andy_enfield 2016 8 0 0.735 -0.735

To get the AvgWinsAdded for each coach I was able to group byCoachSeed from above on the coaches

coachTotalEWins = byCoachSeed.groupby("CoachName", as_index=False)["Diff"].mean()
byCoach = byCoach.merge(coachTotalEWins, how="left", on="CoachName").rename(columns={"Diff":"AvgWinsAdded"})
byCoach.sort_values("AvgWinsAdded",ascending=False, inplace=True)
byCoach.reset_index(drop=True, inplace=True)
byCoach.head(15)

CoachName totalWins totalLosses WPct AvgSeed Appearances AvgWinsAdded
0 kevin_ollie 7 1 0.875 8.0 2 2.7535
1 john_giannini 3 1 0.750 13.0 1 2.7500
2 jim_rosborough 5 1 0.833 2.0 1 2.5760
3 kevin_mackey 2 1 0.667 14.0 1 1.8180
4 brad_stevens 12 5 0.706 7.0 5 1.5090
5 rollie_massimino 11 4 0.733 9.0 5 1.4816
6 russ_pennell 2 1 0.667 12.0 1 1.4620
7 darrin_horn 2 1 0.667 12.0 1 1.4620
8 jim_brandenburg 2 1 0.667 12.0 1 1.4620
9 joe_b_hall 2 1 0.667 12.0 1 1.4620
10 michael_white 3 1 0.750 4.0 1 1.4320
11 craig_esherick 2 1 0.667 10.0 1 1.3710
12 johnny_dawkins 2 1 0.667 10.0 1 1.3710
13 todd_lickliter 4 2 0.667 8.5 2 1.1820
14 greg_gard 4 2 0.667 7.5 2 1.1665

Kevin Ollie leads the pack! He took a 7 seed to the Final Four and won it the next year. However I am more focused on coaches that have more lasting impact. Let’s limit it to 3 tournament appearances.

Top 50 Coaches by Average Wins Above Seed

byCoach3App = byCoach[byCoach["Appearances"] >= 3].reset_index(drop=True)
byCoach3App.head(50)
CoachName totalWins totalLosses WPct AvgSeed Appearances AvgWinsAdded
0 brad_stevens 12 5 0.706 7.000 5 1.509000
1 rollie_massimino 11 4 0.733 9.000 5 1.481600
2 richard_williams 6 3 0.667 5.000 3 0.902000
3 larry_brown 13 4 0.765 4.200 5 0.881800
4 john_groce 4 3 0.571 11.333 3 0.878667
5 andy_enfield 4 3 0.571 11.333 3 0.841000
6 frank_martin 10 5 0.667 6.600 5 0.827400
7 billy_donovan 35 12 0.745 4.071 14 0.733857
8 john_beilein 19 11 0.633 7.818 11 0.725182
9 paul_westhead 4 3 0.571 11.000 3 0.719667
10 sonny_smith 7 5 0.583 9.400 5 0.716600
11 tom_izzo 47 20 0.701 4.950 20 0.702250
12 bill_guthridge 8 3 0.727 4.000 3 0.676667
13 stan_heath 5 4 0.556 10.500 4 0.640000
14 rick_pitino 54 18 0.750 3.550 20 0.615500
15 john_calipari 52 17 0.754 2.889 18 0.564389
16 quin_snyder 5 4 0.556 9.000 4 0.552750
17 jerry_tarkanian 22 8 0.733 4.111 9 0.496556
18 p_j_carlesimo 12 7 0.632 5.000 6 0.488667
19 lefty_driesell 5 4 0.556 8.750 4 0.487000
20 dick_tarrant 3 4 0.429 13.250 4 0.456500
21 denny_crum 21 11 0.656 5.583 12 0.448917
22 archie_miller 5 4 0.556 9.000 4 0.447000
23 jim_calhoun 48 17 0.738 4.500 20 0.435650
24 roy_williams 77 24 0.762 2.741 27 0.409593
25 steve_fisher 26 14 0.650 6.267 15 0.403000
26 dean_smith 37 13 0.740 2.615 13 0.393923
27 thomas_penders 12 11 0.522 9.636 11 0.382273
28 tommy_amaker 4 5 0.444 12.200 5 0.372600
29 jim_j_o'brien 11 7 0.611 5.857 7 0.368143
30 dan_monson 3 3 0.500 10.000 3 0.366000
31 steve_donahue 2 3 0.400 13.333 3 0.366000
32 chris_mack 10 8 0.556 7.571 7 0.335429
33 bo_ryan 27 15 0.643 5.200 15 0.332867
34 clem_haskins 11 7 0.611 6.714 7 0.326714
35 sean_miller 19 10 0.655 5.200 10 0.325000
36 mike_krzyzewski 92 28 0.767 2.219 32 0.313219
37 pete_gillen 8 10 0.444 10.444 9 0.306333
38 paul_hewitt 7 6 0.538 8.167 6 0.300500
39 bill_frieder 14 6 0.700 4.429 7 0.298714
40 gary_williams 28 15 0.651 5.375 16 0.294063
41 nolan_richardson_jr 26 13 0.667 5.500 14 0.291714
42 ben_jacobson 4 4 0.500 9.250 4 0.282250
43 jim_boeheim 52 25 0.675 4.038 26 0.278962
44 jim_valvano 8 5 0.615 5.600 5 0.277400
45 mike_anderson 9 8 0.529 8.250 8 0.276500
46 gary_waters 2 3 0.400 12.333 3 0.275333
47 billy_kennedy 3 3 0.500 10.333 3 0.275333
48 fang_mitchell 1 3 0.250 15.000 3 0.265333
49 wimp_sanderson 11 7 0.611 5.143 7 0.259857

Bottom 50 Coaches

byCoach3App.tail(50)
CoachName totalWins totalLosses WPct AvgSeed Appearances AvgWinsAdded
198 scott_nagy 0 3 0.000 13.000 3 -0.323333
199 bryce_drew 0 3 0.000 12.000 3 -0.331000
200 seth_greenberg 1 3 0.250 9.667 3 -0.340667
201 dave_odom 10 9 0.526 5.222 9 -0.341667
202 bobby_lutz 2 5 0.286 7.800 5 -0.342600
203 mark_fox 2 5 0.286 8.200 5 -0.369800
204 mike_montgomery 18 16 0.529 6.188 16 -0.386000
205 kevin_stallings 6 9 0.400 6.778 9 -0.388778
206 leonard_hamilton 7 8 0.467 6.125 8 -0.403375
207 wayne_tinkle 0 4 0.000 11.750 4 -0.403500
208 fran_dunphy 3 16 0.158 10.625 16 -0.407125
209 tom_asbury 0 4 0.000 12.250 4 -0.416750
210 ray_mccallum 0 3 0.000 12.667 3 -0.426667
211 jud_heathcote 7 7 0.500 5.143 7 -0.441429
212 tad_boyle 1 4 0.200 9.250 4 -0.443250
213 greg_mcdermott 3 7 0.300 8.429 7 -0.448143
214 speedy_morris 1 4 0.200 9.500 4 -0.450750
215 kelvin_sampson 12 14 0.462 6.429 14 -0.454571
216 gene_bartow 2 5 0.286 8.200 5 -0.459200
217 riley_wallace 0 3 0.000 11.667 3 -0.472333
218 larry_eustachy 4 5 0.444 6.800 5 -0.478800
219 rick_stansbury 4 6 0.400 6.667 6 -0.487333
220 travis_ford 1 6 0.143 8.833 6 -0.492500
221 rich_herrin 0 3 0.000 11.667 3 -0.495000
222 randy_ayers 6 3 0.667 3.333 3 -0.497667
223 jamie_dixon 12 12 0.500 5.091 11 -0.498000
224 gene_keady 18 15 0.545 4.933 15 -0.500133
225 ed_cooley 1 4 0.200 9.250 4 -0.509500
226 bob_wenzel 0 3 0.000 10.000 3 -0.515333
227 lou_henson 9 11 0.450 5.800 10 -0.533300
228 skip_prosser 6 9 0.400 7.222 9 -0.536111
229 steve_cleveland 0 3 0.000 12.000 3 -0.538000
230 jerry_green 3 5 0.375 6.000 5 -0.547000
231 john_thompson_iii 9 10 0.474 5.800 10 -0.586300
232 billy_tubbs 14 8 0.636 2.875 8 -0.609875
233 hugh_durham 1 4 0.200 8.000 4 -0.617500
234 tony_bennett 10 7 0.588 3.714 7 -0.619000
235 pat_foster 0 3 0.000 10.000 3 -0.634000
236 rob_evans 1 3 0.250 7.333 3 -0.644000
237 norm_stewart 7 10 0.412 5.900 10 -0.665900
238 jeff_mullins 0 3 0.000 9.000 3 -0.704667
239 tim_welsh 0 3 0.000 9.000 3 -0.755000
240 ralph_miller 0 3 0.000 9.333 3 -0.765333
241 george_raveling 1 4 0.200 7.750 4 -0.865500
242 danny_nee 0 6 0.000 8.667 6 -0.867500
243 oliver_purnell 0 6 0.000 8.167 6 -0.878667
244 frank_haith 1 4 0.200 7.250 4 -0.897750
245 j_d_barnett 1 3 0.250 7.667 3 -0.909000
246 eddie_fogler 2 6 0.250 6.667 6 -0.984833
247 steve_lappas 2 4 0.333 4.500 4 -1.003750

Conclusion

Looking at the Top 50 and bottom 50 provide some insights. Tony Bennett is in the bottom 10% (234 out of 247). Maybe defense doesn’t win championships? Also if Pitt wasn’t happy with Jamie Dixon and his teams’ tournament performance, Kevin Stallings might not have been much of an improvement. On the positive side I can see why Brad Stevens has had success in both NCAA and NBA as he leads the group of coaches with 3 or more appearances. Also a lot of the legends (Izzo, Pitino, Calipari) are up towards the top as well.

Some teams to watch next year if they make the tournament could be Indiana (Archie Miller), South Carolina (Frank Martin), and Michigan (John Beilein). It would be nice to add the results from this year’s tournament as some of these values might change. Beilein would improve as he took #3 Michigan to the Final and Tony Bennett will fall even further.