Skip to content

Positions

In soccer, while players are not bound by strict rules about where they must be positioned on the field, there are traditionally recognized positions that players tend to stay closest to during a match. These positions are commonly tracked by various providers, though each provider may use different naming conventions and classifications. To offer consistency across different data sources, kloppy implements a standardized PositionType with support for the following positions:

UnknownGoalkeeperDefenderFullBackLeftBackRightBackCenterBackLeftCenterBackRightCenterBackWingBackLeftWingBackRightWingBackMidfielderDefensiveMidfieldLeftDefensiveMidfieldCenterDefensiveMidfieldRightDefensiveMidfieldCentralMidfieldLeftCentralMidfieldCenterMidfieldRightCentralMidfieldAttackingMidfieldLeftAttackingMidfieldCenterAttackingMidfieldRightAttackingMidfieldWideMidfieldLeftWingRightWingLeftMidfieldRightMidfieldAttackerLeftForwardStrikerRightForward

Each PositionType has a unique name and code.

1
2
3
>>> from kloppy.domain import PositionType
>>> print(PositionType.LeftCenterBack)
Left Center Back
>>> print(PositionType.LeftCenterBack.code)
LCB

As you might have noticed, positions are ordered hierarchically. This allows you to work on different levels of granularity, from broader categories like "Defender" or "Midfielder" to more specific positions like "Left Back" or "Center Midfield."

Each PositionType has a .parent attribute to get the position's broader category.

1
2
3
>>> pos_lb = PositionType.LeftBack
>>> print(f"{pos_lb} >> {pos_lb.parent} >> {pos_lb.parent.parent}")
Left Back >> Full Back >> Defender

You can check if a position belongs to a broader category using the is_subtype_of method.

>>> print(PositionType.LeftCenterBack.is_subtype_of(PositionType.Defender))
True

A player's position can change throughout a game. A player's positions throughout the game are stored in the .positions attributes as a [TimeContainer][kloppy.domain.TimeContainer]. This container maps Time instances to positions, allowing lookups and time range queries:

  • .ranges(): list positions throughout the game.
  • .value_at(time): get position at a specific time.
  • .at_start(): retrieve the starting position.
  • .last(): retrieve the last position.
1
2
3
4
5
6
7
>>> from kloppy import statsbomb
>>> event_dataset = statsbomb.load_open_data(match_id="15946")
>>> player = event_dataset.metadata.teams[0].get_player_by_jersey_number(5)
>>> for start_time, end_time, position in player.positions.ranges():
...    print(f"{start_time}:{end_time} - {position.code if position is not None else 'SUB'}")
P1T00:00:P2T00:00 - RDM
P2T00:00:P2T39:13 - CDM

You can conveniently retrieve a player's starting position or a player's position at a certain time in the match.

>>> print(player.positions.at_start())
Right Defensive Midfield
>>> print(player.positions.value_at(event_dataset.find("shot.goal").time))
Center Defensive Midfield
>>> print(player.positions.last())
None

Note that a player's position will be None if not on the pitch.