Новый год приносит много радостей; одним из главных из которых является плей-офф НФЛ. Тем не менее, конец регулярного сезона знаменует собой конец сезона фэнтези-футбола, конец веселых мемов и конец болтовни фэнтези-лиги до сентября. В этом году в качестве менеджера лиги (лиги, не входящей в PPR) я постарался сделать все более запоминающимся. Я решил оживить ситуацию наилучшим из известных мне способов: наукой о данных.

Решения о составе сложны для любого фэнтези-футболиста. Хотя я больше, чем обычный футбольный фанат, я также думаю об ожидаемой ценности на основе данных. Я не претендую на то, чтобы обыграть фондовый рынок или знать больше, чем букмекеры в Вегасе, поэтому я также не претендую на то, чтобы знать больше, чем прогнозируемый ESPN счет в фэнтези-футболе. Поэтому, когда я принимаю решения о своем составе, я стремлюсь максимизировать прогнозируемый результат. Тем не менее, в этом году я стартовал в лиге с трудным счетом 1–4. Я начал сомневаться в своей стратегии полагаться на прогнозы ESPN.

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

Данные

Я использую данные из моей собственной лиги фэнтези-футбола. У нас была лига из 10 команд, а сезон длился 18 недель. Итак, есть 180 прогнозируемых и фактических результатов со всей лиги. Ради анонимности я изменил названия команд на team_1, team_2, …, team_10. Кроме того, я относился к играм плей-офф так же, как к играм регулярного сезона. В нашей лиге каждая игра плей-офф длилась две недели, однако для анализа я рассматривал каждую неделю плей-офф как отдельную игру. Кроме того, наша лига не исправляла статистику приостановленной/отмененной игры Bills-Bengals с 17-й недели.

Мы можем прочитать набор данных, который я создал как фрейм данных pandas, и вернуть первые пять строк, используя функцию head(). Есть строки для недели сезона, команды, прогнозируемого результата команды, фактического результата команды, разницы между прогнозируемым и фактическим счетом и команды соперника. Вот как это выглядит:

import numpy as np
import pandas as pd
fantasy_football_df = pd.read_csv('./Fantasy_Football_Proj_Actual.csv')
fantasy_football_df.head()

Сравнение прогнозируемых и фактических показателей: подход с использованием регрессионной модели

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

Во-первых, давайте получим сводную статистику прогнозируемых и фактических оценок, а также разницу между ними:

fantasy_football_df.loc[:,['Projected', 'Actual', 'Delta_proj_minus_actual']].describe()

Сразу несколько важных выводов:

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

Используя plotly, мы можем визуализировать распределение данных:

import plotly.figure_factory as ff

# Group data together
hist_data = [fantasy_football_df['Projected'].values, fantasy_football_df['Actual'].values]

group_labels = ['Projected', 'Actual']

# Create distplot with custom bin_size
fig = ff.create_distplot(hist_data, group_labels, bin_size=5)
fig.show()

Рисунок невероятно информативен, потому что мы можем видеть гистограмму, непрерывное распределение и частотное распределение одновременно. Разница в форме распределения поразительна и подтверждает, что в фактических баллах фэнтези гораздо больше случайности, чем в прогнозируемых баллах. Кроме того, фактические баллы несколько ниже прогнозируемых. Разница в дисперсии распределения не должна удивлять игроков в фэнтези-футбол. Из недели в неделю игроки могут переиграть или получить травму на 5-й минуте игры и больше никогда не замечать падения игрового времени. В конце концов, именно поэтому ESPN также предоставляет статистику взлетов и падений для отдельных игроков. Предсказать, когда произойдут подъемы и спады, было бы намного сложнее, но в ожидаемом распределении совпадают довольно точно.

Используя пакет distfit в Python, мы можем подобрать и сравнить множество различных дистрибутивов, чтобы они соответствовали фактическим и прогнозируемым данным. (Подробнее об этом в моем сообщении здесь.) В этом сообщении в блоге я перейду к результатам. Предполагаемые результаты ESPN невероятно хорошо соответствуют логарифмическому распределению:

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

