{"cells":[{"metadata":{"_uuid":"d629ff2d2480ee46fbb7e2d37f6b5fab8052498a","collapsed":true,"_cell_guid":"79c7e3d0-c299-4dcb-8224-4455121ee9b0","trusted":false},"cell_type":"markdown","source":"# Space-time Advantage\nThis notebook is a participant of [March Madness Analytics 2020 competition](https://www.kaggle.com/c/march-madness-analytics-2020) and is made to tell you about space-time data and its usability in March Madness ML competitions. Also, you can get all the dataframes from this work to have an instant start of your own researches.\n\nEnjoy!\n\n[![logo.png](https://i.postimg.cc/7YbRKcB2/logo.png)](https://postimg.cc/56dp0PW4)\n\n***"},{"metadata":{},"cell_type":"markdown","source":"# Contents\n\n1. [Introduction](#Introduction) <br>\n    1.1. [Tournament structure](#TournamentStructure)<br>\n    1.2. [Goals](#Goals)<br>\n    1.3. [Restrictions](#Restrictions)\n2. [Preparations](#Preparations)<br>\n    2.1. [Load libraries](#LoadLibraries)<br>\n    2.2. [Load dataframes](#LoadDataframes)<br>\n    2.3. [Create dataframes](#CreateDataframes)\n3. [Infographics](#Infographics)<br>\n4. [Relative Proximity](#RelativeProximity)<br>\n    4.1. [Theory](#Theory)<br>\n    4.2. [Critical value and significant games](#CriticalValueAndSignificantGames)<br>\n    4.3. [Analysis](#Analysis)<br>\n5. [Travel distance and recovery time](#TravelDistanceAndRecoveryTime)<br>\n6. [Conclusions](#Conclusions)\n\n***"},{"metadata":{},"cell_type":"markdown","source":"<div id=\"Introduction\">\n</div>\n# 1. Introduction\n\nThis notebook tells you about geography and time influence on teams' performance. I want you to focus on content, so all code is hidden. Anyway, you can expand code cells and look inside the analysis.\n\nWe'll start off with an overall review, then look on some curious infographics and finaly have some research and conclusions.\n\n<div id=\"TournamentStructure\">\n</div>\n## 1.1. Tournament structure\n**First of all, let's examine how March Madness is scheduled for a random team.** \nThe beauty of March Madness: this schedule scheme is equal for every team (60/68 teams skip \"First Four\" round and start right of first round) and every game is played on neutral court. \n\nLet's say the tournament starts at day 1. \n\n[![MMArenas-copy.png](https://i.postimg.cc/3JqWrvSD/MMArenas-copy.png)](https://postimg.cc/LnzmNnMH)\n\n**We see, that there are two scheduled breaks, on which teams are most likely to get back home.** If a team starts from First Four, it has to travel one additional time in the very beginning of the tournament, but all other teams have no trips \"from arena to arena\". This fact suggests to consider all travels as travels from home to game city.\n\n**More over, teams have different amount of days between games.** E.g. \"First Four\" teams have 1-2 days to recover before first round or one team has its last regular season game way before other ones.\n\nIn a single tournament, team travels 4 times at most:\n1. 8 teams travel to \"First Four\".\n2. 64 teams (60 new + \"First Four\") travel to First Round.\n3. 16 teams get back home after Second Round and get back to \"Sweet Sixteen\".\n4. Finally, 4 teams can get back home before \"Final Four\".\n\nAlso team's recovery time depends on:\n1. Day of last regular season game. \n2. \"First Four\" participation.\n3. Even or odd game days in first and second round.\n4. Even or odd game days in \"Sweet Sixteen\" and \"Elite Eight\".\n\nTherefore, 44 out of 67 games are played after home trips.\n\nI see three branches of analysis which set the goals of this report.\n\n<div id=\"Goals\">\n</div>\n## 1.2. Goals\nTo understand:\n1. How teams' hometowns' proximity to a game city effects their performance.\n2. How travel distances effect team's performance.\n3. How recovery time effects team's performance.\n\n<div id=\"Restrictions\">\n</div>\n## 1.3. Restrictions\nSad to know, but we are limited in time period: there is no consistently known data of game cities before 2010. In the 2nd half of this notebook I will operate with given geography data in period from year 2010 to 2019."},{"metadata":{},"cell_type":"markdown","source":"<div id=\"Preparations\">\n</div>\n# 2. Preparations\n<div id=\"LoadLibraries\">\n</div>\nYou can expand code cells by clicking on right tabs.\n* Load libaries"},{"metadata":{"_kg_hide-input":true,"trusted":true},"cell_type":"code","source":"# linear algebra\nimport numpy as np\n# dataframes\nimport pandas as pd\n# ploting\nimport matplotlib.pyplot as plt\n# math functions\nimport math\n# visualisation\nimport seaborn as sns\n# visualisation\nimport plotly.express as px\n# visualisation\nimport plotly.graph_objects as go\n\n# disable warnings\n# pd.set_option('mode.chained_assignment', None)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"* Load dataframes\n<div id=\"LoadDataframes\">\n</div>"},{"metadata":{"_kg_hide-input":true,"trusted":true},"cell_type":"code","source":"# detailed tournament games data \ntourney_results = pd.read_csv('/kaggle/input/march-madness-analytics-2020/MDataFiles_Stage2/MNCAATourneyDetailedResults.csv')\n# detailed tournament games data (for infographics)\nall_tourney_results = tourney_results.copy()\n# compact tournament games data \ntourney_compact_results = pd.read_csv('/kaggle/input/march-madness-analytics-2020/MDataFiles_Stage2/MNCAATourneyCompactResults.csv')\n# seeds data\nseeds = pd.read_csv('/kaggle/input/march-madness-analytics-2020/MDataFiles_Stage2/MNCAATourneySeeds.csv')\n# regular season data (cut it since there was no tournament in 2020)\nregular_results = pd.read_csv('/kaggle/input/march-madness-analytics-2020/MDataFiles_Stage2/MRegularSeasonDetailedResults.csv')\nregular_results = regular_results[regular_results.Season<2020]\n# game cities data\ngame_cities = pd.read_csv('/kaggle/input/march-madness-analytics-2020/MDataFiles_Stage2/MGameCities.csv')\n# city names\ncity_names = pd.read_csv('/kaggle/input/march-madness-analytics-2020/MDataFiles_Stage2/Cities.csv')\n# team names\nteam_names = pd.read_csv('/kaggle/input/march-madness-analytics-2020/MDataFiles_Stage2/MTeams.csv')\n# city coordinates\nlat_long_df = pd.read_csv('/kaggle/input/simplemaps-us-cities-database/uscities.csv')","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"<div id=\"CreateDataframes\">\n</div>\n* Create new dataframes:\n    1. teams with their hometown coordinates.\n    2. tournament games with calculated distances between colleges' hometowns and game cities.\n    2. tournament games with calculated distances between game cities and current teams locations + recovery time."},{"metadata":{"_kg_hide-input":true,"trusted":true},"cell_type":"code","source":"# 1\n# \"teams with their hometown coordinates\" dataframe (location_data)\n\n# a little tweak to merge cities and coordinates\ncity_names = city_names.replace('Mt. Pleasant', 'Mount Pleasant')\n\n#get all teams that won at least a game in Years 2010-2019 and merge it with Cities ID, city and team names\nlocation_data = pd.merge(regular_results[(regular_results['Season']>2009)],game_cities[game_cities['CRType'] == 'Regular'], on=['Season','DayNum','WTeamID','LTeamID'])\nlocation_data = location_data[location_data['WLoc'] == 'H'].drop_duplicates(subset = ['WTeamID','CityID'], keep = 'last').drop(['DayNum', 'LTeamID','LTeamID','Season','LScore','WScore','WLoc','CRType','NumOT'],axis = 1)\nlocation_data = pd.merge(pd.merge(location_data,city_names, on = 'CityID'),team_names, left_on = 'WTeamID',right_on = 'TeamID').drop(['FirstD1Season','WTeamID','LastD1Season'], axis = 1)\nlocation_data = location_data[['TeamID','TeamName','CityID','City','State']]\n\n#leave only needed information in lat_long_df\nlat_long_df = lat_long_df[['city','state_id','lat','lng']]\n\n#merge cities and coordinates\nlocation_data = pd.merge(location_data, lat_long_df, left_on = ['City','State'], right_on = ['city','state_id'], how = 'left')\n\n#add missing data\nlocation_data.at[5,'lat'] = 44.8834\nlocation_data.at[5,'lng'] = -68.6708\nlocation_data.at[15,'lat'] = 40.6960\nlocation_data.at[15,'lng'] = -73.9932\nlocation_data.at[23,'lat'] = 41.1414\nlocation_data.at[23,'lng'] = -73.2637\nlocation_data.at[25,'lat'] = 41.1414\nlocation_data.at[25,'lng'] = -73.2637\nlocation_data.at[26,'lat'] = 40.5080\nlocation_data.at[26,'lng'] = -80.2060\nlocation_data.at[30,'lat'] = 34.4587\nlocation_data.at[30,'lng'] = -82.8687\nlocation_data.at[32,'lat'] = 40.0349\nlocation_data.at[32,'lng'] = -75.3373\nlocation_data.at[34,'lat'] = 40.7489\nlocation_data.at[34,'lng'] = -74.2609\nlocation_data.at[39,'lat'] = 34.7867\nlocation_data.at[39,'lng'] = -86.5698\nlocation_data.at[63,'lat'] = 42.0841\nlocation_data.at[63,'lng'] = -76.0542\nlocation_data.at[85,'lat'] = 40.8870\nlocation_data.at[85,'lng'] = -73.9012\nlocation_data.at[92,'lat'] = 43.7023\nlocation_data.at[92,'lng'] = -72.2895\nlocation_data.at[99,'lat'] = 41.3858\nlocation_data.at[99,'lng'] = -72.9042\nlocation_data.at[148,'lat'] = 38.9904\nlocation_data.at[148,'lng'] = -104.8606\nlocation_data.at[153,'lat'] = 41.9221\nlocation_data.at[153,'lng'] = -71.5496\nlocation_data.at[199,'lat'] = 40.5608\nlocation_data.at[199,'lng'] = -74.4655\nlocation_data.at[202,'lat'] = 31.8190\nlocation_data.at[202,'lng'] = -91.0500\nlocation_data.at[211,'lat'] = 42.3233\nlocation_data.at[211,'lng'] = -71.1423\nlocation_data.at[215,'lat'] = 34.2379\nlocation_data.at[215,'lng'] = -118.5301\nlocation_data.at[288,'lat'] = 43.1338\nlocation_data.at[288,'lng'] = -70.9269\nlocation_data.at[298,'lat'] = 42.3403\nlocation_data.at[298,'lng'] = -72.4968\nlocation_data.at[331,'lat'] = 41.7620\nlocation_data.at[331,'lng'] = -72.7420\nlocation_data.at[362,'lat'] = 40.8121\nlocation_data.at[362,'lng'] = -77.8561\n\n#drop not needed columns\nlocation_data.drop(['city','state_id'], axis = 1, inplace = True)\n\n#add data of 'no teams' cities \nlocation_data = location_data.append([{'TeamID': 0,'TeamName':'none','CityID':4008,'City':'Anaheim','State':'CA','lat':33.839,'lng':-117.8573}], ignore_index = True)\nlocation_data = location_data.append([{'TeamID': 0,'TeamName':'none','CityID':4019,'City':'Auburn Hills','State':'MI','lat':42.6735,'lng':-83.2447}], ignore_index = True)\nlocation_data = location_data.append([{'TeamID': 0,'TeamName':'none','CityID':4130,'City':'Glendale','State':'AZ','lat':33.5797,'lng':-112.2258}], ignore_index = True)\nlocation_data = location_data.append([{'TeamID': 0,'TeamName':'none','CityID':4254,'City':'Oklahoma City','State':'OK','lat':35.4676,'lng':-97.5137}], ignore_index = True)\n\nlocation_data.to_csv('location_data.csv',index = None)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"# 2\n# \"tournament games with calculated distances between colleges' hometowns and game cities\" dataframe (tourney_results)\n\n# since game location data stays consistently known since 2010, we cut our analysis to 2010-2019 period\ntourney_results = tourney_results[tourney_results['Season'] > 2009]\n\n# merge tournament results with game city coordinates\ntourney_results = pd.merge(tourney_results,game_cities[game_cities['CRType'] != 'Regular'].drop(['LTeamID','CRType'], axis = 1), on = ['Season','DayNum','WTeamID'], how = 'left')\ntourney_results = tourney_results.rename(columns = {'CityID':'GameCityID'})\ntourney_results = pd.merge(tourney_results, location_data.drop_duplicates(subset = 'CityID').drop('TeamID', axis = 1), left_on = 'GameCityID', right_on = 'CityID', how = 'left')\ntourney_results.drop(['CityID','State','TeamName','City'], axis = 1, inplace = True)\ntourney_results.rename(columns = {'lat':'GameCityLat','lng':'GameCityLng'}, inplace = True)\n\n# merge tournament results with winning and losing teams' hometown coordinates\ntourney_results = pd.merge(tourney_results, location_data.drop_duplicates(subset = 'TeamID'), left_on = 'WTeamID', right_on = 'TeamID', how = 'left')\ntourney_results.drop(['TeamID','CityID','State','TeamName','City'], axis = 1, inplace = True)\ntourney_results.rename(columns = {'lat':'WCityLat','lng':'WCityLng'}, inplace = True)\n\ntourney_results = pd.merge(tourney_results, location_data.drop_duplicates(subset = 'TeamID'), left_on = 'LTeamID', right_on = 'TeamID', how = 'left')\ntourney_results.drop(['TeamID','CityID','State','TeamName','City'], axis = 1, inplace = True)\ntourney_results.rename(columns = {'lat':'LCityLat','lng':'LCityLng'}, inplace = True)\n\n# save data in \"detailed_tourney_results\" and \"games_and_coordinates\" dataframes\ndetailed_tourney_results = tourney_results.copy()\ngames_and_coordinates = tourney_results[['Season','DayNum','WTeamID','LTeamID','GameCityID','GameCityLat','GameCityLng','WCityLat','WCityLng','LCityLat','LCityLng']]\n\n# saving infromation about all of game cities in \"neutral_loc\" dataframe\nneutral_loc = detailed_tourney_results[['GameCityID','GameCityLat','GameCityLng']]\nneutral_loc = pd.merge(neutral_loc, city_names, left_on = 'GameCityID', right_on = 'CityID')\nneutral_loc.drop('GameCityID', axis = 1, inplace = True)\nneutral_loc.rename(columns = {'GameCityLat':'lat', 'GameCityLng':'lng'}, inplace = True)\n\n# get every coordinate to radians\ntourney_results['GameCityLat'] = np.radians(tourney_results['GameCityLat'])\ntourney_results['GameCityLng'] = np.radians(tourney_results['GameCityLng'])\ntourney_results['WCityLat'] = np.radians(tourney_results['WCityLat'])\ntourney_results['WCityLng'] = np.radians(tourney_results['WCityLng'])\ntourney_results['LCityLat'] = np.radians(tourney_results['LCityLat'])\ntourney_results['LCityLng'] = np.radians(tourney_results['LCityLng'])\n\n# counting distances in miles\n\nwdlon = tourney_results['GameCityLng'] - tourney_results['WCityLng']\nwdlat = tourney_results['GameCityLat'] - tourney_results['WCityLat']\nldlon = tourney_results['GameCityLng'] - tourney_results['LCityLng']\nldlat = tourney_results['GameCityLat'] - tourney_results['LCityLat']\n\nwtemp = 2 * np.arctan2(np.sqrt(np.sin(wdlat / 2)**2 + np.cos(tourney_results['WCityLat']) * np.cos(tourney_results['GameCityLat']) * np.sin(wdlon / 2)**2), np.sqrt(1 - np.sin(wdlat / 2)**2 + np.cos(tourney_results['WCityLat']) * np.cos(tourney_results['GameCityLat']) * np.sin(wdlon / 2)**2))\nltemp = 2 * np.arctan2(np.sqrt(np.sin(ldlat / 2)**2 + np.cos(tourney_results['LCityLat']) * np.cos(tourney_results['GameCityLat']) * np.sin(ldlon / 2)**2), np.sqrt(1 - np.sin(ldlat / 2)**2 + np.cos(tourney_results['LCityLat']) * np.cos(tourney_results['GameCityLat']) * np.sin(ldlon / 2)**2))\n\nR = 3958.8 # Earth radius in miles\ntourney_results['WDistance'] = R * wtemp\ntourney_results['LDistance'] = R * ltemp\n\n# get rid of coordinates in \"tourney results\" dataframe\n\ntourney_results.drop(['GameCityID','GameCityLat','GameCityLng','WCityLat','WCityLng','LCityLat','LCityLng'],axis = 1, inplace = True)\ntourney_results = tourney_results.dropna()\n\n# count Relative Proximity\n\ntourney_results['Relative_proximity'] = tourney_results['LDistance'] - tourney_results['WDistance'] \n\n# save detailed data\ndetailed_tourney_results = tourney_results.copy()\n\n# cut not needed\ntourney_results = tourney_results[['Season','DayNum','WTeamID','LTeamID','WDistance','LDistance','Relative_proximity']]","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"# 3\n\n# reset \"tourney_results\" dataframe\ntourney_results_reset = pd.read_csv('/kaggle/input/march-madness-analytics-2020/MDataFiles_Stage2/MNCAATourneyDetailedResults.csv')\ntourney_results_reset = tourney_results_reset[tourney_results_reset['Season']>2009]\n\n# get information about last loss for every team\nlast_loss = regular_results.drop_duplicates(['Season','LTeamID'], keep = 'last')[['Season','DayNum','LTeamID']]\nlast_loss.rename(columns = {'LTeamID':'TeamID', 'DayNum':'LastLoss'}, inplace = True)\nlast_loss = last_loss[last_loss.Season > 2009]\n\n# get information about last win for every team\nlast_win = regular_results.drop_duplicates(['Season','WTeamID'], keep = 'last')[['Season','DayNum','WTeamID']]\nlast_win.rename(columns = {'WTeamID':'TeamID', 'DayNum':'LastWin'}, inplace = True)\nlast_win = last_win[last_win.Season > 2009]\n\n# get information about last game for every team\nlast_games = pd.merge(last_loss, last_win)\nlast_games['LastGame'] = last_games[['LastLoss','LastWin']].max(axis = 1)\nlast_games = last_games[['Season','TeamID','LastGame']]\n\n# rename columns of \"games_and_coordinates\" dataframe\ngames_and_coordinates = games_and_coordinates.rename(columns = {'WCityLat':'WTeamLat','WCityLng':'WTeamLng','LCityLat':'LTeamLat','LCityLng':'LTeamLng'})\n\n# merge \"games_and_coordinates\" with data of last games\ngames_and_coordinates = pd.merge(games_and_coordinates, last_games, left_on = ['Season','WTeamID'], right_on = ['Season', 'TeamID'])\ngames_and_coordinates.rename(columns = {'LastGame':'WLastGame'}, inplace = True)\ngames_and_coordinates.drop('TeamID', axis = 1, inplace = True)\n\ngames_and_coordinates = pd.merge(games_and_coordinates, last_games, left_on = ['Season','LTeamID'], right_on = ['Season', 'TeamID'])\ngames_and_coordinates.rename(columns = {'LastGame':'LLastGame'}, inplace = True)\ngames_and_coordinates.drop('TeamID', axis = 1, inplace = True)\n\n\n\n# update data of current location\nfor i in range(2010,2020):\n    #list of teams\n    teams_list = set(games_and_coordinates[games_and_coordinates['Season'] == i].LTeamID.unique()).union(set(games_and_coordinates[games_and_coordinates['Season'] == i].WTeamID.unique()))\n    for j in teams_list:\n        #list of games by team\n        temp = games_and_coordinates[((games_and_coordinates.WTeamID == j)|(games_and_coordinates.LTeamID == j)) & (games_and_coordinates.Season == i)].sort_values('DayNum')\n        if temp.shape[0] > 1:\n            k = 0\n            while k < temp.shape[0]-1:\n                # if k game is in \"First Four\" period (DayNum 134 or 135)\n                if temp.iloc[k].DayNum in [134,135]:\n                    next_game_row = temp.iloc[k+1].name\n                    current_day = temp.iloc[k].DayNum\n                    gcLat = temp.iloc[k].GameCityLat\n                    gcLng = temp.iloc[k].GameCityLng\n                    # rewrite location of winning team in next round and last game day date\n                    if (temp.iloc[1].WTeamID == j):\n                        games_and_coordinates.at[next_game_row, 'WLastGame'] = current_day\n                        games_and_coordinates.at[next_game_row, 'WTeamLat'] = gcLat\n                        games_and_coordinates.at[next_game_row, 'WTeamLng'] = gcLng\n                    # rewrite location of losing team in next round and last game day date\n                    else:\n                        games_and_coordinates.at[next_game_row, 'LLastGame'] = current_day\n                        games_and_coordinates.at[next_game_row, 'LTeamLat'] = gcLat\n                        games_and_coordinates.at[next_game_row, 'LTeamLng'] = gcLng\n                    k = k + 1\n                # rewrite last game day date\n                else:\n                    next_game_row = temp.iloc[k+1].name\n                    current_day = temp.iloc[k].DayNum\n                    if (temp.iloc[k+1].WTeamID == j):\n                        games_and_coordinates.at[next_game_row, 'WLastGame'] = current_day\n                    else:\n                        games_and_coordinates.at[next_game_row, 'LLastGame'] = current_day\n                    k = k + 1\n\n# calculate distances\ngames_and_coordinates['GameCityLat'] = np.radians(games_and_coordinates['GameCityLat'])\ngames_and_coordinates['GameCityLng'] = np.radians(games_and_coordinates['GameCityLng'])\ngames_and_coordinates['WTeamLat'] = np.radians(games_and_coordinates['WTeamLat'])\ngames_and_coordinates['WTeamLng'] = np.radians(games_and_coordinates['WTeamLng'])\ngames_and_coordinates['LTeamLat'] = np.radians(games_and_coordinates['LTeamLat'])\ngames_and_coordinates['LTeamLng'] = np.radians(games_and_coordinates['LTeamLng'])\n\nwdlon = games_and_coordinates['GameCityLng'] - games_and_coordinates['WTeamLng']\nwdlat = games_and_coordinates['GameCityLat'] - games_and_coordinates['WTeamLat']\nldlon = games_and_coordinates['GameCityLng'] - games_and_coordinates['LTeamLng']\nldlat = games_and_coordinates['GameCityLat'] - games_and_coordinates['LTeamLat']\n\nwtemp = 2 * np.arctan2(np.sqrt(np.sin(wdlat / 2)**2 + np.cos(games_and_coordinates['WTeamLat']) * np.cos(games_and_coordinates['GameCityLat']) * np.sin(wdlon / 2)**2), np.sqrt(1 - np.sin(wdlat / 2)**2 + np.cos(games_and_coordinates['WTeamLat']) * np.cos(games_and_coordinates['GameCityLat']) * np.sin(wdlon / 2)**2))\nltemp = 2 * np.arctan2(np.sqrt(np.sin(ldlat / 2)**2 + np.cos(games_and_coordinates['LTeamLat']) * np.cos(games_and_coordinates['GameCityLat']) * np.sin(ldlon / 2)**2), np.sqrt(1 - np.sin(ldlat / 2)**2 + np.cos(games_and_coordinates['LTeamLat']) * np.cos(games_and_coordinates['GameCityLat']) * np.sin(ldlon / 2)**2))\n\ngames_and_coordinates['WDistance'] = R * wtemp\ngames_and_coordinates['LDistance'] = R * ltemp\n\n# count recovery time for winning and losing team and their difference\ngames_and_coordinates['WRecoveryTime'] = games_and_coordinates.DayNum - games_and_coordinates.WLastGame\ngames_and_coordinates['LRecoveryTime'] = games_and_coordinates.DayNum - games_and_coordinates.LLastGame\ngames_and_coordinates['Time_advantage'] = games_and_coordinates['WRecoveryTime'] - games_and_coordinates['LRecoveryTime']\n\n# merge \"games_and_coordinates\" with seeds data of every team\ngames_and_coordinates = pd.merge(games_and_coordinates , seeds,left_on = ['Season','WTeamID'], right_on=['Season','TeamID'])\ngames_and_coordinates['WSeed'] = pd.to_numeric(games_and_coordinates ['Seed'].str[1:3])\ngames_and_coordinates.drop(['TeamID', 'Seed'],axis = 1, inplace = True)\n\ngames_and_coordinates  = pd.merge(games_and_coordinates , seeds,left_on = ['Season','LTeamID'], right_on=['Season','TeamID'])\ngames_and_coordinates ['LSeed'] = pd.to_numeric(games_and_coordinates ['Seed'].str[1:3])\ngames_and_coordinates.drop(['TeamID','Seed'],axis = 1, inplace = True)\n\n# count total distance traveled\nteams_travel_distances = pd.merge(games_and_coordinates[games_and_coordinates.DayNum.isin([134,135,136,137,143,144,152])].groupby('LTeamID')['LDistance'].sum().reset_index(),games_and_coordinates[games_and_coordinates.DayNum.isin([134,135,136,137,143,144,152])].groupby('WTeamID')['WDistance'].sum().reset_index(), left_on='LTeamID', right_on='WTeamID', how = 'left').fillna(0)\nteams_travel_distances['Distance'] = teams_travel_distances['LDistance'] + teams_travel_distances['WDistance']\nteams_travel_distances.drop(['LDistance','WTeamID','WDistance'], axis = 1, inplace = True)\nteams_travel_distances.rename(columns = {'LTeamID':'TeamID','LTeamLng':'lng', 'LTeamLat':'lat'}, inplace = True)\n\n# count number of wins in after travel games\nteam_tourney_wins = games_and_coordinates[games_and_coordinates.DayNum.isin([134,135,136,137,143,144,152])].groupby('WTeamID').count().Season.rename(columns = {'Season':'Wins'})\nteam_tourney_wins.name = 'Wins'\nteam_tourney_wins = team_tourney_wins.reset_index().rename(columns = {'WTeamID':'TeamID'})\n\n# count number of loses in after travel games\nteam_tourney_loses = games_and_coordinates[games_and_coordinates.DayNum.isin([134,135,136,137,143,144,152])].groupby('LTeamID').count().Season.rename(columns = {'Season':'Loses'})\nteam_tourney_loses.name = 'Loses'\nteam_tourney_loses = team_tourney_loses.reset_index().rename(columns = {'LTeamID':'TeamID'})\n\n# create dataframe of loses and wins for every team\nteam_tourney_games = pd.merge(team_tourney_loses, team_tourney_wins, how = 'left')\nteam_tourney_games = team_tourney_games.fillna(0)\n\n# save full dataframe for model\nX = games_and_coordinates.copy()\n\n# leave only games after travel\ngames_and_coordinates = games_and_coordinates[games_and_coordinates.DayNum.isin([134,135,136,137,143,144,152])]","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"<div id=\"Infographics\">\n</div>\n# 3. Infographics"},{"metadata":{"_kg_hide-input":true,"trusted":true},"cell_type":"code","source":"# count amount of wins for each team\nteam_tourney_wins = tourney_compact_results.groupby('WTeamID').count().Season.rename(columns = {'Season':'Wins'})\nteam_tourney_wins.name = 'Wins'\nteam_tourney_wins = team_tourney_wins.reset_index().rename(columns = {'WTeamID':'TeamID'})\n\n# count amount of loses for each team\nteam_tourney_loses = tourney_compact_results.groupby('LTeamID').count().Season.rename(columns = {'Season':'Loses'})\nteam_tourney_loses.name = 'Loses'\nteam_tourney_loses = team_tourney_loses.reset_index().rename(columns = {'LTeamID':'TeamID'})\n\n# merge wins and loses data\nteam_tourney_games = pd.merge(team_tourney_loses, team_tourney_wins, how = 'left')\nteam_tourney_games = team_tourney_games.fillna(0)\n\n# merge wins and loses data with each teams location\nmap1_data = pd.merge(team_tourney_games, location_data).drop_duplicates(subset='TeamID')\n\nfig = px.scatter_geo(map1_data, title = 'Map of teams by wins since 1985<br>(zoom in and out)',\n                     lon = map1_data.lng,\n                     lat = map1_data.lat,\n                     color = 'Wins',\n                     size = 'Wins',\n                     hover_name='TeamName',\n                     projection=\"albers usa\")\n\nfig.update_traces(hovertemplate=\"<b>%{text}</b><br><br>Total number of wins: %{customdata}\",text = map1_data.TeamName, customdata = map1_data.Wins , hoverinfo='skip')\n\nfig.show()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"Majority of Division 1 teams are on the East side. Also usually championship winners come from there."},{"metadata":{"_kg_hide-input":true,"trusted":true},"cell_type":"code","source":"# count number of championships\nchamp_count = tourney_compact_results[tourney_compact_results.DayNum == 154].groupby('WTeamID').count().Season.rename(columns = {'Season':'Wins'})\nchamp_count.name = 'Championships'\nchamp_count.dropna()\nchamp_count = champ_count.reset_index().rename(columns = {'WTeamID':'TeamID'})\n\n# merge data about championship teams and location \nmap2_data = pd.merge(champ_count, location_data).drop_duplicates(subset='TeamID')\n\nfig = px.scatter_geo(map2_data, title = 'Map of teams by Championships since 1985<br>(zoom in and out)',\n                     lon = map2_data.lng,\n                     lat = map2_data.lat,\n                     size = 'Championships',\n                     color = 'Championships',\n                     hover_name='TeamName',\n                     projection=\"albers usa\",\n                     color_continuous_scale = 'Rainbow')\n\nfig.update_traces(hovertemplate=\"<b>%{text}</b><br><br>Championships won: %{customdata}\",text = map2_data.TeamName, customdata = map2_data.Championships , hoverinfo='skip')\n\nfig.show()","execution_count":null,"outputs":[]},{"metadata":{"_kg_hide-input":true,"trusted":true},"cell_type":"code","source":"# count games in every city\ncount_games_in_every_city = neutral_loc.groupby('City').State.count().reset_index()\ncount_games_in_every_city.rename(columns = {'State':'Games played'}, inplace = True)\n\n# merge data about game cities and their location\nmap3_data = pd.merge(neutral_loc.drop_duplicates(),count_games_in_every_city, on = 'City')\n\nfig = px.scatter_geo(map3_data, title = 'Map of cities where March Madness games were played since 2010 <br>(zoom in and out)',\n                     lon = map3_data.lng,\n                     lat = map3_data.lat,\n                     size = 'Games played',\n                     color = 'Games played',\n                     hover_name='City',\n                     projection=\"albers usa\")\n\nfig.update_traces(hovertemplate=\"<b>%{text}</b><br><br>Games played since 2010: %{customdata}\",text = map3_data.City, customdata = map3_data['Games played'] , hoverinfo='skip')\n\nfig.show()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"All \"First Four\" games are played in Dayton."},{"metadata":{"trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"# create dataframe, where each game is watched from both teams sides\nall_tourney_results.drop(['WLoc'], axis = 1, inplace = True)\nall_tourney_results_swap = all_tourney_results[[\n    'Season', 'DayNum', 'LTeamID', 'LScore', 'WTeamID', 'WScore', 'NumOT', \n    'LFGM', 'LFGA', 'LFGM3', 'LFGA3', 'LFTM', 'LFTA', 'LOR', 'LDR', 'LAst', 'LTO', 'LStl', 'LBlk', 'LPF', \n    'WFGM', 'WFGA', 'WFGM3', 'WFGA3', 'WFTM', 'WFTA', 'WOR', 'WDR', 'WAst', 'WTO', 'WStl', 'WBlk', 'WPF']]\nall_tourney_results.columns = [x.replace('W','Team ').replace('L','Opponent ') for x in list(all_tourney_results.columns)]\nall_tourney_results_swap.columns = [x.replace('L','Team ').replace('W','Opponent ') for x in list(all_tourney_results.columns)]\n\nall_tourney_results = pd.concat((all_tourney_results,all_tourney_results_swap))\n\n# count number of games of each team\ntourney_games_count = all_tourney_results.groupby(['Team TeamID']).count().reset_index()[['Team TeamID', 'Season']]\n\n# get total stats\ntotal_data = all_tourney_results.groupby(['Team TeamID']).sum().reset_index()\ntotal_data.drop(['Season', 'DayNum'], axis = 1, inplace = True)\ntotal_data = pd.merge(total_data, tourney_games_count)\ntotal_data.rename(columns = {'Season':'Games'}, inplace = True)\ntotal_data = pd.merge(total_data, location_data, left_on = 'Team TeamID', right_on = 'TeamID')\ntotal_data.drop_duplicates(subset = 'Team TeamID', inplace = True)\n\n#count total games\nseason_tourney_games_count = all_tourney_results.groupby(['Season','Team TeamID']).count().reset_index()[['Season','Team TeamID','DayNum']]\nseason_tourney_games_count.rename(columns = {'DayNum':'Games'}, inplace = True)\n\n\nmap5_data = total_data[['Games', 'TeamID', 'TeamName', 'CityID', 'City', 'State', 'lat', 'lng']]\n\nmap5_data = pd.concat((map5_data,total_data[['Team TeamID', 'Team Score', 'Opponent TeamID', 'Opponent Score', 'NumOT', 'Team FGM',\n       'Team FGA', 'Team FGM3', 'Team FGA3', 'Team FTM', 'Team FTA', 'Team OR', 'Team DR',\n       'Team Ast', 'Team TO', 'Team Stl', 'Team Blk', 'Team PF', 'Opponent FGM', 'Opponent FGA',\n       'Opponent FGM3', 'Opponent FGA3', 'Opponent FTM', 'Opponent FTA', 'Opponent OR', 'Opponent DR', 'Opponent Ast',\n       'Opponent TO', 'Opponent Stl', 'Opponent Blk', 'Opponent PF']].div(total_data.Games, axis = 0)), axis = 1)\n\nmap5_data = map5_data[map5_data.Games > 5]\nmap5_data.iloc[:,9:] = map5_data.iloc[:,9:].round(1)\n\nstat_names = ['Team Score', 'Team FGM','Team FGA','Team FGM3','Team FGA3','Team FTM','Team FTA','Team OR','Team DR', 'Team Ast','Team TO','Team Stl','Opponent Score', 'Opponent FGM','Opponent FGA','Opponent FGM3','Opponent FGA3','Opponent FTM','Opponent FTA','Opponent OR','Opponent DR', 'Opponent Ast','Opponent TO','Opponent Stl']\nfig = go.Figure()\nbut_list = list()\n\nfor x in stat_names:\n    map5_data = map5_data.assign(text = map5_data['TeamName'] + ', Average ' + x + ': ' + map5_data[x].astype(str))\n\n    fig.add_trace(go.Scattergeo(\n        lon = map5_data.lng,\n        lat = map5_data.lat,\n        text = map5_data.text,\n        hovertemplate = '%{text}',\n        mode = 'markers',\n        marker=dict(\n        size= np.interp(map5_data[x], (map5_data[x].min(),map5_data[x].max()), (3,25)),\n        cmax=0,\n        cmin=0,\n        color=map5_data[x],\n        colorbar=dict(\n            title= x + \" average\"\n        ),\n        colorscale=\"Bluered\"\n        ),\n        marker_color = map5_data[x],\n        name = x + ' average',\n        visible = (x == 'Team Score')\n        ))\n    \n    ###\n    \n    list_of_visible = [False]*len(stat_names)\n    list_of_visible[stat_names.index(x)] = True\n    \n    d = dict(label=x, method = 'update', args=[{\"visible\": list_of_visible},{\"title\": x + \" average<br>(scroll to zoom; choose stats in dropdown menu)\"}])\n    but_list.append(d)\n\nfig.update_layout(\n    updatemenus=[\n        dict(\n            active=0,\n            buttons=but_list,\n        )\n    ])\n\nfig.update_layout(\n        title = 'Team Scoring average in March Madness<br>(scroll to zoom; choose stats in dropdown menu)',\n        title_text=\"Team Scoring average in March Madness<br>(scroll to zoom; choose stats in dropdown menu)\",\n        geo_scope='usa',\n    )\n\nfig.show()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"# copy regular season statistics\nall_regular_results = regular_results.copy()\n\n# create dataframe, where each game is watched from both teams sides\nall_regular_results.drop(['WLoc'], axis = 1, inplace = True)\nall_regular_results_swap = all_regular_results[['Season', 'DayNum', 'LTeamID', 'LScore', 'WTeamID', 'WScore', 'NumOT', 'LFGM', 'LFGA', 'LFGM3', 'LFGA3', 'LFTM', 'LFTA', 'LOR', 'LDR', 'LAst', 'LTO', 'LStl', 'LBlk', 'LPF', 'WFGM', 'WFGA', 'WFGM3', 'WFGA3', 'WFTM', 'WFTA', 'WOR', 'WDR', 'WAst', 'WTO', 'WStl', 'WBlk', 'WPF']]\nall_regular_results.columns = [x.replace('W','Team ').replace('L','Opponent ') for x in list(all_regular_results.columns)]\nall_regular_results_swap.columns = [x.replace('L','Team ').replace('W','Opponent ') for x in list(all_regular_results.columns)]\n\nall_regular_results = pd.concat((all_regular_results,all_regular_results_swap))\n\n# count regular season games by year\nseason_regular_games_count = all_regular_results.groupby(['Season','Team TeamID']).count().reset_index()[['Season','Team TeamID','DayNum']]\nseason_regular_games_count.rename(columns = {'DayNum':'Games'}, inplace = True)\n\n# get team statistics by season\nseason_regular_total_data = all_regular_results.groupby(['Season','Team TeamID']).sum().reset_index()\nseason_regular_total_data.drop(['DayNum'], axis = 1, inplace = True)\nseason_regular_total_data = pd.merge(season_regular_total_data, season_regular_games_count, left_on = ['Season','Team TeamID'], right_on=['Season','Team TeamID'])\nseason_regular_total_data = pd.merge(season_regular_total_data, location_data, left_on = 'Team TeamID', right_on = 'TeamID')\nseason_regular_total_data.drop_duplicates(subset = ['Season','Team TeamID'], inplace = True)\n\nmap4_data = season_regular_total_data[['Season','Games', 'TeamID', 'TeamName', 'CityID', 'City', 'State', 'lat', 'lng']]\n\n# get average team statistics by season\nmap4_data = pd.concat((map4_data,season_regular_total_data[['Team TeamID', 'Team Score', 'Opponent TeamID', 'Opponent Score', 'NumOT', 'Team FGM',\n       'Team FGA', 'Team FGM3', 'Team FGA3', 'Team FTM', 'Team FTA', 'Team OR', 'Team DR',\n       'Team Ast', 'Team TO', 'Team Stl', 'Team Blk', 'Team PF', 'Opponent FGM', 'Opponent FGA',\n       'Opponent FGM3', 'Opponent FGA3', 'Opponent FTM', 'Opponent FTA', 'Opponent OR', 'Opponent DR', 'Opponent Ast',\n       'Opponent TO', 'Opponent Stl', 'Opponent Blk', 'Opponent PF']].div(map4_data.Games, axis = 0)), axis = 1)\n\nmap4_data = map4_data.round(1)\n\nmap4_data = pd.merge(map4_data, season_tourney_games_count.rename(columns={'Games':'Tourney_played'}), left_on = ['Season','TeamID'], right_on = ['Season','Team TeamID'])\nmap4_data = map4_data.sort_values(['Season','TeamID'])\n\nmap4_data.drop(['Team TeamID_x', 'Team TeamID_y'], axis = 1, inplace = True)\n\nfig = px.scatter_geo(map4_data, title = 'Map of teams by points scored in regular season<br>(scroll to zoom; choose season by slider)',\n                     lon = map4_data.lng,\n                     lat = map4_data.lat,\n                     size = np.interp(map4_data['Team Score'], (map4_data['Team Score'].min(),map4_data['Team Score'].max()), (1,14)),\n                     color = 'Team Score',\n                     hover_name='TeamName',\n                     text = 'Games',\n                     projection=\"albers usa\",\n                     animation_frame = 'Season',\n                     animation_group = 'TeamName',\n                     color_continuous_scale = \"Bluered\")\n\nfig.show()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"<div id=\"RelativeProximity\">\n</div>\n# 4. Relative Proximity\n<div id=\"Theory\">\n</div>\n## 4.1. Theory\n\nAssume we have two teams playing each other in the NCAA Tournament. Let's say $A$ is hometown of team A, $B$ is hometown of team B and $C$ is the city where the game takes place. Distance from $A$ to $C$ indicates as $AC$, from $B$ to $A$ as $BA$ etc.  \n\n**Definition №1.** <br> Relative Proximity of team A, when it plays team B in city C is the difference between $BC$ and $AC$. <br>\n\n[![Relative-Proximity.png](https://i.postimg.cc/fyD4kjqw/Relative-Proximity.png)](https://postimg.cc/FdWCBcQq)\n\n**In other words, Relative Proximity is how closer team's hometown is to the game city than its opponent's. If value is negative, then this team's hometown is farther to the game city. It doesn't matter where this team is now.** <br> Since every March Madness match is played on a neutral court, absolute distance numbers won't give us much information about the specific match. But if we compare both teams - we can do some analysis. \n\n<div id=\"Definition2\">\n</div>\n**Definition №2.** <br> If absolute Relative Proximity value of team in the specific game is *greater than critical value*, this game is **significant**. Else, it's called **neutral**. \n\nLater we'll find this critical value, which is constant.\n\n**Note.** Onwards, I will say team A is **relatively closer** than team B if its Relative Proximity value is greater than team B's one, i.e. team A hometown is closer to the game location. Else, team A is **relatively farther**."},{"metadata":{},"cell_type":"markdown","source":"<div id=\"CriticalValueAndSignificantGames\">\n</div>\n## 4.2. Critical value and significant games"},{"metadata":{},"cell_type":"markdown","source":"Let's plot wins distribution. X-axis stands for Relative Proximity, Y-axis stands for number of wins."},{"metadata":{"_kg_hide-input":true,"trusted":true},"cell_type":"code","source":"#plot\nplt.figure(figsize = (17,5));\nplt.title('Wins distribution', fontsize = 14);\nsns.distplot(tourney_results['Relative_proximity'], axlabel = \"Winning team Relative Proximity\", bins = 24, norm_hist = False, kde = None);\n\n# PRINTS\nprint1 = 'We have Relative Proximity average of ' + str(int(tourney_results['Relative_proximity'].mean())) + ' miles across all of the wins.'","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"* We have Relative Proximity average of 133 miles across all of the wins.\n\n> It's a good sign, but statistically there is not much information: positive average doesn't mean that closer teams win more or anything. But it is obvious, that, let's say, 50 miles Relative Proximity gives none of these teams an edge. It would be more reasonable to only analyse games in which teams have *absolute* Relative Proximity higher than a certain number - **critical value**.\n\nI wrote a script, which calculated this number.\n\nShortly, this script gives the **greatest** value, **while** there are **more significant games** than **neutral games**, and winning percentage in **relatively closer** games is the highest.\n> Second condition allows to get biggest difference in wins by category, first condition prevents situation, when most games are neutral and there is a very small sample of *significant* games."},{"metadata":{"trusted":true,"_kg_hide-input":false},"cell_type":"code","source":"maximum = 0 # variable for maximum value\np = 0 # variable for critical value\nfor i in range(0,1700):\n    # count wins in closer/neutral/farther groups for each i\n    i_count_farther = tourney_results[tourney_results['Relative_proximity'] < -i].shape[0]\n    i_count_neutral = tourney_results[(tourney_results['Relative_proximity'] >= -i) & (tourney_results['Relative_proximity'] < i)].shape[0]\n    i_count_closer = tourney_results[tourney_results['Relative_proximity'] >= i].shape[0]\n    # check first condition\n    if (i_count_neutral > i_count_closer) or (i_count_neutral > i_count_farther):\n        break\n    # check second condition\n    if (i_count_closer/(i_count_closer + i_count_farther) > maximum):\n        p = i\n        maximum = i_count_closer/(i_count_closer + i_count_farther)\n        \nprint(\"The critical value is: \" + str(p))","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"* This critical value appears to be **160 miles**. Getting rid of neutral games we can make a chart of wins total, when teams had significant differences in the distance to game location.\n\n**Note**: if this algorithm counted max winning percentage of relatively farther games, it would give a zero."},{"metadata":{"trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"# create 'no neutral(nn)' dataframe of tournament games named tourney_results_nn\ntourney_results_nn = tourney_results[(tourney_results['Relative_proximity'] < -p) | (tourney_results['Relative_proximity'] > p)];\n# 'swing' distance data, so it starts from zero\ntourney_results_nn = tourney_results_nn.assign(Relative_proximity = tourney_results_nn['Relative_proximity']  - np.sign(tourney_results_nn['Relative_proximity'])*p)\n\n# count wins in each category\ncount_farther = tourney_results[tourney_results['Relative_proximity'] < -p].shape[0];\ncount_closer = tourney_results[tourney_results['Relative_proximity'] >= p].shape[0];\n\n#plot\nplt.figure(figsize=(12,9));\nplt.xticks(np.arange(2), ('teams that are based ' + str(p) + ' miles closer than opponent', 'teams that are based ' + str(p) + ' miles farther than opponent'));\nfig = plt.bar(np.arange(2), (count_closer,count_farther), color=sns.color_palette(\"Blues\",2));\nplt.title('Total games won in significant matches since 2010', fontsize = 14);\nplt.show();\n\n#PRINT\nprint1 = 'Teams that are based ' + str(p) + ' miles closer than opponent won ' + str(count_closer) + ' games and have ' + str(int(round(count_closer/(count_closer+count_farther),2)*100)) + '% winning percentage.'\nprint2 = str(count_farther) + ' games were won by the team ' + str(p) + ' miles farther.'","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"* Teams that are based 160 miles closer than opponent won 310 games and have 62% winning percentage.\n* 192 games were won by the team 160 miles farther.\n\nNow let's look at relative frequency distribution of wins in significant games. All distance values were corrected by 160 miles, so on X-axis:\n * +1 means 161 mile Relative Proximity, +2 means 162 mile Relative Proximity,\n * -1 means -161 mile Relative Proximity, etc.<br>"},{"metadata":{"trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"plt.figure(figsize = (17,5));\nplt.title('Wins distribution of significant games', fontsize = 14);\nsns.distplot(tourney_results_nn['Relative_proximity'], axlabel = \" Significant Relative Proximity\", bins = 24);","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"* Curves peak is on the right side, where positive Relative Proximity is. \n* Right slope is less steep than left one: more wins on right side.\n\nNow we need to learn what causes this disbalance."},{"metadata":{},"cell_type":"markdown","source":"<div id=\"Analysis\">\n</div>\n## 4.3. Analysis\nSo far we've learned, that closer based teams tend to win around 11% more games.\n\n**From here on we'll look only at [significant](#Definition2) games.** Among all this kind of matches, here is top 20 winning teams (by winning percentage) and their average Relative Proximity. Negative value means that team's hometown was farther on average to the game cities than opponents' ones; negative value means that team's hometown was farther. Also team qualifies only if it played more than 5 significant games."},{"metadata":{"_kg_hide-input":true,"trusted":true},"cell_type":"code","source":"# get original distances back (+ or - 160 miles to every value)\ntourney_results_nn['Relative_proximity'] = tourney_results_nn['Relative_proximity'] + np.sign(tourney_results_nn['Relative_proximity'])*p;\n\n# count wins and loses for every team in every significant games\nteam_tourney_wins = tourney_results_nn.groupby('WTeamID').count().Season.rename(columns = {'Season':'Wins'});\nteam_tourney_wins.name = 'Wins';\nteam_tourney_wins = team_tourney_wins.reset_index().rename(columns = {'WTeamID':'TeamID'});\n\nteam_tourney_loses = tourney_results_nn.groupby('LTeamID').count().Season.rename(columns = {'Season':'Loses'});\nteam_tourney_loses.name = 'Loses'\nteam_tourney_loses = team_tourney_loses.reset_index().rename(columns = {'LTeamID':'TeamID'});\n\nteam_tourney_games = pd.merge(team_tourney_loses, team_tourney_wins, how = 'left');\nteam_tourney_games = team_tourney_games.fillna(0);\n\n# count total and average distance traveled for every team in every significant game\nteam_distance_rank = pd.merge(tourney_results_nn.groupby('WTeamID').sum().reset_index()[['WTeamID','Relative_proximity','WDistance']],tourney_results_nn.groupby('LTeamID').sum().reset_index()[['LTeamID','Relative_proximity','LDistance']], left_on = 'WTeamID', right_on = 'LTeamID');\nteam_distance_rank['Relative_proximity'] = team_distance_rank['Relative_proximity_x'] - team_distance_rank['Relative_proximity_y'];\nteam_distance_rank.drop(['Relative_proximity_x', 'Relative_proximity_y','LTeamID'], axis = 1, inplace = True);\n#team_distance_rank['Distance_total'] = team_distance_rank['WDistance'] + team_distance_rank['LDistance'];\nteam_distance_rank.drop(['LDistance', 'WDistance'], axis = 1, inplace = True);\nteam_distance_rank.rename(columns = {'WTeamID':'TeamID'}, inplace = True);\nteam_distance_rank = pd.merge(team_distance_rank, team_names);\nteam_distance_rank.drop(['FirstD1Season', 'LastD1Season'], axis = 1, inplace = True);\n\nteam_dist_and_games = pd.merge(team_distance_rank, team_tourney_games);\nall_team_dist_and_games = team_dist_and_games;\n#team_dist_and_games['Distance_average'] = team_dist_and_games['Distance_total']/(team_dist_and_games['Loses'] + team_dist_and_games['Wins']);\n\n#more than 3 significant games played\nteam_dist_and_games = team_dist_and_games[team_dist_and_games['Loses']+team_dist_and_games['Wins'] > 5];\n\n# count average Relative Proximity and winning percentage for every team\nteam_dist_and_games = team_dist_and_games.assign(Relative_proximity_average = team_dist_and_games['Relative_proximity']/(team_dist_and_games['Loses'] + team_dist_and_games['Wins']))\nteam_dist_and_games['Winning_percentage'] = team_dist_and_games['Wins']/(team_dist_and_games['Loses'] +team_dist_and_games['Wins'])\n\n# Get\n# Top 20 Winning teams in significant matches\ntop_20_winning_teams = team_dist_and_games.sort_values('Winning_percentage', ascending = False).iloc[0:20]\n# Bottom 20 Winning teams in significant matches\nbottom_20_winning_teams = team_dist_and_games.sort_values('Winning_percentage', ascending = True).iloc[0:20]\n# Top 20 team by Relative Proximity in significant matches\ntop_20_Geographical_av = team_dist_and_games.sort_values('Relative_proximity_average', ascending = False).iloc[0:20];\n# Bottom 20 team by Relative Proximity in significant matches\nbottom_20_Geographical_av = team_dist_and_games.sort_values('Relative_proximity_average').iloc[0:20];\n\nplt.figure(figsize=(17,7));\nplot = sns.barplot(x='TeamName', y = 'Relative_proximity_average', data = top_20_winning_teams, palette = 'Blues_d');\nplot.set_xticklabels(top_20_winning_teams['TeamName'], rotation = 90);\nplot.set(xlabel = \"Better team are on the left\", ylabel = \"Relative Proximity average\", title = \"Top 20 winning teams' Relative Proximity (average)\");\n         \n# PRINTS\nprint1 = 'In significant games, winning teams tend to be ' + str(int(top_20_winning_teams['Relative_proximity_average'].mean())) + ' miles closer to the game point'\nprint2 = top_20_winning_teams[top_20_winning_teams.TeamName == 'Connecticut'].Relative_proximity_average.iloc[0]","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"* In significant games, top 20 winning teams' hometowns tend to be 124 miles closer to the game point.\n* Majority of winning teams have positive average Relative Proximity in significant games.\n* Connecticut is a strong exception: on average, it was 606 miles farther.\n\nBottom teams in winning percentage:"},{"metadata":{"trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"plt.figure(figsize=(17,7));\nplot = sns.barplot(bottom_20_winning_teams['TeamName'][::-1], bottom_20_winning_teams['Relative_proximity_average'], palette = 'Blues_d');\nplot.set_xticklabels(bottom_20_winning_teams['TeamName'][::-1], rotation = 90);\nplot.set(xlabel = \"Better teams are on the left\", ylabel = \"Relative Proximity average\", title = \"Bottom 20 winning teams' Relative Proximity (average)\");\n\n# PRINTS\nprint1 = 'In significant games, losing teams tend to be ' + str(int(bottom_20_winning_teams['Relative_proximity_average'].mean())) + ' miles farther to the game point'","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"* In significant games, losing teams' hometowns tend to be 213 miles farther to the game point.\n* There are low peaking teams like St Mary's CA and Washington.\n* Four exceptions.\n\nNow let's sort teams by Relative Proximity:"},{"metadata":{"trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"fig, (ax1,ax2) = plt.subplots(1,2,sharey='row', figsize=(17,7))\n\nplot1 = sns.barplot(-top_20_Geographical_av['Relative_proximity_average'],top_20_Geographical_av['Wins'], palette = 'Blues_d', ax = ax1)\nplot1.set_xticklabels(top_20_Geographical_av['TeamName'], rotation = 90)\nplot1.set(xlabel = \"\", ylabel = \"Wins\", title = 'Top 20 teams by Relative Proximity');\n\nplot2 = sns.barplot(-bottom_20_Geographical_av['Relative_proximity_average'], bottom_20_Geographical_av['Wins'], palette = 'Blues_d', ax = ax2)\nplot2.set_xticklabels(bottom_20_Geographical_av['TeamName'][::-1], rotation = 90)\nplot2.set(xlabel = \"\", ylabel = \"Wins\", title = 'Bottom 20 teams by Relative Proximity');\n\n### PRINTS\nprint1 = 'Average team from top 20 list of teams by Relative Proximity has ' + str(top_20_Geographical_av['Wins'].mean()) + ' wins in 2010-2019 championships'\nprint2 = 'Average team from bottom 20 list of teams by Relative Proximity has ' + str(bottom_20_Geographical_av['Wins'].mean()) + ' wins in 2010-2019 championships'","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"* Average team from top 20 list of teams by Relative Proximity has 10.5 wins in 2010-2019 championships\n* Average team from bottom 20 list of teams by Relative Proximity has 5.0 wins in 2010-2019 championships"},{"metadata":{"trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"fig, (ax1,ax2) = plt.subplots(1,2,sharey='row', figsize=(17,7))\n\nplot1 = sns.barplot(-top_20_Geographical_av['Relative_proximity_average'],top_20_Geographical_av['Winning_percentage'], palette = 'Blues_d', ax = ax1)\nplot1.set_xticklabels(top_20_Geographical_av['TeamName'], rotation = 90)\nplot1.set(xlabel = \"\", ylabel = \"Winning percentage\", title = 'Top 20 teams by Relative Proximity');\n\nplot2 = sns.barplot(-bottom_20_Geographical_av['Relative_proximity_average'], bottom_20_Geographical_av['Winning_percentage'], palette = 'Blues_d', ax = ax2)\nplot2.set_xticklabels(bottom_20_Geographical_av['TeamName'][::-1], rotation = 90)\nplot2.set(xlabel = \"\", ylabel = \"Winning percentage\", title = 'Bottom 20 teams by Relative Proximity');\n\n# PRINTS\nprint1 = 'Average team from top 20 list of teams by Relative Proximity has ' + str(top_20_Geographical_av['Winning_percentage'].mean()- bottom_20_Geographical_av['Winning_percentage'].mean()) + ' wins in 2010-2019 championships'\nprint2 = 'Average team from bottom 20 list of teams by Relative Proximity has ' + str() + ' wins in 2010-2019 championships'","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"* Teams with higher Relative_proximity have almost 9% difference in winning percentage.\n\nNow here is the list of teams which make Top 20 in winning and Bottom 20 in Relative Proximity, and vice versa:"},{"metadata":{"trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"# set of winning teams\nwt = set(top_20_winning_teams.TeamID)\n# set of losing teams\nlt = set(bottom_20_winning_teams.TeamID)\n# set of top teams by Relative Proximity\nadvt = set(top_20_Geographical_av.TeamID)\n# set of bottom teams by Relative Proximity\ndisadvt = set(bottom_20_Geographical_av.TeamID)\npd.concat((top_20_winning_teams[top_20_winning_teams.TeamID.isin(wt.intersection(disadvt))], bottom_20_winning_teams[bottom_20_winning_teams.TeamID.isin(lt.intersection(advt))]), axis = 0).reset_index(drop = True)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"# set color and text for game cities\nneutral_loc.drop_duplicates(inplace = True)\nneutral_loc = neutral_loc.dropna()\nneutral_loc['Teams by Relative Proximity average'] = 'Game City'\nneutral_loc['text'] = neutral_loc['City'] + ', ' + neutral_loc['State']\n\n# set color and text for top teams by Relative Proximity\nadvantagers_loc = pd.merge(top_20_Geographical_av,location_data)\nadvantagers_loc = advantagers_loc.drop_duplicates(subset = 'TeamName')\nadvantagers_loc['Teams by Relative Proximity average'] = 'Top teams'\nadvantagers_loc['text'] = advantagers_loc['TeamName'] + ', City: ' + advantagers_loc['City'] + ', ' + advantagers_loc['State']\n\n# set color and text for bottom teams by Relative Proximity\ndisadvantagers_loc = pd.merge(bottom_20_Geographical_av,location_data)\ndisadvantagers_loc = disadvantagers_loc.drop_duplicates(subset = 'TeamName')\ndisadvantagers_loc['Teams by Relative Proximity average'] = 'Bottom teams'\ndisadvantagers_loc['text'] = disadvantagers_loc['TeamName'] + ', City: ' + disadvantagers_loc['City'] + ', ' + disadvantagers_loc['State']\n\nfig0 = go.Figure()\n\nfig0.add_trace(go.Scattergeo(\n        lon = disadvantagers_loc.lng,\n        lat = disadvantagers_loc.lat,\n        hovertemplate = \"<b>%{text}</b><br><br>Relative Proximity average: %{customdata:.2f}\",\n        text = disadvantagers_loc.text,\n        customdata = disadvantagers_loc.Relative_proximity_average,\n        mode = 'markers',\n        hoverinfo='skip',\n        marker=dict(\n        #size= np.interp(disadvantagers_loc['Relative_proximity_average'], (disadvantagers_loc['Relative_proximity_average'].max(),disadvantagers_loc['Relative_proximity_average'].min()), (5,8)),\n        color = \"#ff0000\",\n        ),\n        name = 'Bottom teams',\n        visible = True\n        ))\n\nfig0.add_trace(go.Scattergeo(\n        lon = advantagers_loc.lng,\n        lat = advantagers_loc.lat,\n        hovertemplate = \"<b>%{text}</b><br><br>Relative Proximity average: %{customdata:.2f}\",\n        text = advantagers_loc.text,\n        customdata = advantagers_loc.Relative_proximity_average,\n        mode = 'markers',\n        hoverinfo='skip',\n        marker=dict(\n        #size= np.interp(advantagers_loc['Relative_proximity_average'], (advantagers_loc['Relative_proximity_average'].max(),advantagers_loc['Relative_proximity_average'].min()), (5,8)),\n        color = \"#0000ff\",\n        ),\n        name = 'Top teams',\n        visible = True\n        ))\n    \n\nfig0.update_layout(\n        title = 'Teams by Relative Proximity<br>(scroll to zoom)',\n        geo_scope='usa',\n    )\n\nfig0.show()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"* From this map we can learn, that top and bottom teams by average Relative Proximity don't have recognizable border, when we could somehow predict teams' relation to either of this groups. Hence, Relative Proximity doesn't strongly correlate with hometown's location."},{"metadata":{"trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"# set color and text for top teams by Relative Proximity\nwinners_loc = pd.merge(top_20_winning_teams,location_data)\nwinners_loc = winners_loc.drop_duplicates(subset = 'TeamName')\nwinners_loc['text'] = winners_loc['TeamName'] + ', City: ' + winners_loc['City'] + ', ' + winners_loc['State']\n\n# set color and text for bottom teams by Relative Proximity\nlosers_loc = pd.merge(bottom_20_winning_teams,location_data)\nlosers_loc = losers_loc.drop_duplicates(subset = 'TeamName')\nlosers_loc['text'] = losers_loc['TeamName'] + ', City: ' + losers_loc['City'] + ', ' + losers_loc['State']\nfig1 = go.Figure()\n\nfig1.add_trace(go.Scattergeo(\n        lon = losers_loc.lng,\n        lat = losers_loc.lat,\n        hovertemplate = \"<b>%{text}</b><br><br>Winning percentage: %{customdata:%.2f}\",\n        text = losers_loc.text,\n        customdata = losers_loc.Winning_percentage,\n        mode = 'markers',\n        hoverinfo='skip',\n        marker=dict(\n        #size= np.interp(disadvantagers_loc['Relative_proximity_average'], (disadvantagers_loc['Relative_proximity_average'].max(),disadvantagers_loc['Relative_proximity_average'].min()), (5,8)),\n        color = \"#ff0000\",\n        ),\n        name = 'Bottom teams',\n        visible = True\n        ))\n\nfig1.add_trace(go.Scattergeo(\n        lon = winners_loc.lng,\n        lat = winners_loc.lat,\n        hovertemplate = \"<b>%{text}</b><br><br>Winning_percentage in significant games: %{customdata:%.2f}\",\n        text = winners_loc.text,\n        customdata = winners_loc.Winning_percentage,\n        mode = 'markers',\n        hoverinfo='skip',\n        marker=dict(\n        #size= np.interp(advantagers_loc['Relative_proximity_average'], (advantagers_loc['Relative_proximity_average'].max(),advantagers_loc['Relative_proximity_average'].min()), (5,8)),\n        color = \"#0000ff\",\n        ),\n        name = 'Top teams',\n        visible = True\n        ))\n    \n\nfig1.update_layout(\n        title = 'Teams by winning percentage<br>(scroll to zoom)',\n        geo_scope='usa'\n    )\n\nfig1.show()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"My guess is schedulers favour better teams with closer locations, the same way they favour better teams with easier opponents through seeding system.\n\nLet's exclude \"First Four\" stage and look at Relative Proximity averages(!!!) in every significant game by seeds:"},{"metadata":{"trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"\ndists_and_seeds = tourney_results_nn[tourney_results_nn.DayNum.isin(range(136,155))]\n\ndists_and_seeds = pd.merge(dists_and_seeds , seeds,left_on = ['Season','WTeamID'], right_on=['Season','TeamID'])\ndists_and_seeds['WSeed'] = pd.to_numeric(dists_and_seeds['Seed'].str[1:3])\ndists_and_seeds.drop(['TeamID', 'Seed'],axis = 1, inplace = True)\n\n\ndists_and_seeds = pd.merge(dists_and_seeds, seeds,left_on = ['Season','LTeamID'], right_on=['Season','TeamID'])\ndists_and_seeds['LSeed'] = pd.to_numeric(dists_and_seeds['Seed'].str[1:3])\ndists_and_seeds.drop(['TeamID','Seed'],axis = 1, inplace = True)\n\nseed_ga_data = list()\nfor i in range(1,17):\n    seed_i_data = list()\n    \n    seed_i_wins = dists_and_seeds[(dists_and_seeds.WSeed == i)]\n    seed_i_loses = dists_and_seeds[(dists_and_seeds.LSeed == i)]\n    seed_i_loses = seed_i_loses.assign(Relative_proximity = -1*seed_i_loses.Relative_proximity)\n    seed_i = pd.concat((seed_i_wins, seed_i_loses))\n    \n    seed_i_data.append(i)\n    seed_i_data.append(seed_i_wins.Relative_proximity.mean())\n    seed_i_data.append(seed_i_loses.Relative_proximity.mean())\n    seed_i_data.append(seed_i.Relative_proximity.mean())\n    seed_i_data.append(seed_i_wins.shape[0])\n    seed_i_data.append(seed_i_loses.shape[0])\n    \n    seed_ga_data.append(seed_i_data)\n\nseeds_ga_df = pd.DataFrame(seed_ga_data, columns = ['Seed','Relative Proximity in wins','Relative Proximity in loses','Relative Proximity', 'Total number of wins', 'Total number of loses'])\n\nfrom IPython.display import display, HTML\ndisplay(HTML(seeds_ga_df.to_html(index=False)))","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"* All but four seeds have smaller Relative Proximity average in loses (2nd seed is pretty much the same).\n* Seeds 7 and 8 have smaller Relative Proximity average than nearest seeds.\n* Highest seeds have enormously great Relative Proximity average and number of games.\n\nBasically, we have NCAA arranging games really close to higher seeds. Since higher seeded teams are better, they win more games and make a huge impact on Relative Proximity.\n\nLet's get rid of four highest seeds and look at the familiar wins bar chart."},{"metadata":{"trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"# count wins in each category\nno_high_low_seeds = dists_and_seeds[((~dists_and_seeds['WSeed'].isin([1,2,3,4]))) & ((~dists_and_seeds['LSeed'].isin([1,2,3,4])))]\n\n# count wins in each category\ncount_farther = no_high_low_seeds[no_high_low_seeds['Relative_proximity'] < 0].shape[0];\ncount_closer = no_high_low_seeds[no_high_low_seeds['Relative_proximity'] >= 0].shape[0];\n\n#plot\nplt.figure(figsize=(12,9));\nplt.xticks(np.arange(2), ('teams that are based ' + str(p) + ' miles closer than opponent', 'teams that are based ' + str(p) + ' miles farther than opponent'));\nfig = plt.bar(np.arange(2), (count_closer,count_farther), color=sns.color_palette(\"Blues\",2));\nplt.title('Total games won in significant matches since 2010 (no 1st, 2nd, 3rd, 4th seeds)', fontsize = 14);\nplt.show();","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"Now the number of relatively closer and farther wins is almost even."},{"metadata":{},"cell_type":"markdown","source":"Also there is no to slight correlation between Relative Proximity and game stats."},{"metadata":{"trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"detailed_tourney_results.drop(['WLoc'], axis = 1, inplace = True)\n\nplt.figure(figsize = (3,10))\nsns.heatmap(detailed_tourney_results.corr()[['Relative_proximity']], center = 0);","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"* W index indicates the winning team (for which Relative Proximity was counted for).\n* L index indicates the losing team."},{"metadata":{},"cell_type":"markdown","source":"### Take №1\n* Seed underdogs have to play relatively farther from their hometowns **more often** than favourits.\n\n***\n\nLet's examine how each team performs in significant games. Here, 'relatively closer' means closer to homecourt, 'relatively farther' means farther from homecourt."},{"metadata":{"trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"# count closer wins\ncloser_wins = tourney_results_nn[tourney_results_nn['Relative_proximity'] > 0].groupby('WTeamID').count().reset_index()[['WTeamID','Season']]\ncloser_wins.rename(columns = {'WTeamID':'TeamID', 'Season':'Wins when relatively closer'}, inplace = True)\n\n# count closer loses\ncloser_loses = tourney_results_nn[tourney_results_nn['Relative_proximity'] > 0].groupby('LTeamID').count().reset_index()[['LTeamID','Season']]\ncloser_loses.rename(columns = {'LTeamID':'TeamID', 'Season':'Loses when relatively closer'}, inplace = True)\n\n# count farther wins\nfarther_wins = tourney_results_nn[tourney_results_nn['Relative_proximity'] < 0].groupby('WTeamID').count().reset_index()[['WTeamID','Season']]\nfarther_wins.rename(columns = {'WTeamID':'TeamID', 'Season':'Wins when relatively farther'}, inplace = True)\n\n# count closer loses\nfarther_loses = tourney_results_nn[tourney_results_nn['Relative_proximity']< 0].groupby('LTeamID').count().reset_index()[['LTeamID','Season']]\nfarther_loses.rename(columns = {'LTeamID':'TeamID', 'Season':'Loses when relatively farther'}, inplace = True)\n\n# merge loses and wins for every team\ncloser_games = pd.merge(closer_wins, closer_loses, how = 'left', on = 'TeamID')\ncloser_games = closer_games.fillna(0)\nfarther_games = pd.merge(farther_wins, farther_loses, how = 'left', on = 'TeamID')\nfarther_games = farther_games.fillna(0)\ngames = pd.merge(closer_games, farther_games)\ngames = games.fillna(0)\n\n# get statistics\ngames = pd.merge(games, team_names)\ngames['Relatively closer games'] = games['Wins when relatively closer'] + games['Loses when relatively closer']\ngames['Relatively farther games'] = games['Wins when relatively farther'] + games['Loses when relatively farther']\ngames.drop(['FirstD1Season','LastD1Season'], axis = 1,inplace = True)\ngames = games[games['Relatively closer games'] + games['Relatively farther games'] > 5]\ngames['Winning % when relatively closer'] = games['Wins when relatively closer']/games['Relatively closer games']\ngames['Winning % when relatively farther'] = games['Wins when relatively farther']/games['Relatively farther games']\ngames = games[['TeamName', 'Winning % when relatively closer','Winning % when relatively farther', 'Wins when relatively closer', 'Loses when relatively closer','Wins when relatively farther', 'Loses when relatively farther']]\n\ngames['Loses when relatively closer'] = games['Loses when relatively closer'].astype('Int32')\ngames['Loses when relatively farther'] = games['Loses when relatively farther'].astype('Int32')\n\ngames","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"Noteble teams, who play better closer to hometown:\n* Villanova: 9 wins in 9 closer games\n* Arizona: 9 wins in 10 closer games; 3 wins in 9 farther games\n* Butler:  10 wins in 11 closer games\n* Kentucky: 9 wins in 10 closer games\n\nNotebale teams, who play better farther from hometown:\n* VCU: 2 wins in 7 closer games; 5 wins in 5 farther games\n* Connecticut: 2 wins in 7 closer games; 7 wins in 7 farther games\n* Wisconsin: 6 wins in 10 closer games; 7 wins in 8 farther games"},{"metadata":{"trusted":true,"_kg_hide-input":true},"cell_type":"markdown","source":"### Take №2\n* Some teams permorm a lot better in closer games, and other permorm better in farther games.\n\n***\n\n<div id=\"TravelDistanceAndRecoveryTime\">\n</div>\n# 5. Travel distance and recovery time\n\nFrom the [introduction](#Introduction) part we've learned that in a single tournament team can travel 4 times at max. \n\nIn this section we'll look at games after travels: \n* \"First Four\", first round, \"Sweet Sixteen\" and first two games of \"Final Four\"."},{"metadata":{"trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"# merge teams' travel distances and data about wins and loses\nmap7_data = pd.merge(teams_travel_distances, team_tourney_games)\n\n# count average distance\nmap7_data['Average_distance'] = map7_data['Distance']/(map7_data['Loses']+map7_data['Wins'])\n\n# merge with location data\nmap7_data = pd.merge(map7_data, location_data)\n\n# merge with team names\nmap7_data = pd.merge(map7_data, team_names)\n\n# round distances for better display\nmap7_data = map7_data.assign(Average_distance = map7_data.Average_distance.round(1))\n\n# minimum 5 games\nmap7_data = map7_data[map7_data['Wins'] + map7_data['Loses'] > 4]\n\n# drop duplicated teams\nmap7_data.drop_duplicates(subset = 'TeamName', inplace = True)\n\n# count winning percetage\nmap7_data['Winning%'] = (map7_data['Wins']/(map7_data['Wins']+map7_data['Loses'])).round(2)\n\n# assign text for display\nmap7_data['text'] = map7_data['TeamName'] + '<br><br>Average distance: ' + map7_data['Average_distance'].astype(str) + '<br> Winning %: ' + map7_data['Winning%'].astype(str)\n\nfig2 = px.scatter_geo(map7_data, title = 'Map of teams by distance average<br>(winning percentage in games after travel = size)',\n                     lon = map7_data.lng,\n                     lat = map7_data.lat,\n                     color= 'Average_distance',\n                     size = 'Winning%',\n                     hover_name=\"TeamName\",\n                     projection=\"albers usa\",\n                     color_continuous_scale = 'YlOrRd')\n\nfig2.update_traces(hovertemplate='%{text}',text = map7_data.text, hoverinfo='skip')\n\nfig2.update_layout(\n        coloraxis_colorbar=dict(title = 'Average distnace traveled')\n    )\n        \nfig2.show()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"Simple game frequency distribution is shown below:"},{"metadata":{"trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"plt.figure(figsize = (17,5))\nplt.title('Relative frequency distribution of games', fontsize = 14)\nsns.distplot(games_and_coordinates['WDistance'], bins = 24, hist = False,label=\"Distance traveled by winning team\");\n\nsns.distplot(games_and_coordinates['LDistance'], axlabel = \"Distance traveled\", label=\"Distance traveled by losing team\",bins = 24, hist = False);\nplt.legend()\n\n# PRINTS\nprint1 = 'Average distance in loses: ' + str(int(games_and_coordinates['LDistance'].mean())) + ' miles.'\nprint2 = 'Average distance in wins: ' + str(int(games_and_coordinates['WDistance'].mean())) + ' miles.'\nprint3 = games_and_coordinates[games_and_coordinates['WDistance'] < games_and_coordinates['LDistance']].shape[0] - games_and_coordinates[games_and_coordinates['WDistance'] > games_and_coordinates['LDistance']].shape[0]","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"* Average distance in loses: 829 miles.\n* Average distance in wins: 724 miles.\n\nTeams won 81 more games (out of 453), when they traveled less. Let's examine, if this statistics have the same roots as Relative Proximity.\n\nNo to slight correlation betwen game stats and distance:"},{"metadata":{"trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"# reset \"all_tourney_results\" dataframe\nall_tourney_results = pd.read_csv('/kaggle/input/march-madness-analytics-2020/MDataFiles_Stage2/MNCAATourneyDetailedResults.csv')\n\n# get data to check correlations\ncorrelation_data = pd.merge(games_and_coordinates,all_tourney_results[all_tourney_results['Season'] > 2009].drop(['WLoc','NumOT'], axis = 1), on = ['Season','DayNum','WTeamID','LTeamID'])\ncorrelation_data.drop(['WTeamID','GameCityID', 'LTeamID', 'Season','GameCityLat','GameCityLng','WTeamLat','WTeamLng','LTeamLat','LTeamLng'],axis = 1, inplace = True)\n\nplt.figure(figsize=(3,10))\nsns.heatmap(correlation_data.corr()[['WDistance','LDistance']], center = 0);","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"no_high_seed_games = games_and_coordinates[(games_and_coordinates['WSeed'] > 4) & (games_and_coordinates['LSeed'] > 4)]\n\nplt.figure(figsize = (17,5))\nplt.title('Relative frequency distribution of games (no 1st, 2nd, 3rd and 4th seed games)', fontsize = 14)\nsns.distplot(no_high_seed_games['WDistance'], bins = 24, hist = False, label=\"Distance traveled by winning team\");\n\nsns.distplot(no_high_seed_games['LDistance'], axlabel = \"Distance traveled\", label=\"Distance traveled by losing team\",bins = 24, hist = False);\nplt.legend()\n\n# PRINTS\nprint1 = 'Average distance in loses: ' + str(int(no_high_seed_games['LDistance'].mean())) + ' miles.'\nprint2 = 'Average distance in wins: ' + str(int(no_high_seed_games['WDistance'].mean())) + ' miles.'\nprint3 = no_high_seed_games[no_high_seed_games['WDistance'] < no_high_seed_games['LDistance']].shape[0] - no_high_seed_games[no_high_seed_games['WDistance'] > no_high_seed_games['LDistance']].shape[0]","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"* Average distance in loses: 871 miles.\n* Average distance in wins: 836 miles.\n\nTeams won 7 more games (out of 207), when they traveled less.\n\nAnd if we include only that games, in which high seeds participated:"},{"metadata":{"trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"high_seed_games = games_and_coordinates[(games_and_coordinates['WSeed']< 5) | (games_and_coordinates['LSeed'] < 5)]\n\nplt.figure(figsize = (17,5))\nplt.title('Relative frequency distribution of games(1st, 2nd, 3rd or 4th seeds are participated)', fontsize = 14)\nsns.distplot(high_seed_games['WDistance'], bins = 24, hist = False,label=\"Distance traveled by winning team\");\n\nsns.distplot(high_seed_games['LDistance'], axlabel = \"Distance traveled\", label=\"Distance traveled by losing team\",bins = 24, hist = False);\nplt.legend()\n\n# PRINTS\nprint1 = 'Average distance in loses: ' + str(int(high_seed_games['LDistance'].mean())) + ' miles.'\nprint2 = 'Average distance in wins: ' + str(int(high_seed_games['WDistance'].mean())) + ' miles.'\nprint3 = high_seed_games[high_seed_games['WDistance'] < high_seed_games['LDistance']].shape[0] - high_seed_games[high_seed_games['WDistance'] > high_seed_games['LDistance']].shape[0]","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"### Take 3\n\nSeed underdogs have to travel farther than favourits.\n\n***\n\nFinally, we'll look at recovery time.\n\n**Time advantage** is simply difference between two teams' recovery times (in days).<br>\nIf one team had a 4-day break and it plays with a team, which had a 5-day break, first teams *time advantage* equals -1."},{"metadata":{"trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"plt.figure(figsize = (17,5))\nplt.title('Wins distribution (1st, 2nd, 3rd or 4th seeds are participated)', fontsize = 14)\nsns.distplot(games_and_coordinates['Time_advantage'], bins = 24, axlabel = \"Time advantage\", hist = False);\n\n# PRINTS\nprint1 = 'Average recovery time of winning teams: ' + str(round(games_and_coordinates['WRecoveryTime'].mean(), 3)) + ' days.'\nprint2 = 'Average recovery time of losing teams: ' + str(round(games_and_coordinates['LRecoveryTime'].mean(), 3)) + ' days.'\nprint3 = games_and_coordinates[games_and_coordinates['WRecoveryTime'] > games_and_coordinates['LRecoveryTime']].shape[0] - games_and_coordinates[games_and_coordinates['WRecoveryTime'] < games_and_coordinates['LRecoveryTime']].shape[0]","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"* Average recovery time of winning teams: 6.216 days.\n* Average recovery time of losing teams: 6.366 days.\n* Very balanced-looking graph.\n\n7 more games won by teams with less recovery time.\n\nLet's group games by seeds:"},{"metadata":{"trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"seed_time_advantages = pd.concat((games_and_coordinates.groupby('WSeed').Time_advantage.sum(),-games_and_coordinates.groupby('LSeed').Time_advantage.sum()), axis = 1)\nseed_time_advantages = pd.concat((seed_time_advantages,pd.concat((games_and_coordinates.groupby('WSeed').WRecoveryTime.sum(),games_and_coordinates.groupby('LSeed').LRecoveryTime.sum()), axis = 1)), axis = 1)\nseed_time_advantages.columns = ['Time advantage in wins', 'Time advantage in loses','Recovery time in wins', 'Recovery time in loses']\nseed_time_advantages = pd.concat((seed_time_advantages,pd.concat((games_and_coordinates.groupby('WSeed').WRecoveryTime.count(),games_and_coordinates.groupby('LSeed').LRecoveryTime.count()), axis = 1)),axis = 1)\nseed_time_advantages.rename(columns = {'WRecoveryTime':'Wins','LRecoveryTime':'Loses'}, inplace = True)\nseed_time_advantages['Average time advantage in wins'] = seed_time_advantages['Time advantage in wins']/seed_time_advantages['Wins']\nseed_time_advantages['Average time advantage in loses'] = seed_time_advantages['Time advantage in loses']/seed_time_advantages['Loses']\nseed_time_advantages['Average time advantage'] = (seed_time_advantages['Time advantage in wins']+seed_time_advantages['Time advantage in loses'])/(seed_time_advantages['Loses']+seed_time_advantages['Wins'])\nseed_time_advantages['Average recovery time in wins'] = seed_time_advantages['Recovery time in wins']/seed_time_advantages['Wins']\nseed_time_advantages['Average recovery time in loses'] = seed_time_advantages['Recovery time in loses']/seed_time_advantages['Loses']\nseed_time_advantages['Average recovery time'] = (seed_time_advantages['Average recovery time in wins'] + seed_time_advantages['Average recovery time in loses'])/2\n\nfrom IPython.display import display, HTML\ndisplay(HTML(seed_time_advantages[['Average time advantage','Average time advantage in wins','Average time advantage in loses', 'Average recovery time in wins', 'Average recovery time in loses','Average recovery time']].reset_index().rename(columns={'index':'Seed'}).to_html(index=False)))","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"* Second seeds has lowest average time advantage\n* First seeds have lowest average recovery time\n* Fifteen seed have higest average recovery time and average time advantage"},{"metadata":{"trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"teams_time_advantages = pd.merge(games_and_coordinates.groupby('WTeamID').Time_advantage.sum().reset_index(),games_and_coordinates.groupby('LTeamID').Time_advantage.sum().reset_index(), left_on = 'WTeamID', right_on = 'LTeamID').dropna()\nteams_time_advantages = teams_time_advantages.assign(Time_advantage_y = -teams_time_advantages['Time_advantage_y'])\nteams_time_advantages.drop('LTeamID', axis = 1, inplace = True)\nteams_time_advantages.columns = ['TeamID','Time advantage in wins', 'Time advantage in loses']\nteams_time_advantages = pd.merge(teams_time_advantages,pd.concat((games_and_coordinates.groupby('WTeamID').WRecoveryTime.count(),games_and_coordinates.groupby('LTeamID').LRecoveryTime.count()), axis = 1).dropna().reset_index().rename(columns = {'index':'TeamID'}))\nteams_time_advantages.rename(columns = {'WRecoveryTime':'Wins','LRecoveryTime':'Loses'}, inplace = True)\nteams_time_advantages['Average time advantage in wins'] = teams_time_advantages['Time advantage in wins']/teams_time_advantages['Wins']\nteams_time_advantages['Average time advantage in loses'] = teams_time_advantages['Time advantage in loses']/teams_time_advantages['Loses']\nteams_time_advantages['Average time advantage'] = (teams_time_advantages['Time advantage in wins']+teams_time_advantages['Time advantage in loses'])/(teams_time_advantages['Loses']+teams_time_advantages['Wins'])\nteams_time_advantages = teams_time_advantages[teams_time_advantages['Wins'] + teams_time_advantages['Loses'] > 5][['TeamID','Average time advantage','Average time advantage in wins','Average time advantage in loses']]\n\nmap8_data = pd.merge(teams_time_advantages, location_data)\nmap8_data = pd.merge(map8_data, team_names)\nmap8_data.drop_duplicates(subset='TeamName', inplace = True)\nmap8_data['text'] = map8_data['TeamName'] + '<br><br>Average time advantage: ' + map8_data['Average time advantage'].round(2).astype(str)\n\nfig3 = px.scatter_geo(map8_data, title = 'Map of teams by average time advantage<br>(more than 5 games after travel)',\n                     lon = map8_data.lng,\n                     lat = map8_data.lat,\n                     color= 'Average time advantage',\n                     hover_name=\"TeamName\",\n                     projection=\"albers usa\",\n                     color_continuous_scale = 'bluered')\n\nfig3.update_traces(hovertemplate='%{text}',text = map8_data.text, hoverinfo='skip')\n        \nfig3.show()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"### Take 4\n* Recovery time doesn't give a significant impact on teams' performance.\n\n<div id=\"Conclusions\">\n</div>\n# 6. Conclusions\n\n**In my view, space-time factors do not effect teams' performance much.**\n\nOften a shorter distance to a game city is the result of team's high seeding. Also winning teams tend to have less recovery time, so we surely need more analysis in this area\n\nMy own XGBoost March Madness prediction model improved by 0.01 (log loss metric) after applying Relative Proximity and travel distance statistics. When applying time advantage, model improves even less.\n\n**Anyway, your feedback is very important for following analysis.**\n* *location_data.csv* is available for download.\n* all code is desirable to use."}],"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}