Skip to content

Commit

Permalink
Merge pull request #70 from rsforbes/master
Browse files Browse the repository at this point in the history
Updates to PlaybyPlay Regex. Added Integration Tests to Validate Game PlaybyPlay Data
  • Loading branch information
swar authored Apr 29, 2019
2 parents 64cdede + 85ec813 commit ffb37a0
Show file tree
Hide file tree
Showing 7 changed files with 587 additions and 492 deletions.
2 changes: 1 addition & 1 deletion .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ flake8-steps: &steps
- run: python -m flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
#- run: pytest
- run: pip install --user pytest
- run: python -m pytest tests/stats/library/playbyplay/test_playbyplayregex.py
- run: python -m pytest tests/stats/library/playbyplay/test_play_by_play_regex.py

jobs:
Python34:
Expand Down
32 changes: 25 additions & 7 deletions nba_api/stats/library/playbyplayregex.py
Original file line number Diff line number Diff line change
@@ -1,33 +1,37 @@
import re
from collections import defaultdict
from nba_api.stats.library.eventmsgtype import EventMsgType

# regex patterns for all playbyplay and playbyplayv2 HOMEDESCRIPTION & VISITORDESCRIPTION fields
pattern_player_char = r'((?#char)(\. \w+)|(\-?\'?\w+))?'
pattern_player_suffix = r'((?#suffix)([\s](Jr\.|III|II|IV)))?'
pattern_referee = r'(?P<referee>.*)'
pattern_team = r'(?P<team>\w+( \w+)?)'
pattern_field_goal_type = r'(?P<field_goal_type>[\w+( |\-)]*)'
pattern_free_throw_type = r'(?P<free_throw_type>(\w+ )?(\d of \d)|\w+)'
pattern_free_throw_type = r'(?P<free_throw_type>(.* )?(\d of \d)|\w+)'
pattern_player = r"(?P<player>\w+{player_char}{player_suffix})".format(player_char=pattern_player_char,player_suffix=pattern_player_suffix)
pattern_points = r"\((?P<points>\d+) PTS\)"
pattern_rebound_team = r'^{team} Rebound$'.format(team=pattern_team)
pattern_turnover_team = r'^{team} Turnover: (?P<turnover_type>\w+\s?\w+) \(T#(?P<turnovers>\d.)\)$'.format(team=pattern_team)
pattern_turnover_team = r'^{team} Turnover: (?P<turnover_type>.*) \(T#(?P<turnovers>\d+)\)$'.format(team=pattern_team)
pattern_timeout = r'^{team} Timeout: ((?P<timeout_type>\w+)) \(\w+ (?P<full>\d+) \w+ (?P<short>\d+)\)$'.format(team=pattern_team)
pattern_block = r"^{player} BLOCK \((?P<blocks>\d+) BLK\)$".format(player=pattern_player)
pattern_ejection = r'^{player} Ejection:(?P<ejection_type>.*)$'.format(player=pattern_player)
pattern_field_goal_made = r"^{player}\s{{1,2}}?((?P<distance>\d+)\' )?{field_goal_type} {points}( \({player_ast} (?P<assists>\d+) AST\))?$".format(
player=pattern_player,player_ast=pattern_player.replace('player','player_ast'), field_goal_type=pattern_field_goal_type,points=pattern_points)
pattern_field_goal_missed = r"^MISS {player}\s{{1,2}}?((?P<distance>\d+)\' )?{field_goal_type}$".format(player=pattern_player,field_goal_type=pattern_field_goal_type)
pattern_foul = r"^{player} (?P<foul_type>.*)\s(?=(\(\w+(?P<personal>\d+)(\.\w+(?P<team>[\d|\w]+))?\) (\((?P<referee>\w.\w+)\)))$)".format(player=pattern_player)
pattern_foul_player = r"^{player} (?P<foul_type>.*)\s(?=(\(\w+(?P<personal>\d+)(\.\w+(?P<team>[\d|\w]+))?\) (\({referee}\)))$)".format(player=pattern_player, referee=pattern_referee)
pattern_foul_team = r"^{team} T.Foul \((?P<foul_type>.*) {player} \) (\({referee}\))".format(player=pattern_player, team=pattern_team, referee=pattern_referee)
pattern_free_throw_made = r"^{player} Free Throw {free_throw_type} {points}$".format(player=pattern_player,free_throw_type=pattern_free_throw_type,points=pattern_points)
pattern_free_throw_miss = r"^MISS {player} Free Throw {free_throw_type}$".format(player=pattern_player,free_throw_type=pattern_free_throw_type)
pattern_jump_ball = r"^Jump Ball {player_home} vs. {player_away}: Tip to {player_tip}$".format(player_home=pattern_player.replace('player','player_home'),
pattern_jump_ball = r"^Jump Ball {player_home} vs. {player_away}: Tip to( {player_tip})?$".format(player_home=pattern_player.replace('player','player_home'),
player_away=pattern_player.replace('player','player_away'),
player_tip=pattern_player.replace('player','player_tip'))
pattern_rebound_player = r"^{player} REBOUND \(Off:(?P<offensive>\d+) Def:(?P<defensive>\d+)\)$".format(player=pattern_player)
pattern_steal = r"^{player}\s{{1,2}}?STEAL \((?P<steals>\d+) STL\)$".format(player=pattern_player)
pattern_substitution = r"^SUB: {player_in} FOR {player_out}$".format(
player_in=pattern_player.replace('player','player_in'),player_out=pattern_player.replace('player','player_out'))
pattern_turnover_player = r"^{player}\s{{1,2}}?(?P<turnover_type>[\w+?\-? ]*) (Turnover[ ]?)+ \(P(?P<personal>\d+)\.T(?P<team>\d+)\)$".format(player=pattern_player)
pattern_violation = r"^{player} Violation:(?P<violation_type>\w+\s?\w+)\s\((?P<referee>\w.\w+)\)$".format(player=pattern_player)
pattern_violation = r"^{player} Violation:(?P<violation_type>\w+\s?\w+)\s\({referee}\)$".format(player=pattern_player, referee=pattern_referee)

# compiled regex for all playbyplay and playbyplayv2 HOMEDESCRIPTION & VISITORDESCRIPTION fields
re_block = re.compile(pattern_block)
Expand All @@ -36,7 +40,8 @@
re_field_goal_missed = re.compile(pattern_field_goal_missed)
re_free_throw_made = re.compile(pattern_free_throw_made)
re_free_throw_miss = re.compile(pattern_free_throw_miss)
re_foul = re.compile(pattern_foul)
re_foul_player = re.compile(pattern_foul_player)
re_foul_team = re.compile(pattern_foul_team)
re_rebound_player = re.compile(pattern_rebound_player)
re_rebound_team = re.compile(pattern_rebound_team)
re_turnover_player = re.compile(pattern_turnover_player)
Expand All @@ -45,4 +50,17 @@
re_substitution = re.compile(pattern_substitution)
re_timeout = re.compile(pattern_timeout)
re_violation = re.compile(pattern_violation)
re_jump_ball = re.compile(pattern_jump_ball)
re_jump_ball = re.compile(pattern_jump_ball)

eventmsgtype_to_re = defaultdict(list, {
EventMsgType.EJECTION : [re_ejection],
EventMsgType.FIELD_GOAL_MADE : [re_field_goal_made],
EventMsgType.FIELD_GOAL_MISSED : [re_field_goal_missed, re_block],
EventMsgType.FREE_THROW : [re_free_throw_made, re_free_throw_miss],
EventMsgType.REBOUND : [re_rebound_player, re_rebound_team],
EventMsgType.TURNOVER : [re_turnover_player, re_steal, re_turnover_team],
EventMsgType.FOUL : [re_foul_player, re_foul_team],
EventMsgType.VIOLATION : [re_violation],
EventMsgType.SUBSTITUTION : [re_substitution],
EventMsgType.TIMEOUT : [re_timeout],
EventMsgType.JUMP_BALL : [re_jump_ball]})
91 changes: 0 additions & 91 deletions tests/stats/library/playbyplay/conftest.py

This file was deleted.

105 changes: 105 additions & 0 deletions tests/stats/library/playbyplay/play_by_play_data.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import pytest
from collections import defaultdict
from nba_api.stats.library.eventmsgtype import EventMsgType

playbyplay = defaultdict(list)

#Block
playbyplay["Block"].append({"description" : "Collins BLOCK (1 BLK)", "player" : "Collins", "blocks" : "1"})

#Ejection
playbyplay["Ejection"].append({"description" : "Robinson Ejection:Second Technical", "player" : "Robinson", "ejection_type" : "Second Technical"})

#Field Goal Made (With Assist)
playbyplay["FieldGoalMade"].append({"description" : "S. Hill 24' 3PT Jump Shot (3 PTS) (Mahinmi 1 AST)", "player" : "S. Hill", "distance" : "24", "field_goal_type" : "3PT Jump Shot", "points" : "3", "player_ast" : "Mahinmi", "assists" : "1"})

#Field Goal Made (Without Assist)
playbyplay["FieldGoalMade"].append({"description" : "Aldridge 6' Turnaround Hook Shot (8 PTS)", "player" : "Aldridge", "distance" : "6", "field_goal_type" : "Turnaround Hook Shot", "points" : "8", "player_ast" : None, "assists" : None})

#Field Goal Made (Without Distance)
playbyplay["FieldGoalMade"].append({"description" : "Broekhoff 3PT Jump Shot (3 PTS) (Lee 2 AST)", "player" : "Broekhoff", "distance" : None, "field_goal_type" : "3PT Jump Shot", "points" : "3", "player_ast" : "Lee", "assists" : "2"})

#Field Goal Missed
playbyplay["FieldGoalMissed"].append({"description" : "MISS O'Quinn 17' Jump Shot", "player" : "O'Quinn", "distance" : "17", "field_goal_type" : "Jump Shot"})

#Foul Player
playbyplay["FoulPlayer"].append({"description" : "Collison P.FOUL (P1.TN) (M.Lindsay)", "player" : "Collison", "foul_type" : "P.FOUL", "personal" : "1", "team" : "N", "referee" : "M.Lindsay"})

#Foul Player (Long Referee Name)
playbyplay["FoulPlayer"].append({"description" : "Allen P.FOUL (P1.T3) (J.Van Duyne)", "player" : "Allen", "foul_type" : "P.FOUL", "personal" : "1", "team" : "3", "referee" : "J.Van Duyne"})

#Foul Team
playbyplay["FoulTeam"].append({"description" : "BUCKS T.Foul (Def. 3 Sec Lopez ) (M.Callahan)", "team" : "BUCKS", "foul_type" : "Def. 3 Sec", "player" : "Lopez", "referee" : "M.Callahan"})

#Free Throw Made
playbyplay["FreeThrowMade"].append({"description" : "Sumner Free Throw 2 of 2 (1 PTS)", "player" : "Sumner", "free_throw_type" : "2 of 2", "points" : "1"})

#Free Throw Made (Clear Path 1 of 2)
playbyplay["FreeThrowMade"].append({"description" : "Powell Free Throw Clear Path 1 of 2 (18 PTS)", "player" : "Powell", "free_throw_type" : "Clear Path 1 of 2", "points" : "18"})

#Free Throw Missed
playbyplay["FreeThrowMissed"].append({"description" : "MISS Prince Free Throw 1 of 2", "player" : "Prince", "free_throw_type" : "1 of 2"})

#Jump Ball
playbyplay["JumpBall"].append({"description" : "Jump Ball Collins vs. O'Quinn: Tip to Leaf", "player_home" : "Collins", "player_away" : "O'Quinn", "player_tip" : "Leaf"})

#Jump Ball (Without player_tip)
playbyplay["JumpBall"].append({"description" : "Jump Ball Green vs. McKinnie: Tip to", "player_home" : "Green", "player_away" : "McKinnie", "player_tip" : None})

#Player (Single Name)
playbyplay["Player"].append({"description" : "Millsap 25' 3PT Jump Shot (9 PTS) (Teague 1 AST)", "player" : "Millsap"})

#Player (Hyphenated Name)
playbyplay["Player"].append({"description" : "Bates-Diop P.FOUL (P1.T4) (M.Boland)", "player" : "Bates-Diop"})

#Player (Apostrophe)
playbyplay["Player"].append({"description" : "O'Quinn 20' Jump Shot (2 PTS) (Oladipo 1 AST)", "player" : "O'Quinn"})

#Player (First Initial dot Last Name)
playbyplay["Player"].append({"description" : "S. Hill 24' 3PT Jump Shot (3 PTS) (Mahinmi 1 AST)", "player" : "S. Hill"})

#Player (Junior)
playbyplay["Player"].append({"description" : "Porter Jr. 10' Driving Floating Jump Shot (2 PTS)", "player" : "Porter Jr."})

#Player (Second)
playbyplay["Player"].append({"description" : "Payton II 2' Driving Reverse Layup (2 PTS) (Middleton 6 AST)", "player" : "Payton II"})

#Player (Third)
playbyplay["Player"].append({"description" : "Robinson III Free Throw 1 of 1 (3 PTS)", "player" : "Robinson III"})

#Player (Fourth)
playbyplay["Player"].append({"description" : "Walker IV REBOUND (Off:0 Def:1)", "player" : "Walker IV"})

#Rebound Player
playbyplay["ReboundPlayer"].append({"description" : "Zubac REBOUND (Off:2 Def:4)", "player" : "Zubac", "offensive" : "2", "defensive" : "4"})

#Rebound Team (One Word)
playbyplay["ReboundTeam"].append({"description" : "Timberwolves Rebound", "team" : "Timberwolves"})

#Rebound Team (Two Words)
playbyplay["ReboundTeam"].append({"description" : "TRAIL BLAZERS Rebound", "team" : "TRAIL BLAZERS"})

#Steal
playbyplay["Steal"].append({"description" : "Bradley STEAL (2 STL)", "player" : "Bradley", "steals" : "2"})

#Substitution
playbyplay["Substitution"].append({"description" : "SUB: Sefolosha FOR Ingles", "player_in" : "Sefolosha", "player_out" : "Ingles"})

#Timeout
playbyplay["Timeout"].append({"description" : "TRAIL BLAZERS Timeout: Regular (Full 5 Short 0)", "team" : "TRAIL BLAZERS", "timeout_type" : "Regular", "full" : "5", "short" : "0"})

#Turnover Player
playbyplay["TurnoverPlayer"].append({"description" : "G. Harrison Double Dribble Turnover (P1.T10)", "player" : "G. Harrison", "turnover_type" : "Double Dribble", "personal" : "1", "team" : "10"})

#Turnover Team (less than 10)
playbyplay["TurnoverTeam"].append({"description" : "NUGGETS Turnover: Shot Clock (T#12)", "team" : "NUGGETS", "turnover_type" : "Shot Clock", "turnovers" : "12"})

#Turnover Team (greater than 9)
playbyplay["TurnoverTeam"].append({"description" : "NETS Turnover: Shot Clock (T#6)", "team" : "NETS", "turnover_type" : "Shot Clock", "turnovers" : "6"})

#Turnover Team (turnover_type starts with digit)
playbyplay["TurnoverTeam"].append({"description" : "WIZARDS Turnover: 5 Second Violation (T#12)", "team" : "WIZARDS", "turnover_type" : "5 Second Violation", "turnovers" : "12"})

#Violation
playbyplay["Violation"].append({"description" : "Dieng Violation:Kicked Ball (T.Brown)", "player" : "Dieng", "violation_type" : "Kicked Ball", "referee" : "T.Brown"})

Loading

0 comments on commit ffb37a0

Please sign in to comment.