В этой статье я покажу вам альтернативный слепой подход к моделированию матчей WC, который может достичь уровней точности традиционных подходов к моделированию с некоторыми преимуществами, такими как чувствительность к «сюрпризам» (что было обычным явлением в Катаре). Также я буду использовать эту статью для критики традиционных подходов к моделированию, которые я видел до сих пор, в отношении их выбора данных и предположений.

Когда я закончил свою модель чемпионата мира, я заметил, что некоторые команды кажутся слишком сильными (например, Япония, Сербия, Иран и другие «сюрпризы»), по крайней мере, по сравнению с общепринятыми мнениями и прогнозами. Сразу после этого я начал сомневаться в качестве своей методики, как Япония собирается победить Германию? Однако по мере того, как матчи были сыграны, я мог видеть согласованность, модель просто отражала то, что она видела на данных. Затем я понял, что смог разработать хороший подход к моделированию турниров.

В моем подходе модель не знает, что Бразилия является победителем ЧМ номер 1 за все время или что Бельгия занимает первое место в ФИФА, она просто знает, что игроки страны А имеют эту статистику в этой лиге, а игроки страны Б - это другая статистика в других лигах. И, что удивительно, модель достигла того же уровня точности, что и традиционные модели «всегда ставь на фаворита».

В этой статье я объясню свой альтернативный «слепой» подход к тому, как смоделировать туалет и как построить его самостоятельно.

Предсказания

Прогнозы можно найти здесь:

https://storage.googleapis.com/public_bucket2/index.html

Прогнозы позиций:

|Group| Place | Country |
|-|-|-|
|A |1 |Netherlands|
|A|2 |Senegal|
|B|1 |England|
|B|2 |IR Iran|
|C|1 |Argentina|
|C|2 |Poland|
|D|1 |France|
|D|2 |Denmark|
|E|1 |Japan|
|E|2 |Germany|
|F|1 |Belgium|
|F|2 |Croatia|
|G|1 |Japan|
|G|2 |Spain|
|H|1 |Portugal|
|H|2| Uruguay|

Проблема моделирования чемпионата мира по футболу: данные

Чемпионат мира по футболу является самым важным футбольным событием в мире (футбол является самым популярным видом спорта в мире) и спортивным событием с самой большой аудиторией. Кубок разыгрывается каждые 4 года 32 странами, которым удалось выйти из сложного, долгого и конкурентного процесса. Первый вопрос, который возникает у специалиста по обработке и анализу данных, заключается в том, можем ли мы смоделировать чемпионат мира как любую другую футбольную лигу или он настолько исключительный, что нам нужно разработать особый подход.

Я видел много попыток смоделировать чемпионат мира с моими коллегами, на моем факультете, в твиттере и по ссылкам экономистов, статистиков и специалистов по данным, которые следовали интуитивному, но все же наивному подходу: собирайте данные о предыдущих международных матчах и обучайте какую-то модель, от простых регрессий к более сложным алгоритмам, от эмпирических моделей к теоретическим, полным предположений (таких как традиционное предположение о распределении Пуассона целей).

Это наивный подход по двум причинам: во-первых, конкуренция в каждой игре разная. Большинство предыдущих матчей — это товарищеские матчи, в которых команды обычно экспериментируют с выбором игроков, стратегиями и схемами. Кроме того, команды не играют с одинаковой интенсивностью, потому что на карту ничего не поставлено, поэтому не стоит рисковать своими лучшими игроками травмами и заставлять их тратить несколько месяцев на восстановление, проиграв в будущих матчах страны и в своих клубах. . В результате вы не найдете закономерности или не той, которую нам нужно правильно смоделировать.

Вторая причина связана с первой: состав каждой команды сильно различается между турнирами и даже между последовательными товарищескими матчами, в основном, чтобы избежать риска травм, чтобы поэкспериментировать с другими игроками, а также из-за того, что некоторые другие игроки недоступны (из-за клубных ограничений, фактические травмы или уровень игры). Как следствие, преемственность состава команды не гарантируется. Так что, если мы пойдем по наивному пути, мы положим в один мешок (например, «Бразилия») десятки разных бразильских команд, которые играли исторически.

