Skip to content

Exploring secondary types: FieldPosition, PossessionTime and Clock

Andrew Gallant edited this page Sep 15, 2013 · 7 revisions

In addition to the central types like Game and Player provided by nfldb, there are also several secondary types that describe common data in the database. Below is a list with a brief description of each and a link to its API documentation:

  • FieldPosition - This type is used to describe field position values in the drive and play tables. This includes knowledge of possession. (i.e., There is a distinction between being on one's own 45 yard line and the opponent's 45 yard line.)
  • PossessionTime - Represents the amount of time that an offense has the ball. This only describes values in the drive table.
  • Clock - Represents the phase and clock time (if applicable) of a point in the game. This includes pregame, half time, over time, etc. This describes values in the drive and play tables.

It is worth skimming the API documentation for each of the above types. Most of this article will focus on how to use those types in the query interface.

Clock values

The representation of the Clock type is essentially a tuple: the first element is the phase of the game (Q1, Half, Q4, Final, etc.) and the second element is the number of seconds elapsed in the phase. For phases that have no time component, the number of elapsed seconds is always 0. Note that this implies a range of valid values for elapsed seconds: 0 <= s <= 900 where s is the number of seconds elapsed.

We can create a new Clock object with its constructor method:

c = nfldb.Clock(nfldb.Enums.game_phase.Q2, 61)
print c

And the output is:

[andrew@Liger nflgame] python2 scratch.py
Q2 13:59

We pass a game phase from the game_phase enumeration, which corresponds to a set of values allowed to be a game phase. Any other value will cause an assertion error.

Notice here that 61 seconds corresponds to the number of seconds elapsed since the start of the quarter. Thus, the game clock should read 13:59. But thinking in terms of elapsed seconds is really inconvenient, and writing out an enumeration value can be a bit painful. Therefore, we can make Clock values with the more convenient Clock.from_str function:

c = nfldb.Clock.from_str('Q2', '13:59')
print c

Which will have the same output as above.

Okay, so how do we use Clock values in the query interface? It's simple! We use them like any other value. For example, to find all plays with pass completions within the last 30 seconds of the 4th quarter in week 1 of the 2013 regular season:

end_4th = nfldb.Clock.from_str('Q4', '0:30')

q = nfldb.Query(db)
q.game(season_year=2013, season_type='Regular', week=1)
q.play(passing_cmp=1, time__ge=end_4th)
for p in q.as_plays():
    print p

And the output is:

[andrew@Liger nflgame] python2 scratch.py
(BUF, OWN 20, Q4, 1 and 10) (:05) (Shotgun) E.Manuel pass short middle to F.Jackson to BUF 32 for 12 yards. FUMBLES, recovered by BUF-E.Manuel at BUF 29. E.Manuel to BUF 29 for no gain (A.Dennard).
(NYJ, OWN 20, Q4, 2 and 10) (:29) (Shotgun) G.Smith pass deep middle to K.Winslow to NYJ 45 for 25 yards (M.Barron).
(ARI, OWN 29, Q4, 2 and 16) (:24) C.Palmer pass short right to J.Brown to ARI 43 for 14 yards (J.Laurinaitis).
(GB, OWN 20, Q4, 1 and 10) (:26) (Shotgun) A.Rodgers pass deep right to R.Cobb to SF 42 for 38 yards (D.Whitner). Caught at GB 49.  9-yds YAC
(NYG, OPP 4, Q4, 3 and 1) (:16) (Shotgun) E.Manning pass short middle to B.Myers for 4 yards, TOUCHDOWN.

The nice thing about the Clock type (and indeed, the other types as well), is that an ordering for them is established by nfldb. So you can use standard Python operators to compare Clock, PossessionTime or FieldPosition values. This ordering is also carries over into the PostgreSQL database automatically, since their representations were carefully chosen to correspond to a natural ordering.

Possession times

