nfldb module
Module nfldb provides command line tools and a library for maintaining and querying a relational database of play-by-play NFL data. The data is imported from nflgame, which in turn gets its data from a JSON feed on NFL.com's live GameCenter pages. This data includes, but is not limited to, game schedules, scores, rosters and play-by-play data for every preseason, regular season and postseason game dating back to 2009.
Here is a small teaser that shows how to use nfldb to find the top five passers in the 2012 regular season:
#!python
import nfldb
db = nfldb.connect()
q = nfldb.Query(db)
q.game(season_year=2012, season_type='Regular')
for pp in q.sort('passing_yds').limit(5).as_aggregate():
print pp.player, pp.passing_yds
And the output is:
[andrew@Liger ~] python2 top-five.py
Drew Brees (NO, QB) 5177
Matthew Stafford (DET, QB) 4965
Tony Romo (DAL, QB) 4903
Tom Brady (NE, QB) 4799
Matt Ryan (ATL, QB) 4719
In theory, both nfldb
and nflgame
provide access to the same data.
The difference is in the execution. In order to search data in nflgame,
a large JSON file needs to be read from disk and loaded into Python
data structures for each game. Conversely, nfldb's data is stored in
a relational database, which can be searched and retrieved faster
than nflgame by a few orders of magnitude. Moreover, the relational
organization of data in nfldb allows for a convenient
query interface to search NFL play data.
The database can be updated with real time data from active games by
running the nfldb-update
script included with this module as often
as you're comfortable pinging NFL.com. (N.B. The JSON data itself only
updates every 15 seconds, so running nfldb-update
faster than that
would be wasteful.) Roster updates are done automatically at a minimum
interval of 12 hours.
nfldb has comprehensive API documentation and a wiki with examples.
nfldb can be used in conjunction with nflvid to search and watch NFL game footage.
If you need help, please join us at our IRC channel #nflgame
on
FreeNode.
"""
Module nfldb provides command line tools and a library for maintaining
and querying a relational database of play-by-play NFL data. The data
is imported from [nflgame](https://github.com/BurntSushi/nflgame),
which in turn gets its data from a JSON feed on NFL.com's live
GameCenter pages. This data includes, but is not limited to, game
schedules, scores, rosters and play-by-play data for every preseason,
regular season and postseason game dating back to 2009.
Here is a small teaser that shows how to use nfldb to find the top five
passers in the 2012 regular season:
#!python
import nfldb
db = nfldb.connect()
q = nfldb.Query(db)
q.game(season_year=2012, season_type='Regular')
for pp in q.sort('passing_yds').limit(5).as_aggregate():
print pp.player, pp.passing_yds
And the output is:
[andrew@Liger ~] python2 top-five.py
Drew Brees (NO, QB) 5177
Matthew Stafford (DET, QB) 4965
Tony Romo (DAL, QB) 4903
Tom Brady (NE, QB) 4799
Matt Ryan (ATL, QB) 4719
In theory, both `nfldb` and `nflgame` provide access to the same data.
The difference is in the execution. In order to search data in nflgame,
a large JSON file needs to be read from disk and loaded into Python
data structures for each game. Conversely, nfldb's data is stored in
a relational database, which can be searched and retrieved faster
than nflgame by a few orders of magnitude. Moreover, the relational
organization of data in nfldb allows for a convenient
[query interface](http://goo.gl/Sd6MN2) to search NFL play data.
The database can be updated with real time data from active games by
running the `nfldb-update` script included with this module as often
as you're comfortable pinging NFL.com. (N.B. The JSON data itself only
updates every 15 seconds, so running `nfldb-update` faster than that
would be wasteful.) Roster updates are done automatically at a minimum
interval of 12 hours.
nfldb has [comprehensive API documentation](http://pdoc.burntsushi.net/nfldb)
and a [wiki with examples](https://github.com/BurntSushi/nfldb/wiki).
nfldb can be used in conjunction with
[nflvid](https://pypi.python.org/pypi/nflvid)
to
[search and watch NFL game footage](http://goo.gl/Mckaf0).
If you need help, please join us at our IRC channel `#nflgame` on
FreeNode.
"""
from __future__ import absolute_import, division, print_function
from nfldb.db import __pdoc__ as __db_pdoc__
from nfldb.db import api_version, connect, now, set_timezone, schema_version
from nfldb.db import Tx
from nfldb.query import __pdoc__ as __query_pdoc__
from nfldb.query import aggregate, current, guess_position, player_search
from nfldb.query import Query, QueryOR
from nfldb.team import standard_team
from nfldb.types import __pdoc__ as __types_pdoc__
from nfldb.types import stat_categories
from nfldb.types import Category, Clock, Enums, Drive, FieldPosition, Game
from nfldb.types import Play, Player, PlayPlayer, PossessionTime, Team
from nfldb.version import __pdoc__ as __version_pdoc__
from nfldb.version import __version__
__pdoc__ = __db_pdoc__
__pdoc__ = dict(__pdoc__, **__query_pdoc__)
__pdoc__ = dict(__pdoc__, **__types_pdoc__)
__pdoc__ = dict(__pdoc__, **__version_pdoc__)
# Export selected identifiers from sub-modules.
__all__ = [
# nfldb.db
'api_version', 'connect', 'now', 'set_timezone', 'schema_version',
'Tx',
# nfldb.query
'aggregate', 'current', 'guess_position', 'player_search',
'Query', 'QueryOR',
# nfldb.team
'standard_team',
# nfldb.types
'select_columns', 'stat_categories',
'Category', 'Clock', 'Enums', 'Drive', 'FieldPosition', 'Game',
'Play', 'Player', 'PlayPlayer', 'PossessionTime', 'Team',
# nfldb.version
'__version__',
]
Module variables
var __version__
The version of the installed nfldb module.
var api_version
The schema version that this library corresponds to. When the schema
version of the database is less than this value, connect
will
automatically update the schema to the latest version before doing
anything else.
var stat_categories
An ordered dictionary of every statistical category available for
play-by-play data. The keys are the category identifier (e.g.,
passing_yds
) and the values are Category
objects.
Functions
def QueryOR(
db)
Creates a disjunctive Query
object, where every
condition is combined disjunctively. Namely, it is an alias for
nfldb.Query(db, orelse=True)
.
def QueryOR(db):
"""
Creates a disjunctive `nfldb.Query` object, where every
condition is combined disjunctively. Namely, it is an alias for
`nfldb.Query(db, orelse=True)`.
"""
return Query(db, orelse=True)
def aggregate(
objs)
Given any collection of Python objects that provide a
play_players
attribute, aggregate
will return a list of
PlayPlayer
objects with statistics aggregated (summed) over each
player. (As a special case, if an element in objs
is itself a
PlayPlayer
object, then it is used and a play_players
attribute is not rquired.)
For example, objs
could be a mixed list of Game
and
Play
objects.
The order of the list returned is stable with respect to the
order of players obtained from each element in objs
.
It is recommended to use aggregate
and
as_aggregate
instead of this function since summing
statistics in the database is much faster. However, this function
is provided for aggregation that cannot be expressed by the query
interface.
def aggregate(objs):
"""
Given any collection of Python objects that provide a
`play_players` attribute, `aggregate` will return a list of
`PlayPlayer` objects with statistics aggregated (summed) over each
player. (As a special case, if an element in `objs` is itself a
`nfldb.PlayPlayer` object, then it is used and a `play_players`
attribute is not rquired.)
For example, `objs` could be a mixed list of `nfldb.Game` and
`nfldb.Play` objects.
The order of the list returned is stable with respect to the
order of players obtained from each element in `objs`.
It is recommended to use `nfldb.Query.aggregate` and
`nfldb.Query.as_aggregate` instead of this function since summing
statistics in the database is much faster. However, this function
is provided for aggregation that cannot be expressed by the query
interface.
"""
summed = OrderedDict()
for obj in objs:
pps = [obj] if isinstance(obj, types.PlayPlayer) else obj.play_players
for pp in pps:
if pp.player_id not in summed:
summed[pp.player_id] = pp._copy()
else:
summed[pp.player_id]._add(pp)
return summed.values()
def connect(
database=None, user=None, password=None, host=None, port=None, timezone=None, config_path='')
Returns a psycopg2._psycopg.connection
object from the
psycopg2.connect
function. If database is None
, then connect
will look for a configuration file using nfldb.config
with
config_path
. Otherwise, the connection will use the parameters
given.
If database
is None
and no config file can be found, then an
IOError
exception is raised.
This function will also compare the current schema version of the
database against the API version api_version
and assert
that they are equivalent. If the schema library version is less
than the the API version, then the schema will be automatically
upgraded. If the schema version is newer than the library version,
then this function will raise an assertion error. An assertion
error will also be raised if the schema version is 0 and the
database is not empty.
N.B. The timezone
parameter should be set to a value that
PostgreSQL will accept. Select from the pg_timezone_names
view
to get a list of valid time zones.
def connect(database=None, user=None, password=None, host=None, port=None,
timezone=None, config_path=''):
"""
Returns a `psycopg2._psycopg.connection` object from the
`psycopg2.connect` function. If database is `None`, then `connect`
will look for a configuration file using `nfldb.config` with
`config_path`. Otherwise, the connection will use the parameters
given.
If `database` is `None` and no config file can be found, then an
`IOError` exception is raised.
This function will also compare the current schema version of the
database against the API version `nfldb.api_version` and assert
that they are equivalent. If the schema library version is less
than the the API version, then the schema will be automatically
upgraded. If the schema version is newer than the library version,
then this function will raise an assertion error. An assertion
error will also be raised if the schema version is 0 and the
database is not empty.
N.B. The `timezone` parameter should be set to a value that
PostgreSQL will accept. Select from the `pg_timezone_names` view
to get a list of valid time zones.
"""
if database is None:
conf, tried = config(config_path=config_path)
if conf is None:
raise IOError("Could not find valid configuration file. "
"Tried the following paths: %s" % tried)
timezone, database = conf['timezone'], conf['database']
user, password = conf['user'], conf['password']
host, port = conf['host'], conf['port']
conn = psycopg2.connect(database=database, user=user, password=password,
host=host, port=port)
# Start the migration. Make sure if this is the initial setup that
# the DB is empty.
sversion = schema_version(conn)
assert sversion <= api_version, \
'Library with version %d is older than the schema with version %d' \
% (api_version, sversion)
assert sversion > 0 or (sversion == 0 and _is_empty(conn)), \
'Schema has version 0 but is not empty.'
set_timezone(conn, 'UTC')
_migrate(conn, api_version)
if timezone is not None:
set_timezone(conn, timezone)
# Bind SQL -> Python casting functions.
from nfldb.types import Clock, _Enum, Enums, FieldPosition, PossessionTime
_bind_type(conn, 'game_phase', _Enum._pg_cast(Enums.game_phase))
_bind_type(conn, 'season_phase', _Enum._pg_cast(Enums.season_phase))
_bind_type(conn, 'game_day', _Enum._pg_cast(Enums.game_day))
_bind_type(conn, 'player_pos', _Enum._pg_cast(Enums.player_pos))
_bind_type(conn, 'player_status', _Enum._pg_cast(Enums.player_status))
_bind_type(conn, 'game_time', Clock._pg_cast)
_bind_type(conn, 'pos_period', PossessionTime._pg_cast)
_bind_type(conn, 'field_pos', FieldPosition._pg_cast)
return conn
def current(
db)
Returns a triple of season_phase
, season year and week
corresponding to values that nfldb
thinks are current.
Note that this only queries the database. Only the nfldb-update
script fetches the current state from NFL.com.
The values retrieved may be None
if the season is over or if they
haven't been updated yet by the nfldb-update
script.
def current(db):
"""
Returns a triple of `nfldb.Enums.season_phase`, season year and week
corresponding to values that `nfldb` thinks are current.
Note that this only queries the database. Only the `nfldb-update`
script fetches the current state from NFL.com.
The values retrieved may be `None` if the season is over or if they
haven't been updated yet by the `nfldb-update` script.
"""
with Tx(db, factory=tuple_cursor) as cursor:
cursor.execute('SELECT season_type, season_year, week FROM meta')
return cursor.fetchone()
return tuple([None] * 3)
def guess_position(
pps)
Given a list of PlayPlayer
objects for the same player,
guess the position of the player based on the statistics recorded.
Note that this only distinguishes the offensive positions of QB, RB, WR, P and K. If defensive stats are detected, then the position returned defaults to LB.
The algorithm used is simple majority vote. Whichever position is
the most common is returned (and this may be UNK
).
def guess_position(pps):
"""
Given a list of `nfldb.PlayPlayer` objects for the same player,
guess the position of the player based on the statistics recorded.
Note that this only distinguishes the offensive positions of QB,
RB, WR, P and K. If defensive stats are detected, then the position
returned defaults to LB.
The algorithm used is simple majority vote. Whichever position is
the most common is returned (and this may be `UNK`).
"""
if len(pps) == 0:
return types.Enums.player_pos.UNK
counts = defaultdict(int)
for pp in pps:
counts[pp.guess_position] += 1
return max(counts.items(), key=lambda (_, count): count)[0]
def now(
)
Returns the current date/time in UTC as a datetime.datetime
object. It can be used to compare against date/times in any of the
nfldb
objects without worrying about timezones.
def now():
"""
Returns the current date/time in UTC as a `datetime.datetime`
object. It can be used to compare against date/times in any of the
`nfldb` objects without worrying about timezones.
"""
return datetime.datetime.now(pytz.utc)
def player_search(
db, full_name, team=None, position=None, limit=1, soundex=False)
Given a database handle and a player's full name, this function searches the database for players with full names similar to the one given. Similarity is measured by the Levenshtein distance, or by Soundex similarity.
Results are returned as tuples. The first element is the is a
Player
object and the second element is the Levenshtein
(or Soundex) distance. When limit
is 1
(the default), then the
return value is a tuple. When limit
is more than 1
, then the
return value is a list of tuples.
If no results are found, then (None, None)
is returned when
limit == 1
or the empty list is returned when limit > 1
.
If team
is not None
, then only players currently on the
team provided will be returned. Any players with an unknown team
are therefore omitted.
If position
is not None
, then only players currently
at that position will be returned. Any players with an unknown
position are therefore omitted.
In order to use this function, the PostgreSQL levenshtein
function must be available. If running this functions gives
you an error about "No function matches the given name and
argument types", then you can install the levenshtein
function
into your database by running the SQL query CREATE EXTENSION
fuzzystrmatch
as a superuser like postgres
. For example:
#!bash
psql -U postgres -c 'CREATE EXTENSION fuzzystrmatch;' nfldb
Note that enabled the fuzzystrmatch
extension also provides
functions for comparing using Soundex.
def player_search(db, full_name, team=None, position=None,
limit=1, soundex=False):
"""
Given a database handle and a player's full name, this function
searches the database for players with full names *similar* to the
one given. Similarity is measured by the
[Levenshtein distance](http://en.wikipedia.org/wiki/Levenshtein_distance),
or by [Soundex similarity](http://en.wikipedia.org/wiki/Soundex).
Results are returned as tuples. The first element is the is a
`nfldb.Player` object and the second element is the Levenshtein
(or Soundex) distance. When `limit` is `1` (the default), then the
return value is a tuple. When `limit` is more than `1`, then the
return value is a list of tuples.
If no results are found, then `(None, None)` is returned when
`limit == 1` or the empty list is returned when `limit > 1`.
If `team` is not `None`, then only players **currently** on the
team provided will be returned. Any players with an unknown team
are therefore omitted.
If `position` is not `None`, then only players **currently**
at that position will be returned. Any players with an unknown
position are therefore omitted.
In order to use this function, the PostgreSQL `levenshtein`
function must be available. If running this functions gives
you an error about "No function matches the given name and
argument types", then you can install the `levenshtein` function
into your database by running the SQL query `CREATE EXTENSION
fuzzystrmatch` as a superuser like `postgres`. For example:
#!bash
psql -U postgres -c 'CREATE EXTENSION fuzzystrmatch;' nfldb
Note that enabled the `fuzzystrmatch` extension also provides
functions for comparing using Soundex.
"""
assert isinstance(limit, int) and limit >= 1
if soundex:
# Careful, soundex distances are sorted in reverse of Levenshtein
# distances.
# Difference yields an integer in [0, 4].
# A 4 is an exact match.
fuzzy = 'difference(full_name, %s)'
q = '''
SELECT {columns}
FROM player
WHERE {where}
ORDER BY distance DESC LIMIT {limit}
'''
else:
fuzzy = 'levenshtein(full_name, %s)'
q = '''
SELECT {columns}
FROM player
WHERE {where}
ORDER BY distance ASC LIMIT {limit}
'''
qteam, qposition = '', ''
results = []
with Tx(db) as cursor:
if team is not None:
qteam = cursor.mogrify('team = %s', (team,))
if position is not None:
qposition = cursor.mogrify('position = %s', (position,))
fuzzy_filled = cursor.mogrify(fuzzy, (full_name,))
columns = types.Player._sql_select_fields(types.Player.sql_fields())
columns.append('%s AS distance' % fuzzy_filled)
q = q.format(
columns=', '.join(columns),
where=sql.ands(fuzzy_filled + ' IS NOT NULL', qteam, qposition),
limit=limit)
cursor.execute(q, (full_name,))
for row in cursor.fetchall():
r = (types.Player.from_row_dict(db, row), row['distance'])
results.append(r)
if limit == 1:
if len(results) == 0:
return (None, None)
return results[0]
return results
def schema_version(
conn)
Returns the schema version of the given database. If the version
is not stored in the database, then 0
is returned.
def schema_version(conn):
"""
Returns the schema version of the given database. If the version
is not stored in the database, then `0` is returned.
"""
with Tx(conn) as c:
try:
c.execute('SELECT version FROM meta LIMIT 1', ['version'])
except psycopg2.ProgrammingError:
return 0
if c.rowcount == 0:
return 0
return c.fetchone()['version']
def set_timezone(
conn, timezone)
Sets the timezone for which all datetimes will be displayed
as. Valid values are exactly the same set of values accepted
by PostgreSQL. (Select from the pg_timezone_names
view to
get a list of valid time zones.)
Note that all datetimes are stored in UTC. This setting only affects how datetimes are viewed from select queries.
def set_timezone(conn, timezone):
"""
Sets the timezone for which all datetimes will be displayed
as. Valid values are exactly the same set of values accepted
by PostgreSQL. (Select from the `pg_timezone_names` view to
get a list of valid time zones.)
Note that all datetimes are stored in UTC. This setting only
affects how datetimes are viewed from select queries.
"""
with Tx(conn) as c:
c.execute('SET timezone = %s', (timezone,))
def standard_team(
team)
Returns a standard abbreviation when team corresponds to a team
known by nfldb (case insensitive). If no team can be found, then
"UNK"
is returned.
def standard_team(team):
"""
Returns a standard abbreviation when team corresponds to a team
known by nfldb (case insensitive). If no team can be found, then
`"UNK"` is returned.
"""
if not team or team.lower() == 'new york':
return 'UNK'
team = team.lower()
for teams in [teams2, teams1]:
for variants in teams:
for variant in variants:
if team == variant.lower():
return variants[0]
return 'UNK'
Classes
class Category
Represents meta data about a statistical category. This includes the category's scope, GSIS identifier, name and short description.
class Category (object):
"""
Represents meta data about a statistical category. This includes
the category's scope, GSIS identifier, name and short description.
"""
__slots__ = ['category_id', 'gsis_number', 'category_type',
'is_real', 'description']
def __init__(self, category_id, gsis_number, category_type,
is_real, description):
self.category_id = category_id
"""
A unique name for this category.
"""
self.gsis_number = gsis_number
"""
A unique numeric identifier for this category.
"""
self.category_type = category_type
"""
The scope of this category represented with
`nfldb.Enums.category_scope`.
"""
self.is_real = is_real
"""
Whether this statistic is a real number or not. Currently,
only the `defense_sk` statistic has `Category.is_real` set to
`True`.
"""
self.description = description
"""
A free-form text description of this category.
"""
@property
def _sql_field(self):
"""
The SQL definition of this column. Statistics are always
NOT NULL and have a default value of `0`.
When `Category.is_real` is `True`, then the SQL type is `real`.
Otherwise, it's `smallint`.
"""
typ = 'real' if self.is_real else 'smallint'
default = '0.0' if self.is_real else '0'
return '%s %s NOT NULL DEFAULT %s' % (self.category_id, typ, default)
def __str__(self):
return self.category_id
def __eq__(self, other):
return self.category_id == other.category_id
Ancestors (in MRO)
- Category
- __builtin__.object
Instance variables
var category_id
A unique name for this category.
var description
A free-form text description of this category.
var gsis_number
A unique numeric identifier for this category.
var is_real
Whether this statistic is a real number or not. Currently,
only the defense_sk
statistic has Category.is_real
set to
True
.
Methods
def __init__(
self, category_id, gsis_number, category_type, is_real, description)
def __init__(self, category_id, gsis_number, category_type,
is_real, description):
self.category_id = category_id
"""
A unique name for this category.
"""
self.gsis_number = gsis_number
"""
A unique numeric identifier for this category.
"""
self.category_type = category_type
"""
The scope of this category represented with
`nfldb.Enums.category_scope`.
"""
self.is_real = is_real
"""
Whether this statistic is a real number or not. Currently,
only the `defense_sk` statistic has `Category.is_real` set to
`True`.
"""
self.description = description
"""
A free-form text description of this category.
"""
class Clock
Represents a single point in time during a game. This includes the quarter and the game clock time in addition to other phases of the game such as before the game starts, half time, overtime and when the game ends.
Note that the clock time does not uniquely identify a play, since not all plays consume time on the clock. (e.g., A two point conversion.)
This class defines a total ordering on clock times. Namely, c1 < c2 if and only if c2 is closer to the end of the game than c1.
class Clock (object):
"""
Represents a single point in time during a game. This includes the
quarter and the game clock time in addition to other phases of the
game such as before the game starts, half time, overtime and when
the game ends.
Note that the clock time does not uniquely identify a play, since
not all plays consume time on the clock. (e.g., A two point
conversion.)
This class defines a total ordering on clock times. Namely, c1 < c2
if and only if c2 is closer to the end of the game than c1.
"""
_nonqs = (Enums.game_phase.Pregame, Enums.game_phase.Half,
Enums.game_phase.Final)
"""
The phases of the game that do not have a time component.
"""
_phase_max = 900
"""
The maximum number of seconds in a game phase.
"""
@staticmethod
def from_str(phase, clock):
"""
Introduces a new `nfldb.Clock` object given strings of the game
phase and the clock. `phase` may be one of the values in the
`nfldb.Enums.game_phase` enumeration. `clock` must be a clock
string in the format `MM:SS`, e.g., `4:01` corresponds to a
game phase with 4 minutes and 1 second remaining.
"""
assert getattr(Enums.game_phase, phase, None) is not None, \
'"%s" is not a valid game phase. choose one of %s' \
% (phase, map(str, Enums.game_phase))
minutes, seconds = map(int, clock.split(':', 1))
elapsed = Clock._phase_max - ((minutes * 60) + seconds)
return Clock(Enums.game_phase[phase], int(elapsed))
@staticmethod
def _pg_cast(sqlv, cursor):
"""
Casts a SQL string of the form `(game_phase, elapsed)` to a
`nfldb.Clock` object.
"""
phase, elapsed = map(str.strip, sqlv[1:-1].split(','))
return Clock(Enums.game_phase[phase], int(elapsed))
def __init__(self, phase, elapsed):
"""
Introduces a new `nfldb.Clock` object. `phase` should
be a value from the `nfldb.Enums.game_phase` enumeration
while `elapsed` should be the number of seconds elapsed in
the `phase`. Note that `elapsed` is only applicable when
`phase` is a quarter (including overtime). In all other
cases, it will be set to `0`.
`elapsed` should be in the range `[0, 900]` where `900`
corresponds to the clock time `0:00` and `0` corresponds
to the clock time `15:00`.
"""
assert isinstance(phase, Enums.game_phase)
assert 0 <= elapsed <= Clock._phase_max
if phase in Clock._nonqs:
elapsed = 0
self.phase = phase
"""
The phase represented by this clock object. It is guaranteed
to have type `nfldb.Enums.game_phase`.
"""
self.elapsed = elapsed
"""
The number of seconds remaining in this clock's phase of the
game. It is always set to `0` whenever the phase is not a
quarter in the game.
"""
def add_seconds(self, seconds):
"""
Adds the number of seconds given to the current clock time
and returns a new clock time. `seconds` may be positive
or negative. If a boundary is reached (e.g., `Pregame` or
`Final`), then subtracting or adding more seconds has no
effect.
"""
elapsed = self.elapsed + seconds
phase_jump = 0
if elapsed < 0 or elapsed > Clock._phase_max:
phase_jump = elapsed // Clock._phase_max
# Always skip over halftime.
phase_val = self.phase.value + phase_jump
if self.phase.value <= Enums.game_phase.Half.value <= phase_val:
phase_val += 1
elif phase_val <= Enums.game_phase.Half.value <= self.phase.value:
phase_val -= 1
try:
phase = Enums.game_phase(phase_val)
return Clock(phase, elapsed % (1 + Clock._phase_max))
except ValueError:
if phase_val < 0:
return Clock(Enums.game_phase.Pregame, 0)
return Clock(Enums.game_phase.Final, 0)
@property
def minutes(self):
"""
If the clock has a time component, then the number of whole
minutes **left in this phase** is returned. Otherwise, `0` is
returned.
"""
if self.elapsed == 0:
return 0
return (Clock._phase_max - self.elapsed) // 60
@property
def seconds(self):
"""
If the clock has a time component, then the number of seconds
**left in this phase** is returned. Otherwise, `0` is returned.
"""
if self.elapsed == 0:
return 0
return (Clock._phase_max - self.elapsed) % 60
def __str__(self):
phase = self.phase
if phase in Clock._nonqs:
return phase.name
else:
return '%s %02d:%02d' % (phase.name, self.minutes, self.seconds)
def __lt__(self, o):
if self.__class__ is not o.__class__:
return NotImplemented
return (self.phase, self.elapsed) < (o.phase, o.elapsed)
def __eq__(self, o):
if self.__class__ is not o.__class__:
return NotImplemented
return self.phase == o.phase and self.elapsed == o.elapsed
def __conform__(self, proto):
if proto is ISQLQuote:
return AsIs("ROW('%s', %d)::game_time"
% (self.phase.name, self.elapsed))
return None
Ancestors (in MRO)
- Clock
- __builtin__.object
Static methods
def from_str(
phase, clock)
Introduces a new Clock
object given strings of the game
phase and the clock. phase
may be one of the values in the
game_phase
enumeration. clock
must be a clock
string in the format MM:SS
, e.g., 4:01
corresponds to a
game phase with 4 minutes and 1 second remaining.
@staticmethod
def from_str(phase, clock):
"""
Introduces a new `nfldb.Clock` object given strings of the game
phase and the clock. `phase` may be one of the values in the
`nfldb.Enums.game_phase` enumeration. `clock` must be a clock
string in the format `MM:SS`, e.g., `4:01` corresponds to a
game phase with 4 minutes and 1 second remaining.
"""
assert getattr(Enums.game_phase, phase, None) is not None, \
'"%s" is not a valid game phase. choose one of %s' \
% (phase, map(str, Enums.game_phase))
minutes, seconds = map(int, clock.split(':', 1))
elapsed = Clock._phase_max - ((minutes * 60) + seconds)
return Clock(Enums.game_phase[phase], int(elapsed))
Instance variables
var elapsed
The number of seconds remaining in this clock's phase of the
game. It is always set to 0
whenever the phase is not a
quarter in the game.
var minutes
If the clock has a time component, then the number of whole
minutes left in this phase is returned. Otherwise, 0
is
returned.
var seconds
If the clock has a time component, then the number of seconds
left in this phase is returned. Otherwise, 0
is returned.
Methods
def __init__(
self, phase, elapsed)
Introduces a new Clock
object. phase
should
be a value from the game_phase
enumeration
while elapsed
should be the number of seconds elapsed in
the phase
. Note that elapsed
is only applicable when
phase
is a quarter (including overtime). In all other
cases, it will be set to 0
.
elapsed
should be in the range [0, 900]
where 900
corresponds to the clock time 0:00
and 0
corresponds
to the clock time 15:00
.
def __init__(self, phase, elapsed):
"""
Introduces a new `nfldb.Clock` object. `phase` should
be a value from the `nfldb.Enums.game_phase` enumeration
while `elapsed` should be the number of seconds elapsed in
the `phase`. Note that `elapsed` is only applicable when
`phase` is a quarter (including overtime). In all other
cases, it will be set to `0`.
`elapsed` should be in the range `[0, 900]` where `900`
corresponds to the clock time `0:00` and `0` corresponds
to the clock time `15:00`.
"""
assert isinstance(phase, Enums.game_phase)
assert 0 <= elapsed <= Clock._phase_max
if phase in Clock._nonqs:
elapsed = 0
self.phase = phase
"""
The phase represented by this clock object. It is guaranteed
to have type `nfldb.Enums.game_phase`.
"""
self.elapsed = elapsed
"""
The number of seconds remaining in this clock's phase of the
game. It is always set to `0` whenever the phase is not a
quarter in the game.
"""
def add_seconds(
self, seconds)
Adds the number of seconds given to the current clock time
and returns a new clock time. seconds
may be positive
or negative. If a boundary is reached (e.g., Pregame
or
Final
), then subtracting or adding more seconds has no
effect.
def add_seconds(self, seconds):
"""
Adds the number of seconds given to the current clock time
and returns a new clock time. `seconds` may be positive
or negative. If a boundary is reached (e.g., `Pregame` or
`Final`), then subtracting or adding more seconds has no
effect.
"""
elapsed = self.elapsed + seconds
phase_jump = 0
if elapsed < 0 or elapsed > Clock._phase_max:
phase_jump = elapsed // Clock._phase_max
# Always skip over halftime.
phase_val = self.phase.value + phase_jump
if self.phase.value <= Enums.game_phase.Half.value <= phase_val:
phase_val += 1
elif phase_val <= Enums.game_phase.Half.value <= self.phase.value:
phase_val -= 1
try:
phase = Enums.game_phase(phase_val)
return Clock(phase, elapsed % (1 + Clock._phase_max))
except ValueError:
if phase_val < 0:
return Clock(Enums.game_phase.Pregame, 0)
return Clock(Enums.game_phase.Final, 0)
class Drive
Represents a single drive in an NFL game. Each drive has an assortment of meta data, possibly including the start and end times, the start and end field positions, the result of the drive, the number of penalties and first downs, and more.
Each drive corresponds to zero or more plays. A drive usually corresponds to at least one play, but if the game is active, there exist valid ephemeral states where a drive has no plays.
class Drive (SQLDrive):
"""
Represents a single drive in an NFL game. Each drive has an
assortment of meta data, possibly including the start and end
times, the start and end field positions, the result of the drive,
the number of penalties and first downs, and more.
Each drive corresponds to **zero or more** plays. A drive usually
corresponds to at least one play, but if the game is active, there
exist valid ephemeral states where a drive has no plays.
"""
__slots__ = SQLDrive.sql_fields() + ['_db', '_game', '_plays']
@staticmethod
def _from_nflgame(db, g, d):
"""
Given `g` as a `nfldb.Game` object and `d` as a
`nflgame.game.Drive` object, `_from_nflgame` converts `d` to a
`nfldb.Drive` object.
Generally, this function should not be used. It is called
automatically by `nfldb.Game._from_nflgame`.
"""
dbd = Drive(db)
dbd.gsis_id = g.gsis_id
dbd.drive_id = d.drive_num
dbd.start_time = _nflgame_clock(d.time_start)
dbd.start_field = FieldPosition(getattr(d.field_start, 'offset', None))
dbd.end_field = FieldPosition(d.field_end.offset)
dbd.end_time = _nflgame_clock(d.time_end)
dbd.pos_team = nfldb.team.standard_team(d.team)
dbd.pos_time = PossessionTime(d.pos_time.total_seconds())
dbd.first_downs = d.first_downs
dbd.result = d.result
dbd.penalty_yards = d.penalty_yds
dbd.yards_gained = d.total_yds
dbd.play_count = d.play_cnt
dbd._game = g
candidates = []
for play in d.plays:
candidates.append(Play._from_nflgame(db, dbd, play))
# At this point, some plays don't have valid game times. Fix it!
# If we absolutely cannot fix it, drop the play. Maintain integrity!
dbd._plays = []
for play in candidates:
if play.time is None:
next = _next_play_with(candidates, play, lambda p: p.time)
play.time = _play_time(dbd, play, next)
if play.time is not None:
dbd._plays.append(play)
dbd._plays.sort(key=lambda p: p.play_id)
return dbd
@staticmethod
def from_id(db, gsis_id, drive_id):
"""
Given a GSIS identifier (e.g., `2012090500`) as a string
and a integer drive id, this returns a `nfldb.Drive` object
corresponding to the given identifiers.
If no corresponding drive is found, then `None` is returned.
"""
import nfldb.query
q = nfldb.query.Query(db)
q.drive(gsis_id=gsis_id, drive_id=drive_id).limit(1)
drives = q.as_drives()
if len(drives) == 0:
return None
return drives[0]
@staticmethod
def fill_games(db, drives):
"""
Given a list of `drives`, fill all of their `game` attributes
using as few queries as possible.
"""
_fill(db, Game, drives, '_game')
def __init__(self, db):
"""
Creates a new and empty `nfldb.Drive` object with the given
database connection.
This constructor should not be used by clients. Instead, you
should get `nfldb.Drive` objects from `nfldb.Query` or from one
of the other constructors, like `nfldb.Drive.from_id` or
`nfldb.Drive.from_row_dict`. (The latter is useful only if you're
writing your own SQL queries.)
"""
self._db = db
self._game = None
self._plays = None
self.gsis_id = None
"""
The GSIS identifier for the game that this drive belongs to.
"""
self.drive_id = None
"""
The numeric drive identifier for this drive. It may be
interpreted as a sequence number.
"""
self.start_field = None
"""
The starting field position of this drive represented
with `nfldb.FieldPosition`.
"""
self.start_time = None
"""
The starting clock time of this drive, represented with
`nfldb.Clock`.
"""
self.end_field = None
"""
The ending field position of this drive represented with
`nfldb.FieldPosition`.
"""
self.end_time = None
"""
The ending clock time of this drive, represented with
`nfldb.Clock`.
"""
self.pos_team = None
"""
The team in possession during this drive, represented as
a team abbreviation string. Use the `nfldb.Team` constructor
to get more information on a team.
"""
self.pos_time = None
"""
The possession time of this drive, represented with
`nfldb.PossessionTime`.
"""
self.first_downs = None
"""
The number of first downs that occurred in this drive.
"""
self.result = None
"""
A freeform text field straight from NFL's GameCenter data that
sometimes contains the result of a drive (e.g., `Touchdown`).
"""
self.penalty_yards = None
"""
The number of yards lost or gained from penalties in this
drive.
"""
self.yards_gained = None
"""
The total number of yards gained or lost in this drive.
"""
self.play_count = None
"""
The total number of plays executed by the offense in this
drive.
"""
self.time_inserted = None
"""The date and time that this drive was added."""
self.time_updated = None
"""The date and time that this drive was last updated."""
@property
def game(self):
"""
Returns the `nfldb.Game` object that contains this drive. The
game is retrieved from the database if it hasn't been already.
"""
if self._game is None:
return Game.from_id(self._db, self.gsis_id)
return self._game
@property
def plays(self):
"""
A list of all `nfldb.Play`s in this drive. They are
automatically retrieved from the database if they haven't been
already.
If there are no plays in the drive, then an empty list is
returned.
"""
if self._plays is None:
import nfldb.query
q = nfldb.query.Query(self._db)
q.sort([('time', 'asc'), ('play_id', 'asc')])
q.play(gsis_id=self.gsis_id, drive_id=self.drive_id)
self._plays = q.as_plays()
for p in self._plays:
p._drive = self
return self._plays
def score(self, before=False):
"""
Returns the score of the game immediately after this drive as a
tuple of the form `(home_score, away_score)`.
If `before` is `True`, then the score will *not* include this
drive.
"""
if before:
return self.game.score_at_time(self.start_time)
else:
return self.game.score_at_time(self.end_time)
@property
def play_players(self):
"""
A list of `nfldb.PlayPlayer` objects in this drive. Data is
retrieved from the database if it hasn't been already.
"""
pps = []
for play in self.plays:
for pp in play.play_players:
pps.append(pp)
return pps
def _save(self, cursor):
super(Drive, self)._save(cursor)
if not self._plays:
return
# Remove any plays that are stale.
cursor.execute('''
DELETE FROM play
WHERE gsis_id = %s AND drive_id = %s AND NOT (play_id = ANY (%s))
''', (self.gsis_id, self.drive_id, [p.play_id for p in self._plays]))
for play in (self._plays or []):
play._save(cursor)
def __str__(self):
s = '[%-12s] %-3s from %-6s to %-6s '
s += '(lasted %s - %s to %s)'
return s % (
self.result, self.pos_team, self.start_field, self.end_field,
self.pos_time, self.start_time, self.end_time,
)
Ancestors (in MRO)
- Drive
- nfldb.types.SQLDrive
- nfldb.sql.Entity
- __builtin__.object
Static methods
def fill_games(
db, drives)
Given a list of drives
, fill all of their game
attributes
using as few queries as possible.
@staticmethod
def fill_games(db, drives):
"""
Given a list of `drives`, fill all of their `game` attributes
using as few queries as possible.
"""
_fill(db, Game, drives, '_game')
def from_id(
db, gsis_id, drive_id)
Given a GSIS identifier (e.g.,<PHONE_NUMBER>
) as a string
and a integer drive id, this returns a Drive
object
corresponding to the given identifiers.
If no corresponding drive is found, then None
is returned.
@staticmethod
def from_id(db, gsis_id, drive_id):
"""
Given a GSIS identifier (e.g., `2012090500`) as a string
and a integer drive id, this returns a `nfldb.Drive` object
corresponding to the given identifiers.
If no corresponding drive is found, then `None` is returned.
"""
import nfldb.query
q = nfldb.query.Query(db)
q.drive(gsis_id=gsis_id, drive_id=drive_id).limit(1)
drives = q.as_drives()
if len(drives) == 0:
return None
return drives[0]
Instance variables
var drive_id
The numeric drive identifier for this drive. It may be interpreted as a sequence number.
var first_downs
The number of first downs that occurred in this drive.
var game
Returns the Game
object that contains this drive. The
game is retrieved from the database if it hasn't been already.
var gsis_id
The GSIS identifier for the game that this drive belongs to.
var penalty_yards
The number of yards lost or gained from penalties in this drive.
var play_count
The total number of plays executed by the offense in this drive.
var play_players
A list of PlayPlayer
objects in this drive. Data is
retrieved from the database if it hasn't been already.
var plays
A list of all Play
s in this drive. They are
automatically retrieved from the database if they haven't been
already.
If there are no plays in the drive, then an empty list is returned.
var pos_team
The team in possession during this drive, represented as
a team abbreviation string. Use the Team
constructor
to get more information on a team.
var result
A freeform text field straight from NFL's GameCenter data that
sometimes contains the result of a drive (e.g., Touchdown
).
var time_inserted
The date and time that this drive was added.
var time_updated
The date and time that this drive was last updated.
var yards_gained
The total number of yards gained or lost in this drive.
Methods
def __init__(
self, db)
Creates a new and empty Drive
object with the given
database connection.
This constructor should not be used by clients. Instead, you
should get Drive
objects from Query
or from one
of the other constructors, like from_id
or
from_row_dict
. (The latter is useful only if you're
writing your own SQL queries.)
def __init__(self, db):
"""
Creates a new and empty `nfldb.Drive` object with the given
database connection.
This constructor should not be used by clients. Instead, you
should get `nfldb.Drive` objects from `nfldb.Query` or from one
of the other constructors, like `nfldb.Drive.from_id` or
`nfldb.Drive.from_row_dict`. (The latter is useful only if you're
writing your own SQL queries.)
"""
self._db = db
self._game = None
self._plays = None
self.gsis_id = None
"""
The GSIS identifier for the game that this drive belongs to.
"""
self.drive_id = None
"""
The numeric drive identifier for this drive. It may be
interpreted as a sequence number.
"""
self.start_field = None
"""
The starting field position of this drive represented
with `nfldb.FieldPosition`.
"""
self.start_time = None
"""
The starting clock time of this drive, represented with
`nfldb.Clock`.
"""
self.end_field = None
"""
The ending field position of this drive represented with
`nfldb.FieldPosition`.
"""
self.end_time = None
"""
The ending clock time of this drive, represented with
`nfldb.Clock`.
"""
self.pos_team = None
"""
The team in possession during this drive, represented as
a team abbreviation string. Use the `nfldb.Team` constructor
to get more information on a team.
"""
self.pos_time = None
"""
The possession time of this drive, represented with
`nfldb.PossessionTime`.
"""
self.first_downs = None
"""
The number of first downs that occurred in this drive.
"""
self.result = None
"""
A freeform text field straight from NFL's GameCenter data that
sometimes contains the result of a drive (e.g., `Touchdown`).
"""
self.penalty_yards = None
"""
The number of yards lost or gained from penalties in this
drive.
"""
self.yards_gained = None
"""
The total number of yards gained or lost in this drive.
"""
self.play_count = None
"""
The total number of plays executed by the offense in this
drive.
"""
self.time_inserted = None
"""The date and time that this drive was added."""
self.time_updated = None
"""The date and time that this drive was last updated."""
def from_row_dict(
cls, db, row)
Introduces a new entity object from a full SQL row result from
the entity's tables. (i.e., row
is a dictionary mapping
column to value.) Note that the column names must be of the
form '{entity_name}_{column_name}'. For example, in the game
table, the gsis_id
column must be named game_gsis_id
in
row
.
@classmethod
def from_row_dict(cls, db, row):
"""
Introduces a new entity object from a full SQL row result from
the entity's tables. (i.e., `row` is a dictionary mapping
column to value.) Note that the column names must be of the
form '{entity_name}_{column_name}'. For example, in the `game`
table, the `gsis_id` column must be named `game_gsis_id` in
`row`.
"""
obj = cls(db)
seta = setattr
prefix = cls._sql_primary_table() + '_'
slice_from = len(prefix)
for k in row:
if k.startswith(prefix):
seta(obj, k[slice_from:], row[k])
return obj
def from_row_tuple(
cls, db, t)
Given a tuple t
corresponding to a result from a SELECT query,
this will construct a new instance for this entity. Note that
the tuple t
must be in exact correspondence with the columns
returned by nfldb.Entity.sql_fields
.
@classmethod
def from_row_tuple(cls, db, t):
"""
Given a tuple `t` corresponding to a result from a SELECT query,
this will construct a new instance for this entity. Note that
the tuple `t` must be in *exact* correspondence with the columns
returned by `nfldb.Entity.sql_fields`.
"""
cols = cls.sql_fields()
seta = setattr
obj = cls(db)
for i, field in enumerate(cols):
seta(obj, field, t[i])
return obj
def score(
self, before=False)
Returns the score of the game immediately after this drive as a
tuple of the form (home_score, away_score)
.
If before
is True
, then the score will not include this
drive.
def score(self, before=False):
"""
Returns the score of the game immediately after this drive as a
tuple of the form `(home_score, away_score)`.
If `before` is `True`, then the score will *not* include this
drive.
"""
if before:
return self.game.score_at_time(self.start_time)
else:
return self.game.score_at_time(self.end_time)
def sql_fields(
cls)
Returns a list of all SQL fields across all tables for this
entity, including derived fields. This method can be used
in conjunction with nfldb.Entity.from_row_tuple
to quickly
create new nfldb
objects without every constructing a dict.
@classmethod
def sql_fields(cls):
"""
Returns a list of all SQL fields across all tables for this
entity, including derived fields. This method can be used
in conjunction with `nfldb.Entity.from_row_tuple` to quickly
create new `nfldb` objects without every constructing a dict.
"""
if not hasattr(cls, '_cached_sql_fields'):
cls._cached_sql_fields = cls._sql_columns()
cls._cached_sql_fields += cls._sql_tables['derived']
return cls._cached_sql_fields
class Enums
Enums groups all enum types used in the database schema. All possible values for each enum type are represented as lists. The ordering of each list is the same as the ordering in the database. In particular, this ordering specifies a total ordering that can be used in Python code to compare values in the same enumeration.
class Enums (object):
"""
Enums groups all enum types used in the database schema.
All possible values for each enum type are represented as lists.
The ordering of each list is the same as the ordering in the
database. In particular, this ordering specifies a total ordering
that can be used in Python code to compare values in the same
enumeration.
"""
game_phase = _Enum('game_phase',
['Pregame', 'Q1', 'Q2', 'Half',
'Q3', 'Q4', 'OT', 'OT2', 'Final'])
"""
Represents the phase of the game. e.g., `Q1` or `Half`.
"""
season_phase = _Enum('season_phase',
['Preseason', 'Regular', 'Postseason'])
"""
Represents one of the three phases of an NFL season: `Preseason`,
`Regular` or `Postseason`.
"""
game_day = _Enum('game_day',
['Sunday', 'Monday', 'Tuesday', 'Wednesday',
'Thursday', 'Friday', 'Saturday'])
"""
The day of the week on which a game was played. The week starts
on `Sunday`.
"""
player_pos = _Enum('player_pos',
['C', 'CB', 'DB', 'DE', 'DL', 'DT', 'FB', 'FS', 'G',
'ILB', 'K', 'LB', 'LS', 'MLB', 'NT', 'OG', 'OL', 'OLB',
'OT', 'P', 'QB', 'RB', 'SAF', 'SS', 'T', 'TE', 'WR',
'UNK'])
"""
The set of all possible player positions in abbreviated form.
"""
player_status = _Enum('player_status',
['Active', 'InjuredReserve', 'NonFootballInjury',
'Suspended', 'PUP', 'UnsignedDraftPick',
'Exempt', 'Unknown'])
"""
The current status of a player that is actively on a
roster. The statuses are taken from the key at the bottom of
http://goo.gl/HHsnjD
"""
category_scope = _Enum('category_scope', ['play', 'player'])
"""
The scope of a particular statistic. Typically, statistics refer
to a specific `player`, but sometimes a statistic refers to the
totality of a play. For example, `third_down_att` is a `play`
statistic that records third down attempts.
Currently, `play` and `player` are the only possible values.
Note that this type is not represented directly in the database
schema. Values of this type are constructed from data in
`category.py`.
"""
_nflgame_season_phase = {
'PRE': season_phase.Preseason,
'REG': season_phase.Regular,
'POST': season_phase.Postseason,
}
"""
Maps a season type in `nflgame` to a `nfldb.Enums.season_phase`.
"""
_nflgame_game_phase = {
'Pregame': game_phase.Pregame,
'Halftime': game_phase.Half,
'Final': game_phase.Final,
'final': game_phase.Final,
1: game_phase.Q1,
2: game_phase.Q2,
3: game_phase.Half,
4: game_phase.Q3,
5: game_phase.Q4,
6: game_phase.OT,
7: game_phase.OT2,
}
"""
Maps a game phase in `nflgame` to a `nfldb.Enums.game_phase`.
"""
_nflgame_game_day = {
'Sun': game_day.Sunday,
'Mon': game_day.Monday,
'Tue': game_day.Tuesday,
'Wed': game_day.Wednesday,
'Thu': game_day.Thursday,
'Fri': game_day.Friday,
'Sat': game_day.Saturday,
}
"""
Maps a game day of the week in `nflgame` to a
`nfldb.Enums.game_day`.
"""
_nflgame_player_status = {
'ACT': player_status.Active,
'RES': player_status.InjuredReserve,
'NON': player_status.NonFootballInjury,
'Suspended': player_status.Suspended,
'PUP': player_status.PUP,
'UDF': player_status.UnsignedDraftPick,
'EXE': player_status.Exempt,
# Everything else is `player_status.Unknown`
}
Ancestors (in MRO)
- Enums
- __builtin__.object
Class variables
var category_scope
The scope of a particular statistic. Typically, statistics refer
to a specific player
, but sometimes a statistic refers to the
totality of a play. For example, third_down_att
is a play
statistic that records third down attempts.
Currently, play
and player
are the only possible values.
Note that this type is not represented directly in the database
schema. Values of this type are constructed from data in
category.py
.
var game_day
The day of the week on which a game was played. The week starts
on Sunday
.
var game_phase
Represents the phase of the game. e.g., Q1
or Half
.
var player_pos
The set of all possible player positions in abbreviated form.
var player_status
The current status of a player that is actively on a roster. The statuses are taken from the key at the bottom of http://goo.gl/HHsnjD
var season_phase
Represents one of the three phases of an NFL season: Preseason
,
Regular
or Postseason
.
class FieldPosition
Represents field position.
The representation is an integer offset where the 50 yard line corresponds to '0'. Being in one's own territory corresponds to a negative offset while being in the opponent's territory corresponds to a positive offset.
e.g., NE has the ball on the NE 45, the offset is -5. e.g., NE has the ball on the NYG 2, the offset is 48.
This class also defines a total ordering on field positions. Namely, given f1 and f2, f1 < f2 if and only if f2 is closer to the goal line for the team with possession of the football.
class FieldPosition (object):
"""
Represents field position.
The representation is an integer offset where the 50 yard line
corresponds to '0'. Being in one's own territory corresponds to a
negative offset while being in the opponent's territory corresponds
to a positive offset.
e.g., NE has the ball on the NE 45, the offset is -5.
e.g., NE has the ball on the NYG 2, the offset is 48.
This class also defines a total ordering on field
positions. Namely, given f1 and f2, f1 < f2 if and only if f2
is closer to the goal line for the team with possession of the
football.
"""
__slots__ = ['_offset']
@staticmethod
def _pg_cast(sqlv, cursor):
if not sqlv:
return FieldPosition(None)
return FieldPosition(int(sqlv[1:-1]))
@staticmethod
def from_str(pos):
"""
Given a string `pos` in the format `FIELD YARDLINE`, this
returns a new `FieldPosition` object representing the yardline
given. `FIELD` must be the string `OWN` or `OPP` and `YARDLINE`
must be an integer in the range `[0, 50]`.
For example, `OPP 19` corresponds to an offset of `31`
and `OWN 5` corresponds to an offset of `-45`. Midfield can be
expressed as either `MIDFIELD`, `OWN 50` or `OPP 50`.
"""
if pos.upper() == 'MIDFIELD':
return FieldPosition(0)
field, yrdline = pos.split(' ')
field, yrdline = field.upper(), int(yrdline)
assert field in ('OWN', 'OPP')
assert 0 <= yrdline <= 50
if field == 'OWN':
return FieldPosition(yrdline - 50)
else:
return FieldPosition(50 - yrdline)
def __init__(self, offset):
"""
Makes a new `nfldb.FieldPosition` given a field `offset`.
`offset` must be in the integer range [-50, 50].
"""
if offset is None:
self._offset = None
return
assert -50 <= offset <= 50
self._offset = offset
def _add_yards(self, yards):
"""
Returns a new `nfldb.FieldPosition` with `yards` added to this
field position. The value of `yards` may be negative.
"""
assert self.valid
newoffset = max(-50, min(50, self._offset + yards))
return FieldPosition(newoffset)
@property
def valid(self):
"""
Returns `True` if and only if this field position is known and
valid.
Invalid field positions cannot be compared with other field
positions.
"""
return self._offset is not None
def __add__(self, other):
if isinstance(other, FieldPosition):
toadd = other._offset
else:
toadd = other
newoffset = max(-50, min(50, self._offset + toadd))
return FieldPosition(newoffset)
def __lt__(self, other):
if self.__class__ is not other.__class__:
return NotImplemented
if not self.valid:
return True
if not other.valid:
return False
return self._offset < other._offset
def __eq__(self, other):
if self.__class__ is not other.__class__:
return NotImplemented
return self._offset == other._offset
def __str__(self):
if not self.valid:
return 'N/A'
elif self._offset > 0:
return 'OPP %d' % (50 - self._offset)
elif self._offset < 0:
return 'OWN %d' % (50 + self._offset)
else:
return 'MIDFIELD'
def __conform__(self, proto):
if proto is ISQLQuote:
if not self.valid:
return AsIs("NULL")
else:
return AsIs("ROW(%d)::field_pos" % self._offset)
return None
Ancestors (in MRO)
- FieldPosition
- __builtin__.object
Static methods
def from_str(
pos)
Given a string pos
in the format FIELD YARDLINE
, this
returns a new FieldPosition
object representing the yardline
given. FIELD
must be the string OWN
or OPP
and YARDLINE
must be an integer in the range [0, 50]
.
For example, OPP 19
corresponds to an offset of 31
and OWN 5
corresponds to an offset of -45
. Midfield can be
expressed as either MIDFIELD
, OWN 50
or OPP 50
.
@staticmethod
def from_str(pos):
"""
Given a string `pos` in the format `FIELD YARDLINE`, this
returns a new `FieldPosition` object representing the yardline
given. `FIELD` must be the string `OWN` or `OPP` and `YARDLINE`
must be an integer in the range `[0, 50]`.
For example, `OPP 19` corresponds to an offset of `31`
and `OWN 5` corresponds to an offset of `-45`. Midfield can be
expressed as either `MIDFIELD`, `OWN 50` or `OPP 50`.
"""
if pos.upper() == 'MIDFIELD':
return FieldPosition(0)
field, yrdline = pos.split(' ')
field, yrdline = field.upper(), int(yrdline)
assert field in ('OWN', 'OPP')
assert 0 <= yrdline <= 50
if field == 'OWN':
return FieldPosition(yrdline - 50)
else:
return FieldPosition(50 - yrdline)
Instance variables
var valid
Returns True
if and only if this field position is known and
valid.
Invalid field positions cannot be compared with other field positions.
Methods
def __init__(
self, offset)
Makes a new FieldPosition
given a field offset
.
offset
must be in the integer range [-50, 50].
def __init__(self, offset):
"""
Makes a new `nfldb.FieldPosition` given a field `offset`.
`offset` must be in the integer range [-50, 50].
"""
if offset is None:
self._offset = None
return
assert -50 <= offset <= 50
self._offset = offset
class Game
Represents a single NFL game in the preseason, regular season or post season. Each game has an assortment of meta data, including a quarterly breakdown of scores, turnovers, the time the game started, the season week the game occurred in, and more.
Each game corresponds to zero or more drives. A game usually corresponds to at least one drive, but if the game is active, there exist valid ephemeral states where a game has no drives.
class Game (SQLGame):
"""
Represents a single NFL game in the preseason, regular season or
post season. Each game has an assortment of meta data, including
a quarterly breakdown of scores, turnovers, the time the game
started, the season week the game occurred in, and more.
Each game corresponds to **zero or more** drives. A game usually
corresponds to at least one drive, but if the game is active, there
exist valid ephemeral states where a game has no drives.
"""
__slots__ = SQLGame.sql_fields() + ['_db', '_drives', '_plays']
# Document instance variables for derived SQL fields.
__pdoc__['Game.winner'] = '''The winner of this game.'''
__pdoc__['Game.loser'] = '''The loser of this game.'''
@staticmethod
def _from_nflgame(db, g):
"""
Converts a `nflgame.game.Game` object to a `nfldb.Game`
object.
`db` should be a psycopg2 connection returned by
`nfldb.connect`.
"""
dbg = Game(db)
dbg.gsis_id = g.eid
dbg.gamekey = g.gamekey
dbg.start_time = _nflgame_start_time(g.schedule)
dbg.week = g.schedule['week']
dbg.day_of_week = Enums._nflgame_game_day[g.schedule['wday']]
dbg.season_year = g.schedule['year']
dbg.season_type = Enums._nflgame_season_phase[g.schedule['season_type']]
dbg.finished = g.game_over()
dbg.home_team = nfldb.team.standard_team(g.home)
dbg.home_score = g.score_home
dbg.home_score_q1 = g.score_home_q1
dbg.home_score_q2 = g.score_home_q2
dbg.home_score_q3 = g.score_home_q3
dbg.home_score_q4 = g.score_home_q4
dbg.home_score_q5 = g.score_home_q5
dbg.home_turnovers = int(g.data['home']['to'])
dbg.away_team = nfldb.team.standard_team(g.away)
dbg.away_score = g.score_away
dbg.away_score_q1 = g.score_away_q1
dbg.away_score_q2 = g.score_away_q2
dbg.away_score_q3 = g.score_away_q3
dbg.away_score_q4 = g.score_away_q4
dbg.away_score_q5 = g.score_away_q5
dbg.away_turnovers = int(g.data['away']['to'])
# If it's been 8 hours since game start, we always conclude finished!
if (now() - dbg.start_time).total_seconds() >= (60 * 60 * 8):
dbg.finished = True
dbg._drives = []
for drive in g.drives:
if not hasattr(drive, 'game'):
continue
dbg._drives.append(Drive._from_nflgame(db, dbg, drive))
dbg._drives.sort(key=lambda d: d.drive_id)
return dbg
@staticmethod
def _from_schedule(db, s):
"""
Converts a schedule dictionary from the `nflgame.schedule`
module to a bare-bones `nfldb.Game` object.
"""
# This is about as evil as it gets. Duck typing to the MAX!
class _Game (object):
def __init__(self):
self.schedule = s
self.home, self.away = s['home'], s['away']
self.eid = s['eid']
self.gamekey = s['gamekey']
self.drives = []
self.game_over = lambda: False
zeroes = ['score_%s', 'score_%s_q1', 'score_%s_q2',
'score_%s_q3', 'score_%s_q4', 'score_%s_q5']
for which, k in itertools.product(('home', 'away'), zeroes):
setattr(self, k % which, 0)
self.data = {'home': {'to': 0}, 'away': {'to': 0}}
return Game._from_nflgame(db, _Game())
@staticmethod
def from_id(db, gsis_id):
"""
Given a GSIS identifier (e.g., `2012090500`) as a string,
returns a `nfldb.Game` object corresponding to `gsis_id`.
If no corresponding game is found, `None` is returned.
"""
import nfldb.query
q = nfldb.query.Query(db)
games = q.game(gsis_id=gsis_id).limit(1).as_games()
if len(games) == 0:
return None
return games[0]
def __init__(self, db):
"""
Creates a new and empty `nfldb.Game` object with the given
database connection.
This constructor should not be used by clients. Instead, you
should get `nfldb.Game` objects from `nfldb.Query` or from one
of the other constructors, like `nfldb.Game.from_id` or
`nfldb.Game.from_row_dict`. (The latter is useful only if you're
writing your own SQL queries.)
"""
self._db = db
"""
The psycopg2 database connection.
"""
self._drives = None
self._plays = None
self.gsis_id = None
"""
The NFL GameCenter id of the game. It is a string
with 10 characters. The first 8 correspond to the date of the
game, while the last 2 correspond to an id unique to the week that
the game was played.
"""
self.gamekey = None
"""
Another unique identifier for a game used by the
NFL. It is a sequence number represented as a 5 character string.
The gamekey is specifically used to tie games to other resources,
like the NFL's content delivery network.
"""
self.start_time = None
"""
A Python datetime object corresponding to the start time of
the game. The timezone of this value will be equivalent to the
timezone specified by `nfldb.set_timezone` (which is by default
set to the value specified in the configuration file).
"""
self.week = None
"""
The week number of this game. It is always relative
to the phase of the season. Namely, the first week of preseason
is 1 and so is the first week of the regular season.
"""
self.day_of_week = None
"""
The day of the week this game was played on.
Possible values correspond to the `nfldb.Enums.game_day` enum.
"""
self.season_year = None
"""
The year of the season of this game. This
does not necessarily match the year that the game was played. For
example, games played in January 2013 are in season 2012.
"""
self.season_type = None
"""
The phase of the season. e.g., `Preseason`,
`Regular season` or `Postseason`. All valid values correspond
to the `nfldb.Enums.season_phase`.
"""
self.finished = None
"""
A boolean that is `True` if and only if the game has finished.
"""
self.home_team = None
"""
The team abbreviation for the home team. Use the `nfldb.Team`
constructor to get more information on a team.
"""
self.home_score = None
"""The current total score for the home team."""
self.home_score_q1 = None
"""The 1st quarter score for the home team."""
self.home_score_q2 = None
"""The 2nd quarter score for the home team."""
self.home_score_q3 = None
"""The 3rd quarter score for the home team."""
self.home_score_q4 = None
"""The 4th quarter score for the home team."""
self.home_score_q5 = None
"""The OT quarter score for the home team."""
self.home_turnovers = None
"""Total turnovers for the home team."""
self.away_team = None
"""
The team abbreviation for the away team. Use the `nfldb.Team`
constructor to get more information on a team.
"""
self.away_score = None
"""The current total score for the away team."""
self.away_score_q1 = None
"""The 1st quarter score for the away team."""
self.away_score_q2 = None
"""The 2nd quarter score for the away team."""
self.away_score_q3 = None
"""The 3rd quarter score for the away team."""
self.away_score_q4 = None
"""The 4th quarter score for the away team."""
self.away_score_q5 = None
"""The OT quarter score for the away team."""
self.away_turnovers = None
"""Total turnovers for the away team."""
self.time_inserted = None
"""The date and time that this game was added."""
self.time_updated = None
"""The date and time that this game was last updated."""
self.winner = None
"""The team abbreviation for the winner of this game."""
self.loser = None
"""The team abbreviation for the loser of this game."""
@property
def is_playing(self):
"""
Returns `True` is the game is currently being played and
`False` otherwise.
A game is being played if it is not finished and if the current
time proceeds the game's start time.
"""
return not self.finished and now() >= self.start_time
@property
def drives(self):
"""
A list of `nfldb.Drive`s for this game. They are automatically
loaded from the database if they haven't been already.
If there are no drives found in the game, then an empty list
is returned.
"""
if self._drives is None:
import nfldb.query
q = nfldb.query.Query(self._db)
self._drives = q.drive(gsis_id=self.gsis_id).as_drives()
for d in self._drives:
d._game = self
return self._drives
@property
def plays(self):
"""
A list of `nfldb.Play` objects in this game. Data is retrieved
from the database if it hasn't been already.
"""
if self._plays is None:
import nfldb.query
q = nfldb.query.Query(self._db)
q.sort([('time', 'asc'), ('play_id', 'asc')])
self._plays = q.play(gsis_id=self.gsis_id).as_plays()
return self._plays
def plays_range(self, start, end):
"""
Returns a list of `nfldb.Play` objects for this game in the
time range specified. The range corresponds to a half-open
interval, i.e., `[start, end)`. Namely, all plays starting at
or after `start` up to plays starting *before* `end`.
The plays are returned in the order in which they occurred.
`start` and `end` should be instances of the
`nfldb.Clock` class. (Hint: Values can be created with the
`nfldb.Clock.from_str` function.)
"""
import nfldb.query as query
q = query.Query(self._db)
q.play(gsis_id=self.gsis_id, time__ge=start, time__lt=end)
q.sort([('time', 'asc'), ('play_id', 'asc')])
return q.as_plays()
def score_in_plays(self, plays):
"""
Returns the scores made by the home and away teams from the
sequence of plays given. The scores are returned as a `(home,
away)` tuple. Note that this method assumes that `plays` is
sorted in the order in which the plays occurred.
"""
# This method is a heuristic to compute the total number of points
# scored in a set of plays. Naively, this should be a simple summation
# of the `points` attribute of each field. However, it seems that
# the JSON feed (where this data comes from) heavily biases toward
# omitting XPs. Therefore, we attempt to add them. A brief outline
# of the heuristic follows.
#
# In *most* cases, a TD is followed by either an XP attempt or a 2 PTC
# attempt by the same team. Therefore, after each TD, we look for the
# next play that fits this criteria, while being careful not to find
# a play that has already counted toward the score. If no play was
# found, then we assume there was an XP attempt and that it was good.
# Otherwise, if a play is found matching the given TD, the point total
# of that play is added to the score.
#
# Note that this relies on the property that every TD is paired with
# an XP/2PTC with respect to the final score of a game. Namely, when
# searching for the XP/2PTC after a TD, it may find a play that came
# after a different TD. But this is OK, so long as we never double
# count any particular play.
def is_twopta(p):
return (p.passing_twopta > 0
or p.receiving_twopta > 0
or p.rushing_twopta > 0)
counted = set() # don't double count
home, away = 0, 0
for i, p in enumerate(plays):
pts = p.points
if pts > 0 and p.play_id not in counted:
counted.add(p.play_id)
if pts == 6:
def after_td(p2):
return (p.pos_team == p2.pos_team
and (p2.kicking_xpa > 0 or is_twopta(p2))
and p2.play_id not in counted)
next = _next_play_with(plays, p, after_td)
if next is None:
pts += 1
elif next.play_id not in counted:
pts += next.points
counted.add(next.play_id)
if p.scoring_team == self.home_team:
home += pts
else:
away += pts
return home, away
def score_at_time(self, time):
"""
Returns the score of the game at the time specified as a
`(home, away)` tuple.
`time` should be an instance of the `nfldb.Clock` class.
(Hint: Values can be created with the `nfldb.Clock.from_str`
function.)
"""
start = Clock.from_str('Pregame', '0:00')
return self.score_in_plays(self.plays_range(start, time))
@property
def play_players(self):
"""
A list of `nfldb.PlayPlayer` objects in this game. Data is
retrieved from the database if it hasn't been already.
"""
pps = []
for play in self.plays:
for pp in play.play_players:
pps.append(pp)
return pps
@property
def players(self):
"""
A list of tuples of player data. The first element is the team
the player was on during the game and the second element is a
`nfldb.Player` object corresponding to that player's meta data
(including the team he's currently on). The list is returned
without duplicates and sorted by team and player name.
"""
pset = set()
players = []
for pp in self.play_players:
if pp.player_id not in pset:
players.append((pp.team, pp.player))
pset.add(pp.player_id)
return sorted(players)
def _save(self, cursor):
super(Game, self)._save(cursor)
if not self._drives:
return
# Remove any drives that are stale.
cursor.execute('''
DELETE FROM drive
WHERE gsis_id = %s AND NOT (drive_id = ANY (%s))
''', (self.gsis_id, [d.drive_id for d in self._drives]))
for drive in (self._drives or []):
drive._save(cursor)
def __str__(self):
return '%s %d week %d on %s at %s, %s (%d) at %s (%d)' \
% (self.season_type, self.season_year, self.week,
self.start_time.strftime('%m/%d'),
self.start_time.strftime('%I:%M%p'),
self.away_team, self.away_score,
self.home_team, self.home_score)
Ancestors (in MRO)
- Game
- nfldb.types.SQLGame
- nfldb.sql.Entity
- __builtin__.object
Static methods
def from_id(
db, gsis_id)
Given a GSIS identifier (e.g.,<PHONE_NUMBER>
) as a string,
returns a Game
object corresponding to gsis_id
.
If no corresponding game is found, None
is returned.
@staticmethod
def from_id(db, gsis_id):
"""
Given a GSIS identifier (e.g., `2012090500`) as a string,
returns a `nfldb.Game` object corresponding to `gsis_id`.
If no corresponding game is found, `None` is returned.
"""
import nfldb.query
q = nfldb.query.Query(db)
games = q.game(gsis_id=gsis_id).limit(1).as_games()
if len(games) == 0:
return None
return games[0]
Instance variables
var away_score
The current total score for the away team.
var away_score_q1
The 1st quarter score for the away team.
var away_score_q2
The 2nd quarter score for the away team.
var away_score_q3
The 3rd quarter score for the away team.
var away_score_q4
The 4th quarter score for the away team.
var away_score_q5
The OT quarter score for the away team.
var away_team
The team abbreviation for the away team. Use the Team
constructor to get more information on a team.
var away_turnovers
Total turnovers for the away team.
var day_of_week
The day of the week this game was played on.
Possible values correspond to the game_day
enum.
var drives
A list of Drive
s for this game. They are automatically
loaded from the database if they haven't been already.
If there are no drives found in the game, then an empty list is returned.
var finished
A boolean that is True
if and only if the game has finished.
var gamekey
Another unique identifier for a game used by the NFL. It is a sequence number represented as a 5 character string. The gamekey is specifically used to tie games to other resources, like the NFL's content delivery network.
var gsis_id
The NFL GameCenter id of the game. It is a string with 10 characters. The first 8 correspond to the date of the game, while the last 2 correspond to an id unique to the week that the game was played.
var home_score
The current total score for the home team.
var home_score_q1
The 1st quarter score for the home team.
var home_score_q2
The 2nd quarter score for the home team.
var home_score_q3
The 3rd quarter score for the home team.
var home_score_q4
The 4th quarter score for the home team.
var home_score_q5
The OT quarter score for the home team.
var home_team
The team abbreviation for the home team. Use the Team
constructor to get more information on a team.
var home_turnovers
Total turnovers for the home team.
var is_playing
Returns True
is the game is currently being played and
False
otherwise.
A game is being played if it is not finished and if the current time proceeds the game's start time.
var loser
The loser of this game.
var play_players
A list of PlayPlayer
objects in this game. Data is
retrieved from the database if it hasn't been already.
var players
A list of tuples of player data. The first element is the team
the player was on during the game and the second element is a
Player
object corresponding to that player's meta data
(including the team he's currently on). The list is returned
without duplicates and sorted by team and player name.
var plays
A list of Play
objects in this game. Data is retrieved
from the database if it hasn't been already.
var season_type
The phase of the season. e.g., Preseason
,
Regular season
or Postseason
. All valid values correspond
to the season_phase
.
var season_year
The year of the season of this game. This does not necessarily match the year that the game was played. For example, games played in January 2013 are in season 2012.
var start_time
A Python datetime object corresponding to the start time of
the game. The timezone of this value will be equivalent to the
timezone specified by set_timezone
(which is by default
set to the value specified in the configuration file).
var time_inserted
The date and time that this game was added.
var time_updated
The date and time that this game was last updated.
var week
The week number of this game. It is always relative to the phase of the season. Namely, the first week of preseason is 1 and so is the first week of the regular season.
var winner
The winner of this game.
Methods
def __init__(
self, db)
Creates a new and empty Game
object with the given
database connection.
This constructor should not be used by clients. Instead, you
should get Game
objects from Query
or from one
of the other constructors, like from_id
or
from_row_dict
. (The latter is useful only if you're
writing your own SQL queries.)
def __init__(self, db):
"""
Creates a new and empty `nfldb.Game` object with the given
database connection.
This constructor should not be used by clients. Instead, you
should get `nfldb.Game` objects from `nfldb.Query` or from one
of the other constructors, like `nfldb.Game.from_id` or
`nfldb.Game.from_row_dict`. (The latter is useful only if you're
writing your own SQL queries.)
"""
self._db = db
"""
The psycopg2 database connection.
"""
self._drives = None
self._plays = None
self.gsis_id = None
"""
The NFL GameCenter id of the game. It is a string
with 10 characters. The first 8 correspond to the date of the
game, while the last 2 correspond to an id unique to the week that
the game was played.
"""
self.gamekey = None
"""
Another unique identifier for a game used by the
NFL. It is a sequence number represented as a 5 character string.
The gamekey is specifically used to tie games to other resources,
like the NFL's content delivery network.
"""
self.start_time = None
"""
A Python datetime object corresponding to the start time of
the game. The timezone of this value will be equivalent to the
timezone specified by `nfldb.set_timezone` (which is by default
set to the value specified in the configuration file).
"""
self.week = None
"""
The week number of this game. It is always relative
to the phase of the season. Namely, the first week of preseason
is 1 and so is the first week of the regular season.
"""
self.day_of_week = None
"""
The day of the week this game was played on.
Possible values correspond to the `nfldb.Enums.game_day` enum.
"""
self.season_year = None
"""
The year of the season of this game. This
does not necessarily match the year that the game was played. For
example, games played in January 2013 are in season 2012.
"""
self.season_type = None
"""
The phase of the season. e.g., `Preseason`,
`Regular season` or `Postseason`. All valid values correspond
to the `nfldb.Enums.season_phase`.
"""
self.finished = None
"""
A boolean that is `True` if and only if the game has finished.
"""
self.home_team = None
"""
The team abbreviation for the home team. Use the `nfldb.Team`
constructor to get more information on a team.
"""
self.home_score = None
"""The current total score for the home team."""
self.home_score_q1 = None
"""The 1st quarter score for the home team."""
self.home_score_q2 = None
"""The 2nd quarter score for the home team."""
self.home_score_q3 = None
"""The 3rd quarter score for the home team."""
self.home_score_q4 = None
"""The 4th quarter score for the home team."""
self.home_score_q5 = None
"""The OT quarter score for the home team."""
self.home_turnovers = None
"""Total turnovers for the home team."""
self.away_team = None
"""
The team abbreviation for the away team. Use the `nfldb.Team`
constructor to get more information on a team.
"""
self.away_score = None
"""The current total score for the away team."""
self.away_score_q1 = None
"""The 1st quarter score for the away team."""
self.away_score_q2 = None
"""The 2nd quarter score for the away team."""
self.away_score_q3 = None
"""The 3rd quarter score for the away team."""
self.away_score_q4 = None
"""The 4th quarter score for the away team."""
self.away_score_q5 = None
"""The OT quarter score for the away team."""
self.away_turnovers = None
"""Total turnovers for the away team."""
self.time_inserted = None
"""The date and time that this game was added."""
self.time_updated = None
"""The date and time that this game was last updated."""
self.winner = None
"""The team abbreviation for the winner of this game."""
self.loser = None
"""The team abbreviation for the loser of this game."""
def from_row_dict(
cls, db, row)
Introduces a new entity object from a full SQL row result from
the entity's tables. (i.e., row
is a dictionary mapping
column to value.) Note that the column names must be of the
form '{entity_name}_{column_name}'. For example, in the game
table, the gsis_id
column must be named game_gsis_id
in
row
.
@classmethod
def from_row_dict(cls, db, row):
"""
Introduces a new entity object from a full SQL row result from
the entity's tables. (i.e., `row` is a dictionary mapping
column to value.) Note that the column names must be of the
form '{entity_name}_{column_name}'. For example, in the `game`
table, the `gsis_id` column must be named `game_gsis_id` in
`row`.
"""
obj = cls(db)
seta = setattr
prefix = cls._sql_primary_table() + '_'
slice_from = len(prefix)
for k in row:
if k.startswith(prefix):
seta(obj, k[slice_from:], row[k])
return obj
def from_row_tuple(
cls, db, t)
Given a tuple t
corresponding to a result from a SELECT query,
this will construct a new instance for this entity. Note that
the tuple t
must be in exact correspondence with the columns
returned by nfldb.Entity.sql_fields
.
@classmethod
def from_row_tuple(cls, db, t):
"""
Given a tuple `t` corresponding to a result from a SELECT query,
this will construct a new instance for this entity. Note that
the tuple `t` must be in *exact* correspondence with the columns
returned by `nfldb.Entity.sql_fields`.
"""
cols = cls.sql_fields()
seta = setattr
obj = cls(db)
for i, field in enumerate(cols):
seta(obj, field, t[i])
return obj
def plays_range(
self, start, end)
Returns a list of Play
objects for this game in the
time range specified. The range corresponds to a half-open
interval, i.e., [start, end)
. Namely, all plays starting at
or after start
up to plays starting before end
.
The plays are returned in the order in which they occurred.
start
and end
should be instances of the
Clock
class. (Hint: Values can be created with the
from_str
function.)
def plays_range(self, start, end):
"""
Returns a list of `nfldb.Play` objects for this game in the
time range specified. The range corresponds to a half-open
interval, i.e., `[start, end)`. Namely, all plays starting at
or after `start` up to plays starting *before* `end`.
The plays are returned in the order in which they occurred.
`start` and `end` should be instances of the
`nfldb.Clock` class. (Hint: Values can be created with the
`nfldb.Clock.from_str` function.)
"""
import nfldb.query as query
q = query.Query(self._db)
q.play(gsis_id=self.gsis_id, time__ge=start, time__lt=end)
q.sort([('time', 'asc'), ('play_id', 'asc')])
return q.as_plays()
def score_at_time(
self, time)
Returns the score of the game at the time specified as a
(home, away)
tuple.
time
should be an instance of the Clock
class.
(Hint: Values can be created with the from_str
function.)
def score_at_time(self, time):
"""
Returns the score of the game at the time specified as a
`(home, away)` tuple.
`time` should be an instance of the `nfldb.Clock` class.
(Hint: Values can be created with the `nfldb.Clock.from_str`
function.)
"""
start = Clock.from_str('Pregame', '0:00')
return self.score_in_plays(self.plays_range(start, time))
def score_in_plays(
self, plays)
Returns the scores made by the home and away teams from the
sequence of plays given. The scores are returned as a (home,
away)
tuple. Note that this method assumes that plays
is
sorted in the order in which the plays occurred.
def score_in_plays(self, plays):
"""
Returns the scores made by the home and away teams from the
sequence of plays given. The scores are returned as a `(home,
away)` tuple. Note that this method assumes that `plays` is
sorted in the order in which the plays occurred.
"""
# This method is a heuristic to compute the total number of points
# scored in a set of plays. Naively, this should be a simple summation
# of the `points` attribute of each field. However, it seems that
# the JSON feed (where this data comes from) heavily biases toward
# omitting XPs. Therefore, we attempt to add them. A brief outline
# of the heuristic follows.
#
# In *most* cases, a TD is followed by either an XP attempt or a 2 PTC
# attempt by the same team. Therefore, after each TD, we look for the
# next play that fits this criteria, while being careful not to find
# a play that has already counted toward the score. If no play was
# found, then we assume there was an XP attempt and that it was good.
# Otherwise, if a play is found matching the given TD, the point total
# of that play is added to the score.
#
# Note that this relies on the property that every TD is paired with
# an XP/2PTC with respect to the final score of a game. Namely, when
# searching for the XP/2PTC after a TD, it may find a play that came
# after a different TD. But this is OK, so long as we never double
# count any particular play.
def is_twopta(p):
return (p.passing_twopta > 0
or p.receiving_twopta > 0
or p.rushing_twopta > 0)
counted = set() # don't double count
home, away = 0, 0
for i, p in enumerate(plays):
pts = p.points
if pts > 0 and p.play_id not in counted:
counted.add(p.play_id)
if pts == 6:
def after_td(p2):
return (p.pos_team == p2.pos_team
and (p2.kicking_xpa > 0 or is_twopta(p2))
and p2.play_id not in counted)
next = _next_play_with(plays, p, after_td)
if next is None:
pts += 1
elif next.play_id not in counted:
pts += next.points
counted.add(next.play_id)
if p.scoring_team == self.home_team:
home += pts
else:
away += pts
return home, away
def sql_fields(
cls)
Returns a list of all SQL fields across all tables for this
entity, including derived fields. This method can be used
in conjunction with nfldb.Entity.from_row_tuple
to quickly
create new nfldb
objects without every constructing a dict.
@classmethod
def sql_fields(cls):
"""
Returns a list of all SQL fields across all tables for this
entity, including derived fields. This method can be used
in conjunction with `nfldb.Entity.from_row_tuple` to quickly
create new `nfldb` objects without every constructing a dict.
"""
if not hasattr(cls, '_cached_sql_fields'):
cls._cached_sql_fields = cls._sql_columns()
cls._cached_sql_fields += cls._sql_tables['derived']
return cls._cached_sql_fields
class Play
Represents a single play in an NFL game. Each play has an
assortment of meta data, possibly including the time on the clock
in which the ball was snapped, the starting field position, the
down, yards to go, etc. Not all plays have values for each field
(for example, a timeout is considered a play but has no data for
down
or yardline
).
In addition to meta data describing the context of the game at the time
the ball was snapped, plays also have statistics corresponding to the
fields in stat_categories
with a category_type
of play
. For example, third_down_att
, fourth_down_failed
and
fourth_down_conv
. While the binary nature of these fields suggest
a boolean value, they are actually integers. This makes them amenable
to aggregation.
Plays are also associated with player statistics or "events" that
occurred in a play. For example, in a single play one player could
pass the ball to another player. This is recorded as two different
player statistics: a pass and a reception. Each one is represented
as a PlayPlayer
object. Plays may have zero or more of
these player statistics.
Finally, it is important to note that there are (currently) some useful statistics missing. For example, there is currently no reliable means of determining the time on the clock when the play finished. Also, there is no field describing the field position at the end of the play, although this may be added in the future.
Most of the statistical fields are documented on the statistical categories wiki page. Each statistical field is an instance attribute in this class.
class Play (SQLPlay):
"""
Represents a single play in an NFL game. Each play has an
assortment of meta data, possibly including the time on the clock
in which the ball was snapped, the starting field position, the
down, yards to go, etc. Not all plays have values for each field
(for example, a timeout is considered a play but has no data for
`nfldb.Play.down` or `nfldb.Play.yardline`).
In addition to meta data describing the context of the game at the time
the ball was snapped, plays also have statistics corresponding to the
fields in `nfldb.stat_categories` with a `nfldb.Category.category_type`
of `play`. For example, `third_down_att`, `fourth_down_failed` and
`fourth_down_conv`. While the binary nature of these fields suggest
a boolean value, they are actually integers. This makes them amenable
to aggregation.
Plays are also associated with player statistics or "events" that
occurred in a play. For example, in a single play one player could
pass the ball to another player. This is recorded as two different
player statistics: a pass and a reception. Each one is represented
as a `nfldb.PlayPlayer` object. Plays may have **zero or more** of
these player statistics.
Finally, it is important to note that there are (currently) some
useful statistics missing. For example, there is currently no
reliable means of determining the time on the clock when the play
finished. Also, there is no field describing the field position at
the end of the play, although this may be added in the future.
Most of the statistical fields are documented on the
[statistical categories](http://goo.gl/YY587P)
wiki page. Each statistical field is an instance attribute in
this class.
"""
__slots__ = SQLPlay.sql_fields() + ['_db', '_drive', '_play_players']
# Document instance variables for derived SQL fields.
# We hide them from the public interface, but make the doco
# available to nfldb-mk-stat-table. Evil!
__pdoc__['Play.offense_yds'] = None
__pdoc__['_Play.offense_yds'] = \
'''
Corresponds to any yardage that is manufactured by the offense.
Namely, the following fields:
`nfldb.Play.passing_yds`,
`nfldb.Play.rushing_yds`,
`nfldb.Play.receiving_yds` and
`nfldb.Play.fumbles_rec_yds`.
This field is useful when searching for plays by net yardage
regardless of how the yards were obtained.
'''
__pdoc__['Play.offense_tds'] = None
__pdoc__['_Play.offense_tds'] = \
'''
Corresponds to any touchdown manufactured by the offense via
a passing, reception, rush or fumble recovery.
'''
__pdoc__['Play.defense_tds'] = None
__pdoc__['_Play.defense_tds'] = \
'''
Corresponds to any touchdown manufactured by the defense.
e.g., a pick-6, fumble recovery TD, punt/FG block TD, etc.
'''
__pdoc__['Play.points'] = \
"""
The number of points scored in this player statistic. This
accounts for touchdowns, extra points, two point conversions,
field goals and safeties.
"""
@staticmethod
def _from_nflgame(db, d, p):
"""
Given `d` as a `nfldb.Drive` object and `p` as a
`nflgame.game.Play` object, `_from_nflgame` converts `p` to a
`nfldb.Play` object.
"""
# Fix up some fields so they meet the constraints of the schema.
# The `time` field is cleaned up afterwards in
# `nfldb.Drive._from_nflgame`, since it needs data about surrounding
# plays.
time = None if not p.time else _nflgame_clock(p.time)
yardline = FieldPosition(getattr(p.yardline, 'offset', None))
down = p.down if 1 <= p.down <= 4 else None
team = p.team if p.team is not None and len(p.team) > 0 else 'UNK'
dbplay = Play(db)
dbplay.gsis_id = d.gsis_id
dbplay.drive_id = d.drive_id
dbplay.play_id = int(p.playid)
dbplay.time = time
dbplay.pos_team = team
dbplay.yardline = yardline
dbplay.down = down
dbplay.yards_to_go = p.yards_togo
dbplay.description = p.desc
dbplay.note = p.note
for k in _play_categories.keys():
if p._stats.get(k, 0) != 0:
setattr(dbplay, k, p._stats[k])
# Note that `Play` objects also normally contain aggregated
# statistics, but we forgo that here because this constructor
# is only used to load plays into the database.
dbplay._drive = d
dbplay._play_players = []
for pp in p.players:
dbpp = PlayPlayer._from_nflgame(db, dbplay, pp)
dbplay._play_players.append(dbpp)
return dbplay
@staticmethod
def from_id(db, gsis_id, drive_id, play_id):
"""
Given a GSIS identifier (e.g., `2012090500`) as a string,
an integer drive id and an integer play id, this returns a
`nfldb.Play` object corresponding to the given identifiers.
If no corresponding play is found, then `None` is returned.
"""
import nfldb.query
q = nfldb.query.Query(db)
q.play(gsis_id=gsis_id, drive_id=drive_id, play_id=play_id).limit(1)
plays = q.as_plays()
if len(plays) == 0:
return None
return plays[0]
@staticmethod
def fill_drives(db, plays):
"""
Given a list of `plays`, fill all of their `drive` attributes
using as few queries as possible. This will also fill the
drives with game data.
"""
_fill(db, Drive, plays, '_drive')
Drive.fill_games(db, [p._drive for p in plays])
def __init__(self, db):
"""
Creates a new and empty `nfldb.Play` object with the given
database connection.
This constructor should not be used by clients. Instead, you
should get `nfldb.Play` objects from `nfldb.Query` or from one
of the other constructors, like `nfldb.Play.from_id` or
`nfldb.Play.from_row_dict`. (The latter is useful only if you're
writing your own SQL queries.)
"""
self._db = db
self._drive = None
self._play_players = None
self.gsis_id = None
"""
The GSIS identifier for the game that this play belongs to.
"""
self.drive_id = None
"""
The numeric drive identifier for this play. It may be
interpreted as a sequence number.
"""
self.play_id = None
"""
The numeric play identifier for this play. It can typically
be interpreted as a sequence number scoped to the week that
this game was played, but it's unfortunately not completely
consistent.
"""
self.time = None
"""
The time on the clock when the play started, represented with
a `nfldb.Clock` object.
"""
self.pos_team = None
"""
The team in possession during this play, represented as
a team abbreviation string. Use the `nfldb.Team` constructor
to get more information on a team.
"""
self.yardline = None
"""
The starting field position of this play represented with
`nfldb.FieldPosition`.
"""
self.down = None
"""
The down on which this play begin. This may be `0` for
"special" plays like timeouts or 2 point conversions.
"""
self.yards_to_go = None
"""
The number of yards to go to get a first down or score a
touchdown at the start of the play.
"""
self.description = None
"""
A (basically) free-form text description of the play. This is
typically what you see on NFL GameCenter web pages.
"""
self.note = None
"""
A miscellaneous note field (as a string). Not sure what it's
used for.
"""
self.time_inserted = None
"""
The date and time that this play was added to the
database. This can be very useful when sorting plays by the
order in which they occurred in real time. Unfortunately, such
a sort requires that play data is updated relatively close to
when it actually occurred.
"""
self.time_updated = None
"""The date and time that this play was last updated."""
@property
def drive(self):
"""
The `nfldb.Drive` object that contains this play. The drive is
retrieved from the database if it hasn't been already.
"""
if self._drive is None:
self._drive = Drive.from_id(self._db, self.gsis_id, self.drive_id)
return self._drive
@property
def play_players(self):
"""
A list of all `nfldb.PlayPlayer`s in this play. They are
automatically retrieved from the database if they haven't been
already.
If there are no players attached to this play, then an empty
list is returned.
"""
if self._play_players is None:
import nfldb.query
q = nfldb.query.Query(self._db)
q.play_player(gsis_id=self.gsis_id, drive_id=self.drive_id,
play_id=self.play_id)
self._play_players = q.as_play_players()
for pp in self._play_players:
pp._play = self
return self._play_players
@property
def scoring_team(self):
"""
If this is a scoring play, returns the team that scored points.
Otherwise, returns None.
N.B. `nfldb.Play.scoring_team` returns a valid team if and only
if `nfldb.Play.points` is greater than 0.
"""
for pp in self.play_players:
t = pp.scoring_team
if t is not None:
return t
return None
def score(self, before=False):
"""
Returns the score of the game immediately after this play as a
tuple of the form `(home_score, away_score)`.
If `before` is `True`, then the score will *not* include this
play.
"""
game = Game.from_id(self._db, self.gsis_id)
if not before:
return game.score_at_time(self.time.add_seconds(1))
s = game.score_at_time(self.time)
# The heuristic in `nfldb.Game.score_in_plays` blends TDs and XPs
# into a single play (with respect to scoring). So we have to undo
# that if we want the score of the game after a TD but before an XP.
if self.kicking_xpmade == 1:
score_team = self.scoring_team
if score_team == game.home_team:
return (s[0] - 1, s[1])
return (s[0], s[1] - 1)
return s
def _save(self, cursor):
super(Play, self)._save(cursor)
# Remove any "play players" that are stale.
cursor.execute('''
DELETE FROM play_player
WHERE gsis_id = %s AND drive_id = %s AND play_id = %s
AND NOT (player_id = ANY (%s))
''', (self.gsis_id, self.drive_id, self.play_id,
[p.player_id for p in (self._play_players or [])]))
for pp in (self._play_players or []):
pp._save(cursor)
def __str__(self):
if self.down:
return '(%s, %s, %s, %d and %d) %s' \
% (self.pos_team, self.yardline, self.time.phase,
self.down, self.yards_to_go, self.description)
elif self.pos_team:
return '(%s, %s, %s) %s' \
% (self.pos_team, self.yardline, self.time.phase,
self.description)
else:
return '(%s) %s' % (self.time.phase, self.description)
def __getattr__(self, k):
if k in Play.__slots__:
return 0
raise AttributeError(k)
Ancestors (in MRO)
- Play
- nfldb.types.SQLPlay
- nfldb.sql.Entity
- __builtin__.object
Static methods
def fill_drives(
db, plays)
Given a list of plays
, fill all of their drive
attributes
using as few queries as possible. This will also fill the
drives with game data.
@staticmethod
def fill_drives(db, plays):
"""
Given a list of `plays`, fill all of their `drive` attributes
using as few queries as possible. This will also fill the
drives with game data.
"""
_fill(db, Drive, plays, '_drive')
Drive.fill_games(db, [p._drive for p in plays])
def from_id(
db, gsis_id, drive_id, play_id)
Given a GSIS identifier (e.g.,<PHONE_NUMBER>
) as a string,
an integer drive id and an integer play id, this returns a
Play
object corresponding to the given identifiers.
If no corresponding play is found, then None
is returned.
@staticmethod
def from_id(db, gsis_id, drive_id, play_id):
"""
Given a GSIS identifier (e.g., `2012090500`) as a string,
an integer drive id and an integer play id, this returns a
`nfldb.Play` object corresponding to the given identifiers.
If no corresponding play is found, then `None` is returned.
"""
import nfldb.query
q = nfldb.query.Query(db)
q.play(gsis_id=gsis_id, drive_id=drive_id, play_id=play_id).limit(1)
plays = q.as_plays()
if len(plays) == 0:
return None
return plays[0]
Instance variables
var description
A (basically) free-form text description of the play. This is typically what you see on NFL GameCenter web pages.
var down
The down on which this play begin. This may be 0
for
"special" plays like timeouts or 2 point conversions.
var drive
The Drive
object that contains this play. The drive is
retrieved from the database if it hasn't been already.
var drive_id
The numeric drive identifier for this play. It may be interpreted as a sequence number.
var game_date
var gsis_id
The GSIS identifier for the game that this play belongs to.
var note
A miscellaneous note field (as a string). Not sure what it's used for.
var play_id
The numeric play identifier for this play. It can typically be interpreted as a sequence number scoped to the week that this game was played, but it's unfortunately not completely consistent.
var play_players
A list of all PlayPlayer
s in this play. They are
automatically retrieved from the database if they haven't been
already.
If there are no players attached to this play, then an empty list is returned.
var points
The number of points scored in this player statistic. This accounts for touchdowns, extra points, two point conversions, field goals and safeties.
var pos_team
The team in possession during this play, represented as
a team abbreviation string. Use the Team
constructor
to get more information on a team.
var scoring_team
If this is a scoring play, returns the team that scored points. Otherwise, returns None.
N.B. scoring_team
returns a valid team if and only
if points
is greater than 0.
var time_inserted
The date and time that this play was added to the database. This can be very useful when sorting plays by the order in which they occurred in real time. Unfortunately, such a sort requires that play data is updated relatively close to when it actually occurred.
var time_updated
The date and time that this play was last updated.
var yards_to_go
The number of yards to go to get a first down or score a touchdown at the start of the play.
Methods
def __init__(
self, db)
Creates a new and empty Play
object with the given
database connection.
This constructor should not be used by clients. Instead, you
should get Play
objects from Query
or from one
of the other constructors, like from_id
or
from_row_dict
. (The latter is useful only if you're
writing your own SQL queries.)
def __init__(self, db):
"""
Creates a new and empty `nfldb.Play` object with the given
database connection.
This constructor should not be used by clients. Instead, you
should get `nfldb.Play` objects from `nfldb.Query` or from one
of the other constructors, like `nfldb.Play.from_id` or
`nfldb.Play.from_row_dict`. (The latter is useful only if you're
writing your own SQL queries.)
"""
self._db = db
self._drive = None
self._play_players = None
self.gsis_id = None
"""
The GSIS identifier for the game that this play belongs to.
"""
self.drive_id = None
"""
The numeric drive identifier for this play. It may be
interpreted as a sequence number.
"""
self.play_id = None
"""
The numeric play identifier for this play. It can typically
be interpreted as a sequence number scoped to the week that
this game was played, but it's unfortunately not completely
consistent.
"""
self.time = None
"""
The time on the clock when the play started, represented with
a `nfldb.Clock` object.
"""
self.pos_team = None
"""
The team in possession during this play, represented as
a team abbreviation string. Use the `nfldb.Team` constructor
to get more information on a team.
"""
self.yardline = None
"""
The starting field position of this play represented with
`nfldb.FieldPosition`.
"""
self.down = None
"""
The down on which this play begin. This may be `0` for
"special" plays like timeouts or 2 point conversions.
"""
self.yards_to_go = None
"""
The number of yards to go to get a first down or score a
touchdown at the start of the play.
"""
self.description = None
"""
A (basically) free-form text description of the play. This is
typically what you see on NFL GameCenter web pages.
"""
self.note = None
"""
A miscellaneous note field (as a string). Not sure what it's
used for.
"""
self.time_inserted = None
"""
The date and time that this play was added to the
database. This can be very useful when sorting plays by the
order in which they occurred in real time. Unfortunately, such
a sort requires that play data is updated relatively close to
when it actually occurred.
"""
self.time_updated = None
"""The date and time that this play was last updated."""
def from_row_dict(
cls, db, row)
Introduces a new entity object from a full SQL row result from
the entity's tables. (i.e., row
is a dictionary mapping
column to value.) Note that the column names must be of the
form '{entity_name}_{column_name}'. For example, in the game
table, the gsis_id
column must be named game_gsis_id
in
row
.
@classmethod
def from_row_dict(cls, db, row):
"""
Introduces a new entity object from a full SQL row result from
the entity's tables. (i.e., `row` is a dictionary mapping
column to value.) Note that the column names must be of the
form '{entity_name}_{column_name}'. For example, in the `game`
table, the `gsis_id` column must be named `game_gsis_id` in
`row`.
"""
obj = cls(db)
seta = setattr
prefix = cls._sql_primary_table() + '_'
slice_from = len(prefix)
for k in row:
if k.startswith(prefix):
seta(obj, k[slice_from:], row[k])
return obj
def from_row_tuple(
cls, db, t)
Given a tuple t
corresponding to a result from a SELECT query,
this will construct a new instance for this entity. Note that
the tuple t
must be in exact correspondence with the columns
returned by nfldb.Entity.sql_fields
.
@classmethod
def from_row_tuple(cls, db, t):
"""
Given a tuple `t` corresponding to a result from a SELECT query,
this will construct a new instance for this entity. Note that
the tuple `t` must be in *exact* correspondence with the columns
returned by `nfldb.Entity.sql_fields`.
"""
cols = cls.sql_fields()
seta = setattr
obj = cls(db)
for i, field in enumerate(cols):
seta(obj, field, t[i])
return obj
def score(
self, before=False)
Returns the score of the game immediately after this play as a
tuple of the form (home_score, away_score)
.
If before
is True
, then the score will not include this
play.
def score(self, before=False):
"""
Returns the score of the game immediately after this play as a
tuple of the form `(home_score, away_score)`.
If `before` is `True`, then the score will *not* include this
play.
"""
game = Game.from_id(self._db, self.gsis_id)
if not before:
return game.score_at_time(self.time.add_seconds(1))
s = game.score_at_time(self.time)
# The heuristic in `nfldb.Game.score_in_plays` blends TDs and XPs
# into a single play (with respect to scoring). So we have to undo
# that if we want the score of the game after a TD but before an XP.
if self.kicking_xpmade == 1:
score_team = self.scoring_team
if score_team == game.home_team:
return (s[0] - 1, s[1])
return (s[0], s[1] - 1)
return s
def sql_fields(
cls)
Returns a list of all SQL fields across all tables for this
entity, including derived fields. This method can be used
in conjunction with nfldb.Entity.from_row_tuple
to quickly
create new nfldb
objects without every constructing a dict.
@classmethod
def sql_fields(cls):
"""
Returns a list of all SQL fields across all tables for this
entity, including derived fields. This method can be used
in conjunction with `nfldb.Entity.from_row_tuple` to quickly
create new `nfldb` objects without every constructing a dict.
"""
if not hasattr(cls, '_cached_sql_fields'):
cls._cached_sql_fields = cls._sql_columns()
cls._cached_sql_fields += cls._sql_tables['derived']
return cls._cached_sql_fields
class PlayPlayer
A "play player" is a statistical grouping of categories for a
single player inside a play. For example, passing the ball to
a receiver necessarily requires two "play players": the pass
(by player X) and the reception (by player Y). Statistics that
aren't included, for example, are blocks and penalties. (Although
penalty information can be gleaned from a play's free-form
description
attribute.)
Each PlayPlayer
object belongs to exactly one
Play
and exactly one Player
.
Any statistical categories not relevant to this particular play
and player default to 0
.
Most of the statistical fields are documented on the statistical categories wiki page. Each statistical field is an instance attribute in this class.
class PlayPlayer (SQLPlayPlayer):
"""
A "play player" is a statistical grouping of categories for a
single player inside a play. For example, passing the ball to
a receiver necessarily requires two "play players": the pass
(by player X) and the reception (by player Y). Statistics that
aren't included, for example, are blocks and penalties. (Although
penalty information can be gleaned from a play's free-form
`nfldb.Play.description` attribute.)
Each `nfldb.PlayPlayer` object belongs to exactly one
`nfldb.Play` and exactly one `nfldb.Player`.
Any statistical categories not relevant to this particular play
and player default to `0`.
Most of the statistical fields are documented on the
[statistical categories](http://goo.gl/wZstcY)
wiki page. Each statistical field is an instance attribute in
this class.
"""
__slots__ = SQLPlayPlayer.sql_fields() \
+ ['_db', '_play', '_player', '_fields']
# Document instance variables for derived SQL fields.
# We hide them from the public interface, but make the doco
# available to nfldb-mk-stat-table. Evil!
__pdoc__['PlayPlayer.offense_yds'] = None
__pdoc__['_PlayPlayer.offense_yds'] = \
'''
Corresponds to any yardage that is manufactured by the offense.
Namely, the following fields:
`nfldb.PlayPlayer.passing_yds`,
`nfldb.PlayPlayer.rushing_yds`,
`nfldb.PlayPlayer.receiving_yds` and
`nfldb.PlayPlayer.fumbles_rec_yds`.
This field is useful when searching for plays by net yardage
regardless of how the yards were obtained.
'''
__pdoc__['PlayPlayer.offense_tds'] = None
__pdoc__['_PlayPlayer.offense_tds'] = \
'''
Corresponds to any touchdown manufactured by the offense via
a passing, reception, rush or fumble recovery.
'''
__pdoc__['PlayPlayer.defense_tds'] = None
__pdoc__['_PlayPlayer.defense_tds'] = \
'''
Corresponds to any touchdown manufactured by the defense.
e.g., a pick-6, fumble recovery TD, punt/FG block TD, etc.
'''
__pdoc__['PlayPlayer.points'] = \
"""
The number of points scored in this player statistic. This
accounts for touchdowns, extra points, two point conversions,
field goals and safeties.
"""
@staticmethod
def _from_nflgame(db, p, pp):
"""
Given `p` as a `nfldb.Play` object and `pp` as a
`nflgame.player.PlayPlayerStats` object, `_from_nflgame`
converts `pp` to a `nfldb.PlayPlayer` object.
"""
team = nfldb.team.standard_team(pp.team)
dbpp = PlayPlayer(db)
dbpp.gsis_id = p.gsis_id
dbpp.drive_id = p.drive_id
dbpp.play_id = p.play_id
dbpp.player_id = pp.playerid
dbpp.team = team
for k in _player_categories.keys():
if pp._stats.get(k, 0) != 0:
setattr(dbpp, k, pp._stats[k])
dbpp._play = p
dbpp._player = Player._from_nflgame(db, pp)
return dbpp
@staticmethod
def fill_plays(db, play_players):
"""
Given a list of `play_players`, fill all of their `play` attributes
using as few queries as possible. This will also fill the
plays with drive data and each drive with game data.
"""
_fill(db, Play, play_players, '_play')
Play.fill_drives(db, [pp._play for pp in play_players])
Drive.fill_games(db, [pp._play._drive for pp in play_players])
@staticmethod
def fill_players(db, play_players):
"""
Given a list of `play_players`, fill all of their `player`
attributes using as few queries as possible.
"""
_fill(db, Player, play_players, '_player')
def __init__(self, db):
"""
Creates a new and empty `nfldb.PlayPlayer` object with the
given database connection.
This constructor should not be used by clients. Instead,
you should get `nfldb.PlayPlayer` objects
from `nfldb.Query` or from one of the other
constructors, like `nfldb.PlayPlayer.from_id` or
`nfldb.PlayPlayer.from_row_dict`. (The latter is useful only if
you're writing your own SQL queries.)
"""
self._db = db
self._play = None
self._player = None
self._fields = None
self.gsis_id = None
"""
The GSIS identifier for the game that this "play player"
belongs to.
"""
self.drive_id = None
"""
The numeric drive identifier for this "play player". It may be
interpreted as a sequence number.
"""
self.play_id = None
"""
The numeric play identifier for this "play player". It can
typically be interpreted as a sequence number scoped to its
corresponding game.
"""
self.player_id = None
"""
The player_id linking these stats to a `nfldb.Player` object.
Use `nfldb.PlayPlayer.player` to access player meta data.
N.B. This is the GSIS identifier string. It always has length
10.
"""
self.team = None
"""
The team that this player belonged to when he recorded the
statistics in this play.
"""
@property
def fields(self):
"""The set of non-zero statistical fields set."""
if self._fields is None:
self._fields = set()
for k in _player_categories.keys():
if getattr(self, k, 0) != 0:
self._fields.add(k)
return self._fields
@property
def play(self):
"""
The `nfldb.Play` object that this "play player" belongs
to. The play is retrieved from the database if necessary.
"""
if self._play is None:
self._play = Play.from_id(self._db, self.gsis_id, self.drive_id,
self.play_id)
return self._play
@property
def player(self):
"""
The `nfldb.Player` object that this "play player"
corresponds to. The player is retrieved from the database if
necessary.
"""
if self._player is None:
self._player = Player.from_id(self._db, self.player_id)
return self._player
@property
def scoring_team(self):
"""
If this is a scoring statistic, returns the team that scored.
Otherwise, returns None.
N.B. `nfldb.PlayPlayer.scoring_team` returns a valid team if
and only if `nfldb.PlayPlayer.points` is greater than 0.
"""
if self.points > 0:
return self.team
return None
@property
def guess_position(self):
"""
Guesses the position of this player based on the statistical
categories present.
Note that this only distinguishes the offensive positions of
QB, RB, WR, P and K. If defensive stats are detected, then
the position returned defaults to LB.
"""
stat_to_pos = [
('passing_att', 'QB'), ('rushing_att', 'RB'),
('receiving_tar', 'WR'), ('punting_tot', 'P'),
('kicking_tot', 'K'), ('kicking_fga', 'K'), ('kicking_xpa', 'K'),
]
for c in stat_categories:
if c.startswith('defense_'):
stat_to_pos.append((c, 'LB'))
for stat, pos in stat_to_pos:
if getattr(self, stat) != 0:
return Enums.player_pos[pos]
return Enums.player_pos.UNK
def _save(self, cursor):
if self._player is not None:
self._player._save(cursor)
super(PlayPlayer, self)._save(cursor)
def _add(self, b):
"""
Given two `nfldb.PlayPlayer` objects, `_add` accumulates `b`
into `self`. Namely, no new `nfldb.PlayPlayer` objects are
created.
Both `self` and `b` must refer to the same player, or else an
assertion error is raised.
The `nfldb.aggregate` function should be used to sum collections
of `nfldb.PlayPlayer` objects (or objects that can provide
`nfldb.PlayPlayer` objects).
"""
a = self
assert a.player_id == b.player_id
a.gsis_id = a.gsis_id if a.gsis_id == b.gsis_id else None
a.drive_id = a.drive_id if a.drive_id == b.drive_id else None
a.play_id = a.play_id if a.play_id == b.play_id else None
a.team = a.team if a.team == b.team else None
for cat in _player_categories:
setattr(a, cat, getattr(a, cat) + getattr(b, cat))
# Try to copy player meta data too.
if a._player is None and b._player is not None:
a._player = b._player
# A play attached to aggregate statistics is always wrong.
a._play = None
def _copy(self):
"""Returns a copy of `self`."""
pp = PlayPlayer(self._db)
pp.gsis_id = self.gsis_id
pp.drive_id = self.drive_id
pp.play_id = self.play_id
pp.player_id = self.player_id
pp.team = self.team
ga, sa = getattr, setattr
for k in _player_categories:
v = getattr(self, k, 0)
if v != 0:
sa(pp, k, v)
pp._player = self._player
pp._play = self._play
return pp
def __add__(self, b):
pp = self._copy()
pp.add(b)
return pp
def __str__(self):
d = {}
for cat in _player_categories:
v = getattr(self, cat, 0)
if v != 0:
d[cat] = v
return repr(d)
def __getattr__(self, k):
if k in PlayPlayer.__slots__:
return 0
raise AttributeError(k)
Ancestors (in MRO)
- PlayPlayer
- nfldb.types.SQLPlayPlayer
- nfldb.sql.Entity
- __builtin__.object
Static methods
def fill_players(
db, play_players)
Given a list of play_players
, fill all of their player
attributes using as few queries as possible.
@staticmethod
def fill_players(db, play_players):
"""
Given a list of `play_players`, fill all of their `player`
attributes using as few queries as possible.
"""
_fill(db, Player, play_players, '_player')
def fill_plays(
db, play_players)
Given a list of play_players
, fill all of their play
attributes
using as few queries as possible. This will also fill the
plays with drive data and each drive with game data.
@staticmethod
def fill_plays(db, play_players):
"""
Given a list of `play_players`, fill all of their `play` attributes
using as few queries as possible. This will also fill the
plays with drive data and each drive with game data.
"""
_fill(db, Play, play_players, '_play')
Play.fill_drives(db, [pp._play for pp in play_players])
Drive.fill_games(db, [pp._play._drive for pp in play_players])
Instance variables
var drive_id
The numeric drive identifier for this "play player". It may be interpreted as a sequence number.
var fields
The set of non-zero statistical fields set.
var gsis_id
The GSIS identifier for the game that this "play player" belongs to.
var guess_position
Guesses the position of this player based on the statistical categories present.
Note that this only distinguishes the offensive positions of QB, RB, WR, P and K. If defensive stats are detected, then the position returned defaults to LB.
var play
The Play
object that this "play player" belongs
to. The play is retrieved from the database if necessary.
var play_id
The numeric play identifier for this "play player". It can typically be interpreted as a sequence number scoped to its corresponding game.
var player
The Player
object that this "play player"
corresponds to. The player is retrieved from the database if
necessary.
var player_id
The player_id linking these stats to a Player
object.
Use player
to access player meta data.
N.B. This is the GSIS identifier string. It always has length 10.
var points
The number of points scored in this player statistic. This accounts for touchdowns, extra points, two point conversions, field goals and safeties.
var scoring_team
If this is a scoring statistic, returns the team that scored. Otherwise, returns None.
N.B. scoring_team
returns a valid team if
and only if points
is greater than 0.
var team
The team that this player belonged to when he recorded the statistics in this play.
Methods
def __init__(
self, db)
Creates a new and empty PlayPlayer
object with the
given database connection.
This constructor should not be used by clients. Instead,
you should get PlayPlayer
objects
from Query
or from one of the other
constructors, like nfldb.PlayPlayer.from_id
or
from_row_dict
. (The latter is useful only if
you're writing your own SQL queries.)
def __init__(self, db):
"""
Creates a new and empty `nfldb.PlayPlayer` object with the
given database connection.
This constructor should not be used by clients. Instead,
you should get `nfldb.PlayPlayer` objects
from `nfldb.Query` or from one of the other
constructors, like `nfldb.PlayPlayer.from_id` or
`nfldb.PlayPlayer.from_row_dict`. (The latter is useful only if
you're writing your own SQL queries.)
"""
self._db = db
self._play = None
self._player = None
self._fields = None
self.gsis_id = None
"""
The GSIS identifier for the game that this "play player"
belongs to.
"""
self.drive_id = None
"""
The numeric drive identifier for this "play player". It may be
interpreted as a sequence number.
"""
self.play_id = None
"""
The numeric play identifier for this "play player". It can
typically be interpreted as a sequence number scoped to its
corresponding game.
"""
self.player_id = None
"""
The player_id linking these stats to a `nfldb.Player` object.
Use `nfldb.PlayPlayer.player` to access player meta data.
N.B. This is the GSIS identifier string. It always has length
10.
"""
self.team = None
"""
The team that this player belonged to when he recorded the
statistics in this play.
"""
def from_row_dict(
cls, db, row)
Introduces a new entity object from a full SQL row result from
the entity's tables. (i.e., row
is a dictionary mapping
column to value.) Note that the column names must be of the
form '{entity_name}_{column_name}'. For example, in the game
table, the gsis_id
column must be named game_gsis_id
in
row
.
@classmethod
def from_row_dict(cls, db, row):
"""
Introduces a new entity object from a full SQL row result from
the entity's tables. (i.e., `row` is a dictionary mapping
column to value.) Note that the column names must be of the
form '{entity_name}_{column_name}'. For example, in the `game`
table, the `gsis_id` column must be named `game_gsis_id` in
`row`.
"""
obj = cls(db)
seta = setattr
prefix = cls._sql_primary_table() + '_'
slice_from = len(prefix)
for k in row:
if k.startswith(prefix):
seta(obj, k[slice_from:], row[k])
return obj
def from_row_tuple(
cls, db, t)
Given a tuple t
corresponding to a result from a SELECT query,
this will construct a new instance for this entity. Note that
the tuple t
must be in exact correspondence with the columns
returned by nfldb.Entity.sql_fields
.
@classmethod
def from_row_tuple(cls, db, t):
"""
Given a tuple `t` corresponding to a result from a SELECT query,
this will construct a new instance for this entity. Note that
the tuple `t` must be in *exact* correspondence with the columns
returned by `nfldb.Entity.sql_fields`.
"""
cols = cls.sql_fields()
seta = setattr
obj = cls(db)
for i, field in enumerate(cols):
seta(obj, field, t[i])
return obj
def sql_fields(
cls)
Returns a list of all SQL fields across all tables for this
entity, including derived fields. This method can be used
in conjunction with nfldb.Entity.from_row_tuple
to quickly
create new nfldb
objects without every constructing a dict.
@classmethod
def sql_fields(cls):
"""
Returns a list of all SQL fields across all tables for this
entity, including derived fields. This method can be used
in conjunction with `nfldb.Entity.from_row_tuple` to quickly
create new `nfldb` objects without every constructing a dict.
"""
if not hasattr(cls, '_cached_sql_fields'):
cls._cached_sql_fields = cls._sql_columns()
cls._cached_sql_fields += cls._sql_tables['derived']
return cls._cached_sql_fields
class Player
A representation of an NFL player. Note that the representation is inherently ephemeral; it always corresponds to the most recent knowledge about a player.
Most of the fields in this object can have a None
value. This is
because the source JSON data only guarantees that a GSIS identifier
and abbreviated name will be available. The rest of the player meta
data is scraped from NFL.com's team roster pages (which invites
infrequent uncertainty).
class Player (SQLPlayer):
"""
A representation of an NFL player. Note that the representation
is inherently ephemeral; it always corresponds to the most recent
knowledge about a player.
Most of the fields in this object can have a `None` value. This is
because the source JSON data only guarantees that a GSIS identifier
and abbreviated name will be available. The rest of the player meta
data is scraped from NFL.com's team roster pages (which invites
infrequent uncertainty).
"""
__slots__ = SQLPlayer.sql_fields() + ['_db']
_existing = None
"""
A cache of existing player ids in the database.
This is only used when saving data to detect if a player
needs to be added.
"""
@staticmethod
def _from_nflgame(db, p):
"""
Given `p` as a `nflgame.player.PlayPlayerStats` object,
`_from_nflgame` converts `p` to a `nfldb.Player` object.
"""
dbp = Player(db)
dbp.player_id = p.playerid
dbp.gsis_name = p.name
if p.player is not None:
meta = ['full_name', 'first_name', 'last_name', 'team', 'position',
'profile_id', 'profile_url', 'uniform_number', 'birthdate',
'college', 'height', 'weight', 'years_pro', 'status']
for k in meta:
v = getattr(p.player, k, '')
if not v:
# Normalize all empty values to `None`
v = None
setattr(dbp, k, v)
# Convert position and status values to an enumeration.
dbp.position = getattr(Enums.player_pos,
dbp.position or '',
Enums.player_pos.UNK)
trans = Enums._nflgame_player_status
dbp.status = trans.get(dbp.status or '',
Enums.player_status.Unknown)
if getattr(dbp, 'position', None) is None:
dbp.position = Enums.player_pos.UNK
if getattr(dbp, 'status', None) is None:
dbp.status = Enums.player_status.Unknown
dbp.team = nfldb.team.standard_team(getattr(dbp, 'team', ''))
return dbp
@staticmethod
def _from_nflgame_player(db, p):
"""
Given `p` as a `nflgame.player.Player` object,
`_from_nflgame_player` converts `p` to a `nfldb.Player` object.
"""
# This hack translates `nflgame.player.Player` to something like
# a `nflgame.player.PlayPlayerStats` object that can be converted
# with `nfldb.Player._from_nflgame`.
class _Player (object):
def __init__(self):
self.playerid = p.player_id
self.name = p.gsis_name
self.player = p
return Player._from_nflgame(db, _Player())
@staticmethod
def from_id(db, player_id):
"""
Given a player GSIS identifier (e.g., `00-0019596`) as a string,
returns a `nfldb.Player` object corresponding to `player_id`.
This function will always execute a single SQL query.
If no corresponding player is found, `None` is returned.
"""
import nfldb.query
q = nfldb.query.Query(db)
players = q.player(player_id=player_id).limit(1).as_players()
if len(players) == 0:
return None
return players[0]
def __init__(self, db):
"""
Creates a new and empty `nfldb.Player` object with the given
database connection.
This constructor should not be used by clients. Instead, you
should get `nfldb.Player` objects from `nfldb.Query` or from
one of the other constructors, like `nfldb.Player.from_id` or
`nfldb.Player.from_row_dict`. (The latter is useful only if
you're writing your own SQL queries.)
"""
self._db = db
self.player_id = None
"""
The player_id linking this object `nfldb.PlayPlayer` object.
N.B. This is the GSIS identifier string. It always has length
10.
"""
self.gsis_name = None
"""
The name of a player from the source GameCenter data. This
field is guaranteed to contain a name.
"""
self.full_name = None
"""The full name of a player."""
self.first_name = None
"""The first name of a player."""
self.last_name = None
"""The last name of a player."""
self.team = None
"""
The team that the player is currently active on. If the player
is no longer playing or is a free agent, this value may
correspond to the `UNK` (unknown) team.
"""
self.position = None
"""
The current position of a player if it's available. This may
be **not** be `None`. If the position is not known, then the
`UNK` enum is used from `nfldb.Enums.player_pos`.
"""
self.profile_id = None
"""
The profile identifier used on a player's canonical NFL.com
profile page. This is used as a foreign key to connect varying
sources of information.
"""
self.profile_url = None
"""The NFL.com profile URL for this player."""
self.uniform_number = None
"""A player's uniform number as an integer."""
self.birthdate = None
"""A player's birth date as a free-form string."""
self.college = None
"""A player's college as a free-form string."""
self.height = None
"""A player's height as a free-form string."""
self.weight = None
"""A player's weight as a free-form string."""
self.years_pro = None
"""The number of years a player has played as an integer."""
self.status = None
"""The current status of this player as a free-form string."""
def _save(self, cursor):
if Player._existing is None:
Player._existing = set()
cursor.execute('SELECT player_id FROM player')
for row in cursor.fetchall():
Player._existing.add(row['player_id'])
if self.player_id not in Player._existing:
super(Player, self)._save(cursor)
Player._existing.add(self.player_id)
def __str__(self):
name = self.full_name if self.full_name else self.gsis_name
if not name:
name = self.player_id # Yikes.
return '%s (%s, %s)' % (name, self.team, self.position)
def __lt__(self, other):
if self.__class__ is not other.__class__:
return NotImplemented
if self.full_name and other.full_name:
return self.full_name < other.full_name
return self.gsis_name < other.gsis_name
def __eq__(self, other):
if self.__class__ is not other.__class__:
return NotImplemented
return self.player_id == other.player_id
Ancestors (in MRO)
- Player
- nfldb.types.SQLPlayer
- nfldb.sql.Entity
- __builtin__.object
Static methods
def from_id(
db, player_id)
Given a player GSIS identifier (e.g., 00-0019596
) as a string,
returns a Player
object corresponding to player_id
.
This function will always execute a single SQL query.
If no corresponding player is found, None
is returned.
@staticmethod
def from_id(db, player_id):
"""
Given a player GSIS identifier (e.g., `00-0019596`) as a string,
returns a `nfldb.Player` object corresponding to `player_id`.
This function will always execute a single SQL query.
If no corresponding player is found, `None` is returned.
"""
import nfldb.query
q = nfldb.query.Query(db)
players = q.player(player_id=player_id).limit(1).as_players()
if len(players) == 0:
return None
return players[0]
Instance variables
var birthdate
A player's birth date as a free-form string.
var college
A player's college as a free-form string.
var first_name
The first name of a player.
var full_name
The full name of a player.
var gsis_name
The name of a player from the source GameCenter data. This field is guaranteed to contain a name.
var height
A player's height as a free-form string.
var last_name
The last name of a player.
var player_id
The player_id linking this object PlayPlayer
object.
N.B. This is the GSIS identifier string. It always has length 10.
var position
The current position of a player if it's available. This may
be not be None
. If the position is not known, then the
UNK
enum is used from player_pos
.
var profile_id
The profile identifier used on a player's canonical NFL.com profile page. This is used as a foreign key to connect varying sources of information.
var profile_url
The NFL.com profile URL for this player.
var status
The current status of this player as a free-form string.
var team
The team that the player is currently active on. If the player
is no longer playing or is a free agent, this value may
correspond to the UNK
(unknown) team.
var uniform_number
A player's uniform number as an integer.
var weight
A player's weight as a free-form string.
var years_pro
The number of years a player has played as an integer.
Methods
def __init__(
self, db)
Creates a new and empty Player
object with the given
database connection.
This constructor should not be used by clients. Instead, you
should get Player
objects from Query
or from
one of the other constructors, like from_id
or
from_row_dict
. (The latter is useful only if
you're writing your own SQL queries.)
def __init__(self, db):
"""
Creates a new and empty `nfldb.Player` object with the given
database connection.
This constructor should not be used by clients. Instead, you
should get `nfldb.Player` objects from `nfldb.Query` or from
one of the other constructors, like `nfldb.Player.from_id` or
`nfldb.Player.from_row_dict`. (The latter is useful only if
you're writing your own SQL queries.)
"""
self._db = db
self.player_id = None
"""
The player_id linking this object `nfldb.PlayPlayer` object.
N.B. This is the GSIS identifier string. It always has length
10.
"""
self.gsis_name = None
"""
The name of a player from the source GameCenter data. This
field is guaranteed to contain a name.
"""
self.full_name = None
"""The full name of a player."""
self.first_name = None
"""The first name of a player."""
self.last_name = None
"""The last name of a player."""
self.team = None
"""
The team that the player is currently active on. If the player
is no longer playing or is a free agent, this value may