Мы также можем построить график разницы между прогнозируемой и фактической оценкой:

import plotly.figure_factory as ff

# Group data together
hist_data = [fantasy_football_df['Delta_proj_minus_actual'].values]

group_labels = ['Projected - Actual']

# Create distplot with custom bin_size
fig = ff.create_distplot(hist_data, group_labels, bin_size=5)
fig.show()

Здесь мы видим, что распределение смещено влево. В левой части графика есть несколько выбросов, обозначающих недели серьезной неуспеваемости. В целом, сообщение состоит в том, что прогнозируемая оценка кажется достойным предиктором для целей определения вашего состава, и ей можно доверять в пределах +/- 20-балльного диапазона.

Общие показатели точности регрессии

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

fantasy_football_df['Raw Error'] = fantasy_football_df['Delta_proj_minus_actual']

import plotly.express as px
import plotly.graph_objects as go
# Create figure
fig = go.Figure()
fig.add_trace(go.Scatter(x=np.linspace(50,200), y=np.linspace(50,200), mode='lines', line=dict(color="black"),
                         showlegend=False))
fig.add_trace(go.Scatter(x = fantasy_football_df['Actual'], y=fantasy_football_df['Projected'], name = 'Proj - Actual',
                         mode='markers',
                         marker=dict(size=8, color=fantasy_football_df['Raw Error'], colorscale='Jet', showscale=True)))
fig.update_layout(xaxis=go.layout.XAxis(title=go.layout.xaxis.Title(text="Actual")),
                 yaxis=go.layout.YAxis(title=go.layout.yaxis.Title(text="Projected")))

fig.show()

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

Теперь нам нужен способ количественной оценки точности модели прогнозируемых результатов ESPN по всем прогнозам. Существует несколько канонических показателей, включая среднеквадратичную ошибку (RMSE), среднюю абсолютную ошибку (MAE) и среднюю абсолютную ошибку в процентах (MAPE).

Мы можем легко рассчитать метрики ошибок и распечатать их на Python:

RMSE = round(np.sqrt(np.mean(fantasy_football_df['Raw Error']**2)), 2)
MAE = round(np.mean(fantasy_football_df['Absolute Error']), 2)
MAPE = round(np.mean(fantasy_football_df['Absolute Error']/fantasy_football_df['Actual'])*100, 2)
print('ESPN Projected Score Accuracy:\nRMSE = '+str(RMSE)+'\nMAE = '+str(MAE)+'\nMAPE = '+str(MAPE)+'%')

Эти показатели позволяют нам описывать производительность модели как в абсолютном, так и в относительном выражении. В то время как исходные гистограммы показывали среднюю ошибку около 2, они учитывали завышенные и заниженные прогнозы. Однако RMSE, MAE и MAPE одинаково обрабатывают завышенные и заниженные прогнозы. Например, средняя ошибка -10 и 10 будет равна 0, тогда как MAE или RMSE для -10 и 10 будет равна 10. Мы можем видеть, что в среднем проекционная модель ESPN имеет ошибку около 18–23 пунктов, что составляет примерно на 18% выше или ниже фактического результата.

Оценка ошибок по неделям

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

Используя блочную диаграмму, мы можем визуализировать квартили данных. Прямоугольник представляет собой межквартильный диапазон (25–75-й процентиль), а средняя линия каждого прямоугольника представляет собой медиану.

import plotly.express as px
import plotly.graph_objects as go
# Create figure
fig = px.box(fantasy_football_df, x="Week", y="Raw Error", color = 'Week')
fig.add_trace(go.Scatter(x = fantasy_football_df['Week'], y=np.zeros(len(fantasy_football_df['Week'])), mode='lines', line=dict(color="black"),
                         showlegend=False))
fig.show()