Я видел модели, использующие данные матчей 1934 года! В свое время состав команды был другим, но также и мировое распределение талантов; способ игры и даже страны (подумайте о Югославии и Федеративной Германии). Очевидно, что эти модели не будут достаточно чувствительными к современным тенденциям в футболе и будут сильно предвзяты к показателям страны за последнее столетие: например, в этих моделях Уругвай непобедим.

Я потратил много времени, размышляя над этими двумя проблемами, пытаясь решить их наилучшим образом.Мои выводы были такими: если я хочу получить хорошие прогнозы для этого единственного WC, мне нужно:

  • данные матчей, в которых я уверен, что уровень состязательности аналогичен уровню ЧМ (может быть, предыдущие ЧМ или, может быть, квалификации ФИФА, турниры местных конфедераций и т. д.)
  • данные о прошлых матчах, но не так давно (вероятно, более 20 лет будут бесполезны, потому что игра сильно изменилась).
  • способ хоть как-то решить проблему преемственности состава команды

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

Проблема моделирования чемпионата мира по футболу: особенности

Теперь, когда мы отфильтровали данные, которые нам понадобятся, пришло время поговорить о функциях. При моделировании матча из лиги эта проблема довольно проста: используйте положение команды, ее местоположение, возможно, некоторые переменные окна веселья (такие как голы, передачи, владение мячом в течение последних n матчей). С правильными функциями вы можете легко создавать модели с точностью более 70% (некоторые из них приближаются к 90%). Мы можем достичь этих уровней, потому что регулярные сезоны дают регулярные данные!

Регулярность является нормой во многих лигах, где вы играете 38 матчей за сезон, и где у вас есть много различий в конкурентоспособности каждой команды (где у вас есть действительно четкие группы команд, которые играют за лигу, некоторые играют за средние места за столом и некоторые игры, чтобы держаться подальше от зоны вылета). Подумайте о Ла Лиге последнего десятилетия, когда победители были почти дуополиями: либо «Барселона», либо «Реал Мадрид».

Однако в WC у нас нет такого уровня регулярности, и на самом деле попытка найти регулярность — это палка о двух концах. Вы можете найти ее, используя такие переменные, как очки в рейтинге ФИФА, позиция или среднее значение. голов на команду, и вы получите регулярность, но ваша модель потеряет потенциал в обнаружении «сюрпризов».

Я видел другое семейство моделей, в которых они правильно решают проблемы качества данных, однако они много внимания уделяют регулярности и, как следствие, строят модели, нечувствительные к неожиданностям: в моделях этого типа нет места для неудачников, и они предсказывают одни и те же результаты снова и снова. Например, в этих моделях непобедимы Германия и Бельгия (одна из-за истории матчей, а другая из-за рейтинга ФИФА).

Альтернатива: слепая эмпирическая модель

В этот момент вы можете задаться вопросом: «Ну, тогда скажи мне, каков правильный подход?». Я скажу, что нет рецепта или безопасного решения при построении модели, вы должны рисковать и балансировать в зависимости от проблемы и характеристик, которые вы хотите улучшить.

Сначала я начал предлагать следующие предположения:

  • Предположение 1: футбол сильно изменился за последние 2 десятилетия с точки зрения стиля игры, способностей, формаций, стратегий, конкурентоспособности лиг и интернационализации высших мировых лиг.
  • Предположение 2. конкурентоспособность команды ЧМ тесно связана с качеством ее игроков, и лучший способ проверить качество отдельного игрока — это его статистика.
  • Предположение 3: релевантная статистика для ЧМ — это статистика в обычных лигах, где отдельные игроки играют только в течение предыдущего сезона или во время чемпионата мира.

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

Таким образом я решаю проблему наивной регулярности, не используя ранжирование или средние цели; и я решаю проблему непрерывности состава, потому что в модели каждый WC имеет уникальную комбинацию характеристик выбранных 23 игроков в течение определенного времени, а не использует команду как единое целое.

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

