«Непередбачувана гра»? Як я за допомогою Python намагався вирахувати переможця Чемпіонату світу з футболу 2022

Оленка Пилипчак

Багато хто називає футбол «непередбачуваною грою». Чому? Бо є безліч факторів, що можуть вплинути на рахунок. 

Це так… Але лише певною мірою. Так вважає розробник та дата-саєнтист Френк Ендрейд. Він пропонує вирахувати, хто все ж таки переможе на Чемпіонаті світу з футболу 2022. Передаємо йому слово.


Важко передбачити остаточний рахунок або переможця матчу, але можна дізнатись, хто переможе у змаганні. Останні п’ять років Bayern Munich вигравав всі Бундесліги, а Manchester City виграв чотири Прем’єр-ліги. Не думаю, що це випадковість…

У середині сезону 20-21 я створив модель для передбачення переможця Прем’єр-ліги, Ла Ліги, Серії А та Бундесліги. З її допомогою я правильно назвав усіх переможців.

Цей прогноз було не так вже й важко зробити, оскільки на той момент уже відбулось 19 матчів. Зараз я запускаю ту саму модель, щоб передбачити Чемпіонат світу з футболу 2022 року.

Ось результат мого передбачення, виконаний з допомогою Python. Щоб дізнатися більше про код, перегляньте цей годинний відеоурок:

Як ми будемо прогнозувати матчі?

Є різні способи робити прогнози. Я міг би працювати з машинним навчанням та різноманітними змінними, але кілька прочитаних статей переконали мене спробувати розподіл Пуассона. 

Зараз поясню, чому. Давайте почнемо з визначення розподілу Пуассона.

Розподіл Пуассона — це дискретний розподіл ймовірностей, який описує кількість подій, що відбуваються протягом фіксованого інтервалу часу або області можливостей.

Якщо ми розглядаємо гол як подію, яка може статися протягом 90 хвилин футбольного матчу, ми можемо обчислити, скільки голів ймовірно заб’є команда А і команда Б.

Нам потрібно, щоб виконувались такі умови:

  1. Кількість подій можна підрахувати (під час матчу можуть забити 1 або більше голів).
  2. Події незалежні одна від одної (те, що забили перший гол, не повинно впливати на ймовірність іншого голу).
  3. Швидкість, з якою відбуваються події, є постійною (ймовірність досягнення мети в певний проміжок часу має бути однаковою для будь-якого часового проміжку такої ж довжини).
  4. Дві події не можуть відбутися в один і той самий момент часу (не можна одночасно забити два гола).

Без сумніву, припущення 1 і 4 виконуються, але 2 і 3 — частково вірні. Проте припустімо, що припущення 2 і 3 завжди вірні.

Коли я прогнозував переможців вищих європейських ліг, я побудував гістограму кількості голів у кожному матчі за останні п’ять років для чотирьох найкращих ліг:

Якщо ви подивитеся на відповідну криву будь-якої ліги, вона виглядає як розподіл Пуассона.

Тепер можна сказати, що можна використовувати розподіл Пуассона для розрахунку ймовірності кількості голів, які можуть бути забиті в матчі.

Ось формула розподілу Пуассона.

Щоб зробити передбачення, я розглянув:

lambda: медіана голів за 90 хвилин (Команда A і B)

x: кількість голів у матчі, які можуть забити команди A та B

Щоб обчислити lambda, нам потрібні середні результати, забиті/пропущені кожною національною командою. Тому переходимо до наступного пункту.

Голи, забиті / пропущені кожною збірною

Зібравши дані з усіх матчів чемпіонату світу з 1930 по 2018 рік, я підрахував середню кількість забитих і пропущених голів кожною національною командою:

У своєму прогнозі для чотирьох найкращих європейських ліг я врахував фактор «дома/виїзд», але оскільки на Чемпіонаті світу майже всі команди грають на нейтральному стадіоні, я не враховував його для цього аналізу.

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

Прогноз групового етапу

Нижче наведено код, який я використав, щоб передбачити кількість очок, які кожна збірна отримає на груповому етапі:

def predict_points(home, away):
    if home in df_team_strength.index and away in df_team_strength.index:
        lamb_home = df_team_strength.at[home,'GoalsScored'] * df_team_strength.at[away,'GoalsConceded']
        lamb_away = df_team_strength.at[away,'GoalsScored'] * df_team_strength.at[home,'GoalsConceded']
        prob_home, prob_away, prob_draw = 0, 0, 0
        for x in range(0,11): #number of goals home team
            for y in range(0, 11): #number of goals away team
                p = poisson.pmf(x, lamb_home) * poisson.pmf(y, lamb_away)
                if x == y:
                    prob_draw += p
                elif x > y:
                    prob_home += p
                else:
                    prob_away += p
        
        points_home = 3 * prob_home + prob_draw
        points_away = 3 * prob_away + prob_draw
        return (points_home, points_away)
    else:
        return (0, 0)