Есть несколько недель (2, 9, 12, 15), когда медианная разница между прогнозируемыми и фактическими оценками почти равна нулю. Между тем, такие недели, как 1, 3, 14, 17 и 18, показывают завышенный прогноз по лиге по модели ESPN, а 8-я неделя показывает сильное недооценивание. В течение большинства недель прогнозируемый результат лиги попадает в межквартильный диапазон фактического результата лиги (другими словами, черная линия на графике проходит через прямоугольник).

Оценка ошибки командой

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

import plotly.express as px
import plotly.graph_objects as go
# Create figure
fig = px.box(fantasy_football_df, x="Team", y="Raw Error", color = 'Team')
fig.add_trace(go.Scatter(x = fantasy_football_df['Team'], y=np.zeros(len(fantasy_football_df['Team'])), mode='lines', line=dict(color="black"),
                         showlegend=False))
fig.show()

Как видно из графика, была только одна команда, которая постоянно превосходила прогнозы: team_2. Эта команда стала окончательным чемпионом лиги. Самой предсказуемой командой была team_7, а наименее предсказуемой — team_3. Хотя прогнозируемый счет может быть важен для принятия решения о составе, в фэнтези-футболе в конечном счете важна победа.

Оценка прогнозируемой оценки ESPN как модели классификации

В фэнтези-футболе важнее всего победа. Косвенно прогнозируемый балл ESPN служит моделью классификации, сравнивая два прогнозируемых балла и предсказывая вероятного победителя. Поэтому мы воспользуемся этой возможностью, чтобы оценить прогнозы как модель бинарной классификации, предсказывающую W/L для каждой команды.

Во-первых, я начинаю заново в новой записной книжке и загружаю данные, добавляя новые фиктивные столбцы, чтобы отслеживать некоторые важные результаты типа Лас-Вегаса, включая прямой рост (S/U), измеренный путем сравнения прогнозируемого W/L с фактическим результатом W/L, против разброса (ATS) путем сравнения прогнозируемой разницы в очках между двумя командами в игре и фактической разницы в очках, а также больше/меньше (O/U) путем сравнения суммы прогнозируемых очков с суммой фактических оценки игры.

import numpy as np
import pandas as pd
fantasy_football_df = pd.read_csv('./Fantasy_Football_Proj_Actual.csv')

#Set up new columns
fantasy_football_df['Projected team_i_minus_team_j'] = 0
fantasy_football_df['Actual team_i_minus_team_j'] = 0
fantasy_football_df['Projected Result'] = 'L'
fantasy_football_df['Actual Result'] = 'L'
fantasy_football_df['ATS Result'] = 'L'
fantasy_football_df['Game Over Under'] = 'Under'

# For every row in the data, determine the result of the projected, actual, ATS, and O/U results of the game
for i in range(len(fantasy_football_df)):
    week = fantasy_football_df['Week'].values[i]
    
    #Get projected and actual score of first team
    proj_score_team_1 = fantasy_football_df['Projected'].values[i]
    actual_score_team_1 = fantasy_football_df['Actual'].values[i]
    
    #Get projected and actual score of opponent
    opponent = fantasy_football_df['Opponent'].values[i]
    proj_score_team_2 = fantasy_football_df.loc[(fantasy_football_df['Week'] == week) & (fantasy_football_df['Team'] == opponent), 'Projected'].values[0]
    actual_score_team_2 = fantasy_football_df.loc[(fantasy_football_df['Week'] == week) & (fantasy_football_df['Team'] == opponent), 'Actual'].values[0]
    
    #Determine the projected and actual results
    fantasy_football_df.loc[i, 'Projected team_i_minus_team_j'] = proj_score_team_1 - proj_score_team_2
    fantasy_football_df.loc[i, 'Actual team_i_minus_team_j'] = actual_score_team_1 - actual_score_team_2
    
    #Update labels
    # S/U
    if (proj_score_team_1 - proj_score_team_2) > 0:
        fantasy_football_df.loc[i, 'Projected Result'] = 'W'
        
    if (actual_score_team_1 - actual_score_team_2) > 0:
        fantasy_football_df.loc[i, 'Actual Result'] = 'W'
    
    #ATS
    if (proj_score_team_1 - proj_score_team_2) < (actual_score_team_1 - actual_score_team_2):
        fantasy_football_df.loc[i, 'ATS Result'] = 'W'
        
    #O/U
    if (proj_score_team_1 + proj_score_team_2) < (actual_score_team_1 + actual_score_team_2):
        fantasy_football_df.loc[i, 'Game Over Under'] = 'Over'
        
    if (proj_score_team_1 + proj_score_team_2) == (actual_score_team_1 + actual_score_team_2):
        fantasy_football_df.loc[i, 'Game Over Under'] = 'Line'

