State¶
While working with event data it can be convenient to also use the current score. This can be used to determine if a team is winning, losing or drawing. A more generic name for the score is 'state'.
In this quickstart we'll look at how to use the add_state
method of kloppy to add state to the events for later use.
Loading some statsbomb data¶
First we'll load Barcelona - Deportivo Alaves from the statsbomb open-data project.
from kloppy import statsbomb
from kloppy.domain import EventType
dataset = statsbomb.load_open_data()
print([team.name for team in dataset.metadata.teams])
print(dataset.events[0].state)
/Users/koen/Developer/Projects/PySport/kloppy/.venv/lib/python3.10/site-packages/kloppy-3.7.1-py3.10.egg/kloppy/_providers/statsbomb.py:67: UserWarning: You are about to use StatsBomb public data. By using this data, you are agreeing to the user agreement. The user agreement can be found here: https://github.com/statsbomb/open-data/blob/master/LICENSE.pdf warnings.warn(
['Barcelona', 'Deportivo Alavés'] {}
Add state - score¶
kloppy contains some default state builders: score, lineup and sequence. Let's have a look at the score
state builder.
dataset = dataset.add_state('score')
dataset.events[0].state
{'score': Score(home=0, away=0)}
As you can see state
is now filled with a score object. The object contains two attributes: home
and away
. Every event contains a score object which is automatically updated when a goal is scored.
Now lets have a look at how we can use the score state. First we filter on only shots.
dataset = dataset.filter(lambda event: event.event_type == EventType.SHOT)
shots = dataset.events
len(shots)
28
for shot in shots:
print(shot.state['score'], shot.player.team, '-', shot.player, '-', shot.result)
0-0 Barcelona - Lionel Andrés Messi Cuccittini - OFF_TARGET 0-0 Barcelona - Jordi Alba Ramos - OFF_TARGET 0-0 Barcelona - Lionel Andrés Messi Cuccittini - SAVED 0-0 Deportivo Alavés - Rubén Sobrino Pozuelo - OFF_TARGET 0-0 Barcelona - Luis Alberto Suárez Díaz - OFF_TARGET 0-0 Barcelona - Ousmane Dembélé - OFF_TARGET 0-0 Barcelona - Ivan Rakitić - OFF_TARGET 0-0 Barcelona - Lionel Andrés Messi Cuccittini - POST 0-0 Barcelona - Gerard Piqué Bernabéu - OFF_TARGET 0-0 Barcelona - Ousmane Dembélé - SAVED 0-0 Barcelona - Luis Alberto Suárez Díaz - OFF_TARGET 0-0 Barcelona - Ousmane Dembélé - OFF_TARGET 0-0 Barcelona - Jordi Alba Ramos - SAVED 0-0 Deportivo Alavés - Mubarak Wakaso - BLOCKED 0-0 Barcelona - Luis Alberto Suárez Díaz - BLOCKED 0-0 Barcelona - Philippe Coutinho Correia - BLOCKED 0-0 Barcelona - Jordi Alba Ramos - BLOCKED 0-0 Barcelona - Lionel Andrés Messi Cuccittini - OFF_TARGET 0-0 Barcelona - Philippe Coutinho Correia - BLOCKED 0-0 Barcelona - Lionel Andrés Messi Cuccittini - GOAL 1-0 Barcelona - Lionel Andrés Messi Cuccittini - POST 1-0 Barcelona - Ivan Rakitić - BLOCKED 1-0 Barcelona - Luis Alberto Suárez Díaz - SAVED 1-0 Deportivo Alavés - Adrián Marín Gómez - OFF_TARGET 1-0 Barcelona - Philippe Coutinho Correia - SAVED 1-0 Barcelona - Philippe Coutinho Correia - GOAL 2-0 Barcelona - Lionel Andrés Messi Cuccittini - SAVED 2-0 Barcelona - Lionel Andrés Messi Cuccittini - GOAL
dataframe = dataset.to_df(
"*",
home_score=lambda event: event.state['score'].home,
away_score=lambda event: event.state['score'].away
)
dataframe
event_id | event_type | result | success | period_id | timestamp | end_timestamp | ball_state | ball_owning_team | team_id | player_id | coordinates_x | coordinates_y | end_coordinates_x | end_coordinates_y | body_part_type | home_score | away_score | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 65f16e50-7c5d-4293-b2fc-d20887a772f9 | SHOT | OFF_TARGET | False | 1 | 149.094 | None | alive | 217 | 217 | 5503 | 0.930417 | 0.645625 | None | None | RIGHT_FOOT | 0 | 0 |
1 | b0f73423-3990-45ae-9dda-3512c2d1aff3 | SHOT | OFF_TARGET | False | 1 | 339.239 | None | alive | 217 | 217 | 5211 | 0.949583 | 0.336875 | None | None | LEFT_FOOT | 0 | 0 |
2 | 13b1ddab-d22e-43d9-bfe4-12632fea1a27 | SHOT | SAVED | False | 1 | 928.625 | None | alive | 217 | 217 | 5503 | 0.766250 | 0.430625 | None | None | LEFT_FOOT | 0 | 0 |
3 | 391bfb74-07a6-4afe-9568-02a9b23f5bd4 | SHOT | OFF_TARGET | False | 1 | 979.616 | None | alive | 206 | 206 | 6613 | 0.908750 | 0.483125 | None | None | HEAD | 0 | 0 |
4 | 5e55f5a5-954f-4cc4-ba6e-a9cf6d6e249e | SHOT | OFF_TARGET | False | 1 | 1095.914 | None | alive | 217 | 217 | 5246 | 0.891250 | 0.311875 | None | None | RIGHT_FOOT | 0 | 0 |
5 | 1c0347cd-14dc-4aa8-91eb-520672a6cfe1 | SHOT | OFF_TARGET | False | 1 | 1842.287 | None | alive | 217 | 217 | 5477 | 0.900417 | 0.341875 | None | None | LEFT_FOOT | 0 | 0 |
6 | 7c3182af-c8a8-4c7c-934e-5c41c7b93c6a | SHOT | OFF_TARGET | False | 1 | 2104.861 | None | alive | 217 | 217 | 5470 | 0.932917 | 0.545625 | None | None | HEAD | 0 | 0 |
7 | 39f231e5-0072-461c-beb0-a9bedb420f83 | SHOT | POST | False | 1 | 2248.168 | None | alive | 217 | 217 | 5503 | 0.807917 | 0.674375 | None | None | LEFT_FOOT | 0 | 0 |
8 | 062cdd08-8773-424f-8fc5-2e3d441c3c5c | SHOT | OFF_TARGET | False | 1 | 2250.989 | None | alive | 217 | 217 | 5213 | 0.935417 | 0.516875 | None | None | HEAD | 0 | 0 |
9 | c09e904d-6c8e-479d-af2e-c2c5863aca71 | SHOT | SAVED | False | 1 | 2308.083 | None | alive | 217 | 217 | 5477 | 0.853750 | 0.364375 | None | None | RIGHT_FOOT | 0 | 0 |
10 | 9eda2385-88bb-4c37-b039-a738c4cd8d4b | SHOT | OFF_TARGET | False | 1 | 2434.592 | None | alive | 217 | 217 | 5246 | 0.910417 | 0.370625 | None | None | RIGHT_FOOT | 0 | 0 |
11 | 0b56df83-958e-4a31-9e5a-d741b4bf95cf | SHOT | OFF_TARGET | False | 1 | 2610.612 | None | alive | 217 | 217 | 5477 | 0.887083 | 0.418125 | None | None | RIGHT_FOOT | 0 | 0 |
12 | 79851101-0f47-48e2-bdac-2de2b2bc89cb | SHOT | SAVED | False | 2 | 159.524 | None | alive | 217 | 217 | 5211 | 0.973750 | 0.389375 | None | None | LEFT_FOOT | 0 | 0 |
13 | d37ddbf4-05b6-4127-8ca6-287a069bd912 | SHOT | BLOCKED | False | 2 | 367.400 | None | alive | 206 | 206 | 6626 | 0.882917 | 0.286875 | None | None | LEFT_FOOT | 0 | 0 |
14 | b8f9567a-b33c-4013-91a4-dd5c088dd14a | SHOT | BLOCKED | False | 2 | 534.355 | None | alive | 217 | 217 | 5246 | 0.925417 | 0.300625 | None | None | RIGHT_FOOT | 0 | 0 |
15 | 2e2b7445-812c-4162-a5a2-4a99290359ae | SHOT | BLOCKED | False | 2 | 596.388 | None | alive | 217 | 217 | 3501 | 0.786250 | 0.305625 | None | None | RIGHT_FOOT | 0 | 0 |
16 | 480a2132-0c0e-4a0b-95e7-eb994a1542ff | SHOT | BLOCKED | False | 2 | 634.490 | None | alive | 217 | 217 | 5211 | 0.894583 | 0.323125 | None | None | LEFT_FOOT | 0 | 0 |
17 | dfb55124-1e6a-475c-a460-3d9bf920bf04 | SHOT | OFF_TARGET | False | 2 | 740.847 | None | alive | 217 | 217 | 5503 | 0.833750 | 0.725625 | None | None | LEFT_FOOT | 0 | 0 |
18 | ff334a7e-6ec2-4c71-9957-6d8accaf48df | SHOT | BLOCKED | False | 2 | 936.156 | None | alive | 217 | 217 | 3501 | 0.834583 | 0.345625 | None | None | RIGHT_FOOT | 0 | 0 |
19 | 4c7c4ab1-6b9f-4504-a237-249c2e0c549f | SHOT | GOAL | True | 2 | 1091.954 | None | alive | 217 | 217 | 5503 | 0.800417 | 0.563125 | None | None | LEFT_FOOT | 0 | 0 |
20 | 28175830-bbf0-4be9-baef-75c1de731809 | SHOT | POST | False | 2 | 1243.588 | None | alive | 217 | 217 | 5503 | 0.883750 | 0.588125 | None | None | LEFT_FOOT | 1 | 0 |
21 | 89cef214-dec8-43c7-8222-3ea8942ae868 | SHOT | BLOCKED | False | 2 | 1413.375 | None | alive | 217 | 217 | 5470 | 0.738750 | 0.534375 | None | None | RIGHT_FOOT | 1 | 0 |
22 | 0a78095f-dde1-4cba-8639-3ac2750d3a4d | SHOT | SAVED | False | 2 | 1647.492 | None | alive | 217 | 217 | 5246 | 0.908750 | 0.375625 | None | None | LEFT_FOOT | 1 | 0 |
23 | 6e7d482d-30cc-4f5c-9b65-84c52ef8247c | SHOT | OFF_TARGET | False | 2 | 1997.679 | None | alive | 206 | 206 | 6935 | 0.944583 | 0.418125 | None | None | RIGHT_FOOT | 1 | 0 |
24 | 5f8f624e-eae9-4e6b-a086-d864bc8f4140 | SHOT | SAVED | False | 2 | 2191.606 | None | alive | 217 | 217 | 3501 | 0.944583 | 0.388125 | None | None | RIGHT_FOOT | 1 | 0 |
25 | 683c6752-13bc-4892-94ed-22e1c938f1f7 | SHOT | GOAL | True | 2 | 2261.578 | None | alive | 217 | 217 | 3501 | 0.875417 | 0.388125 | None | None | RIGHT_FOOT | 1 | 0 |
26 | 252b3061-7d3f-4922-b04f-0b37a44c6300 | SHOT | SAVED | False | 2 | 2662.638 | None | alive | 217 | 217 | 5503 | 0.883750 | 0.575625 | None | None | LEFT_FOOT | 2 | 0 |
27 | 55d71847-9511-4417-aea9-6f415e279011 | SHOT | GOAL | True | 2 | 2802.770 | None | alive | 217 | 217 | 5503 | 0.932917 | 0.431875 | None | None | LEFT_FOOT | 2 | 0 |
Now filter the dataframe. We only want to see shots when we are winning by at least two goals difference.
dataframe[dataframe['home_score'] - dataframe['away_score'] >= 2]
event_id | event_type | result | success | period_id | timestamp | end_timestamp | ball_state | ball_owning_team | team_id | player_id | coordinates_x | coordinates_y | end_coordinates_x | end_coordinates_y | body_part_type | home_score | away_score | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
26 | 252b3061-7d3f-4922-b04f-0b37a44c6300 | SHOT | SAVED | False | 2 | 2662.638 | None | alive | 217 | 217 | 5503 | 0.883750 | 0.575625 | None | None | LEFT_FOOT | 2 | 0 |
27 | 55d71847-9511-4417-aea9-6f415e279011 | SHOT | GOAL | True | 2 | 2802.770 | None | alive | 217 | 217 | 5503 | 0.932917 | 0.431875 | None | None | LEFT_FOOT | 2 | 0 |
Add state - lineup¶
We are able to add more state. In this example we'll look at adding lineup state.
from kloppy import statsbomb
from kloppy.domain import EventType
dataset = statsbomb.load_open_data()
home_team, away_team = dataset.metadata.teams
/Users/koen/Developer/Projects/PySport/kloppy/.venv/lib/python3.10/site-packages/kloppy-3.7.1-py3.10.egg/kloppy/_providers/statsbomb.py:67: UserWarning: You are about to use StatsBomb public data. By using this data, you are agreeing to the user agreement. The user agreement can be found here: https://github.com/statsbomb/open-data/blob/master/LICENSE.pdf warnings.warn(
Arturo Vidal is a substitute on the side of Barcelona. We add lineup to all events so we are able to filter out events where Arturo Vidal is on the pitch.
arturo_vidal = home_team.get_player_by_id(8206)
dataframe = (
dataset
.add_state('lineup')
.filter(lambda event: arturo_vidal in event.state['lineup'].players)
.to_df()
)
print(f"time on pitch: {dataframe['timestamp'].max() - dataframe['timestamp'].min()} seconds")
time on pitch: 490.6479999999997 seconds
dataframe = (
dataset
.add_state('lineup')
.filter(lambda event: event.event_type == EventType.PASS and event.team == home_team)
.to_df(
"*",
vidal_on_pitch=lambda event: arturo_vidal in event.state['lineup'].players
)
)
dataframe = dataframe.groupby(['vidal_on_pitch'])['success'].agg(['sum', 'count'])
dataframe['percentage'] = dataframe['sum'] / dataframe['count'] * 100
dataframe
sum | count | percentage | |
---|---|---|---|
vidal_on_pitch | |||
False | 709 | 798 | 88.847118 |
True | 83 | 88 | 94.318182 |
Add state - formation¶
In this example we'll look at adding formation state to all shots.
from kloppy import statsbomb
from kloppy.domain import EventType
dataset = statsbomb.load_open_data()
dataframe = (
dataset
.add_state('formation')
.filter(
lambda event: event.event_type == EventType.SHOT
)
.to_df(
"*",
Team=lambda event: str(event.team),
Formation=lambda event: str(
event.state['formation'].home
if event.team == dataset.metadata.teams[0]
else event.state['formation'].away
)
)
)
/Users/koen/Developer/Projects/PySport/kloppy/.venv/lib/python3.10/site-packages/kloppy-3.7.1-py3.10.egg/kloppy/_providers/statsbomb.py:67: UserWarning: You are about to use StatsBomb public data. By using this data, you are agreeing to the user agreement. The user agreement can be found here: https://github.com/statsbomb/open-data/blob/master/LICENSE.pdf warnings.warn(
dataframe_stats = (
dataframe
.groupby(['Team', 'Formation'])['success']
.agg(['sum', 'count'])
)
dataframe_stats['Percentage'] = (
dataframe_stats['sum']
/ dataframe_stats['count']
* 100
)
dataframe_stats.rename(
columns={
'sum': 'Goals',
'count': 'Shots'
}
)
Goals | Shots | Percentage | ||
---|---|---|---|---|
Team | Formation | |||
Barcelona | 4-3-3 | 3 | 14 | 21.428571 |
4-4-2 | 0 | 11 | 0.000000 | |
Deportivo Alavés | 4-1-4-1 | 0 | 2 | 0.000000 |
4-4-2 | 0 | 1 | 0.000000 |