Футбольная аналитика для всех.

Введение

Футбольная аналитика стала трендом последних лет. Многие футбольные клубы начинают нанимать специалистов по данным в свои команды. Даже BBC создала заголовок, что эксперты по данным — лучшие новички в футболе [1].

Из-за высоких требований и разоблачений люди начинают увлекаться футбольной аналитикой. Существует множество инструментов и данных с открытым исходным кодом, которые можно использовать для начала работы в этой области. Mplsoccer — один из инструментов для создания визуализаций футбольных данных [2].

В этой статье я покажу вам, как реализовать эти визуализации с помощью таких библиотек, как mplsoccer и matplotlib. Без лишних слов, давайте начнем!

Выполнение

О библиотеке

Mplsoccer — это библиотека на основе Python для визуализации футбольных диаграмм. Визуализация футбольных данных в Python не так проста, как визуализация точечной диаграммы или гистограммы.

Благодаря этой библиотеке мы можем представить любые футбольные графики на основе имеющихся данных. Некоторые визуализации, которые мы можем создать с помощью mplsoccer, — это радарные диаграммы, тепловые карты, карты выстрелов и многое другое, и эта библиотека поможет вам сразу же создать визуализацию.

Также эта библиотека может помочь загрузить данные StatsBomb. В этой статье мы не будем использовать функции для загрузки данных StatsBomb, в которых мы попытаемся загрузить данные с нуля.

Чтобы установить библиотеку, это просто. Все, что вам нужно, это команда pip, которая выглядит так:

! pip install mplsoccer

После запуска команды вы можете использовать библиотеку для визуализации футбольных данных.

Загрузите данные

Но прежде чем мы сможем визуализировать данные, нам нужно сначала получить доступ к нашим данным. В этой статье мы будем использовать данные из StatsBomb, доступ к которым вы можете получить через репозиторий StatsBomb на GitHub здесь.

В отличие от других наборов данных, использование футбольных данных, особенно StatsBomb, и доступ к ним довольно сложны.

Нам нужно сделать три шага. Эти шаги просматривают идентификатор соревнования, идентификатор матча и, наконец, загрузку беспорядочного файла JSON. Итак, давайте углубимся в это.

Нам нужно получить данные о событиях финала Лиги чемпионов УЕФА 2005 года между «Ливерпулем» и «Миланом».

Но поскольку папка с событиями содержит много файлов, и они назвали ее с помощью идентификатора, нам нужно сначала открыть файл Competitions.json.

Мы открываем данные в виде фрейма данных и фильтруем данные, содержащие Лигу чемпионов в качестве названия соревнования. Вот код для этого:

import pandas as pd
competitions = pd.read_json('open-data/data/competitions.json')
competitions[competitions.competition_name == 'Champions League']

Вот предварительный просмотр фрейма данных:

Как видно из фрейма данных, есть такая информация, как время проведения матча и его соответствующий идентификатор для сезона и соревнования соответственно. Игра между «Ливерпулем» и «Миланом» произошла в 2005 году. Поэтому мы берем идентификатор соревнования 16 и идентификатор сезона 37.

Поскольку соревнование содержит огромное количество матчей, нам нужно посмотреть идентификатор матча для соответствующей игры. Для этого вы можете запустить следующие строки кода:

import json
with open('open-data/data/matches/16/37.json') as f:
   data = json.load(f)
   for i in data:
      print(i['match_id'], i['home_team']['home_team_name'],     
      i['home_score'], "-", i['away_score'], i['away_team'] 
      ['away_team_name'])

Из этого кода мы получили только одно совпадение, предоставленное StatsBomb, которое является окончательным совпадением. Соответствующий идентификатор матча — 23202764. С этим идентификатором мы можем получить доступ к данным события для анализа игры.

Как вы знаете, как и в файле Competition.json, данные о событиях также используют формат JSON и содержат вложенную форму.

Сначала казалось сложным загрузить такой файл, как dataframe. Но нам не нужно об этом беспокоиться, потому что библиотека Pandas предоставляет функцию с именем json_normalize.

Вот код для этого:

with open('open-data/data/events/2302764.json') as f:
   data = json.load(f)
   df = pd.json_normalize(data, sep="_")
   df.head()

Теперь из этого фрейма данных мы можем создавать любые визуализации, которые нам нравятся. Для удобства разделим данные на первую и вторую половину. Итак, давайте сделаем это. Вот код для этого:

first_half = df.loc[:1808, :]
second_half = df.loc[1809:3551, :]

Карта выстрелов

Получив данные, давайте создадим из них несколько визуализаций. Первая визуализация, которую я хочу вам показать, — это шот-карта. Но прежде чем сделать это, давайте сначала узнаем, как создать поле.

