{"cells":[{"metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true},"cell_type":"code","source":"## This Python 3 environment comes with many helpful analytics libraries installed\n# It is defined by the kaggle/python docker image: https://github.com/kaggle/docker-python\n# For example, here's several helpful packages to load in \n\nimport numpy as np # linear algebra\nimport numpy.random as npr # random stuff\nimport pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)\n\n# Input data files are available in the \"../input/\" directory.\n# For example, running this (by clicking run or pressing Shift+Enter) will list all files under the input directory\n\nimport os\nfor dirname, _, filenames in os.walk('/kaggle/input'):\n    for filename in filenames:\n        print(os.path.join(dirname, filename))\n\n# Any results you write to the current directory are saved as output.","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"If you use efficiency stats such as the [four factors](https://www.basketball-reference.com/about/factors.html), you may want to remove garbage time before computing them. Garbage time occurs when a team gets a lead that the players and coaches treat as insurmountable, and keeps it until the end of the game. Garbage time can confound efficiency stats: coaches may put in players who normally don't play, teams may take low-percentage shots they normally wouldn't take, or miss shots they might make with a better shooter in the game, or give less effort on defense than they normally would.\n\nObviously, not all games have garbage time, and it's possible to have good discussions about what constitutes garbage time, especially if adult beverages are available. This notebook implements one definition of garbage time, with a garbage time rules parameter you can set."},{"metadata":{"trusted":true},"cell_type":"code","source":"# This list defines the garbage time parameters used here. Each entry in the list should be a (minutes,points) pair.\n# If the first pair is (n,p), it means that during the last n minutes of the game, a lead of p points or more is\n# considered insurmountable. If the next pair is (m,q), it means that during the preceding m minutes, a lead of q points or more\n# is considered insurmountable. And so on. Garbage time begins the first time the winning team acquires an insurmountable lead,\n# provided it keeps an insurmountable lead for the remainder of the game.\n#\n# Given this rule, if the winning team makes a shot that increases their lead from 13 points to 15 points with 1.5 min left,\n# and they maintain a 15+ point lead for the rest of the game, then garbage time starts at ElapsedSeconds = 38.5 * 60 + 1 = 2311.\n# If the winning team reaches a 20-point lead with 3 min left, maintains a 20+ point lead from then until the 2 min mark,\n# and maintains a 15+ point lead through the last 2 min of the game, then garbage time starts at ElapsedSeconds = 37 *60 + 1 = 2221.\n# On the other hand, if a team acquires a 15-point lead with 2 min left but wins the game by 14, then there is no garbage time.\ngarbage_time_rule = [(2,15),(3,20)]","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"import re\nimport time","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"home = '/kaggle/input/google-cloud-ncaa-march-madness-2020-division-1-mens-tournament'\nevent_files = '/kaggle/input/partially-fix-event-files'","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# validate a garbage-time rule\n# 1. the time range must be entirely within the second half\n# 2. the points threshhold must be non-decreasing \n# times are converted to seconds for ease of use\ndef validate_gbg_rule(rule,game_length=40):\n    thresh = 0\n    end_of_part = game_length\n    halftime = game_length/2\n    by_sec = []\n    for i,r in enumerate(rule):\n        if r[1] < thresh:\n            raise ValueError(f'lead threshhold {r[1]} in part {i} must be at least {thresh}')\n        start_of_part = end_of_part - r[0]\n        if start_of_part < halftime:\n            raise ValueError(f'part {i} begins at {start_of_part}, which is in the first half')\n        by_sec += [(start_of_part*60,r[1])]\n        end_of_part = start_of_part\n        thresh = r[1]\n    return by_sec","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# quick and dirty unit test\nve = None\ntry:\n    validate_gbg_rule([(10,12),(10.5,15)])\nexcept ValueError as e:\n    ve = e\nassert ve is not None\n\nve2 = None\ntry:\n    validate_gbg_rule([(3,12),(3,11)])\nexcept ValueError as e:\n    ve2 = e\nassert ve2 is not None\n\nassert validate_gbg_rule([(4,13)]) == [(2160,13)]\nassert validate_gbg_rule([(2,15),(3,20)]) == [(2280,15),(2100,20)]\nassert validate_gbg_rule([(2,12),(2,15),(2,18)]) == [(2280,12),(2160,15),(2040,18)]","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# if rule hasn't been validated, you get what you deserve :-)\ndef is_insurmountable(row,rule):\n    es = row['ElapsedSeconds']\n    lead = row['WCurScore'] - row['LCurScore']\n    for r in rule:\n        if es >= r[0] and lead >= r[1]:\n            return 1\n    return 0\n\n# game_frame should be an events DataFrame for a single game\n# rule should be a validated garbage time rule\ndef flag_garbage_time(df,rule):\n    insurm = list(df.apply(lambda row: is_insurmountable(row,rule),axis=1))\n    last_before_garbage = np.max([n for n in range(df.shape[0]) if insurm[n]==0])\n    df.loc[:,'gbg_time'] = [int(n>last_before_garbage) for n in range(df.shape[0])]","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"gtr = validate_gbg_rule(garbage_time_rule)\n\nfor year in [2015, 2016, 2017, 2018, 2019]:\n    print(f'working on {year}')\n    \n    # read an event dataset\n    read_start = time.time()\n    events = pd.read_csv(f'{event_files}/Mevents_reg_season_{year}.csv')\n    read_end = time.time()\n    read_elapsed = read_end - read_start\n    print(f'{read_elapsed:.2f} sec to read {events.shape[0]} rows ({events.shape[0]/read_elapsed:.2f} rows/sec)')\n    \n    # find the games in the event file\n    games = events.loc[:,['Season','DayNum','WTeamID','LTeamID']].drop_duplicates()\n    starts = [game[0] for game in games.iterrows()]\n    ends = [game[0]-1 for game in games.iterrows()][1:] + [events.shape[0]-1]\n    print(f'found {len(games)} games in {year}')\n    \n    # fill in the garbage time column\n    process_start = time.time()\n\n    game_frames = []\n    for n in range(len(games)):\n        gm = events.loc[starts[n]:ends[n],:].copy()\n        flag_garbage_time(gm,gtr)\n        game_frames += [gm]\n    pd.concat(game_frames).to_csv(f'Mevents_reg_season_{year}_gt.csv',header=True,index=False)\n    \n    process_end = time.time()\n    process_elapsed = process_end - process_start\n    print(f'{process_elapsed:.2f} sec to process {events.shape[0]} events ({events.shape[0]/process_elapsed:.2f} events/sec)')\n    print('')","execution_count":null,"outputs":[]}],"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"pygments_lexer":"ipython3","nbconvert_exporter":"python","version":"3.6.4","file_extension":".py","codemirror_mode":{"name":"ipython","version":3},"name":"python","mimetype":"text/x-python"}},"nbformat":4,"nbformat_minor":4}