Введение

Недавно я отправился в путешествие, чтобы объединить свою страсть к футболу и анализу данных. В качестве первого шага в этом направлении я создал свою собственную модель xG (ожидаемые цели).

Итак, что такое xG?

Я думаю, что одно из лучших объяснений модели xG дает Статсбомба:

Ожидаемое количество голов (xG) — это показатель, предназначенный для измерения вероятности того, что удар приведет к голу.

Модель xG использует историческую информацию о тысячах ударов со схожими характеристиками для оценки вероятности гола по шкале от 0 до 1.

Например, мы ожидаем, что удар со значением xG 0,2 будет реализован дважды за каждые 10 попыток.

Я начал свой проект с поиска соответствующих данных и наткнулся на данные о футбольных событиях, которые были получены Wyscout и размещены в открытом доступе. В частности, я стремился разработать модель, основанную на данных о событиях, собранных в пяти ведущих европейских лигах (Ла Лига, Английская Премьер-лига, Лига 1, Немецкая Бундеслига и Серия А), а также чемпионатах мира и Лиге чемпионов.

В футболе удары имеют решающее значение для определения команды-победителя, поскольку они часто приводят к голам. Поскольку голы случаются редко (в моем наборе данных их всего 2,69 за игру), а разница голов может быть небольшой, важно использовать модель xG для оценки качества каждого выполненного броска. Чтобы лучше понять это, я нанес на карту места ударов из моих данных на футбольное поле и рассчитал процент ударов, в результате которых были забиты голы, на каждом участке поля.

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

а затем отфильтруйте удары, которые завершились голом:

Наконец, я подсчитал долю ударов, в результате которых был забит гол с каждого участка поля:

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

Поэтому я начал улучшать модель, добавляя дополнительные функции. Углы съемки — отличная функция.

При выполнении броска угол (θ) образуется двумя линиями, соединяющими точку выполнения броска с каждой стойкой ворот. Угол становится уже, когда удар производится с большего расстояния, и шире, когда выстрел производится с более близкой точки к цели.

Выстрелы обычно производятся с расстояния от 10 до 20 метров. Реже выстрелы производятся в пределах 6 метров, чем за пределами 10 метров. Заметно уменьшилось количество снимков, сделанных в локальном районе между 18 и 25 метрами. Как и ожидалось, игроки редко наносят удары с углов менее 5 градусов, обычно это удары по центру возле ворот.

мы видим, что градусы угла также коррелируют с расстоянием.

Очевидно, что удары, приводящие к забитию ворот, выполняются с гораздо более близкого расстояния по сравнению с ударами, промахивающимися мимо цели. Средняя дальность успешных бросков – 12,26, неудачных – 19,33. Это указывает на то, что съемка на близких дистанциях является сложной задачей, но результаты благоприятны.

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

а затем построил простые модели на основе моих данных и сравнил их вместе

from sklearn.model_selection import train_test_split, GridSearchCV
from sklearn.linear_model import LogisticRegression
from sklearn.ensemble import RandomForestClassifier, GradientBoostingClassifier
from sklearn.metrics import accuracy_score, roc_auc_score
from sklearn.preprocessing import StandardScaler
from imblearn.over_sampling import SMOTE
from sklearn.pipeline import Pipeline

# Select features for training
features = ['Distance', 'Angle Degrees', 'rebound', 'prev_cross', 'prev_touch', 'prev_pass', 'prev_smart_pass']

# Split data into features (X) and target (y)
X = shots[features]
y = shots['Goal']

# Split data into training and testing sets
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

# Set up preprocessing steps (standardization) and model pipelines
scaler = StandardScaler()
smote = SMOTE(random_state=42)
logreg = LogisticRegression()
rf = RandomForestClassifier(random_state=42)
gb = GradientBoostingClassifier(random_state=42)

pipelines = {
    'Logistic Regression': Pipeline([('scaler', scaler), ('logreg', logreg)]),
    'Random Forest': Pipeline([('scaler', scaler), ('rf', rf)]),
    'Gradient Boosting': Pipeline([('scaler', scaler), ('gb', gb)])
}

# Grid of hyperparameters to search over
param_grids = {
    'Logistic Regression': {'logreg__C': [0.1, 1, 10]},
    'Random Forest': {'rf__n_estimators': [50, 100, 200], 'rf__max_depth': [None, 10, 20]},
    'Gradient Boosting': {'gb__n_estimators': [50, 100, 200], 'gb__learning_rate': [0.01, 0.1, 0.2]}
}

# Evaluate and compare models
for model_name, pipeline in pipelines.items():
    print(f"Training {model_name}...")
    grid_search = GridSearchCV(pipeline, param_grid=param_grids[model_name], scoring='roc_auc', cv=5, n_jobs=-1)
    grid_search.fit(X_train, y_train)
    
    # Make predictions on the testing set
    y_pred_proba = grid_search.predict_proba(X_test)[:, 1]
    
    # Evaluate the model
    roc_auc = roc_auc_score(y_test, y_pred_proba)
    print(f"{model_name} ROC AUC:", roc_auc)

В следующем коде используется конвейер для включения предварительной обработки стандартизации и различных алгоритмов машинного обучения, таких как логистическая регрессия, случайный лес и повышение градиента. Кроме того, GridSearchCV используется для настройки гиперпараметров для каждого алгоритма. Чтобы устранить дисбаланс классов, используется SMOTE для избыточной выборки класса меньшинства.

Обучение логистической регрессии…
Логистическая регрессия ROC AUC: 0,772450567394865
Обучение случайного леса…
ROC случайного леса AUC: 0,7740408162036831
Обучение градиентного повышения…
Градиентное повышение ROC AUC: 0,7744384468095702

так что у меня одинаковые результаты для каждой модели (это неплохо)

Теперь у меня есть собственная модель xG, и мы можем проверить ее эффективность на реальных данных, например, гол Эдинсона Кавани в матче Кубка мира против России.

у моей модели xG 0,86 для этого кадра, это означает

или этот удар Ремо Фройлера Фиорентина — Аталанта, 1–1, который не был реализован в гол, имел xG 0,87.

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