Теперь давайте сравним производительность каждой команды за сезон на простой гистограмме, используя график.

import plotly.graph_objects as go

fig = go.Figure(data=[
    go.Bar(name='Projected Wins', x = list(np.unique(fantasy_football_df['Team'])), 
           y = list(fantasy_football_df[fantasy_football_df['Projected Result'] == 'W'].groupby('Team').count()['Projected Result'].values)),
    go.Bar(name='Actual Wins', x = list(np.unique(fantasy_football_df['Team'])), 
           y = list(fantasy_football_df[fantasy_football_df['Actual Result'] == 'W'].groupby('Team').count()['Actual Result'].values)),
    go.Bar(name='ATS Wins', x = list(np.unique(fantasy_football_df['Team'])), 
           y = list(fantasy_football_df[fantasy_football_df['ATS Result'] == 'W'].groupby('Team').count()['ATS Result'].values)),
    go.Bar(name='Hits the Over', x = list(np.unique(fantasy_football_df['Team'])), 
           y = list(fantasy_football_df[fantasy_football_df['Game Over Under'] == 'Over'].groupby('Team').count()['Game Over Under'].values))
])
# Change the bar mode
fig.update_layout(barmode='group')
fig.add_trace(go.Scatter(x = list(np.unique(fantasy_football_df['Team'])), 
                         y=np.zeros(len(list(np.unique(fantasy_football_df['Team'])))) + 9, mode='lines', 
                         line=dict(color="black"), name = '0.500', showlegend=True))
fig.show()

Модель прогноза ESPN предсказывала, что только четыре команды пробьют 0,500: team_1, team_2, team_4 и team_10. Пять команд, преодолевших отметку 0,500, выглядят несколько иначе: team_2, team_3, team_4, team_5 и team_8. Таким образом, несмотря на относительно точный прогнозируемый результат, прогнозируемый результат игры был гораздо менее последовательным. Если вы играете в фэнтези, это должно быть разумным выводом, поскольку вы знаете, что каждую неделю разница между прогнозируемыми очками каждой команды (также известная как спред) обычно выражается однозначными числами. Только team_1, team_5, team_7 и team_8 имели выигрышные рекорды ATS. Тем не менее, три другие команды пошли 0,500 ATS. Наконец, как и следовало ожидать из нашего предыдущего анализа, попадание в овер было редкостью во всей лиге (за исключением team_2). Мы предвидели это, поскольку видели, что прогнозируемые результаты ESPN обычно превышали фактические результаты команд в лиге.

Общие показатели точности классификации

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

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

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

# Create lists to track W/L metrics
teams_list_for_df = ['team_1', 'team_2', 'team_3', 'team_4', 'team_5', 'team_6', 'team_7', 'team_8', 'team_9', 'team_10']#list(np.unique(fantasy_football_df['Team']))
projected_wins_list_for_df = []
projected_losses_list_for_df = []
actual_wins_list_for_df = []
actual_losses_list_for_df = []
ats_wins_list_for_df = []
ats_losses_list_for_df = []
over_list_for_df = []
under_list_for_df = []
tp_list_for_df = []
fp_list_for_df = []
tn_list_for_df = []
fn_list_for_df = []