Якщо говорити простою мовою, predict_points обчислює, скільки очок отримають місцеві та гості-команди. Для цього я обчислив lambda для кожної команди за формулою середні_забиті_голи * середні_пропущені_голи.

Потім я змоделював всі можливі рахунки матчу від 0–0 до 10–10. Я маю lambda та x та використовую формулу розподілу Пуассона, щоб обчислити p.

Prob_home, prob_draw і prob_away накопичує значення p, якщо, скажімо, матч закінчується з рахунком 1–0 (перемога господарів), 1–1 (нічия) або 0–1 (перемога гостей) відповідно. Потім бали розраховуються за наведеною нижче формулою:

points_home = 3 * prob_home + prob_draw
points_away = 3 * prob_away + prob_draw

Якщо ми використаємо predict_points для прогнозування матчу Англія – США, ми отримаємо це:

>>> predict_points('England', 'United States')
(2.2356147635326007, 0.5922397535606193)

Якщо ми застосуємо цю функцію predict_points до всіх матчів на груповому етапі, ми отримаємо першу та другу позицію в кожній групі, отже, наступні матчі в нокаутах.

Прогнозування нокаутів

Для нокаутів мені потрібно прогнозувати не очки, а переможця кожного bracket. Ось чому я створив нову функцію get_winner на основі попередньої функції predict_points.

def get_winner(df_fixture_updated):
    for index, row in df_fixture_updated.iterrows():
        home, away = row['home'], row['away']
        points_home, points_away = predict_points(home, away)
        if points_home > points_away:
            winner = home
        else:
            winner = away
        df_fixture_updated.loc[index, 'winner'] = winner
    return df_fixture_updated

Простіше кажучи, якщо points_home більше, ніж points_away, переможцем є команда господарів, якщо ні — переможцем є команда гостей.

Завдяки функції get_winner я можу отримати результати попередніх brackets:

Прогнозування чвертьфіналу, півфіналу та фіналу

Якщо я знову використаю get_winner, я зможу передбачити переможця чемпіонату світу. Я запускаю функцію ще раз, і дізнаюся, що переможцем буде… Бразилія!

Ось як я передбачив Чемпіонат світу з футболу 2022 року за допомогою Python і розподілу Пуассона. Щоб побачити повний код, перегляньте мій GitHub, а всі статті про цей проєкт можна знайти на моєму блозі Medium.

Автор: Френк Ендрейд

Примітка редакції: у п’ятницю, 9 грудня, у матчі ¼ фіналу зійшлися збірні Хорватії та Бразилії. В основний час та екстратаймах суперники не визначили найсильнішого (1:1), все вирішилося в серії пенальті, яка несподівано завершилася на користь хорватів — 4:2.

Текст адаптувала Євгенія Козловська

Останні статті

SET University пропонує військовим та їх родинам безплатну магістратуру за 4 спеціальностями

Некомерційний навчальний заклад SET University запускає стипендійну програму за рядом напрямків. Українці та українки можуть…

29.04.2024

Від РЕБ до «плащів-невидимок»: за рік в рамках Brave1 створили 1 671 інноваційну розробку

За рік в рамках defense-tech кластеру Brave1 963 розробники створили 1 671 інноваційну розробку —…

29.04.2024

Треба «дешева» робоча сила: Google повністю звільнила команду Python в США

Корпорація Google звільнила всю команду Python в США. Про це стало відомо з публікації Social.coop…

29.04.2024

Українські школярі перемогли на міжнародному ШІ-хакатоні зі застосунком для вивчення жестової мови

Команда з ліцею КПІ PL of KPI Igor Sikorsky перемогла на міжнародному хакатоні зі штучного…

29.04.2024

Більше 50% Go i Ruby розробників з досвідом 3+ роки найняли на $5000. PHP — на самому дні

Більше половини Go i Ruby розробників з досвідом 3+ роки найняли на $5000 або більше.…

26.04.2024

Програмісти намагалися втекти з України в Молдову, щоб влаштуватись на роботу

Прикордонники недалеко від с. Кучурган Одеської області затримали двох програмістів, які намагалися втекти з України…

26.04.2024