Визуализация поля — важный шаг для визуализации футбольных данных. До появления mplsoccer люди создавали свои футбольные чарты, что, как я знал, было непросто, потому что нам приходилось рисовать линии самостоятельно.

Поэтому визуализация футбольных данных не для всех, пока не появится библиотека mplsoccer. Чтобы визуализировать поле, все, что нам нужно сделать, это добавить следующие строки кода:

from mplsoccer import Pitch
pitch = Pitch(pitch_type='statsbomb')
pitch.draw()

Вот превью результата:

Нам не нужно добавлять линии или указывать длину шага. Все, что вам нужно, это объект, и бум, он у вас есть.

Поскольку мы хотим визуализировать карту бросков, нам нужно полувертикально ориентированное футбольное поле. Для этого все, что вам нужно сделать, это изменить предыдущий код следующим образом:

from mplsoccer import VerticalPitch
pitch = VerticalPitch(pitch_type='statsbomb', half=True)

Вот превью результата:

Разве это не просто?! Теперь давайте создадим карту выстрела.

Перед созданием визуализации нам нужно подготовить данные, которые содержат информацию о самом ударе, начиная от места удара, от того, какая команда забила мяч, кто это сделал и насколько вероятно, что он стал голом.

Давайте подготовим кадр данных, содержащий удары «Милана» в первом тайме. Вот код для этого:

# Retrieve rows that record shots
shots = first_half[first_half.type_name == 'Shot']
# Filter the data that record AC Milan
shots = shots[shots.team_name == 'AC Milan']
# Select the columns
shots = shots[['team_name', 'player_name', 'minute', 'second', 'location', 'shot_statsbomb_xg', 'shot_outcome_name']]
# Because the location data is on list format (ex: [100, 80]), we extract the x and y coordinate using apply method.
shots['x'] = shots.location.apply(lambda x: x[0])
shots['y'] = shots.location.apply(lambda x: x[1])
shots = shots.drop('location', axis=1)
# Divide the dataset based on the outcome
goals = shots[shots.shot_outcome_name == 'Goal']
shots = shots[shots.shot_outcome_name != 'Goal']
shots.head()

Вот предварительный просмотр данных:

После того, как у нас есть данные, следующим шагом будет создание визуализации. Давайте сначала построим поле. Вот код для этого:

from mplsoccer import VerticalPitch
pitch = VerticalPitch(pitch_type='statsbomb', half=True, goal_type='box', goal_alpha=0.8, pitch_color='#22312b', line_color='#c7d5cc')
fig, axs = pitch.grid(figheight=10, title_height=0.08, endnote_space=0, axis=False,title_space=0, grid_height=0.82, endnote_height=0.05)
fig.set_facecolor("#22312b")

После этого добавим точки выстрела. Добавьте эти строки кода ниже:

scatter_shots = pitch.scatter(shots.x, shots.y, s=(shots.shot_statsbomb_xg * 900) + 100, c='red', edgecolors='black', marker='o', ax=axs['pitch'])
scatter_goals = pitch.scatter(goals.x, goals.y, s=(goals.shot_statsbomb_xg * 900) + 100, c='red', edgecolors='black', marker='*', ax=axs['pitch'])

После добавления точек добавим текст, описывающий саму визуализацию. Добавьте эти строки кода ниже:

axs['endnote'].text(0.85, 0.5, '[YOUR NAME]', color='#c7d5cc', va='center', ha='center', fontsize=15)
axs['title'].text(0.5, 0.7, 'The Shots Map from AC Milan', color='#c7d5cc', va='center', ha='center', fontsize=30)
axs['title'].text(0.5, 0.25, 'The Game\'s First Half', color='#c7d5cc', va='center', ha='center', fontsize=18)

И, наконец, нам нужно добавить стрелку для очистки направлений атаки происходящего матча. Добавьте эти строки кода ниже:

pitch.arrows(70, 5, 100, 5, ax=axs['pitch'], color='#c7d5cc')

Полный код выглядит так:

from mplsoccer import VerticalPitch
pitch = VerticalPitch(pitch_type='statsbomb', half=True, goal_type='box', goal_alpha=0.8, pitch_color='#22312b', line_color='#c7d5cc')
fig, axs = pitch.grid(figheight=10, title_height=0.08, endnote_space=0, axis=False, title_space=0, grid_height=0.82, endnote_height=0.05)
fig.set_facecolor("#22312b")
scatter_shots = pitch.scatter(shots.x, shots.y, s=(shots.shot_statsbomb_xg * 900) + 100, c='red', edgecolors='black', marker='o', ax=axs['pitch'])
scatter_goals = pitch.scatter(goals.x, goals.y, s=(goals.shot_statsbomb_xg * 900) + 100, c='red', edgecolors='black', marker='*', ax=axs['pitch'])
pitch.arrows(70, 5, 100, 5, ax=axs['pitch'], color='#c7d5cc')
axs['endnote'].text(0.85, 0.5, '[YOUR NAME]', color='#c7d5cc', va='center', ha='center', fontsize=15)
axs['title'].text(0.5, 0.7, 'The Shots Map from AC Milan', color='#c7d5cc', va='center', ha='center', fontsize=30)
axs['title'].text(0.5, 0.25, 'The Game\'s First Half', color='#c7d5cc', va='center', ha='center', fontsize=18)
plt.show()

В итоге визуализация снимков будет выглядеть так:

Тепловая карта давления

Вторая визуализация, которую я хочу вам показать, — это тепловая карта давления. Эта тепловая карта представляет частоту давления в определенном месте. Чем выше давление, тем ярче цвет в этом месте.

Генерация тепловой карты аналогична созданию предыдущей карты выстрелов. Единственная разница в том, что мы визуализируем статистическую сводку на поле. Но прежде чем сделать это, мы сначала подготовим данные. Вот код для этого:

pressure = first_half[df.type_name == 'Pressure']
pressure = pressure[['team_name', 'player_name', 'location']]
pressure = pressure[pressure.team_name == 'AC Milan']
pressure['x'] = pressure.location.apply(lambda x: x[0])
pressure['y'] = pressure.location.apply(lambda x: x[1])
pressure = pressure.drop('location', axis=1)
pressure.head()

Вот предварительный просмотр данных:

Теперь давайте создадим диаграмму. Код выглядит следующим образом:

from scipy.ndimage import gaussian_filter
import matplotlib.pyplot as plt
pitch = Pitch(pitch_type='statsbomb', line_zorder=2, pitch_color='#22312b', line_color='#efefef')
fig, axs = pitch.grid(figheight=10, title_height=0.08, endnote_space=0, axis=False, title_space=0, grid_height=0.82, endnote_height=0.05)
fig.set_facecolor('#22312b')
bin_statistic = pitch.bin_statistic(pressure.x, pressure.y, statistic='count', bins=(25, 25)) 
bin_statistic['statistic'] = gaussian_filter(bin_statistic['statistic'], 1)
pcm = pitch.heatmap(bin_statistic, ax=axs['pitch'], cmap='hot', edgecolors='#22312b')
cbar = fig.colorbar(pcm, ax=axs['pitch'], shrink=0.6)
cbar.outline.set_edgecolor('#efefef')
cbar.ax.yaxis.set_tick_params(color='#efefef')
plt.setp(plt.getp(cbar.ax.axes, 'yticklabels'), color='#efefef')
axs['endnote'].text(0.8, 0.5, '[YOUR NAME]', color='#c7d5cc', va='center', ha='center', fontsize=10)
axs['endnote'].text(0.4, 0.95, 'Attacking Direction', va='center', ha='center', color='#c7d5cc', fontsize=12)
axs['endnote'].arrow(0.3, 0.6, 0.2, 0, head_width=0.2, head_length=0.025, ec='w', fc='w')
axs['endnote'].set_xlim(0, 1)
axs['endnote'].set_ylim(0, 1)
axs['title'].text(0.5, 0.7, 'The Pressure\'s Heat Map from AC Milan', color='#c7d5cc', va='center', ha='center', fontsize=30)
axs['title'].text(0.5, 0.25, 'The Game\'s First Half', color='#c7d5cc', va='center', ha='center', fontsize=18)

Вы видите разницу между этим кодом и предыдущим? Почти ничего!

Кроме того, мы добавляем функцию gaussian_filter для генерации распределения давления на «Милан» в первом тайме. С этим результатом мы создаем тепловую карту, используя его.

Вот окончательный результат визуализации:

Заключительные замечания

Отличная работа! Вы узнали, как создавать визуализацию футбольных данных с помощью mplsoccer в Python.

Надеюсь, вы узнаете здесь что-то новое, а также поможете в анализе матчей, особенно данных StatsBomb. Вы можете прочитать о mplsoccer библиотеке через этот сайт здесь.

Спасибо, что прочитали мою статью!

Рекомендации

[1] Би-би-си. Эксперты по данным становятся лучшими новичками в футболе. https://www.bbc.com/news/business-56164159
[2] Документация Mplsoccer. https://mplsoccer.readthedocs.io/en/latest/index.html