# Loop over all teams
for team in teams_list_for_df:
    # Get number of projected wins
    projected_wins_list_for_df.append(np.unique(fantasy_football_df.loc[fantasy_football_df['Team'] == team, 'Projected Result'], return_counts = True)[1][1])
    # Get number of projected losses
    projected_losses_list_for_df.append(np.unique(fantasy_football_df.loc[fantasy_football_df['Team'] == team, 'Projected Result'], return_counts = True)[1][0])
    # Get number of actual wins
    actual_wins_list_for_df.append(np.unique(fantasy_football_df.loc[fantasy_football_df['Team'] == team, 'Actual Result'], return_counts = True)[1][1])
    # Get number of actual losses
    actual_losses_list_for_df.append(np.unique(fantasy_football_df.loc[fantasy_football_df['Team'] == team, 'Actual Result'], return_counts = True)[1][0])
    # Get number of ATS wins
    ats_wins_list_for_df.append(np.unique(fantasy_football_df.loc[fantasy_football_df['Team'] == team, 'ATS Result'], return_counts = True)[1][1])
    # Get number of ATS losses
    ats_losses_list_for_df.append(np.unique(fantasy_football_df.loc[fantasy_football_df['Team'] == team, 'ATS Result'], return_counts = True)[1][0])
    # Get number of games hitting Over
    over_list_for_df.append(np.unique(fantasy_football_df.loc[fantasy_football_df['Team'] == team, 'Game Over Under'], return_counts = True)[1][0])
    # Get number of games hitting Under
    under_list_for_df.append(np.unique(fantasy_football_df.loc[fantasy_football_df['Team'] == team, 'Game Over Under'], return_counts = True)[1][1])
    # Get number of projected Ws that are Ws
    tp_list_for_df.append(len(fantasy_football_df.loc[((fantasy_football_df['Projected Result'] == fantasy_football_df['Actual Result']) & (fantasy_football_df['Team'] == team) & (fantasy_football_df['Projected Result'] == 'W')), 'Projected Result']))
    # Get number of projected Ws that are Ls
    fp_list_for_df.append(len(fantasy_football_df.loc[((fantasy_football_df['Projected Result'] != fantasy_football_df['Actual Result']) & (fantasy_football_df['Team'] == team) & (fantasy_football_df['Projected Result'] == 'W')), 'Projected Result']))
    # Get number of projected Ls that are Ls
    tn_list_for_df.append(len(fantasy_football_df.loc[((fantasy_football_df['Projected Result'] == fantasy_football_df['Actual Result']) & (fantasy_football_df['Team'] == team) & (fantasy_football_df['Projected Result'] == 'L')), 'Projected Result']))
    # Get number of projected Ls that are Ws
    fn_list_for_df.append(len(fantasy_football_df.loc[((fantasy_football_df['Projected Result'] != fantasy_football_df['Actual Result']) & (fantasy_football_df['Team'] == team) & (fantasy_football_df['Projected Result'] == 'L')), 'Projected Result']))
    
# Create data frame
team_wins_df = pd.DataFrame(list(zip(teams_list_for_df, projected_wins_list_for_df, projected_losses_list_for_df,
                                    actual_wins_list_for_df, actual_losses_list_for_df, ats_wins_list_for_df,
                                    ats_losses_list_for_df, over_list_for_df, under_list_for_df,
                                    tp_list_for_df, fp_list_for_df, tn_list_for_df, fn_list_for_df)), 
                            columns = ['Team', 'Projected Wins', 'Projected Losses', 'Actual Wins', 'Actual Losses',
                                      'ATS Wins', 'ATS Losses', 'Games Over', 'Games Under', 'TP', 'FP', 'TN', 'FN'])
