Футбольная аналитика для всех.
Введение
Футбольная аналитика стала трендом последних лет. Многие футбольные клубы начинают нанимать специалистов по данным в свои команды. Даже 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