{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Event data\n",
"\n",
"One of the main benefits of working with kloppy is that it loads metadata with the event data. This metadata includes teams (name, ground and provider id) and players (name, jersey number, optional position and provider id). Using this metadata, it becomes very easy to create an analysis that is usable by humans, because it includes names instead of only numbers.\n",
"\n",
"This section shows how metadata is organized and some use-cases."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Loading statsbomb data\n",
"\n",
"The datasets module of kloppy makes it trivial to load statsbomb data. Keep in mind that by using the data you accept the license of the open-data project.\n"
]
},
{
"cell_type": "code",
"execution_count": 55,
"metadata": {
"scrolled": true
},
"outputs": [],
"source": [
"from kloppy import statsbomb\n",
"\n",
"dataset = statsbomb.load_open_data(event_types=[\"pass\", \"shot\"])"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Exploring metadata\n",
"\n",
"kloppy always loads the metadata for you and makes it available at the `metadata` property. "
]
},
{
"cell_type": "code",
"execution_count": 56,
"metadata": {},
"outputs": [],
"source": [
"metadata = dataset.metadata\n",
"home_team, away_team = metadata.teams"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"After loading the data, the metadata can be used to iterate over teams and players. By default `metadata.teams` contain `[HomeTeam, AwayTeam]`. `Team` and `Player` entities have the `__str__` magic method implemented to help you cast it to a string. When you want to "
]
},
{
"cell_type": "code",
"execution_count": 57,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"home - Barcelona\n",
"away - Deportivo Alavés\n"
]
}
],
"source": [
"print(f\"{home_team.ground} - {home_team}\")\n",
"print(f\"{away_team.ground} - {away_team}\")"
]
},
{
"cell_type": "code",
"execution_count": 58,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"['Malcom Filipe Silva de Oliveira (14)',\n",
" 'Philippe Coutinho Correia (7)',\n",
" 'Sergio Busquets i Burgos (5)',\n",
" 'Jordi Alba Ramos (18)',\n",
" 'Gerard Piqué Bernabéu (3)',\n",
" 'Luis Alberto Suárez Díaz (9)',\n",
" 'Ivan Rakitić (4)',\n",
" 'Ousmane Dembélé (11)',\n",
" 'Samuel Yves Umtiti (23)',\n",
" 'Lionel Andrés Messi Cuccittini (10)',\n",
" 'Nélson Cabral Semedo (2)',\n",
" 'Sergi Roberto Carnicer (20)',\n",
" 'Clément Lenglet (15)',\n",
" 'Rafael Alcântara do Nascimento (12)',\n",
" 'Arturo Erasmo Vidal Pardo (22)',\n",
" 'Jasper Cillessen (13)',\n",
" 'Arthur Henrique Ramos de Oliveira Melo (8)',\n",
" 'Marc-André ter Stegen (1)']"
]
},
"execution_count": 58,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"[f\"{player} ({player.jersey_no})\" for player in home_team.players]\n"
]
},
{
"cell_type": "code",
"execution_count": 59,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"'statsbomb team id: 217 - 206'"
]
},
"execution_count": 59,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"# get provider id for team\n",
"f\"statsbomb team id: {home_team.team_id} - {away_team.team_id}\""
]
},
{
"cell_type": "code",
"execution_count": 60,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"['Malcom Filipe Silva de Oliveira id=3109',\n",
" 'Philippe Coutinho Correia id=3501',\n",
" 'Sergio Busquets i Burgos id=5203',\n",
" 'Jordi Alba Ramos id=5211',\n",
" 'Gerard Piqué Bernabéu id=5213',\n",
" 'Luis Alberto Suárez Díaz id=5246',\n",
" 'Ivan Rakitić id=5470',\n",
" 'Ousmane Dembélé id=5477',\n",
" 'Samuel Yves Umtiti id=5492',\n",
" 'Lionel Andrés Messi Cuccittini id=5503',\n",
" 'Nélson Cabral Semedo id=6374',\n",
" 'Sergi Roberto Carnicer id=6379',\n",
" 'Clément Lenglet id=6826',\n",
" 'Rafael Alcântara do Nascimento id=6998',\n",
" 'Arturo Erasmo Vidal Pardo id=8206',\n",
" 'Jasper Cillessen id=8652',\n",
" 'Arthur Henrique Ramos de Oliveira Melo id=11392',\n",
" 'Marc-André ter Stegen id=20055']"
]
},
"execution_count": 60,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"# same for the players\n",
"[f\"{player} id={player.player_id}\" for player in metadata.teams[0].players]\n"
]
},
{
"cell_type": "code",
"execution_count": 61,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Jonathan Rodríguez Menéndez\n",
"Deportivo Alavés\n",
"Teams are comparable? True\n"
]
}
],
"source": [
"# get player from first event\n",
"player = dataset.events[0].player\n",
"print(player)\n",
"print(player.team)\n",
"print(f\"Teams are comparable? {player.team == away_team}\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The `Team` and `Player` entities also contain the magic methods to use those keys in dictionaries or use them in sets. This makes it easy to do some calculations, and show the results without mapping the player_id to a name."
]
},
{
"cell_type": "code",
"execution_count": 62,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Jonathan Rodríguez Menéndez has 14 passes\n",
"Guillermo Alfonso Maripán Loaysa has 18 passes\n",
"Sergio Busquets i Burgos has 79 passes\n",
"Ivan Rakitić has 138 passes\n",
"Ousmane Dembélé has 65 passes\n",
"Jordi Alba Ramos has 121 passes\n",
"Víctor Laguardia Cisneros has 11 passes\n",
"Marc-André ter Stegen has 23 passes\n",
"Gerard Piqué Bernabéu has 79 passes\n",
"Nélson Cabral Semedo has 31 passes\n",
"Sergi Roberto Carnicer has 85 passes\n",
"Samuel Yves Umtiti has 63 passes\n",
"Lionel Andrés Messi Cuccittini has 92 passes\n",
"Rubén Duarte Sánchez has 25 passes\n",
"Ibai Gómez Pérez has 35 passes\n",
"Mubarak Wakaso has 23 passes\n",
"Manuel Alejandro García Sánchez has 23 passes\n",
"Rubén Sobrino Pozuelo has 17 passes\n",
"Luis Alberto Suárez Díaz has 38 passes\n",
"Fernando Pacheco Flores has 16 passes\n",
"Martín Aguirregabiria Padilla has 20 passes\n",
"Daniel Alejandro Torres Rojas has 16 passes\n",
"Philippe Coutinho Correia has 51 passes\n",
"Jorge Franco Alviz has 11 passes\n",
"Adrián Marín Gómez has 6 passes\n",
"Arthur Henrique Ramos de Oliveira Melo has 18 passes\n",
"Borja González Tomás has 7 passes\n",
"Arturo Erasmo Vidal Pardo has 7 passes\n"
]
}
],
"source": [
"from collections import defaultdict\n",
"\n",
"passes_per_player = defaultdict(list)\n",
"for event in dataset.events:\n",
" if event.event_name == \"pass\":\n",
" passes_per_player[event.player].append(event)\n",
" \n",
"for player, passes in passes_per_player.items():\n",
" print(f\"{player} has {len(passes)} passes\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Now let's filter on home_team."
]
},
{
"cell_type": "code",
"execution_count": 63,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Sergio Busquets i Burgos has 79 passes\n",
"Ivan Rakitić has 138 passes\n",
"Ousmane Dembélé has 65 passes\n",
"Jordi Alba Ramos has 121 passes\n",
"Marc-André ter Stegen has 23 passes\n",
"Gerard Piqué Bernabéu has 79 passes\n",
"Nélson Cabral Semedo has 31 passes\n",
"Sergi Roberto Carnicer has 85 passes\n",
"Samuel Yves Umtiti has 63 passes\n",
"Lionel Andrés Messi Cuccittini has 92 passes\n",
"Luis Alberto Suárez Díaz has 38 passes\n",
"Philippe Coutinho Correia has 51 passes\n",
"Arthur Henrique Ramos de Oliveira Melo has 18 passes\n",
"Arturo Erasmo Vidal Pardo has 7 passes\n"
]
}
],
"source": [
"for player, passes in passes_per_player.items():\n",
" if player.team == home_team:\n",
" print(f\"{player} has {len(passes)} passes\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Use metadata when transforming to pandas dataframe\n",
"\n",
"The metadata can also be used when transforming a dataset to a pandas dataframe. Using keyword argument additional columns can be created."
]
},
{
"cell_type": "code",
"execution_count": 64,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"
\n",
"\n",
"
\n",
" \n",
" \n",
" \n",
" event_id \n",
" event_type \n",
" result \n",
" timestamp \n",
" player_id \n",
" player_name \n",
" team_name \n",
" \n",
" \n",
" \n",
" \n",
" 0 \n",
" 34208ade-2af4-45c3-970e-655937cad938 \n",
" PASS \n",
" COMPLETE \n",
" 0.098 \n",
" 6581 \n",
" Jonathan Rodríguez Menéndez \n",
" Deportivo Alavés \n",
" \n",
" \n",
" 1 \n",
" d1cccb73-c7ef-4b02-8267-ebd7f149904b \n",
" PASS \n",
" INCOMPLETE \n",
" 3.497 \n",
" 6855 \n",
" Guillermo Alfonso Maripán Loaysa \n",
" Deportivo Alavés \n",
" \n",
" \n",
" 2 \n",
" f1cc47d6-4b19-45a6-beb9-33d67fc83f4b \n",
" PASS \n",
" COMPLETE \n",
" 6.785 \n",
" 5203 \n",
" Sergio Busquets i Burgos \n",
" Barcelona \n",
" \n",
" \n",
" 3 \n",
" f774571f-4b65-43a0-9bfc-6384948d1b82 \n",
" PASS \n",
" COMPLETE \n",
" 8.431 \n",
" 5470 \n",
" Ivan Rakitić \n",
" Barcelona \n",
" \n",
" \n",
" 4 \n",
" 46f0e871-3e72-4817-9a53-af27583ba6c1 \n",
" PASS \n",
" COMPLETE \n",
" 10.433 \n",
" 5477 \n",
" Ousmane Dembélé \n",
" Barcelona \n",
" \n",
" \n",
"
\n",
"
"
],
"text/plain": [
" event_id event_type result timestamp \\\n",
"0 34208ade-2af4-45c3-970e-655937cad938 PASS COMPLETE 0.098 \n",
"1 d1cccb73-c7ef-4b02-8267-ebd7f149904b PASS INCOMPLETE 3.497 \n",
"2 f1cc47d6-4b19-45a6-beb9-33d67fc83f4b PASS COMPLETE 6.785 \n",
"3 f774571f-4b65-43a0-9bfc-6384948d1b82 PASS COMPLETE 8.431 \n",
"4 46f0e871-3e72-4817-9a53-af27583ba6c1 PASS COMPLETE 10.433 \n",
"\n",
" player_id player_name team_name \n",
"0 6581 Jonathan Rodríguez Menéndez Deportivo Alavés \n",
"1 6855 Guillermo Alfonso Maripán Loaysa Deportivo Alavés \n",
"2 5203 Sergio Busquets i Burgos Barcelona \n",
"3 5470 Ivan Rakitić Barcelona \n",
"4 5477 Ousmane Dembélé Barcelona "
]
},
"execution_count": 64,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"\n",
"dataframe = dataset.to_df(\n",
" \"*\", # Get all default columns\n",
" player_name=lambda event: str(event.player),\n",
" team_name=lambda event: str(event.player.team)\n",
")\n",
"\n",
"dataframe[[\n",
" 'event_id', 'event_type', 'result', 'timestamp', 'player_id', \n",
" 'player_name', 'team_name'\n",
"]].head()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Attribute transformers\n",
"\n",
"Attribute transformer make it possible to add predefined attributes to a dataset. The attributes are calculated during export to a pandas DataFrame. Kloppy does provide some Transformers like one to calculate the angle to the goal, and one to calculate the distance to the goal. When you need additional Transformers you can write your one by providing a `Callable` to `to_df`. "
]
},
{
"cell_type": "code",
"execution_count": 65,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"\n",
"\n",
"
\n",
" \n",
" \n",
" \n",
" angle_to_goal \n",
" distance_to_goal \n",
" \n",
" \n",
" \n",
" \n",
" 0 \n",
" 90.481466 \n",
" 59.502101 \n",
" \n",
" \n",
" 1 \n",
" 82.249964 \n",
" 85.278954 \n",
" \n",
" \n",
" 2 \n",
" 69.187354 \n",
" 91.468574 \n",
" \n",
" \n",
" 3 \n",
" 77.005383 \n",
" 86.720816 \n",
" \n",
" \n",
" 4 \n",
" 66.562013 \n",
" 94.278842 \n",
" \n",
" \n",
" ... \n",
" ... \n",
" ... \n",
" \n",
" \n",
" 1155 \n",
" 121.578165 \n",
" 63.972650 \n",
" \n",
" \n",
" 1156 \n",
" 104.393593 \n",
" 58.330952 \n",
" \n",
" \n",
" 1157 \n",
" 39.559668 \n",
" 44.749302 \n",
" \n",
" \n",
" 1158 \n",
" 71.095424 \n",
" 38.581083 \n",
" \n",
" \n",
" 1159 \n",
" 55.901268 \n",
" 9.721368 \n",
" \n",
" \n",
"
\n",
"
1160 rows × 2 columns
\n",
"
"
],
"text/plain": [
" angle_to_goal distance_to_goal\n",
"0 90.481466 59.502101\n",
"1 82.249964 85.278954\n",
"2 69.187354 91.468574\n",
"3 77.005383 86.720816\n",
"4 66.562013 94.278842\n",
"... ... ...\n",
"1155 121.578165 63.972650\n",
"1156 104.393593 58.330952\n",
"1157 39.559668 44.749302\n",
"1158 71.095424 38.581083\n",
"1159 55.901268 9.721368\n",
"\n",
"[1160 rows x 2 columns]"
]
},
"execution_count": 65,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"from kloppy import statsbomb\n",
"\n",
"from kloppy.domain.services.transformers.attribute import (\n",
" BodyPartTransformer, AngleToGoalTransformer, DistanceToGoalTransformer\n",
")\n",
"\n",
"dataset = statsbomb.load_open_data(\n",
" event_types=[\"pass\", \"shot\"], \n",
" coordinates=\"statsbomb\"\n",
")\n",
"\n",
"dataset.to_df(\n",
" AngleToGoalTransformer(),\n",
" DistanceToGoalTransformer()\n",
")"
]
},
{
"cell_type": "code",
"execution_count": 66,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"{'is_body_part_right_foot': False, 'is_body_part_left_foot': True, 'is_body_part_head': False, 'is_body_part_both_hands': False, 'is_body_part_chest': False, 'is_body_part_left_hand': False, 'is_body_part_right_hand': False, 'is_body_part_drop_kick': False, 'is_body_part_keeper_arm': False, 'is_body_part_other': False, 'is_body_part_no_touch': False}\n"
]
},
{
"data": {
"text/plain": [
"{'angle_to_goal': 90.48146580583835}"
]
},
"execution_count": 66,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"event = dataset.events[0]\n",
"\n",
"transformer = BodyPartTransformer(encoding=\"one-hot\")\n",
"print(transformer(event))\n",
"\n",
"\n",
"transformer = AngleToGoalTransformer()\n",
"transformer(event)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Wildcard\n",
"\n",
"When you want to export a set of attributes you can specify a wildcard pattern. This pattern is matched against all default (exported by the `Default` Transformer) attributes."
]
},
{
"cell_type": "code",
"execution_count": 67,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"\n",
"\n",
"
\n",
" \n",
" \n",
" \n",
" period_id \n",
" timestamp \n",
" coordinates_x \n",
" coordinates_y \n",
" end_coordinates_x \n",
" end_coordinates_y \n",
" \n",
" \n",
" \n",
" \n",
" 0 \n",
" 1 \n",
" 0.098 \n",
" 60.50 \n",
" 40.50 \n",
" 35.5 \n",
" 25.5 \n",
" \n",
" \n",
" 1 \n",
" 1 \n",
" 3.497 \n",
" 35.50 \n",
" 28.50 \n",
" 85.5 \n",
" 72.5 \n",
" \n",
" \n",
" 2 \n",
" 1 \n",
" 6.785 \n",
" 34.50 \n",
" 7.50 \n",
" 34.5 \n",
" 20.5 \n",
" \n",
" \n",
" 3 \n",
" 1 \n",
" 8.431 \n",
" 35.50 \n",
" 20.50 \n",
" 35.5 \n",
" 1.5 \n",
" \n",
" \n",
" 4 \n",
" 1 \n",
" 10.433 \n",
" 33.50 \n",
" 2.50 \n",
" 25.5 \n",
" 1.5 \n",
" \n",
" \n",
" ... \n",
" ... \n",
" ... \n",
" ... \n",
" ... \n",
" ... \n",
" ... \n",
" \n",
" \n",
" 1155 \n",
" 2 \n",
" 2787.914 \n",
" 65.50 \n",
" 73.50 \n",
" 59.5 \n",
" 60.5 \n",
" \n",
" \n",
" 1156 \n",
" 2 \n",
" 2791.395 \n",
" 63.50 \n",
" 54.50 \n",
" 89.5 \n",
" 5.5 \n",
" \n",
" \n",
" 1157 \n",
" 2 \n",
" 2795.127 \n",
" 91.50 \n",
" 5.50 \n",
" 90.5 \n",
" 26.5 \n",
" \n",
" \n",
" 1158 \n",
" 2 \n",
" 2798.906 \n",
" 83.50 \n",
" 27.50 \n",
" 106.5 \n",
" 44.5 \n",
" \n",
" \n",
" 1159 \n",
" 2 \n",
" 2802.770 \n",
" 111.95 \n",
" 34.55 \n",
" NaN \n",
" NaN \n",
" \n",
" \n",
"
\n",
"
1160 rows × 6 columns
\n",
"
"
],
"text/plain": [
" period_id timestamp coordinates_x coordinates_y end_coordinates_x \\\n",
"0 1 0.098 60.50 40.50 35.5 \n",
"1 1 3.497 35.50 28.50 85.5 \n",
"2 1 6.785 34.50 7.50 34.5 \n",
"3 1 8.431 35.50 20.50 35.5 \n",
"4 1 10.433 33.50 2.50 25.5 \n",
"... ... ... ... ... ... \n",
"1155 2 2787.914 65.50 73.50 59.5 \n",
"1156 2 2791.395 63.50 54.50 89.5 \n",
"1157 2 2795.127 91.50 5.50 90.5 \n",
"1158 2 2798.906 83.50 27.50 106.5 \n",
"1159 2 2802.770 111.95 34.55 NaN \n",
"\n",
" end_coordinates_y \n",
"0 25.5 \n",
"1 72.5 \n",
"2 20.5 \n",
"3 1.5 \n",
"4 1.5 \n",
"... ... \n",
"1155 60.5 \n",
"1156 5.5 \n",
"1157 26.5 \n",
"1158 44.5 \n",
"1159 NaN \n",
"\n",
"[1160 rows x 6 columns]"
]
},
"execution_count": 67,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"dataset.to_df(\n",
" 'period_id',\n",
" 'timestamp',\n",
" '*coordinates*',\n",
")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## User-defined Transformers\n",
"\n",
"Transformers are nothing more than a function which accepts a `Event` and returns `Dict` (`Callable[[Event], Dict])`). The Transformers provided by kloppy are actually classes that define a `__call__` method. You can also use a `lambda` function or any other function to transform attributes.\n",
"\n",
"When you use named attributes (specified using a keyword argument) the returned value can be any type (`Callable[[Event], Any]`). "
]
},
{
"cell_type": "code",
"execution_count": 68,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"\n",
"\n",
"
\n",
" \n",
" \n",
" \n",
" period \n",
" timestamp \n",
" some_columns \n",
" other_column \n",
" \n",
" \n",
" \n",
" \n",
" 0 \n",
" 1 \n",
" 0.098 \n",
" 1234 \n",
" 62 \n",
" \n",
" \n",
" 1 \n",
" 1 \n",
" 3.497 \n",
" 1234 \n",
" 252 \n",
" \n",
" \n",
" 2 \n",
" 1 \n",
" 6.785 \n",
" 1234 \n",
" 194 \n",
" \n",
" \n",
" 3 \n",
" 1 \n",
" 8.431 \n",
" 1234 \n",
" 121 \n",
" \n",
" \n",
" 4 \n",
" 1 \n",
" 10.433 \n",
" 1234 \n",
" 161 \n",
" \n",
" \n",
" ... \n",
" ... \n",
" ... \n",
" ... \n",
" ... \n",
" \n",
" \n",
" 1155 \n",
" 2 \n",
" 2787.914 \n",
" 1234 \n",
" 230 \n",
" \n",
" \n",
" 1156 \n",
" 2 \n",
" 2791.395 \n",
" 1234 \n",
" 153 \n",
" \n",
" \n",
" 1157 \n",
" 2 \n",
" 2795.127 \n",
" 1234 \n",
" 151 \n",
" \n",
" \n",
" 1158 \n",
" 2 \n",
" 2798.906 \n",
" 1234 \n",
" 160 \n",
" \n",
" \n",
" 1159 \n",
" 2 \n",
" 2802.770 \n",
" 1234 \n",
" 242 \n",
" \n",
" \n",
"
\n",
"
1160 rows × 4 columns
\n",
"
"
],
"text/plain": [
" period timestamp some_columns other_column\n",
"0 1 0.098 1234 62\n",
"1 1 3.497 1234 252\n",
"2 1 6.785 1234 194\n",
"3 1 8.431 1234 121\n",
"4 1 10.433 1234 161\n",
"... ... ... ... ...\n",
"1155 2 2787.914 1234 230\n",
"1156 2 2791.395 1234 153\n",
"1157 2 2795.127 1234 151\n",
"1158 2 2798.906 1234 160\n",
"1159 2 2802.770 1234 242\n",
"\n",
"[1160 rows x 4 columns]"
]
},
"execution_count": 68,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"import random\n",
"\n",
"dataset.to_df(\n",
" # Unnamed transformer must always be defined as a Callable. The function must return a Dictionary\n",
" lambda event: {'period': event.period.id, 'timestamp': event.timestamp},\n",
" \n",
" # Named transformer can be specified as a constant\n",
" some_columns=1234,\n",
" \n",
" # Or as a callable\n",
" other_column=lambda x: random.randint(0, 255)\n",
")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Polars\n",
"\n",
"Since version 3.8.0 it's possible to export a `Dataset` to a [Polars](https://www.pola.rs/) dataframe."
]
},
{
"cell_type": "code",
"execution_count": 78,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"\n",
"\n",
"\n",
"
\n",
"shape: (5, 19) \n",
"\n",
"\n",
"\n",
"event_id\n",
" \n",
"\n",
"event_type\n",
" \n",
"\n",
"result\n",
" \n",
"\n",
"success\n",
" \n",
"\n",
"period_id\n",
" \n",
"\n",
"timestamp\n",
" \n",
"\n",
"end_timestamp\n",
" \n",
"\n",
"ball_state\n",
" \n",
"\n",
"ball_owning_team\n",
" \n",
"\n",
"team_id\n",
" \n",
"\n",
"player_id\n",
" \n",
"\n",
"coordinates_x\n",
" \n",
"\n",
"coordinates_y\n",
" \n",
"\n",
"end_coordinates_x\n",
" \n",
"\n",
"end_coordinates_y\n",
" \n",
"\n",
"receiver_player_id\n",
" \n",
"\n",
"set_piece_type\n",
" \n",
"\n",
"body_part_type\n",
" \n",
"\n",
"pass_type\n",
" \n",
" \n",
"\n",
"\n",
"str\n",
" \n",
"\n",
"str\n",
" \n",
"\n",
"str\n",
" \n",
"\n",
"bool\n",
" \n",
"\n",
"i64\n",
" \n",
"\n",
"f64\n",
" \n",
"\n",
"f64\n",
" \n",
"\n",
"str\n",
" \n",
"\n",
"str\n",
" \n",
"\n",
"str\n",
" \n",
"\n",
"str\n",
" \n",
"\n",
"f64\n",
" \n",
"\n",
"f64\n",
" \n",
"\n",
"f64\n",
" \n",
"\n",
"f64\n",
" \n",
"\n",
"str\n",
" \n",
"\n",
"str\n",
" \n",
"\n",
"str\n",
" \n",
"\n",
"str\n",
" \n",
" \n",
" \n",
"\n",
"\n",
"\n",
""19edeac2-e63f-...\n",
" \n",
"\n",
""GENERIC:Starti...\n",
" \n",
"\n",
"null\n",
" \n",
"\n",
"null\n",
" \n",
"\n",
"1\n",
" \n",
"\n",
"0.0\n",
" \n",
"\n",
"null\n",
" \n",
"\n",
""alive"\n",
" \n",
"\n",
""909"\n",
" \n",
"\n",
""909"\n",
" \n",
"\n",
"null\n",
" \n",
"\n",
"null\n",
" \n",
"\n",
"null\n",
" \n",
"\n",
"null\n",
" \n",
"\n",
"null\n",
" \n",
"\n",
"null\n",
" \n",
"\n",
"null\n",
" \n",
"\n",
"null\n",
" \n",
"\n",
"null\n",
" \n",
" \n",
"\n",
"\n",
""89072e2e-b64f-...\n",
" \n",
"\n",
""GENERIC:Starti...\n",
" \n",
"\n",
"null\n",
" \n",
"\n",
"null\n",
" \n",
"\n",
"1\n",
" \n",
"\n",
"0.0\n",
" \n",
"\n",
"null\n",
" \n",
"\n",
""alive"\n",
" \n",
"\n",
""909"\n",
" \n",
"\n",
""914"\n",
" \n",
"\n",
"null\n",
" \n",
"\n",
"null\n",
" \n",
"\n",
"null\n",
" \n",
"\n",
"null\n",
" \n",
"\n",
"null\n",
" \n",
"\n",
"null\n",
" \n",
"\n",
"null\n",
" \n",
"\n",
"null\n",
" \n",
"\n",
"null\n",
" \n",
" \n",
"\n",
"\n",
""46c6901e-3b12-...\n",
" \n",
"\n",
""GENERIC:Half S...\n",
" \n",
"\n",
"null\n",
" \n",
"\n",
"null\n",
" \n",
"\n",
"1\n",
" \n",
"\n",
"0.0\n",
" \n",
"\n",
"null\n",
" \n",
"\n",
""alive"\n",
" \n",
"\n",
""909"\n",
" \n",
"\n",
""914"\n",
" \n",
"\n",
"null\n",
" \n",
"\n",
"null\n",
" \n",
"\n",
"null\n",
" \n",
"\n",
"null\n",
" \n",
"\n",
"null\n",
" \n",
"\n",
"null\n",
" \n",
"\n",
"null\n",
" \n",
"\n",
"null\n",
" \n",
"\n",
"null\n",
" \n",
" \n",
"\n",
"\n",
""9e5b0646-91cc-...\n",
" \n",
"\n",
""GENERIC:Half S...\n",
" \n",
"\n",
"null\n",
" \n",
"\n",
"null\n",
" \n",
"\n",
"1\n",
" \n",
"\n",
"0.0\n",
" \n",
"\n",
"null\n",
" \n",
"\n",
""alive"\n",
" \n",
"\n",
""909"\n",
" \n",
"\n",
""909"\n",
" \n",
"\n",
"null\n",
" \n",
"\n",
"null\n",
" \n",
"\n",
"null\n",
" \n",
"\n",
"null\n",
" \n",
"\n",
"null\n",
" \n",
"\n",
"null\n",
" \n",
"\n",
"null\n",
" \n",
"\n",
"null\n",
" \n",
"\n",
"null\n",
" \n",
" \n",
"\n",
"\n",
""bbc398f7-c784-...\n",
" \n",
"\n",
""PASS"\n",
" \n",
"\n",
""COMPLETE"\n",
" \n",
"\n",
"true\n",
" \n",
"\n",
"1\n",
" \n",
"\n",
"0.878\n",
" \n",
"\n",
"2.788504\n",
" \n",
"\n",
""alive"\n",
" \n",
"\n",
""909"\n",
" \n",
"\n",
""909"\n",
" \n",
"\n",
""11086"\n",
" \n",
"\n",
"59.95\n",
" \n",
"\n",
"39.95\n",
" \n",
"\n",
"32.45\n",
" \n",
"\n",
"28.75\n",
" \n",
"\n",
""8963"\n",
" \n",
"\n",
""KICK_OFF"\n",
" \n",
"\n",
""RIGHT_FOOT"\n",
" \n",
"\n",
"null\n",
" \n",
" \n",
" \n",
"
\n",
"
"
],
"text/plain": [
"shape: (5, 19)\n",
"┌─────────┬──────────┬──────────┬─────────┬─────┬────────────┬────────────┬────────────┬───────────┐\n",
"│ event_i ┆ event_ty ┆ result ┆ success ┆ ... ┆ receiver_p ┆ set_piece_ ┆ body_part_ ┆ pass_type │\n",
"│ d ┆ pe ┆ --- ┆ --- ┆ ┆ layer_id ┆ type ┆ type ┆ --- │\n",
"│ --- ┆ --- ┆ str ┆ bool ┆ ┆ --- ┆ --- ┆ --- ┆ str │\n",
"│ str ┆ str ┆ ┆ ┆ ┆ str ┆ str ┆ str ┆ │\n",
"╞═════════╪══════════╪══════════╪═════════╪═════╪════════════╪════════════╪════════════╪═══════════╡\n",
"│ 19edeac ┆ GENERIC: ┆ null ┆ null ┆ ... ┆ null ┆ null ┆ null ┆ null │\n",
"│ 2-e63f- ┆ Starting ┆ ┆ ┆ ┆ ┆ ┆ ┆ │\n",
"│ 4795-8a ┆ XI ┆ ┆ ┆ ┆ ┆ ┆ ┆ │\n",
"│ 8b-17a6 ┆ ┆ ┆ ┆ ┆ ┆ ┆ ┆ │\n",
"│ e9fd... ┆ ┆ ┆ ┆ ┆ ┆ ┆ ┆ │\n",
"│ 89072e2 ┆ GENERIC: ┆ null ┆ null ┆ ... ┆ null ┆ null ┆ null ┆ null │\n",
"│ e-b64f- ┆ Starting ┆ ┆ ┆ ┆ ┆ ┆ ┆ │\n",
"│ 4099-84 ┆ XI ┆ ┆ ┆ ┆ ┆ ┆ ┆ │\n",
"│ 6b-b22c ┆ ┆ ┆ ┆ ┆ ┆ ┆ ┆ │\n",
"│ f000... ┆ ┆ ┆ ┆ ┆ ┆ ┆ ┆ │\n",
"│ 46c6901 ┆ GENERIC: ┆ null ┆ null ┆ ... ┆ null ┆ null ┆ null ┆ null │\n",
"│ e-3b12- ┆ Half ┆ ┆ ┆ ┆ ┆ ┆ ┆ │\n",
"│ 495a-b6 ┆ Start ┆ ┆ ┆ ┆ ┆ ┆ ┆ │\n",
"│ 8a-19ca ┆ ┆ ┆ ┆ ┆ ┆ ┆ ┆ │\n",
"│ 1579... ┆ ┆ ┆ ┆ ┆ ┆ ┆ ┆ │\n",
"│ 9e5b064 ┆ GENERIC: ┆ null ┆ null ┆ ... ┆ null ┆ null ┆ null ┆ null │\n",
"│ 6-91cc- ┆ Half ┆ ┆ ┆ ┆ ┆ ┆ ┆ │\n",
"│ 49a1-bf ┆ Start ┆ ┆ ┆ ┆ ┆ ┆ ┆ │\n",
"│ 88-39bd ┆ ┆ ┆ ┆ ┆ ┆ ┆ ┆ │\n",
"│ e773... ┆ ┆ ┆ ┆ ┆ ┆ ┆ ┆ │\n",
"│ bbc398f ┆ PASS ┆ COMPLETE ┆ true ┆ ... ┆ 8963 ┆ KICK_OFF ┆ RIGHT_FOOT ┆ null │\n",
"│ 7-c784- ┆ ┆ ┆ ┆ ┆ ┆ ┆ ┆ │\n",
"│ 4958-a5 ┆ ┆ ┆ ┆ ┆ ┆ ┆ ┆ │\n",
"│ 04-37b5 ┆ ┆ ┆ ┆ ┆ ┆ ┆ ┆ │\n",
"│ 83ca... ┆ ┆ ┆ ┆ ┆ ┆ ┆ ┆ │\n",
"└─────────┴──────────┴──────────┴─────────┴─────┴────────────┴────────────┴────────────┴───────────┘"
]
},
"execution_count": 78,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"# %pip install polars\n",
"# you might need to install polars\n",
"\n",
"df = dataset.to_df(\n",
" engine=\"polars\"\n",
")\n",
"df.head()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## to_records\n",
"\n",
"Under the hood the `to_df` method uses the `to_records` method. "
]
},
{
"cell_type": "code",
"execution_count": 69,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"[{'period': 1, 'timestamp': 0.098, 'some_columns': 1234, 'other_column': 42},\n",
" {'period': 1, 'timestamp': 3.497, 'some_columns': 1234, 'other_column': 72},\n",
" {'period': 1, 'timestamp': 6.785, 'some_columns': 1234, 'other_column': 135},\n",
" {'period': 1, 'timestamp': 8.431, 'some_columns': 1234, 'other_column': 100},\n",
" {'period': 1, 'timestamp': 10.433, 'some_columns': 1234, 'other_column': 193},\n",
" {'period': 1, 'timestamp': 11.15, 'some_columns': 1234, 'other_column': 64},\n",
" {'period': 1, 'timestamp': 24.687, 'some_columns': 1234, 'other_column': 22},\n",
" {'period': 1, 'timestamp': 30.008, 'some_columns': 1234, 'other_column': 157},\n",
" {'period': 1, 'timestamp': 34.738, 'some_columns': 1234, 'other_column': 73},\n",
" {'period': 1, 'timestamp': 37.467, 'some_columns': 1234, 'other_column': 226}]"
]
},
"execution_count": 69,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"records = dataset.to_records(\n",
" # Unnamed transformer must always be defined as a Callable. The function must return a Dictionary\n",
" lambda event: {'period': event.period.id, 'timestamp': event.timestamp},\n",
" \n",
" # Named transformer can be specified as a constant\n",
" some_columns=1234,\n",
" \n",
" # Or as a callable\n",
" other_column=lambda x: random.randint(0, 255)\n",
")\n",
"records[:10]"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## EventFactory\n",
"\n",
"In some cases like to use your own `Event` classes. This can be useful when you need certain data that isn't stored in the regular `Event` classes"
]
},
{
"cell_type": "code",
"execution_count": 70,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"\n",
"\n",
"
\n",
" \n",
" \n",
" \n",
" statsbomb_xg \n",
" player \n",
" timestamp \n",
" \n",
" \n",
" \n",
" \n",
" 0 \n",
" 0.075164 \n",
" Lionel Andrés Messi Cuccittini \n",
" 149.094 \n",
" \n",
" \n",
" 1 \n",
" 0.062892 \n",
" Jordi Alba Ramos \n",
" 339.239 \n",
" \n",
" \n",
" 2 \n",
" 0.020535 \n",
" Lionel Andrés Messi Cuccittini \n",
" 928.625 \n",
" \n",
" \n",
" 3 \n",
" 0.096234 \n",
" Rubén Sobrino Pozuelo \n",
" 979.616 \n",
" \n",
" \n",
" 4 \n",
" 0.035420 \n",
" Luis Alberto Suárez Díaz \n",
" 1095.914 \n",
" \n",
" \n",
" 5 \n",
" 0.089920 \n",
" Ousmane Dembélé \n",
" 1842.287 \n",
" \n",
" \n",
" 6 \n",
" 0.071365 \n",
" Ivan Rakitić \n",
" 2104.861 \n",
" \n",
" \n",
" 7 \n",
" 0.078886 \n",
" Lionel Andrés Messi Cuccittini \n",
" 2248.168 \n",
" \n",
" \n",
" 8 \n",
" 0.171218 \n",
" Gerard Piqué Bernabéu \n",
" 2250.989 \n",
" \n",
" \n",
" 9 \n",
" 0.226095 \n",
" Ousmane Dembélé \n",
" 2308.083 \n",
" \n",
" \n",
" 10 \n",
" 0.257290 \n",
" Luis Alberto Suárez Díaz \n",
" 2434.592 \n",
" \n",
" \n",
" 11 \n",
" 0.145402 \n",
" Ousmane Dembélé \n",
" 2610.612 \n",
" \n",
" \n",
" 12 \n",
" 0.143644 \n",
" Jordi Alba Ramos \n",
" 2864.792 \n",
" \n",
" \n",
" 13 \n",
" 0.034266 \n",
" Mubarak Wakaso \n",
" 3072.668 \n",
" \n",
" \n",
" 14 \n",
" 0.018334 \n",
" Luis Alberto Suárez Díaz \n",
" 3239.623 \n",
" \n",
" \n",
" 15 \n",
" 0.014615 \n",
" Philippe Coutinho Correia \n",
" 3301.656 \n",
" \n",
" \n",
" 16 \n",
" 0.039418 \n",
" Jordi Alba Ramos \n",
" 3339.758 \n",
" \n",
" \n",
" 17 \n",
" 0.026228 \n",
" Lionel Andrés Messi Cuccittini \n",
" 3446.115 \n",
" \n",
" \n",
" 18 \n",
" 0.031532 \n",
" Philippe Coutinho Correia \n",
" 3641.424 \n",
" \n",
" \n",
" 19 \n",
" 0.137812 \n",
" Lionel Andrés Messi Cuccittini \n",
" 3797.222 \n",
" \n",
" \n",
" 20 \n",
" 0.081403 \n",
" Lionel Andrés Messi Cuccittini \n",
" 3948.856 \n",
" \n",
" \n",
" 21 \n",
" 0.009953 \n",
" Ivan Rakitić \n",
" 4118.643 \n",
" \n",
" \n",
" 22 \n",
" 0.337188 \n",
" Luis Alberto Suárez Díaz \n",
" 4352.760 \n",
" \n",
" \n",
" 23 \n",
" 0.137859 \n",
" Adrián Marín Gómez \n",
" 4702.947 \n",
" \n",
" \n",
" 24 \n",
" 0.379632 \n",
" Philippe Coutinho Correia \n",
" 4896.874 \n",
" \n",
" \n",
" 25 \n",
" 0.086874 \n",
" Philippe Coutinho Correia \n",
" 4966.846 \n",
" \n",
" \n",
" 26 \n",
" 0.262502 \n",
" Lionel Andrés Messi Cuccittini \n",
" 5367.906 \n",
" \n",
" \n",
" 27 \n",
" 0.289481 \n",
" Lionel Andrés Messi Cuccittini \n",
" 5508.038 \n",
" \n",
" \n",
"
\n",
"
"
],
"text/plain": [
" statsbomb_xg player timestamp\n",
"0 0.075164 Lionel Andrés Messi Cuccittini 149.094\n",
"1 0.062892 Jordi Alba Ramos 339.239\n",
"2 0.020535 Lionel Andrés Messi Cuccittini 928.625\n",
"3 0.096234 Rubén Sobrino Pozuelo 979.616\n",
"4 0.035420 Luis Alberto Suárez Díaz 1095.914\n",
"5 0.089920 Ousmane Dembélé 1842.287\n",
"6 0.071365 Ivan Rakitić 2104.861\n",
"7 0.078886 Lionel Andrés Messi Cuccittini 2248.168\n",
"8 0.171218 Gerard Piqué Bernabéu 2250.989\n",
"9 0.226095 Ousmane Dembélé 2308.083\n",
"10 0.257290 Luis Alberto Suárez Díaz 2434.592\n",
"11 0.145402 Ousmane Dembélé 2610.612\n",
"12 0.143644 Jordi Alba Ramos 2864.792\n",
"13 0.034266 Mubarak Wakaso 3072.668\n",
"14 0.018334 Luis Alberto Suárez Díaz 3239.623\n",
"15 0.014615 Philippe Coutinho Correia 3301.656\n",
"16 0.039418 Jordi Alba Ramos 3339.758\n",
"17 0.026228 Lionel Andrés Messi Cuccittini 3446.115\n",
"18 0.031532 Philippe Coutinho Correia 3641.424\n",
"19 0.137812 Lionel Andrés Messi Cuccittini 3797.222\n",
"20 0.081403 Lionel Andrés Messi Cuccittini 3948.856\n",
"21 0.009953 Ivan Rakitić 4118.643\n",
"22 0.337188 Luis Alberto Suárez Díaz 4352.760\n",
"23 0.137859 Adrián Marín Gómez 4702.947\n",
"24 0.379632 Philippe Coutinho Correia 4896.874\n",
"25 0.086874 Philippe Coutinho Correia 4966.846\n",
"26 0.262502 Lionel Andrés Messi Cuccittini 5367.906\n",
"27 0.289481 Lionel Andrés Messi Cuccittini 5508.038"
]
},
"execution_count": 70,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"from dataclasses import dataclass\n",
"\n",
"from kloppy.domain import EventFactory, create_event, ShotEvent\n",
"from kloppy import statsbomb\n",
"\n",
"\n",
"@dataclass(repr=False)\n",
"class StatsBombShotEvent(ShotEvent):\n",
" statsbomb_xg: float = None\n",
" \n",
" \n",
"class StatsBombEventFactory(EventFactory):\n",
" def build_shot(self, **kwargs) -> ShotEvent:\n",
" kwargs['statsbomb_xg'] = kwargs['raw_event']['shot']['statsbomb_xg']\n",
" return create_event(StatsBombShotEvent, **kwargs)\n",
" \n",
" \n",
"event_factory = StatsBombEventFactory()\n",
"\n",
"dataset = statsbomb.load_open_data(event_factory=event_factory)\n",
"\n",
"dataset.filter(\"shot\").to_df(\n",
" \"statsbomb_xg\",\n",
" \"player\",\n",
" timestamp=lambda event: event.period.start_timestamp + event.timestamp,\n",
")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Freeze frame\n",
"\n",
"For event data it's very useful to have additional context about the event. This can be a metric like packing or xG, but also the frame of tracking data. This freeze frame contains player coordinates. Some providers retrieve the information from broadcast video feeds and therefore only the coordinates of players visible in the feed are known.\n",
"Furthermore the vendor might include only the team of the player, and not a player identifier."
]
},
{
"cell_type": "code",
"execution_count": 71,
"metadata": {},
"outputs": [],
"source": [
"dataset = statsbomb.load_open_data(\n",
" match_id='3788741',\n",
" coordinates=\"statsbomb\"\n",
")"
]
},
{
"cell_type": "code",
"execution_count": 72,
"metadata": {},
"outputs": [],
"source": [
"event = dataset.find(\"shot\")"
]
},
{
"cell_type": "code",
"execution_count": 73,
"metadata": {},
"outputs": [
{
"data": {
"image/png": "iVBORw0KGgoAAAANSUhEUgAAAnYAAAG3CAYAAAA5CBh1AAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjcuMSwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/bCgiHAAAACXBIWXMAAA9hAAAPYQGoP6dpAAAtr0lEQVR4nO3deWyk+V3n8c/z1F2usl0+yrf7mOme6UzPTGYGJoEQJSHZKAgirXaCGAJajuwIlmy0aKVogbAcAol/WK1WCBZYtCCGQYw2mygEoUxWESQkAZKZyWwzR9+Hy3a7y0e5fJTrfJ79o+zH5enDnW7X87h+9X5J0VSVq/N8bZef5/P8Tst1XVcAAADoeHbQBQAAAOBgEOwAAAAMQbADAAAwBMEOAADAEAQ7AAAAQxDsAAAADEGwAwAAMATBDgAAwBAEOwAAAEMQ7AAAAAxBsAMAADAEwQ4AAMAQBDsAAABDEOwAAAAMQbADAAAwBMEOAADAEAQ7AAAAQxDsAAAADEGwAwAAMATBDgAAwBAEOwAAAEMQ7AAAAAxBsAMAADAEwQ4AAMAQBDsAAABDEOwAAAAMQbADAAAwRNjvAy6vFZTLz8txGn4fGkCXqtZr3uNoOBJgJQC6iW2HNJUd12Bvxrdj+h7scvl5latlvw8LAJL2hjwAaK+acvl5s4Nda0sdd84A/ECLHQC/7Zx3/O6h9D3Y7YiGI3ry5GNBHR5AF3n1/BlV6zXOOwB8s3Pe8RuTJwAAAAxBsAMAADAEwQ4AAMAQBDsAAABDEOwAAAAMQbADAAAwBMEOAADAEAQ7AAAAQxDsAAAADEGwAwAAMATBDgAAwBAEOwAAAEMQ7AAAAAxBsAMAADAEwQ4AAMAQBDsAAABDEOwAAAAMQbADAAAwBMEOAADAEAQ7AAAAQxDsAAAADEGwAwAAMATBDgAAwBAEOwAAAEMQ7AAAAAxBsAMAADAEwQ4AAMAQBDsAAABDEOwAAAAMQbADAAAwBMEOAADAEAQ7AAAAQxDsAAAADEGwAwAAMATBDgAAwBAEOwAAAEMQ7AAAAAxBsAMAADAEwQ4AAMAQBDsAAABDEOwAAAAMQbADAAAwBMEOAADAEAQ7AAAAQxDsAAAADEGwAwAAMATBDgAAwBAEOwAAAEMQ7AAAAAxBsAMAADAEwQ4AAMAQBDsAAABDEOwAAAAMQbADAAAwBMEOAADAEAQ7AAAAQxDsAAAADEGwAwAAMATBDgAAwBAEOwAAAEMQ7AAAAAxBsAMAADAEwQ4AAMAQBDsAAABDEOwAAAAMQbADAAAwBMEOAADAEAQ7AAAAQxDsAAAADEGwAwAAMATBDgAAwBAEOwAAAEMQ7AAAAAxBsAMAADAEwQ4AAMAQBDsAAABDEOwAAAAMQbADAAAwBMEOAADAEAQ7AAAAQxDsAAAADBH2+4C1el2SVK3X9Or5M34f3mi2HdJUdlyDvZmgSwEAdIjltYJy+Xk5TiPoUoxSrdck7eYev/ge7Fy53uOdbxoHpaZcfp5gBwC4a7n8vMrVctBlGKs19/jB92BnyfK+yWg44vfhjbUTkrnjAgB8N1qvG1yXD87OddmS5etxfQ92kXBY1XpN0XBET558zO/DG+vV82doAQUA3DOuywdr57ocCfsbtZg8AQAAYAiCHQAAgCEIdgAAAIYg2AEAABiCYAcAAGAIgh0AAIAhCHYAAACGINgBAAAYgmAHAABgCIIdAACAIQh2AAAAhiDYAQAAGIJgBwAAYAiCHQAAgCEIdgAAAIYg2AEAABiCYAcAAGAIgh0AAIAhCHYAAACGINgBAAAYgmAHAABgCIIdAACAIQh2AAAAhiDYAQAAGIJgBwAAYAiCHQAAgCEIdgAAAIYg2AEAABiCYAcAAGAIgh0AAIAhCHYAAACGINgBAAAYgmAHAABgCIIdAACAIQh2AAAAhiDYAQAAGIJgBwAAYAiCHQAAgCEIdgAAAIYg2AEAABiCYAcAAGAIgh0AAIAhCHYAAHSxUq2kxa1FVRvVoEvBAQgHXQAAAPDfawuv6Te/+pv667N/LUeOYqGYfuadP6Nfe9+vaSw9FnR5uEe02AEA0GW+du1revefvFtfPPdFOXIkSZVGRX/y6p/oe/7n92h2bTbgCnGvCHYAAHSRhtPQT3zuJ1Rzamq4jT1fq7t15Tfz+sUv/WIwxeG+EewAAOgiX770Zc2uzcpxnVt+ve7U9fmzn9fCxoLPleEgEOwAAOgiby6+qZAVuuN7HNfRheULPlWEg0SwAwCgi/REe27bWvf296HzEOwAAOgiHz35UVmWdcf3TPVO6Z2j7/SnIBwogh0AAF1kondCn3jiE7Kt20eAX3/fr9/x6zi8+K0BANBlfu+Hfk/Pnn5WkhSyQgpZYdmyFbJC+p0P/o4+8eQnAq4Q94oFigEA6DKxcEwv/JsX9Ms/8Mv6b1/971opr2gyPalf/uB/1nh6POjycB8IdgA6nuu62qqWValWVavXVK1XVa3XVKvX5bqOao26JKnWqOtc7pIi4bCi4Yii4agi4Yhi0agS0fi+444A05zOntYnH/+UqvWaouEIoc4Avge7Wr15gq3Wa3r1/BnfjmvbIU1lxzXYm/HtmADao1Kram1zXZvlTW1slVQqb93VLD/XdVVYX73l12zLVjKeUCqRVE+8R709acUi0QOuHEAQltcKyuXn5TiN/d98QKr1mqTd3OMX34OdK9d7vPNN+6OmXH6eYAd0INd1tVkuqbBeVGFjVaXy1oEfw3EdbWxtamNrU9KiJCkZTyiT6lcm3aeeeJIWPaBD5fLzKlfLgRy7Nff4wfdgZ8nyvsloOOLLMXcCpJ9JHcD9q9ZryheWlF9dUrVWve37YpGoeuJJJWIJRSMRRcMRRbb/F7Jt/b+Lb6jWqCsSCuv08VOq1WvbXbY1VWs1bVW2tFkuqfK2Y5TKWyqVtzS3dF3RSFTZ/iFlM0O+nbsAHIzW67/f2cOSvzeEvge7SDjs9eU/efIxX4756vkzPrcOArhXrutqvbShG4VFrawVbnmv2xNPKpPuUyqRUk88qUj4zqeynZY2y7IUi0Rv28Vaq9e1WS5pY2tDhfWiNssl72vVWlWzi/OaW5zXQG9GI5lhpZMpWvGADhJE9tjv/HTQmDwB4NBYK20od2NW61ubN32tr6dXA739yqT6FG3T2LdIOKz+VK/6U72aHB5XtVZVYaOolbVVFTfXJEmumuN1ltcKSidSmh6ZUDqZaks9APDdItgBCFypvKWZ/JxWN4p7Xg+HwspmhjTSP6RYNOZ7XdFIVCOZYY1khlWuVrxu4fr2LNv1rQ29cfWcMqk+TWUnlIwnfK8RAFoR7AAEptFo6Fp+VvnC0p7X49G4JodHNZDOyLYPxzrq8WhM0yMTmhwe08p6QbOLC95g7MJGUYWNokYyw5rOTigUuvMG6wDQLgQ7AIEobqzp0vVreyZFRMMRTQ6Pa7h/8NCOXbNtW0N9gxrsHdDi6rJmF+e9Mbw3CosqbBT1wPgR9fX0BlwpgG5EsAPgq4bT0LUbe1vpbMvW5PCYRgeyh6aFbj+WZSmbGdJQ34AWVvKaXbwux3VUrVX11rULymaGdGRkUiGb1jsA/iHYGcZx9l+kFQhKuVrRudxFbVV215NKJ1N6YPyo4gGMoTsItm1rfGhUA70ZXZq/qvXShiQpX1jSemlDD0092LHfG8xXWC+qwVJgRumMW2PsayfQ1Z2Gcvl5ua6/CyIC+ylurun1K295oc62bB0dndI7jpw0IvjEozG948hJHR2dkm01T61blbJev/KWipvrAVcH7OW6rnL5eZ3LXVSDBgGjEOwM0bqd0tzSdV2ev0a4w6GxsJLXW9cuqN5otgzEozE9evyURgeyh3Ys3b2wLEujA1k9evyUF1brjYbeunZeCyv5gKsDmlzX1aX5a5pbuh50KWgDgp0h3j6OZ7G4rAtzV+5q/0ygXZqtAnO6upDzXutP9er0sVNKxOIBVtZeiVhcp4+dUn9qdwLF1YWccvk5brgQKMd1dGH2spaKy95roQ4Z14q7w2/TEDutHmE75G1fsrJW0PncZcbdIRCu62omP6e5pQXvtbHBET009aDCXbAcSDgU0kNTD2pscMR7bW5pQTOEOwTEcRydz13SyvqqpOZWVycnjzPBxzAEO8PYtq2Hph/wgt7qRlFnZy4yOBa+2hm/c335hvfa0dEpHRmZNKrrdT+WZenIyKSOjk55r11fvsE4WPiu4TR0duaiVjeaO6hYlqWHph/QQG8m4Mpw0Ah2BupP9enU9Alv2Yi10rouzV3lQgLfzC/f0PzybkvdsbFpjQ5kA6woWKMDWR0bm/aezy8v7Am9QDu5rquLc1e1VmpO4rFtW6emT6g/1RdwZWgHgp2henvSOjV90hs7sbK+qtnF+YCrQjdYWV9VLj/nPT86OqWRzHCAFR0OI5nhPS13M/k5Fba7xIB2yi3Oe5+1kG3rHUdOqrcnHWxRaBuCncHSyR49OHHcez63tKCl4kqAFcF0pfKWLs5d8Z5PDo93dUvd240OZDU5PO49vzh3RaXKVoAVwXRLxWXNt4xzPTF5XKlET4AVod0IdobLpPt0ZGTSe95cQHUzwIpgqlq9rnO5i95kncHejCaGRgOu6vCZ2F7MWJIajqNzM5dUb9QDrgomWi9t6tL8Ne/5kZFJul+7AMGuC4wOZJXtH5LUHGtxYfaSt54YcBBc19Xl+auqbO/7mowndHz8aFdNlLhblmXpgfEjSsYTkqRKrcIYWBy4eqOuC7OXvM9Vtn+I1vMuQbDrApZl6ejYlNLJlCSpWq9p5sZswFXBJEvFFRU2ipKkcCish6YeZG2sOwjZoe1lX5q7OhY2ilpeY5gEDs7MjTlV6zVJzW37jo5NcaPVJTjzdgnbsvXgxDFvpmx+dUnFzbWAq4IJqrXangWIj49NKxaJBlhRZ4hFojreMlP2yvWcqrVagBXBFMWNNeVXlyQ1Z8A+OHHM2+YO5uM33UVikaimsxPe88vz11jfDvfFdV1dub77ORrszbAu1ndhoDejQW+8XUNXrrMVIO5Pw2no8vXdcXXT2UlutLoMwa7LjGSGvS7ZSq2qXJ4lUHDvChtFrws2Egrr6Oj0Pv8Cb3d0dFqRli7ZnZ8ncC9y+XlvrGs6mdJIZijgiuA3gl2XsSxLx8eOeGMtFlby2qqUA64KnWhnH9gdR8emFQmHA6yoM0XCYR1t6ZJlP1ncq61KWQsreUk3n+vRPQh2XSgRi2tiaMx7zsLFuBeLxWXvpiCV6NFAuj/YgjrYQLrfW1tsq1LWYssG7cDdaj2XTwyNKRGLB1gNguJbsLs8f02vXzmrWr25XlOtXtfllvV14K+xwaw3I295raDNcingitBJHMfRbEs3/nR2gpaB+2BZ1p7xr7OL1731AIG7sVkuaXmtIKk5LGJskKVNghJ03vEt2JUqW9rY2pSrZheDK5cV1wMUskN7Fo9tXZkc2E9+dclbSqE/1cf2RAegtyet/lSvJKlaq3qzGoG70XoOHx8aVcgOBVhNdws679AV28WymeE9rXblKmPtsD/XdbWwsug9n8qO3+Hd+G5MtbTa3VhZZKwd7kq5WvZa68KhsLLszdzVCHZdLGTbGmtZifxGgRYC7G+ttOHdBKSTKfXEkwFXZI6eeNKbtb5VLWuttBFwRegEN1Z2z91jA1kWB+9y/Pa7XDYzrJ2RUUvFFVoIsK8b27PuJGmUloEDN9LyM71RWLzDO4FmC/rS9q4llixa60Cw63aRcNjbFLpWr2ltcz3ginCYVWs1rayvSmp+djK9/YHWY6KB3v7dde3WCuxGgTsqbq6rtjPeNd3LkkMQnwBoqH/QWxR1sbisvu0B3MDbrawXvMfZ/iG2KWoD27I1nBnS/NKCXEkr66saHaAVBre21LI0zlDfYICV3Jvl5WW98sorWlhYUDgc1sMPP6zTp08rGmW3jHtFsIMyqT6F7JAaTkMr66tqOA1mVOGWCuu7uyKwdVj7DPZmvFmOBYIdbqPhNLSytiqpudJBZrv3pVP80z/9k1566SVZluUNA7pw4YL+/u//Xj/1Uz+lwcHOC6qHAbfbkG3b3n6VjuNodWMt4IpwGNUbDa+rPhqJKhlLBFyRuZKxhKLhiCRprbSuRoM9nXGz1Y01OW5zvcPB3ozsDpo0ceHCBb300kuSdNPY7o2NDT3//PN87u9R53wK0FaZll0D1pmJh1sobq556zJlUn0sSNxGlmV5f5Ou62p1k5st3Kz1XN16Dl8tr+p3v/m7eux/PKaJ/zqh9/6v9+ovzvyFao3DM17zG9/4xm3PIa7rqlgs6uzZsz5XZQa6YiFJSid7vMcEO9zKasvm9Bm2D2u7TLrPmxW7ulH0WtWBHWul3cluO+fwa6vX9N4/fa/m1ue81ryFzQV9Pfd1/dlrf6a/+fjfKB4OdquxRqOha9fuvBODbdu6ePGiHnnkEZ+qMgctdpDUXNRyp2tts1yiCRw32dza3XZuZ601tE86ububR+vPHpCaQyNK5eZuBslYQuFQWK7r6mP/+2O6vn7dC3WSvMd/d/Xv9JmvfCaQelvdzXZ5ruuyrd49ItjB03qxXt/aDLASHDaO43hb4iRjCRZA9UHItr1N3EuVLS5y2GOj5Ry9c+7+9vy39fL8y6q79Vv+G8d19Eev/JE2q8Ge38Ph8L4TI1zX1fg4u9rcC87O8OwJdnTHosVmebfFqCfBThN+ScV3h0hsltlbG7taz9E75+6vz3x93yWINmub+pf8v7S1tv1YlqV3v/vdd3xPJBLR448/7lNFZiHYwdO6NdQW+8aixZ5gxxZivmkN0ZtlWtGxa6tlU/nU9ufE0t1NaLrb97XTk08+6Y2fa51EYdu2bNvWj/7ojyoeD3YsYKdi8gQ8scjugpDVWjXASnDYbFV2g36SYOeb1p91mZsttKjWd2e4RsPNc/cHjn1gz9i6W+mL9emxkcfaWtvdsG1bzzzzjE6cOKFvfetbyufzCoVCOnXqlL7v+75P2Wx2//8T3BLBDh7bthUJh1Wr11Uh2KFFreUiEtteXw3tF235WbO1GFrtnKMj4Yi3ft07R9+p906/V/84+4+qOzePs7Nk6ZPf+0klIodjDUrLsvT444/T5XrA6IrFHjt3frV6bd87P3SP1taBCMHON3uCXZ1ghybHdbybrdaeFkl68WMv6njmuKTdLteQ1dxJ6KMPfVS/8f7f8K9QBIIWO+wRi0S98VTVWk3xaCzginAY7FxEwqFQR61u3+ls21Y4FFK90djTaoru1tp6G33bjdZYekzf+bnv6IUzL+j5M89rcXNRDw48qOeeek4/cvJH2N+5CxDssEe05e6vVifYobnswE5r0U6LLvwTCUdUbzRUrdfkui47fmBPyI9Gbv6bTEaSeu6p5/TcU8/5WRYOCaL7AXNdV0vFFc0uznfkOLXWuzm6YiFJrnb3cuzE9escx1HDaS64/fY9KTtB2G52o3Vi7WiP1jUNO7EFvVKranZxXkvFFT7XbUCL3QFbLC7r8nxzq5R8YUlPnHi0o+6wW2vlDw6SpJbPQSd9lndcun5Nje0LYd3pwB1V3vY32Ym/Axwsp/VvMsClS2q1ms6ePatCoaB4PK5Tp04pnU7f8d+4rqs3rpz1egEc11G2f8iPcrsGwe6AlVoWEa3Wa2o4jsKhUIAVfXdarxnkOkiSq9YPQueFilLLGnydeLPSeuHe+7sA9p6z/fTGG2/oi1/8oiqVimzbluu6+tKXvqSnn35aH/7wh2/bkthwnD0TgUosvH3gOq8N95Ab7h9UaLvrZLh/sKNCnbT3wkfLAKTODxbjg6Pe452/zU7S+jPnbxJS8DfgFy9e1Gc/+1lVKhVJza5h13Xluq7++Z//WV/+8pdv+29Dtq3h/sHtxyHvMQ4OLXYHrCee1JMnH1W90bhptlInINjh7Tq9e364f1AzN2ZVa9Q7coyge0i63XB4WC1joYP4m/zKV75yx69/61vf0nve855bdstalqXjY0c0OTyucCjUkTdbh13nneU6QMgOKRaJdmQwqjd2xyB14kUQB8+yLO/kW6vfenPxw64T/xZ37MyADNmhjv4+cHBaz823Woi4nVZWVrSwsLDv+958883bfs2yLMUiUUJdm3Dlxh6tM3nfvvAlutdO63Nte8kN+MN1XS/YRSOd1wOA9ghy+8dyef+t7SzLuqv3oT0IdtijWm+eJCzLUjhETz2aItuhwnEdb4Yp2q/hNLwZkJ04tAPtEQ6FvdZbv5fV6uvr27fl2HEcZTIZnyrC2xHs4HFd1ztJdGpXMtqjNVSwA4J/amzlhlvY6cqU/G+x6+np0cmTJ+94fYjFYjp16pSPVaEVwQ6ehtPwFr681Wrm6F6tO06Uq3Sx+KVcrXiP2fUDrXY+Dw3H2TM22g8f/vCHFYvFbgp3O89/+Id/2Gvlh/8IdvC0XkQYX4dWyXjCe7zZsi4c2qv1Z936OwBaz9F+32wNDAzoueee08mTJ/e8ns1m9eM//uN69NFHfa0HezGICp710ob3uCeeDLASHDatn4fNLYKdX1p/1qkEf5PY1ZNIarG4LKl57k4lenw9/sDAgJ599lltbGyoWCwqHo9rYGCAITyHAMEOntZgl06mAqwEh008GlPIttVwHG3QYuebnZ91cwmlWMDV4DBpPUevlzY0NjgSSB2pVEqpFNeLw4SuWEhqTpzYCXYhO6RkjG4f7LIsSz3xZotArV7zfcB2N6rWqt7kiZ54kpYQ7JGMJbz17NZLGyxDBA/BDpKa4+tqjeZCl+lkDxcR3CSd3O3qWd1YC7CS7tD6M2792QNS82YrlWi2lNUadVVqlX3+BdphdXVVFy9e1OrqatCleAh2kCStlda9x+kEzeq4WX+q33tcWF8NrI5u0fozzqT7A6sDh1dvS3dscXP9Du/EQXvzzTf1zDPPaHBwUCdOnNDg4KCeeeYZvfHGG0GXRrBD03Kx4D3uS/UGWAkOq1Qiqcj2otXFzTVvaRwcvIbjqLjZbLGLhMNMZsIttZ6rW8/haK/XXntNTz/9tL7whS9450HHcfSFL3xB73rXu/Tm68GGO4IdVKlVvRa7eDTGRQS3ZFmW13LkuK4XPHDw1jbXvB0nMql+hkbglnriScWjzUk1a6V133eh6FY/+7M/q3K5rMbb1g9sNBoql8v69V/6LwFV1kSwg5aKK97joT6mq+P2Muk+7/Fiy+cGB2txddl73PozB1pZlqWhvgHv+TJ/k2332muv6Tvf+c5NoW5Ho9HQ2Tff0rk3z/pc2S6CXZdzXVdLxd2LyFDfYIDV4LDrS/V63bGFtQKzY9ugWqtqZXt8XSQcZmgE7qj1nL1YXGZ2bJudO3furt537crV9hZyBwS7LrdZLmmr0ly1PJXo8Zr1gVuxLVvZzJAkyZWUX10KtiADtf5Ms/3Dsi1O07i9eDTmLU68VSmzM0ybpdPpu3pfsie4meycMbrc9eUb3uPhflrrsL9sZth7fKOw5I0Fw/1zXFc3Ci3BbjtEA3fSeu5uPafj4H3gAx9Qb++dW9F7Uj36nnd9r08V3Yxg18XK1bKW15ozqSKhsIbphsVdiEWi3iSKWr3GuJ4DtFRc9hYlzqT72bMZd2Wob1Dh7SESy2sF3/eO7SaJREKf+cxn7vief/fvf07xRNynim7mW7BLxhJKJXpkqTkw35LF7gYBm1287j0eHczKtsn5uDtjA1nvcW5xnqVPDoDjOJrNz3vPW3/GwJ2EbHvP52V2cSHAasz36U9/Wp/5zGdk27Zs21YkEvEe/8qv/Io+9R8/FWje8W2v2OPjRyRJr54/o2q9pkg47L0G/5XKW95s2HAopJEMFxHcvd6etPpTvVrdWFO1VtWNwmJge1Wa4kZhUdXt1rr+VK96e+5uLA8gSSMDWc0v31DDaWipuKzxoREaT9rEsiz99m//tn7hF35BL7zwgq5fv66xsTF9/OMf18TEhPe+oPKOb8EOh4fruprJz3rPxwdHFQ6FAqwInWgqO+FtezW3dF3D/UN8ju5RvdHQ3NJuC/pUduIO7wZuFg6FNDE0qpn8nCRp5sacHpp6gOWr2mh8fFyf/vSngy7jJvS9daHltYJ3QY6EIxqhywf3oCee1GBvcw2teqOh2cX5ff4Fbmd2cV717XWxhvoGWCQc92RkIKtIOCJJWt0oemOo0V0Idl2mVq/p6sKM9/zY6JRCjK3DPZrKjnstAgsrea2xX+V3bW1zXQsreUnNLp7J4fGAK0KnCtm2jo1Oec+vLuS8yTjoHlzRu8yVhZzXMjDQm9FAbybgitDJ4tGYplu6DS/NX1PDufWK7LhZw2no0vxV7/l0doK1JHFfWs/r9UZdVxdyAVcEvxHsusjK2qpWtpvmw6HQnjs74F6NDmSV3l4gtVKrKJenS/Zu5fJz3v6e6USPRhkWgQNwdHTKG++6vFbQytpqsAXBVwS7LrFVKe9pGTg6OuWNxQDuh2VZOj5+dE+XLGvb7W+puKKFlUVJN/8MgfsRDUd0ZGT3xv3S/FVvhyGYj2DXBeqNus7lLnpdZJl0vzfoHTgIiVhcR0YmveeX5q9qc4utjW5nc6ukyy03WkdGJpWIBbegKcwz1DfgLSTecBo6l7uoeqMebFHwBcHOcI7r6vzsZZWrFUnNhaIfpGUAbTCSGfY2JHdcV+dyF7112bCrWq/pXO6itxXbcN+gRlq2aQMOgmVZenD8qLeWXbla0fnZy2wB2AUIdgZzXVfXFnLeTMVwKKyHph5QiLXG0AaWZen42LS3IXm1XtP53CUmU7RoOA2dz13yAm8q0aNjY9PcaKEtQqGQTk494G03tra5rmsLObmEO6MR7AzVXIR4TjcKu2N4Hpp6QDFm3KGNbNvWyckHFN0ev7mxtalzM5fYckxSw3F0buaiNrY2JTXHQZ2ceoCt/NBW8WhMJ6ce8La3ulFY1Ex+jnBnMM4oBnJdV1cXcrq+fMN77fjYEaWTqQCrQreIRiJ6aOpBhexmy/BaaV1nW8Z4dqOdMU5rpQ1JUsgO6aGpB70ADLRTbzK1Z0ur68s3dJWWO2MR7Azjuq4uzV/1Wuok6djYtIb7BwOsCt2mJ5HUw9MnvMWv1zbXdXamOwdv1xt1nZ256A2JCNm2Hp4+oZ4Eu0vAP8P9gzo2Nu09v1FY1KX5a4Q7AxHsDFN3GlpqWWriwYmjDMxGINLJnu1w12y5Wy9t6PUrZ7tq2YWtypZev3JW6y0tdQ9Pn1A62RNwZehGI5lhPTB+1Hu+VFzWhdnLhDvDEOwMsfOHufNfy7J0cvK4N0sRCEI6mdKpIye8wdvlakWvX3lLhfViwJW1X2G9qNevnPVmpIdDYZ06coIhEQjUcP+gTk4e98bcrayvdmVLerFY1Llz53Tx4kVVKpWgyzlQ4aALwMGot4xfsi1bJ6eOqz/VF2BFQFMq0aPTxx7W+dwllSpbzUkEuYuaGh7X+NCocTNCXdfV/NKCcou7O3AkYwmdnHqA7cJwKAz0ZvTQtK3zuctyXEfd1F5X3irrr/7qr3Tu3DnvtXA4rKefflo/+IM/aMSqEbTYGSJs734YTx97mFCHQyUejemRYw9pYHvBVEnKLc5vd81uBVfYAdvpem0NdQPpfj1y7CFCHQ6V/lSfTh97uKs+l7VqVd/4v1/VhQsX9rxer9f1zW9+U5///OeN6JYm2Blip9UjEgorGU8EXA1ws5Ad0onJ45ocHvNe2yyXdObyW5pbWujoE6rruppbWtCZy29ps7y748bk8JhOTB73xhkCh0kyntDpY6e8oRKmu3bxqkql0m2XX3rjjTeUy+V8rurgdcdvs4uY1q0Fs1iWpcnhcfX19OrS/FWVqxW5rqtcfk5LxWVNZyfUn+rrmM+x67pa3ShqJj+3Z1JIPBrTA+NHGU+HQy8cCsnukL+3+zV7eUZ36ne2bVuvvfaapqenb/+mDkCwA+C7dDKlx46/Q7n8vK6vNNdb3KqUdS53SelEStMjE4c+FK2XNjRzY1br2wsO7xgbHNHU8DgLDwOHTLV850kSjuOoWOz8iV0EOwCBsG1bR0YnNdDbr6sLOa8Lc31rQ29cPae+nrRGMlll0oenBc91XRXWi7pRyKu4vS7djp54UkdHpw59IAW6VSQWvWO4syxL6XTax4rag2AHIFDpZEqnjz2swvqqZvJz3vIgxc11FTfXFQ1HlM0MK9s/qGgkGkiN1VpV+dVl5QuL3j6vO+LRuKaz48qk+w9NAAVws6HRo/ri/+nRzMyULMvVsWNX9Y53vKFIpLmqhOu6euyxxwKu8v4R7AAEzrIsDfRmlEn3a3F1WXNLC6rUmgGvWq9pdnFes4vzSiV6lEn1KZPuVyIWb1uQcl1XW5WyCuurKmwUvf1dW8UiMU0MjWq4f5BABxxyr72S1H/61Me0sR6SZTmyLOm1157Ql7/8r/STP/kXGh/P6/jx4zp27FjQpd43gh2AQ8OyLGUzQxruH1Rxc003VhZV2Ngd87KxtamNrU3lFucVi0SVSvQolehRTzypZDyp8D2uQVVvNFQql7RRLmlz+xiVWvWW782k+jQyMKy+nl4CHdABZmak//CJY6pUbEmWXDeknUn4pVJSf/7n/1Z//Mdf04/92AeN+Jsm2AE4dCzLUn+qT/2pPlWqFeVXl1VYX1WpZc27Sq2qSq2q5bWC91o0ElU0HFEkHPH+G7JtNbYX8G44Dc0uXletXlO1XvP+W71NiNuRjCWUSfcr2z+oWBet+wWY4Pd/X6pUbTnOzaHNdW1VKgnNz39EkUgAxbWB78GuVm9uXVKt1/Tq+TO+HPPtY2IAdI5YNKap7LimsuMqVysqrBdV2FjVemnjprXvqrXqHUNaw3E027J48O1YlqXeZEqZdL/6U31dtYgrYJrPflZyGrdviXMc6c9fKOlD//rigR53J3vs5B6/+B7s3JZFZPwOXDaLhAIdLR6NaWwwq7HBrBzX0Va5rM1ySRvlTW1ulVSpVb/rfS/DobBikah6Ekml4j3qSSSViCW6Zm0vwHRb+25uY2mrZLUtk7g+b9rme7CzZHnfZDTsX7unbYc0lR337XgA2su2bPUkkupJJJXVkPe64zqq1eteN6vruro8f00Np6GwHdLxiaNeN20kHJZtsd4cYLInnpDyeVeN27TahUKuTj2ydeCZZCcoWvL3JtH3YBcJh1Wt1xQNR/Tkyc6fVgzgcLEtW7FIVLGWpVGuLeTUcBqybXvPfrUAzPfJT0p/+7e3D1eNhqVf/aUBPXly4ECP++r5M6rWa4qE/Y1a3KoCAABj/dAPST//883HrRvC7Dz+1V+V3v1u/+tqF4IdAAAwlmVJf/AH0p/+qfTII7uvP/WU9OKL0m/9VnC1tQPLnQAAAKNZlvTTP938X6nUfJ5IBF1VexDsAABA10gmg66gveiKBQAAMATBDgAAwBAEOwAAAEMQ7AAAAAxBsAMAADAEwQ4AAMAQBDsAAABDEOwAAAAMQbADAAAwBMEOAADAEAQ7AAAAQxDsAAAADEGwAwAAMATBDgAAwBAEOwAAAEMQ7AAAAAxBsAMAADAEwQ4AAMAQBDsAAABDEOwAAAAMQbADAAAwBMEOAADAEAQ7AAAAQxDsAAAADEGwAwAAMATBDgAAwBDhoAsAAAA47PL5vM6dO6daraaRkRE9/PDDCoVCQZd1E4IdAADAbVQqFX3uc5/T+fPnZVmWLMuS4zhKJpN65plndPz48aBL3IOuWAAAgFtwXVcvvviiLly44D13HEeStLW1pb/8y7/UwsJCkCXehGAHAABwC7lcTleuXJHrujd9bSfk/cM//EMAld0ewQ4AAOAWXn/9ddn27aOS67p666231Gg0fKzqzgh2AAAAt1CpVG7ZWtfKdV3VajWfKtofwQ4AAOAWBgYG9n1PIpFQLBbzoZq7Q7ADAAC4hSeeeOKOX7csS0899ZQsy/Kpov0R7AAAAG6ht7dXH/rQh275NcuyNDg4qPe85z0+V3VnrGMHAABwG9///d+vdDqtr371q1peXpYkhcNhPf744/rgBz+oeDwecIV7EewAAADu4NFHH9Xp06dVKBRUr9fV39+vaDR60/vq9bpefvllffvb31ahUJAdCmniyKSODE5ocHDQl1rpigUAANiHZVkaGBhQNpu9Zair1Wp6/vnn9dJLL2llZUWu66pRryt3+Zr+8A//UDMzM77USbADAAC4T1/72teUy+Vuet11XTUaDb344ou+rHdHsAMAALgPjUZDL7/88m3XvHNdV6VSSWfPnm17LQQ7AACA+1AsFlUul+/4Htu2NT8/3/ZaCHYAAAD3IRQKHej77gfBDgAA4D709vbuO+vVcRydOHGi7bUQ7AAAAO6DZVn6gR/4gdt+3bZtTUxMaHJysu21EOwAAADu0+OPP673ve99kppBTpK0vdXY0NCQnn32WV+2HmOBYgAAgPtkWZbe//7365FHHtErr7yiKzNXZYdDmjwyqY+8/8O+jK+TCHYAAAAHZnh4WB/5yEf06vkzqtZrioYjvoU6ia5YAAAAYxDsAAAADOF7V2ytXpckVes1vXr+jN+HN1a1Xgu6BABAB+O6fLB2rss7uccvvgc7V7vbbRBGDp5t+9ePDwDofM3rRvN6zHX54LXmHj/4HuwsWd43GQ1H/D680Ww7pKnseNBlAAA6yFR2XLn8vByn/RvUd5OdkGyp/UuctPI92EXCYW+WyJMnH/P78AAAoMVgb0aDvZmgyzDOzqzYSNjfqMXkCQAAAEMQ7AAAAAxBsAMAADAEwQ4AAMAQBDsAAABDEOwAAAAMQbADAAAwBMEOAADAEAQ7AAAAQxDsAAAADEGwAwAAMATBDgAAwBAEOwAAAEMQ7AAAAAxBsAMAADAEwQ4AAMAQBDsAAABDEOwAAAAMQbADAAAwBMEOAADAEAQ7AAAAQxDsAAAADEGwAwAAMATBDgAAwBAEOwAAAEMQ7AAAAAxBsAMAADAEwQ4AAMAQBDsAAABDEOwAAAAMQbADAAAwBMEOAADAEAQ7AAAAQxDsAAAADEGwAwAAMATBDgAAwBAEOwAAAEMQ7AAAAAxBsAMAADAEwQ4AAMAQBDsAAABDEOwAAAAMQbADAAAwBMEOAADAEAQ7AAAAQxDsAAAADEGwAwAAMATBDgAAwBAEOwAAAEMQ7AAAAAxBsAMAADAEwQ4AAMAQBDsAAABDEOwAAAAMQbADAAAwBMEOAADAEAQ7AAAAQxDsAAAADEGwAwAAMATBDgAAwBAEOwAAAEMQ7AAAAAxBsAMAADAEwQ4AAMAQBDsAAABDEOwAAAAMQbADAAAwBMEOAADAEAQ7AAAAQxDsAAAADEGwAwAAMATBDgAAwBAEOwAAAEMQ7AAAAAxBsAMAADAEwQ4AAMAQBDsAAABDEOwAAAAMQbADAAAwBMEOAADAEAQ7AAAAQxDsAAAADBEO6sDVek2vnj8T1OEBdJFqveb9l/MOAD/snHf85nuws+2QpN2TLAD4ifMOAD81c49/fA92U9lx5fLzcpyG34cG0KVaw1w0HAmwEgDdxLZDmsqO+3pMy3Vd19cjAgAAoC2YPAEAAGAIgh0AAIAhCHYAAACGINgBAAAYgmAHAABgCIIdAACAIQh2AAAAhiDYAQAAGIJgBwAAYAiCHQAAgCEIdgAAAIYg2AEAABiCYAcAAGAIgh0AAIAhCHYAAACGINgBAAAYgmAHAABgCIIdAACAIQh2AAAAhiDYAQAAGIJgBwAAYAiCHQAAgCEIdgAAAIYg2AEAABiCYAcAAGAIgh0AAIAh/j8u91Af0gLW9wAAAABJRU5ErkJggg==",
"text/plain": [
""
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"# You might need to install mplsoccer package\n",
"# %pip install mplsoccer\n",
"\n",
"from mplsoccer.pitch import Pitch\n",
"\n",
"home_team = dataset.metadata.teams[0]\n",
"\n",
"pitch = Pitch(pitch_type='statsbomb',\n",
" pitch_color='white', line_color='#c7d5cc')\n",
"fig, ax = pitch.draw()\n",
"\n",
"def get_color(player):\n",
" if player == event.player:\n",
" return \"blue\"\n",
" elif player.team == event.player.team:\n",
" return \"green\"\n",
" elif player.position.position_id == '1':\n",
" return \"black\"\n",
" else:\n",
" return \"grey\"\n",
"\n",
"x, y, color = zip(*[\n",
" (coordinates.x, coordinates.y, get_color(player))\n",
" for player, coordinates in event.freeze_frame.players_coordinates.items()\n",
"])\n",
"\n",
"_ = pitch.scatter(x, y, color=color, ax=ax)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": "kloppy-venv",
"language": "python",
"name": "kloppy-venv"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.10.6"
},
"toc": {
"base_numbering": 1,
"nav_menu": {},
"number_sections": true,
"sideBar": true,
"skip_h1_title": false,
"title_cell": "Table of Contents",
"title_sidebar": "Contents",
"toc_cell": false,
"toc_position": {},
"toc_section_display": true,
"toc_window_display": false
}
},
"nbformat": 4,
"nbformat_minor": 4
}