# Calculate Accuracy Metrics
team_wins_df['Accuracy'] = (team_wins_df['TP'] + team_wins_df['TN'])/(team_wins_df['TP'] + team_wins_df['TN'] + team_wins_df['FP'] + team_wins_df['FN'])
team_wins_df['Recall'] = (team_wins_df['TP'])/(team_wins_df['TP'] + team_wins_df['FN'])
team_wins_df['Specificity'] = (team_wins_df['TN'])/(team_wins_df['TN'] + team_wins_df['FP'])
team_wins_df['Precision'] = (team_wins_df['TP'])/(team_wins_df['TP'] + team_wins_df['FP'])
team_wins_df['F Score'] = (2*team_wins_df['TP'])/(2*team_wins_df['TP'] + team_wins_df['FP'] + team_wins_df['FN'])

Мы можем вывести полученный фрейм данных, чтобы увидеть данные всей лиги сразу.

В качестве альтернативы мы можем создать простую диаграмму рассеяния для упрощения визуализации:

import plotly.express as px
import plotly.graph_objects as go
# Create figure
fig = go.Figure()
# Add scatter plots to see how teams compare on different metrics
fig.add_trace(go.Scatter(x = team_wins_df['Team'], y=team_wins_df['Accuracy'], mode='markers', name = 'Accuracy',
                         marker=dict(size=8, color = 'black')))
fig.add_trace(go.Scatter(x = team_wins_df['Team'], y=team_wins_df['Recall'], mode='markers', name = 'Recall',
                         marker=dict(size=8, color = 'blue')))
fig.add_trace(go.Scatter(x = team_wins_df['Team'], y=team_wins_df['Specificity'], mode='markers', name = 'Specificity',
                         marker=dict(size=8, color = 'red')))
fig.add_trace(go.Scatter(x = team_wins_df['Team'], y=team_wins_df['Precision'], mode='markers', name = 'Precision',
                         marker=dict(size=8, color = 'green')))
fig.add_trace(go.Scatter(x = team_wins_df['Team'], y=team_wins_df['F Score'], mode='markers', name = 'F Score',
                         marker=dict(size=8, color = 'orange')))
fig.add_trace(go.Scatter(x = team_wins_df['Team'], y=np.zeros(len(team_wins_df['Team']))+0.5, mode='lines', line=dict(color="black"),
                         showlegend=False))

fig.show()

В целом мы видим, что метрики разнятся по всей лиге. Однако для всех, кроме team_3 и team_10, мы видим, что точность была лучше 50% и, следовательно, лучше, чем просто подбрасывание монеты. Это свидетельствует о полезности прогнозируемых результатов ESPN для прогнозирования результатов фэнтези-игр. Прогнозы работают хуже всего с точки зрения специфичности, а это означает, что прогноз проигрыша для большинства команд был наименее надежным.

Ограничения

Объем этого анализа был ограничен данными, которые я собрал в своей фэнтези-футбольной лиге, состоящей из 10 команд. Я подозреваю, что среди миллионов команд фэнтези-футбола в фэнтезийной вселенной ESPN прогнозируемый счет работает еще лучше в качестве регрессионной модели. Однако точность модели бинарной классификации была не такой хорошей. Он в значительной степени работал лучше, чем подбрасывание монеты по нескольким показателям, но недостаточно надежен, чтобы в течение недели чувствовать себя комфортно в прогнозируемом выигрыше. В соответствии с моделью ESPN приложение выводит вероятность выигрыша, а не двоичный вывод. Обычно вероятности победы между двумя командами довольно близки — 45–55%.

Выводы

Данные, по-видимому, предполагают, что прогнозы результатов фэнтези-футбола ESPN надежны в пределах примерно +/-15%, а прогнозы W/L чаще всего точны, чем нет. В таком случае я планирую продолжить свою стратегию максимизации прогнозируемого результата. У меня это может не всегда срабатывать, но в ожидании это отличный показатель.

Весь код и данные можно найти на GitHub здесь: gspmalloy/fantasy_football (github.com)

Подпишитесь на меня в Твиттере: @malloy_giovanni

Я хотел бы услышать от других менеджеров лиги. Это то, что вы планируете реализовать? Пожалуйста, прокомментируйте ниже свои мысли или опыт!