PossessionTime objects are fairly straight forward: they represent the time elapsed by a single drive. The only value that has this type is the Drive.pos_time attribute.

Instances of the PossessionTime class can be instantiated with its constructor method. For example, this represents a possession time of 298 seconds:

p = nfldb.PossessionTime(298)
print p

Which has output:

[andrew@Liger nflgame] python2 scratch.py
04:58

But wouldn't it be nice to be able to just say 04:58? Just like Clock has the Clock.from_str convenience function, PossessionTime has the PossessionTime.from_str convenience function:

p = nfldb.PossessionTime.from_str('04:58')
print p

Which has the same output as above.

Using possession times in queries is just as easy with clock times. For example, to find all drives longer than 10 minutes in the 2012 season:

long_drive = nfldb.PossessionTime.from_str('10:00')

q = nfldb.Query(db)
q.game(season_year=2012, season_type='Regular')
q.drive(pos_time__ge=long_drive)
for d in q.as_drives():
    print d

And the output is:

[andrew@Liger nflgame] python2 scratch.py
[Touchdown   ] PIT from OWN 25 to OPP 2  (lasted 10:13 - Q4 13:47 to Q4 03:34)
[Field Goal  ] DAL from OWN 8  to OPP 1  (lasted 10:10 - Q1 09:26 to Q2 14:16)
[Downs       ] WAS from OWN 20 to OPP 2  (lasted 10:11 - Q1 00:24 to Q2 05:13)
[Field Goal  ] GB  from OWN 14 to OPP 13 (lasted 11:00 - Q4 15:00 to Q4 04:00)
[Field Goal  ] TEN from OWN 20 to OPP 7  (lasted 10:08 - Q3 05:36 to Q4 10:28)

As with the Clock type, PossessionTime objects can be compared:

print nfldb.PossessionTime.from_str('04:35') < nfldb.PossessionTime.from_str('05:12')

Output:

[andrew@Liger nflgame] python2 scratch.py
True

Field position

The representation of a field position is a single signed integer such that -50 <= fp <= 50, where fp is a field position offset. Negative values correspond to your own territory, positive values correspond to your opponent's territory and 0 corresponds to midfield. As 50 - |fp| gets smaller (where |fp| is its absolute value), you get closer to an endzone. So -45 corresponds to your own 5 yard line while 45 corresponds to your opponent's 5 yard line.

New instances of FieldPosition can be created with its constructor method:

fp = nfldb.FieldPosition(5)
print fp

fp = nfldb.FieldPosition(-13)
print fp

fp = nfldb.FieldPosition(0)
print fp

And the output is:

[andrew@Liger nflgame] python2 scratch.py
OPP 45
OWN 37
MIDFIELD

Note here that a FieldPosition does not contain contextual information about which team has possession of the ball, so the field territory is labeled with OPP for "opponent" and OWN for your "own" territory.

Just like with the previous types, thinking about field positions in terms of offsets is pretty strange. Thus, there is a more convenient FieldPosition.from_str function that lets you make FieldPosition objects from a string:

fp = nfldb.FieldPosition.from_str('OPP 45')
print fp

fp = nfldb.FieldPosition.from_str('OWN 37')
print fp

fp1 = nfldb.FieldPosition.from_str('MIDFIELD')
fp2 = nfldb.FieldPosition.from_str('OWN 50')
fp3 = nfldb.FieldPosition.from_str('OPP 50')
print '%s :: %s :: %s' % (fp1, fp2, fp3)

And the output is:

[andrew@Liger nflgame] python2 scratch.py
OPP 45
OWN 37
MIDFIELD :: MIDFIELD :: MIDFIELD

As with PossessionTime and Clock, FieldPosition objects can be used in queries. For example, to find all drives that start on your own 1 yard line from the 2012 regular season:

own1 = nfldb.FieldPosition.from_str('OWN 1')