Итак, для сборки модели мне понадобилось:

  • данные всех команд и матчей всех чемпионатов мира с 2006 года
  • данные состава игроков каждой команды всех чемпионатов мира с 2006 года
  • данные статистики игроков за последний сезон непосредственно перед чемпионатом мира.

Затем я создал набор данных игр WC с 2006 года, со статистикой игроков каждой страны в качестве X и исходом матча в качестве Y. Затем случайным образом разделил набор данных на тестовый и обучающий наборы. Наконец, я построил несколько моделей на нескольких разбиениях только для того, чтобы понять, что этот слепой подход дает регулярные прогнозы (без необходимости наивных регулярных данных) и что он может иметь среднюю точность около 70%. Вы можете проверить прогнозы Out of Bag (Чемпионат Катара) здесь: https://storage.googleapis.com/public_bucket2/index.html

Важно то, что моя модель не знает, что Бразилия является победителем ЧМ номер 1 или что Бельгия занимает первое место в ФИФА, она просто знает, что игроки страны А имеют эту статистику в этой лиге, а игроки страны Б имеют эта другая статистика в этой лиге. И, что удивительно, модель достигла того же уровня точности, что и традиционные модели «всегда делать ставки на фаворитов», но она смогла обнаружить «сюрпризы», такие как победа Японии над Германией, среди многих других.

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

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

Руки на данных

В этом разделе я объясню, как я разработал модель. Цель состоит в том, чтобы показать общую идею, а не сосредотачиваться на конкретном технологическом стеке, чтобы каждый мог воспроизвести общую методологию со своим собственным API и выбором технологий. Если вы хотите увидеть фактическое кодирование, я загружу файлы в общедоступный репозиторий github в конце WC.

Требования к данным

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

  • каталог стран: список всех стран, сыгравших ЧМ с 2006 г.
  • каталог матчей: список всех матчей, которые произошли и будут происходить в ЧМ с 2006 года.
  • каталог игроков: список всех игроков, входивших в каждую команду ЧМ с 2006 г.
  • каталог лиг: список всех лиг, в которых эти игроки играли или играют в настоящее время
  • каталог сезонов: из каждой лиги список соответствующих сезонов (до или во время ЧМ)
  • статистика игроков: из каждого соответствующего сезона и лиги список статистики каждого игрока WC.

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

Каталог стран:

Сначала нам нужно сохранить данные для всех стран, которые играли в ЧМ с 2006 года.

Каталог матчей:

Затем нам нужны данные всех матчей ЧМ. Я решил смоделировать только WC, потому что я считаю, что это исключительный турнир, и поскольку это был экспериментальный подход, цена моего решения заключается в том, что это действительно очень маленький набор данных, который может вызвать проблемы в момент прогнозов из сумки (также это уменьшает наши степени свободы и может вызвать проблемы, если мы планируем добавить много функций). Однако, если у нас есть чистый и масштабируемый конвейер, было бы очень легко добавить данные из основных турниров УЕФА, КОНКАКАФ, КОНМЕБОЛ, КАФ, АФК, ОФК и ОФК (Копа Америка, Еврокопа, Кубок Африки и т. д.), чтобы спасти больше наблюдения и эмпирически посмотреть, как это работает (возможно, в будущем я мог бы написать статью под названием «Сложнее ли моделировать ЧМ, чем локальные турниры?»).

Каталог игроков:

Затем нам нужно построить каталог всех игроков во время каждого турнира. Например, для одной команды во время ЧМ у нас есть список их игроков, который выглядит так:

Таким образом, общая таблица для всех команд во время всех соответствующих ЧМ выглядит так:

Каталог лиги:

Затем мы создаем простой каталог лиг: в этом смысле мы можем иметь столько лиг, сколько захотим, однако, поскольку наш набор данных очень мал (и я не хочу создавать много шума в модели), я решил оставить только несколько лиг, которые имеют лучших игроков в мире и чьи игроки кормят подавляющее большинство игроков WC: Premier League, La Liga, Seria A, Bundesliga, Eredivise, Liga NOS, Ligue 1, Champions League и Europa. Лига.

Сезонный каталог:

Теперь, после обнаружения всех сезонов, в которых играли все игроки ЧМ с 2006 года, мы фильтруем соответствующие сезоны:

  • 2005/2006 для ЧМ 2006
  • 2009/2010 на ЧМ-2010
  • 2013/2014 на ЧМ-2014
  • 2017/2018 для ЧМ-2018
  • 2022/2023 на ЧМ-2022

Статистика игрока

Наконец, для каждого игрока в каталоге игроков мы запрашиваем его сезонную статистику в соответствующие годы, а также в лиге и команде, в которой он играл во время ЧМ. Данные, которые мы получаем, выглядят так для каждого игрока.

[{'player_id': 31000,
  'team_id': 68,
  'league_id': 82,
  'season_id': 213,
  'captain': 0,
  'minutes': 1585,
  'appearences': 33,
  'lineups': 15,
  'substitute_in': 18,
  'substitute_out': 9,
  'substitutes_on_bench': 19,
  'goals': 8,
  'owngoals': 0,
  'assists': 1,
  'saves': 0,
  'inside_box_saves': 0,
  'dispossesed': 0,
  'interceptions': 0,
  'yellowcards': 4,
  'yellowred': 0,
  'redcards': 0,
  'type': 'domestic',
  'tackles': None,
  'blocks': None,
  'hit_post': None,
  'cleansheets': 13,
  'rating': None,
  'fouls': {'committed': None, 'drawn': None},
  'crosses': {'total': None, 'accurate': None},
  'dribbles': {'attempts': None, 'success': None, 'dribbled_past': None},
  'shots': {'shots_total': None, 'shots_on_target': None},
  'duels': {'total': None, 'won': None},
  'passes': {'total': None, 'accuracy': None, 'key_passes': None},
  'penalties': {'won': None,
   'scores': None,
   'missed': None,
   'committed': None,
   'saves': None}}

Теперь у нас есть все данные для продолжения нашего эксперимента.

Разработка функций

Первое, что нам нужно сделать, это построить нашу статистику X игроков по командам и мою Y результатов.

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

from sqlalchemy import create_engine
import pandas as pd
import numpy as np
# Credentials to database connection
hostname="XX.XX.XX.XX"
dbname="xxxxx"
uname="xxxx"
pwd="xxxx"
# Create SQLAlchemy engine to connect to MySQL Database
engine = create_engine("mysql+pymysql://{user}:{pw}@{host}/{db}"
            .format(host=hostname, db=dbname, user=uname, pw=pwd))

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

 local and visitor tags just make reference to the order 
# of how the teams are announced in the match
# local being the firt team.
df = pd.read_sql('SELECT * FROM wc_games', con=engine)
df['y_local'] = np.where(df['winner_team_id'] == df['localteam_id'], 1, 0)
df['y_visitor'] = np.where(df['winner_team_id'] == df['visitorteam_id'], 1, 0)
df['y_tie'] = np.where((df['y_visitor'] + df['y_local'] == 0), 1, 0)
df=df[['id','season_id','stage_id','round_id','localteam_id','visitorteam_id','y_local','y_visitor','y_tie']]
df

Соответствующая статистика

Теперь, когда у нас есть наши матчи и готовые Y, нам нужен наш X. Чтобы построить наш X, нам нужна наша статистика, таблицы сезонов и лиг. Во-первых, нам нужно выяснить, какие сезоны и лиги относятся к делу (помните? года во время ЧМ), чтобы отфильтровать нашу таблицу статистики игроков.

# Request the stats, seasons and league tables
stats = pd.read_sql('SELECT * FROM wc_player_allstats', con=engine)
seasons = pd.read_sql('SELECT * FROM seasons', con=engine)
leagues = pd.read_sql('SELECT * FROM leagues', con=engine)

# filter to relevant leagues
relevant_leagues=leagues[0:25] 
relevant_leagues=list(relevant_leagues.id.values)
relevant_seasons = seasons[seasons['league_id'].isin(relevant_leagues)] 

# filter to relevant seasons
leagues_names=["2005/2006","2009/2010","2013/2014","2017/2018","2022/2023"]

# Then we just take the stats for each player 
# for each relevant league and each relevant season
relevant_seasons = relevant_seasons[relevant_seasons['name'].isin(leagues_names)] 
relevant_seasons=relevant_seasons[["id","name","league_id"]]
relevant_seasons_list=list(relevant_seasons.id.values)
relevant_stats = stats[stats['season_id'].isin(relevant_seasons_list)]
# this are our variables to play with!
relevant_stats

Обобщить по командам

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

preproces_data=relevant_stats.merge(players, left_on='player_id', right_on='player_id')
# We are filling NAs with 0s because we want a monothonic variable
preproces_data = preproces_data.fillna(0)
# Here we make the aggregate sum of stats for a country in a single WC
preproces_data = preproces_data.groupby(['season_id', 'country_id'],as_index=False)['minutes','appearences','lineups','substitute_out',
                                                    'substitutes_on_bench','goals','owngoals','assists',
                                                    'saves','inside_box_saves','dispossesed','interceptions',
                                                    'yellowcards','yellowred','redcards','tackles',
                                                    'blocks','hit_post','cleansheets','rating','committed','drawn',
                                                    'total','accurate','attempts','success','dribbled_past',
                                                    'shots_total','shots_on_target','won','accuracy','key_passes',
                                                    'scores','missed'].apply(lambda x : x.astype(float).sum())
preproces_data=preproces_data.merge(relevant_seasons, left_on='season_id', right_on='id')
preproces_data = preproces_data.drop('id', axis=1)
preproces_data = preproces_data.drop('season_id', axis=1)
preproces_data

Конкуренция в лиге

Теперь, когда у нас есть все сводные статистические данные для одной команды с учетом одного соответствующего сезона, я создам переменную для конкурентоспособности лиги в 3 категориях:

  • вверху: Лига чемпионов, Премьер-лига и Ла Лига
  • середина: Бундеслига, Серия А
  • низкие: Лига Европы, Eredivise, Liga Nos и Ligue 1

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

experiment["wc_id"]=experiment["country_id"]+"-"+experiment["name"]
experiment = experiment.drop('country_id', axis=1)
experiment = experiment.drop('name', axis=1)

league_rating= { 2:'top',
  8:'top',
  564:'top',
  384:'mid',
  82:'mid',
  5:'low',
  301:'low',
  72:'low',
  462:'low'}

experiment["league_id"]=experiment["league_id"].replace(league_rating)
experiment = experiment.groupby(['wc_id', 'league_id'],as_index=False)["minutes","goals","assists","interceptions","appearences","yellowcards","cleansheets","key_passes"].apply(lambda x : x.astype(float).sum())
experiment

Последний спор

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

Сначала мы создаем небольшую таблицу с уникальным идентификатором для каждой команды на каждом отдельном ЧМ (поскольку Бразилия 2006 сильно отличается от Бразилии 2018, поэтому мы концептуализируем их как разные команды и не даем машине понять, что обе команды находятся в одной стране).

# First we create a table with an unique id for each team at each single WC
# because Brazil 2006 is far different for Brazil 2018

preproces_data["wc_id"]=preproces_data["country_id"]+"-"+preproces_data["name"]
wc_ids_catalogue=preproces_data[["country_id","name","wc_id"]]
wc_ids_catalogue=wc_ids_catalogue.drop_duplicates()
wc_ids_catalogue

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

di = {5794: "2005/2006", 5795: "2009/2010",5796:"2013/2014",892:"2017/2018",18017:"2022/2023"}
df=df.replace({"season_id": di})
df["localteam_wc_id"]=df["localteam_id"].astype(str)+"-"+df["season_id"]
df["visitorteam_wc_id"]=df["visitorteam_id"].astype(str)+"-"+df["season_id"]
df

# We prepare our summary stats table in a wide format
# for the merge with the previous table
X=experiment.pivot(index='wc_id', columns='league_id').fillna(0)
X.columns = X.columns.map(lambda index: f'{index[0]}_{index[1]}')
X.reset_index(inplace=True)
X

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

local_stats=df[["localteam_wc_id"]].merge(X,how='left', left_on='localteam_wc_id', right_on='wc_id')
local_stats=local_stats.drop('localteam_wc_id', axis=1)
local_stats=local_stats.drop('wc_id', axis=1)

visitor_stats=df[["visitorteam_wc_id"]].merge(X,how='left', left_on='visitorteam_wc_id', right_on='wc_id')
visitor_stats=visitor_stats.drop('visitorteam_wc_id', axis=1)
visitor_stats=visitor_stats.drop('wc_id', axis=1)

Итак, каждый из этих фреймов данных выглядит так:

После простого отдыха мы получаем матрицу разностей! это как раз то, что нужно нашим моделям: нейтральный, объективный, эмпирический, основанный на данных рейтинг популярности для конкурентоспособности команды.

core=local_stats.fillna(0)-visitor_stats.fillna(0)

Наконец, мы делаем наш окончательный кадр данных и наш готовый кадр данных для этих матчей ЧМ в Катаре:

data=pd.concat([df, core], axis = 1)
data = data.drop([ 'stage_id','round_id','localteam_id',
                  'visitorteam_id','localteam_wc_id','visitorteam_wc_id'], axis=1)
oob = data[data['season_id'] == '2022/2023']
oob = oob.drop('season_id', axis=1)

data = data[data['season_id'] != '2022/2023']
data = data.drop('season_id', axis=1)

oob

И мы готовы моделировать! На данный момент эти данные очень богаты (мы действительно усердно добывали золото), и мы можем использовать любую модель, которую захотим.

Моделирование

Для простоты я решил использовать 2 модели RF: одну для регрессии и одну для классификации (обе измеряют вероятность «локальной» победы в каждом матче). Я создал следующие функции, чтобы разделить наборы данных для обучения и тестирования и сравнить уровни точности для каждой модели.

import numpy as np
from sklearn.model_selection import train_test_split
from sklearn.ensemble import RandomForestRegressor
from random import randint
from sklearn.ensemble import RandomForestClassifier

def compare_models_regressor(y,size):

    # Use numpy to convert to arrays
    # Labels are the values we want to predict
    labels = np.array(data[y])
    index=np.array(data['id'])
    # Remove the labels from the features
    # axis 1 refers to the columns
    features= data.drop(['y_local','y_visitor','y_tie', 'id'], axis = 1)

    # Saving feature names for later use
    feature_list = list(features.columns)
    # Convert to numpy array
    features = np.array(features)
    # Using Skicit-learn to split data into training and testing sets
    # Split the data into training and testing sets
    train_features, test_features, train_labels, test_labels,index_features, index_labels = train_test_split(features, labels,index, test_size = size, random_state = randint(0,200))
    # Import the model we are using
    # Instantiate model with 1000 decision trees
    rf = RandomForestRegressor(n_estimators = 100, random_state = 0)
    # Train the model on training data
    rf.fit(train_features, train_labels);
    # Use the forest's predict method on the test data
    predictions = rf.predict(test_features)
    # Calculate the absolute errors
    errors = abs(predictions - test_labels)
    # Print out the mean absolute error (mae)
    netric= sum(np.round(predictions)==test_labels)/len(test_labels)
    #print('netric:', netric)
 
    return(round(netric*100,2))

def compare_models_class(y,size,n):

    # Use numpy to convert to arrays
    # Labels are the values we want to predict
    labels = np.array(data[y])
    index=np.array(data['id'])
    # Remove the labels from the features
    # axis 1 refers to the columns
    features= data.drop(['y_local','y_visitor','y_tie', 'id'], axis = 1)

    # Saving feature names for later use
    feature_list = list(features.columns)
    # Convert to numpy array
    features = np.array(features)
    # Using Skicit-learn to split data into training and testing sets
    # Split the data into training and testing sets
    train_features, test_features, train_labels, test_labels,index_features, index_labels = train_test_split(features, labels,index, test_size = size, random_state = randint(0,200))

  
    
    clf = RandomForestClassifier(max_depth=n, random_state=randint(0,200))
    clf.fit(train_features, train_labels)

    partidos_train=clf.score(train_features,train_labels)
    partidos_test=clf.score(test_features,test_labels)
    

    return(round(partidos_test*100,2))
## Fine tuning

Эмпирическая эффективность

Теперь мы оцениваем точность обеих моделей для 50 различных случайных разделений, используя 20% наблюдений для тестового набора. Я использовал только 20%, потому что у нас очень мало наблюдений, поэтому нам нужно расставить приоритеты в наборе поездов:

x=0
for z in range(50): 
    y= compare_models_regressor('y_visitor',.2)
    x=x+y
    print(y)
x/50


75.0
65.38
57.69
67.31
67.31
71.15
78.85
69.23
73.08
73.08
69.23
75.0
75.0
71.15
78.85
63.46
65.38
63.46
71.15
65.38
71.15
69.23
67.31
63.46
67.31
63.46
59.62
57.69
61.54
71.15
65.38
75.0
75.0
73.08
65.38
75.0
75.0
67.31
71.15
71.15
67.31
63.46
63.46
73.08
71.15
71.15
67.31
65.38
63.46
59.62

средний = 68,5

x=0
for z in range(50): 
    y= compare_models_class('y_visitor',.1,6)
    x=x+y
    print(y)

x/50
73.08
73.08
69.23
80.77
69.23
65.38
69.23
80.77
76.92
73.08
69.23
57.69
76.92
76.92
73.08
69.23
80.77
57.69
65.38
57.69
69.23
84.62
73.08
69.23
65.38
84.62
65.38
61.54
73.08
57.69
61.54
65.38
65.38
69.23
76.92
80.77
73.08
84.62
61.54
76.92
65.38
69.23
57.69
84.62
69.23
69.23
61.54
73.08
73.08
76.92
70.692

среднее значение = 70,62

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

Итак, теперь мы будем использовать вторую модель, чтобы делать прогнозы для нашего готового набора данных, или, как его еще называют: Qatar WC.

## Прогнозы

Наш набор данных oob выглядит так

# this are the first 48 group stages matches
oob=oob[0:48]
oob= oob.drop(['y_local','y_visitor','y_tie'], axis = 1)
# the image is just a sample
oob

Ансамбль

Как вы можете видеть в разных моделях с разным разделением, точность сосредоточена в 70%, однако есть модели с более низкой и высокой точностью. Мы хотели бы снизить риск делать прогнозы с помощью плохой модели, поэтому для этого мы можем делать прогнозы с помощью ансамбля различных моделей. Опять же, в этой части можно быть максимально умным и креативным, думаю, можно было бы экспериментировать с подходами бэггинга и бустинга, однако из-за нехватки времени я их не реализовал.

Поскольку переобучить модель очень быстро и дешево, и я хочу гарантировать, что совпадения oob прогнозируются на основе модели с точностью 70%, я создам 200 моделей и сделаю среднее значение прогнозов, таким образом я предотвратю использование одна модель, которая может быть ниже или выше эталонного показателя 70%. Поэтому я уменьшаю дисперсию в пользу уменьшения смещения. Опять же, это тот выбор, который нужно сделать.

Кроме того, я воссоздал тот же процесс для создания дополнительных моделей: одну для ничьей и одну для победителя посетителя (хотя мы знаем, что это не влияет на то, является ли он местным или приезжим). Делая это, я мог предсказать вероятности каждого из 3 исходов: выигрыш, ничья или проигрыш.

После выполнения этого процесса я создаю следующую HTML-таблицу с прогнозами для группового этапа.

https://storage.googleapis.com/public_bucket2/index.html

Финальный ремак

И последнее замечание: математика, CS, DS предоставляют нам мощные инструменты для моделирования реальности, но если мы используем их также со знанием предметной области и интуицией, мы можем строить модели, которые предсказывают хорошие, простые для понимания, а также помогают нам понять более тонкие и сложные вещи (не на глаз) по каждой теме, в данном случае наш любимый вид спорта: футбол. Я надеюсь, что мое сообщение было доставлено, и надеюсь, что вы сможете развлечься (или заработать немного денег), используя мои прогнозы.