Source code for nhlscrapi.games.cumstats

from abc import ABCMeta, abstractmethod

from nhlscrapi.games import events as EV
from nhlscrapi.games.events import EventFactory as EF

from nhlscrapi.games.playbyplay import Strength as St

# base class for accumulators
class AccumulateStats(object):
[docs] """ Base class for accumulator classes. These classes keep tallies of specified events and are updated each time a play from :py:class:`.PlayByPlay` is processed. Examples include :py:class:`.ShotCt` and :py:class:`.Score`. This class is not intended to be used directly. """ __metaclass__ = ABCMeta def __init__(self): self.total = { } self.teams = [] def initialize_teams(self, teams):
[docs] self.teams = teams self.total = { t: 0 for t in teams } @abstractmethod
def update(self, play):
[docs] """ Update the accumulator with the current play :returns: new tally, ``{ 'period': per, 'time': clock, 'team': cumul, 'play': play }`` """ pass class TeamIncrementor(AccumulateStats):
[docs] """ Accumulator base class for team vs team stats such as score, shot count et c. :param get_team: function, takes a play and returns the team associated with it :param count_play: function, takes a play and returns True if it is a tally for the given accumulator's definition. """ __metaclass__ = ABCMeta def __init__(self, get_team=None, count_play=None): super(TeamIncrementor, self).__init__() self.tally = [] """List of plays that lead to tallies, i.e. increments of the stat accumulator. E.g. a goal or shot.""" self._get_team = get_team self._count_play = count_play def update(self, play):
[docs] """ Update the accumulator with the current play :returns: new tally :rtype: dict, ``{ 'period': per, 'time': clock, 'team': cumul, 'play': play }`` """ new_tally = { } #if any(isinstance(play.event, te) for te in self.trigger_event_types): if self._count_play(play): # the team who made the play / triggered the event team = self._get_team(play) try: self.total[team] += 1 except: self.total[team] = 1 self.teams.append(team) for i in range(len(self.tally)): self.tally[i][team] = 0 try: new_tally = { k:v for k,v in self.tally[len(self.tally)-1].iteritems() } new_tally['period'] = play.period new_tally['time'] = play.time new_tally[team] += 1 new_tally['play'] = play except: new_tally = { 'period': play.period, 'time': play.time, team: 1, 'play': play } self.tally.append(new_tally) return new_tally class ShotEventTallyBase(TeamIncrementor):
[docs] """Base class for all shot attempt based events""" def __init__(self, count_play): super(ShotEventTallyBase, self).__init__( get_team=lambda play: play.event.shooter['team'], count_play=count_play ) class ShotCt(ShotEventTallyBase):
[docs] """ Tallies shots on goal for each team. Increments if * the play event inherits from :py:class:`.Shot` """ def __init__(self): super(ShotCt, self).__init__( count_play=lambda play: isinstance(play.event, EV.Shot) ) class EvenStShotCt(ShotEventTallyBase):
[docs] """ Tallies even strength shots on goal for each team. Increments if * the play event inherits from :py:class:`.Shot` * play happened at even strength """ def __init__(self): super(EvenStShotCt, self).__init__( count_play=lambda play: isinstance(play.event, EV.Shot) and play.strength == St.Even ) class ShotAttemptCt(ShotEventTallyBase):
[docs] """ Tallies even strength shots on goal for each team. Increments if * the play event inherits from :py:class:`.ShotAttempt` """ def __init__(self): super(ShotAttemptCt, self).__init__( count_play=lambda play: isinstance(play.event, EV.ShotAttempt) ) class EvenStShotAttCt(ShotEventTallyBase):
[docs] """ Tallies even strength shots on goal for each team. Increments if * the play event inherits from :py:class:`.ShotAttempt` * play happened at even strength """ def __init__(self): super(EvenStShotAttCt, self).__init__( count_play=lambda play: isinstance(play.event, EV.ShotAttempt) and play.strength == St.Even ) class Corsi(EvenStShotAttCt):
[docs] """ Tallies even strength shots on goal for each team. Increments if * the play event inherits from :py:class:`.Shot` * play happened at even strength Defined more for convention/nostalgia. Same as :py:class:`.EvenStShotAttCt` """ def __init__(self): super(Corsi, self).__init__() def share(self):
[docs] """ The Cori-share (% of shot attempts) for each team :returns: dict, ``{ 'home_name': %, 'away_name': % }`` """ tot = sum(self.total.values()) return { k: v/float(tot) for k,v in self.total.iteritems() } class ShootOut(ShotEventTallyBase):
[docs] """ Tallies shootout goals. Increments if * the play event inherits from :py:class:`.ShootOutGoal` """ def __init__(self): super(ShootOut, self).__init__( count_play=lambda play: isinstance(play.event, EV.ShootOutGoal) ) # doesn't fit nicely into ShotEventTallyBase framework # due to dual source nature, it doesn't quite fit class Score(ShotEventTallyBase):
[docs] """Tallies if a goal is scored. Also tracks shootout goals. Increments if * the play event inherits from :py:class:`.Goal` * the play event inherits from :py:class:`.ShootOutGoal` """ def __init__(self): self.shootout = ShootOut() def _count_play(self, play): if isinstance(play.event, EV.ShootOutEnd): self.__set_shootout_winner() return isinstance(play.event, EV.Goal) super(Score, self).__init__( count_play=lambda play: _count_play(self, play) ) def update(self, play):
[docs] self.shootout.update(play) super(Score, self).update(play) def initialize_teams(self, teams):
[docs] super(Score, self).initialize_teams(teams) self.shootout.initialize_teams(teams) def __set_shootout_winner(self):
t1 = self.shootout.teams[0] if len(self.shootout.teams) == 1: try: self.total[t1] += 1 except: self.total[t1] = 1 else: t2 = self.shootout.teams[1] if len(self.teams) != 2: self.teams = [t1, t2] t1wins = 1 if self.shootout.total[t1] > self.shootout.total[t2] else 0 self.total[t1] += t1wins self.total[t2] += 1-t1wins class Fenwick(ShotEventTallyBase):
[docs] """Tallies if a goal is scored. Also tracks shootout goals. Increments if * the play event inherits from :py:class:`.ShotAttempt` * the play event does not inherit from :py:class:`.Block` * play happened at even strength """ def __init__(self): self.score = Score() def _count_play(self, play): # if not the right event type, don't bother checking 'close' is_p_type = isinstance(play.event, EV.ShotAttempt) \ and not isinstance(play.event, EV.Block) \ and play.strength == St.Even close = False if is_p_type: scr = 0 if len(self.score.teams): t = self.score.teams[0] scr = self.score.total[t] if len(self.score.teams) > 1: t = self.score.teams[1] scr -= self.score.total[t] close = (scr <= 2 and play.period < 3) or (scr <= 1 and play.period >= 3) return is_p_type and close super(Fenwick, self).__init__( count_play=lambda play: _count_play(self, play) ) def update(self, play):
[docs] self.score.update(play) super(Fenwick, self).update(play) def initialize_teams(self, teams):
[docs] super(Fenwick, self).initialize_teams(teams) self.score.initialize_teams(teams) def share(self):
[docs] """ :returns: The Fenwick-share (% of unblocked even strength shot attempts) for each team :rtype: dict, ``{ 'home_name': %, 'away_name': % }`` """ tot = sum(self.total.values()) return { k: v/float(tot) for k,v in self.total.iteritems() }