Xg предсказывает результаты матчей?
Для начала определим для кого эта статья? Моя цель заинтересовать не только обыкновенных зрителей, но и тех, кто уже занимается футбольной аналитикой. В статье я постараюсь показать интересные исследования об Xg.
Многие из тех, кто смотрит футбол и читает новости когда-нибудь видел метрику «xg». Что она вообще означает? Простыми словами Xg это количество ожидаемых голов. Т.е. каждый нанесённый удар по воротам имеет вероятность конвертироваться в забитый мяч, но с каждой позиции эта вероятность разная (если углубляться, то станет очевидным, что xg зависит от нескольких параметров, а не от одной позиции). К примеру, самая высокая вероятность забить мяч при исполнении пенальти. Чаще всего с пенальти дают 0.79 xg. Необходимо учитывать, что единой формулы расчёта xg нет, каждый провайдер рассчитывает её по-своему. Так например, для написания этой статьи я использовал данные с сайта https://understat.com/, но, если мы посмотрим другие источники, цифры будут отличаться.
Моя задача узнать, насколько точно Xg предсказывает количество голов в матче. Исследование будем проводить для АПЛ сезона 2022/2023. В данном исследовании мы ограничимся простыми методами анализа. Я составил таблицу из 380 матчей АПЛ. Пример таблицы с первыми 10 матчами АПЛ.
Table_EPL <- read.csv("F:\\EPL.csv")# Загружаем наш csv файл
Order — отвечает в таблице за количество матчей от 1 до 380
Week — в каком туре был сыгран матч, в Англии туры называют неделями. Предварительно для удобства все матчи объединены по турам, вне зависимости от даты, например матч 28 тура Брайтон — МЮ был сыгран 4 мая, хотя 28 тур игрался в середине марта.
Team1 — команда хозяин
Xg1 — показатель xg домашней команды
Goal1 — сколько голов забила домашняя команда
Goal2 — сколько голов забила гостевая команда
Xg2 — показатель xg гостевой команды
Team2 — команда гость
Изначальная таблица построена, далее будем считать разницу между забитыми мячами и xg у каждой из команд (diff1 и diff2), а потом напишем «yes» если разница меньше или равна 0.5, и, если разница строго больше 0.5 пишем «no» (Res1 и Res2).
Table_EPL$diff1 <- (Table_EPL$Xg1 - Table_EPL$Goal1)# Считаем разность Xg и забитых мячей для команд хозяев
Table_EPL$diff2 <- (Table_EPL$Xg2 - Table_EPL$Goal2)# Считаем разность Xg и забитых мячей для команд гостей
Table_EPL$Res1 <- ifelse(abs(Table_EPL$diff1) > 0.5, 'no', 'yes')# Условие "успешности" прдесказанных забитых голов для команд хозяев
Table_EPL$Res2 <- ifelse(abs(Table_EPL$diff2) > 0.5, 'no', 'yes')# Условие "успешности" прдесказанных забитых голов для команд гостей
Получится данная таблица:
Далее проведём два анализа строгий и нестрогий. В строгом анализе будем выводить «yes» если в Res1 и Res2 указано «yes», в нестрогом анализе если хотя бы в одном из столбцов имеется значение «yes».
Проведём строгий анализ
Table_EPL$success <- ifelse(Table_EPL$Res1 == 'yes' & Table_EPL$Res2 == 'yes', 'yes', 'no')# Условие "успешного" предсказания результата матча
Всего успешно предсказанных матчей получается 65, неуспешных соответственно 315.
length(which(Table_EPL$success == 'no' ))# Подсчёт "неуспешных" результатов
Теперь рассмотрим нестрогий анализ
Table_EPL$success <- ifelse(Table_EPL$Res1 == 'yes' | Table_EPL$Res2 == 'yes', 'yes', 'no')# Условие "успешного" предсказания результата матча
Получится таблица:
Всего успешно предсказанных матчей получается 241, неуспешных соответственно 139.
length(which(Table_EPL$success == 'no' ))# Подсчёт "неуспешных" результатов
Суммарные значения
sum(Table_EPL$Xg1,Table_EPL$Xg2)# Суммарный Xg
sum(Table_EPL$Goal1,Table_EPL$Goal2)# Суммарное количество голов
Суммарный xg равен 1136.54, мячей забито 1084, разница равна 52.54
Столь незначительное отклонение от забитых мячей говорит, что Xg может не быть точным в каждом конкретном матче, но на дистанции сезона, данная метрика покажет более чем хорошие результаты. Незначительное оно т.к. я рассматриваю ошибку предсказания в матче больше 0,5. В нашем случае разница значительно меньше 190(380 матчей * 0,5).
Далее приведу статистику средних значений.
Весь чемпионат:
summary(c(Table_EPL$Xg1,Table_EPL$Xg2))# Вычисляем средний Xg
Средний Xg — 1,48
summary(c(Table_EPL$Goal1,Table_EPL$Goal2))# Вычисляем среднее количество голов
Среднее кол-во голов — 1,43
Для команд хозяев:
summary(Table_EPL$Xg1)# Вычисляем средний Xg
Средний Xg — 1,67
summary(Table_EPL$Goal1)# Вычисляем среднее количество голов
Среднее кол-во голов — 1,63
Для команд гостей:
summary(Table_EPL$Xg2)# Вычисляем средний Xg
Средний Xg — 1,3
summary(Table_EPL$Goal2)# Вычисляем среднее количество голов
Среднее кол-во голов — 1,2
Ну и в конце проведём некую оценку точности показателей Xg. Я здесь имею ввиду, что чем больше забито мячей, тем большую неточность показывает Xg. Пример матч Ливерпуль 9:0 Борнмут, Xg в этом матче Ливерпуль 4,86 Борнмут 0,18.
Попробуем оценить в какой момент Xg не даёт удовлетворительную точность. Замечу, что здесь поменяем способ оценки и будем рассчитывать не интервал ± 0,5 xg, а значение xg большее или равное минимально допустимой границе для количества забитых мячей.
Для домашних игр:
length(which(Table_EPL$Goal1 == '4'))# Количество матчей с 4 голами
length(which(Table_EPL$Goal1 == '4' & Table_EPL$Xg1 >= '3.5'))# Количество матчей с 4 голами и Xg более 3.5
Матчей с 4 забитыми мячами — 28, xg ≥ 3,5 — 4 матча, точность ~ 14,3%
length(which(Table_EPL$Goal1 == '3'))# Количество матчей с 3 голами
length(which(Table_EPL$Goal1 == '3' & Table_EPL$Xg1 >= '2.5'))# Количество матчей с 3 голами и Xg более 2.5
Матчей с 3 забитыми мячами — 43, xg ≥ 2,5 — 19 матчей, точность ~ 44,2%
length(which(Table_EPL$Goal1 == '2'))# Количество матчей с 2 голами
length(which(Table_EPL$Goal1 == '2' & Table_EPL$Xg1 >= '1.5'))# Количество матчей с 2 голами и Xg более 1.5
Матчей с 2 забитыми мячами — 90, xg ≥ 1,5 — 53 матча, точность ~ 58,9%
length(which(Table_EPL$Goal1 == '1'))# Количество матчей с 1 голом
length(which(Table_EPL$Goal1 == '1' & Table_EPL$Xg1 >= '0.5'))# Количество матчей с 1 голом и Xg более 0.5
Матчей с 1 забитым мячом — 124, xg ≥ 0,5 — 112 матчей, точность ~ 90,3%
Построим простой график для иллюстрации
library(ggplot2)# Подключаем ggplot2 для построения графика
plot(Table_XG$week, Table_XG$Goal1, type = "o", pch = 16,# Ось Х, Ось У, Тип линии графика, Тип точки на графике
main = 'Xg in match',# Название графика
xlab = 'week',# Подпись оси Х
ylab = 'xg and goal',# Подпись оси У
col = 'green3',# Цвет линии
lwd = 2)# Толщина линии
xlines = seq(min(Table_XG$week), max(Table_XG$week), 1)# Создаём последовательность с указанием минимального и максимального значения
ylines = seq(min(Table_XG$Goal1), max(Table_XG$Goal1), 1)# Создаём последовательность с указанием минимального и максимального значения
abline(h = ylines, v = xlines, col = "lightgray")# Рисуем сетку по нашим последовательностям
lines(Table_XG$week, Table_XG$Xg1, type = "o", pch = 19, col = 'red', lwd = 2)# Добавляем вторую линию на график, аналогично первой
legend("topleft",# Создаём легенду и указываем её положение
legend = c("Goals", "Xg"),# Что будет указано в легенде
col = c("green3", "red"),# указываем цвета в легенде
lty = 7,# Тип линии в легенде
pch = 19)# Тип точки в легенде
Для гостевых игр:
length(which(Table_EPL$Goal2 == '4'))# Количество матчей с 4 голами
length(which(Table_EPL$Goal2 == '4' & Table_EPL$Xg2 >= '3.5'))# Количество матчей с 4 голами и Xg более 3.5
Матчей с 4 забитыми мячами — 15, xg ≥ 3,5 — 2 матча, точность ~ 13,3%
length(which(Table_EPL$Goal2 == '3'))# Количество матчей с 3 голами
length(which(Table_EPL$Goal2 == '3' & Table_EPL$Xg2 >= '2.5'))# Количество матчей с 3 голами и Xg более 2.5
Матчей с 3 забитыми мячами — 35, xg ≥ 2,5 — 7 матчей, точность ~ 20%
length(which(Table_EPL$Goal2 == '2'))# Количество матчей с 2 голами
length(which(Table_EPL$Goal2 == '2' & Table_EPL$Xg2 >= '1.5'))# Количество матчей с 2 голами и Xg более 1.5
Матчей с 2 забитыми мячами — 76, xg ≥ 1,5 — 44 матча, точность ~ 57,9%
length(which(Table_EPL$Goal2 == '1'))# Количество матчей с 1 голом
length(which(Table_EPL$Goal2 == '1' & Table_EPL$Xg2 >= '0.5'))# Количество матчей с 1 голом и Xg более 0.5
Матчей с 1 забитым мячом — 125, xg ≥ 0,5 — 112 матчей, точность ~ 89,6%
Построим простой график для иллюстрации
library(ggplot2)# Подключаем ggplot2 для построения графика
plot(Table_XG$week, Table_XG$Goal2, type = "o", pch = 16,# Ось Х, Ось У, Тип линии графика, Тип точки на графике
main = 'Xg in match',# Название графика
xlab = 'week',# Подпись оси Х
ylab = 'xg and goal',# Подпись оси У
col = 'green3',# Цвет линии
lwd = 2)# Толщина линии
xlines = seq(min(Table_XG$week), max(Table_XG$week), 1)# Создаём последовательность с указанием минимального и максимального значения
ylines = seq(min(Table_XG$Goal2), max(Table_XG$Goal2), 1)# Создаём последовательность с указанием минимального и максимального значения
abline(h = ylines, v = xlines, col = "lightgray")# Рисуем сетку по нашим последовательностям
lines(Table_XG$week, Table_XG$Xg2, type = "o", pch = 19, col = 'red', lwd = 2)# Добавляем вторую линию на график, аналогично первой
legend("topleft",# Создаём легенду и указываем её положение
legend = c("Goals", "Xg"),# Что будет указано в легенде
col = c("green3", "red"),# указываем цвета в легенде
lty = 7,# Тип линии в легенде
pch = 19)# Тип точки в легенде
Выводы
Исходя из проведённого анализа можно сделать вывод, что xg показывает высокую точность предсказания результатов матчей на дистанции, а не в одном отдельном взятом матче. Это можно понять увидев, что при строгом анализе предсказано ~ 17,1% матчей, а при нестрогом ~ 63,4% матчей, что конечно хорошо, но как по мне недостаточно, учитывая, что мы считали матч успешным, если хотя бы у одной из команд предсказано значение. Также у Xg есть такой недостаток, как потеря точности при забитых мячах больше 2. В итоге можно сказать, что xg действительно хорошая и нужная метрика, однако она не является единственной главной.
Для опытных аналитиков статья будет просто интересной информацией, а для обыкновенных зрителей, я надеюсь, она станет толчком к началу углубленного изучения футбольной статистики.