q = nfldb.Query(db)
q.game(season_year=2012, season_type='Regular')
q.drive(start_field__le=own1)
for d in q.as_drives():
    print d

And the output is:

[andrew@Liger nflgame] python2 scratch.py
[Punt        ] DEN from OWN 1  to OWN 15 (lasted 02:58 - Q1 02:58 to Q1 00:00)
[Punt        ] OAK from OWN 1  to OWN 6  (lasted 01:33 - Q3 11:07 to Q3 09:34)
[Interception] CIN from OWN 1  to OWN 2  (lasted 00:45 - Q1 11:45 to Q1 11:00)
[Punt        ] GB  from OWN 1  to OWN 16 (lasted 04:28 - Q1 05:59 to Q1 01:31)
[End of Half ] SEA from OWN 1  to OWN 20 (lasted 02:17 - Q2 02:17 to Q2 00:00)
[Field Goal  ] ATL from OWN 1  to OPP 22 (lasted 00:54 - Q4 00:59 to Q4 00:05)
[Punt        ] BAL from OWN 1  to OWN 6  (lasted 01:41 - Q4 11:56 to Q4 10:15)
[Safety      ] SEA from OWN 1  to OWN 18 (lasted 02:48 - Q4 03:47 to Q4 00:59)
[End of Half ] TB  from OWN 1  to OWN 14 (lasted 00:52 - Q2 00:52 to Q2 00:00)
[End of Half ] WAS from OWN 1  to OWN 7  (lasted 00:32 - Q2 00:32 to Q2 00:00)
[End of Game ] SF  from OWN 1  to OWN 1  (lasted 00:49 - Q4 00:49 to Q4 00:00)
[Interception] DEN from OWN 1  to OWN 3  (lasted 01:16 - Q3 01:17 to Q3 00:01)
[Punt        ] NE  from OWN 1  to OWN 1  (lasted 00:19 - Q4 09:35 to Q4 09:16)
[Interception] MIA from OWN 1  to OWN 32 (lasted 02:48 - Q4 04:42 to Q4 01:54)
[Safety      ] NE  from OWN 1  to OWN 1  (lasted 00:05 - Q3 06:52 to Q3 06:47)
[Punt        ] BUF from OWN 1  to OWN 16 (lasted 01:55 - Q1 00:17 to Q2 13:22)
[Field Goal  ] SF  from OWN 1  to OPP 5  (lasted 08:13 - Q3 01:56 to Q4 08:43)
[Punt        ] HOU from OWN 1  to OWN 46 (lasted 01:49 - Q2 02:17 to Q2 00:28)
[Punt        ] SD  from OWN 1  to OWN 5  (lasted 01:02 - Q4 03:35 to Q4 02:33)
[Interception] TEN from OWN 1  to OWN 1  (lasted 00:04 - Q3 05:40 to Q3 05:36)
[Punt        ] MIN from OWN 1  to OPP 41 (lasted 06:35 - Q3 00:48 to Q4 09:13)
[Punt        ] HOU from OWN 1  to OWN 7  (lasted 01:40 - Q2 10:02 to Q2 08:22)
[Punt        ] HOU from OWN 1  to OWN 10 (lasted 01:33 - Q4 12:18 to Q4 10:45)
[Punt        ] CHI from OWN 1  to OWN 6  (lasted 01:18 - Q3 01:43 to Q3 00:25)
[Punt        ] NE  from OWN 1  to MIDFIELD (lasted 03:02 - Q3 04:13 to Q3 01:11)
[Punt        ] SF  from OWN 1  to OWN 10 (lasted 02:04 - Q1 13:59 to Q1 11:55)

Finally, like the other types, FieldPosition objects have an ordering defined on them too. Namely, field positions closer to the goal line are greater than field positions farther away from the goal line:

print nfldb.FieldPosition.from_str('OWN 1') < nfldb.FieldPosition.from_str('OPP 1')

And the output is:

[andrew@Liger nflgame] python2 scratch.py
True