Predicción de resultados de fútbol | Parte 3

Antes que nada, agradecerte por haber llegado hasta aquí y por completar las entradas anteriores. Esta entrada es la ultima de una serie de tres, sobre como usar la Distribución de Poisson para predecir resultados o marcadores en X partido de futbol.

Como descubrimos en las entradas anteriores, un modelo simple de Poisson es un buen punto de partida y una buena forma intuitiva de aprender sobre el modelado estadístico. Para este post obtendremos nuestro set de datos haciendo uso del sitio football-data.co.uk y buscaremos los resultados de la temporada 2017-2018 en formato CSV.

Ahora vamos a importar nuestros paquetes:

import pandas as pd
import matplotlib.pyplot as plt
import numpy as np
import seaborn
from scipy.stats import poisson,skellam

Procedemos a leer nuestro set de datos:

lfp = pd.read_csv("http://www.football-data.co.uk/mmz4281/1718/SP1.csv")
lfp = epl[['HomeTeam','AwayTeam','FTHG','FTAG']]
lfp = epl.rename(columns={'FTHG': 'HomeGoals', 'FTAG': 'AwayGoals'})

Importamos un csv como marco de datos de pandas, que contiene información diversa para cada uno de los juegos de LFP en la temporada 2016-17 de la Liga Española. Restringimos el marco de datos a las columnas en las que estamos interesados (específicamente, los nombres de los equipos y el número de goles anotados por cada equipo. Nuestra tarea es modelar la ronda final de partidos en la temporada, por lo que debemos eliminar las últimas 10 filas

lfp = lfp[:-10]
lfp.mean()

Notará que, en promedio, el equipo local marca más goles que el equipo visitante. Esta es la llamada "ventaja de casa (campo)" y no solamente se relaciona al futbol. Tenga en cuenta que consideramos que la cantidad de goles marcados por cada equipo son eventos independientes (es decir, P (A n B) = P (A) P (B)). La diferencia de dos distribuciones de Poisson en realidad se llama una distribución de Skellam. Así que podemos calcular la probabilidad de un empate al ingresar los valores promedio de los objetivos en esta distribución.

# Probabilidad de empate
skellam.pmf(0.0,  lfp.mean()[0],  lfp.mean()[1])
# Probabilidad de local gane por 1 gol
skellam.pmf(1,  lfp.mean()[0],  lfp.mean()[1])

Ahora deberías estar convencido de que la cantidad de goles marcados por cada equipo puede ser aproximada por una distribución de Poisson. Ahora podríamos calcular la probabilidad de varios eventos en este partido. Pero en lugar de tratar cada coincidencia por separado, construiremos un modelo de regresión de Poisson más general.

import statsmodels.api as sm
import statsmodels.formula.api as smf
goal_model_data = pd.concat([lfp[['HomeTeam','AwayTeam','HomeGoals']].assign(home=1).rename(
            columns={'HomeTeam':'team', 'AwayTeam':'opponent','HomeGoals':'goals'}),
           lfp[['AwayTeam','HomeTeam','AwayGoals']].assign(home=0).rename(
            columns={'AwayTeam':'team', 'HomeTeam':'opponent','AwayGoals':'goals'})])
poisson_model = smf.glm(formula="goals ~ home + team + opponent", data=goal_model_data,
                        family=sm.families.Poisson()).fit()
poisson_model.summary()

Vamos a empezar a hacer algunas predicciones para los próximos partidos. Simplemente pasamos nuestros equipos a poisson_model y devolveremos el promedio de goles esperado para ese equipo.

poisson_model.predict(
  pd.DataFrame(
    data={
      'team': 'Barcelona',
      'opponent': 'Sociedad',
      'home': 1
    },
    index=[1]
  )
)

Necesitamos ejecutarlo dos veces: calculamos el número promedio esperado de goles para cada equipo por separado.

poisson_model.predict(
  pd.DataFrame(
    data={
      'team': 'Sociedad',
      'opponent': 'Barcelona',
      'home': 0
    },
    index=[1]
  )
)

Al igual que antes, tenemos dos distribuciones de Poisson. A partir de esto, podemos calcular la probabilidad de varios eventos. Envolveré esto en una función simulate_match.

def simulate_match(foot_model, homeTeam, awayTeam, max_goals=10):
    home_goals_avg = foot_model.predict(pd.DataFrame(data={'team': homeTeam,
                                                            'opponent': awayTeam,'home':1},
                                                      index=[1])).values[0]
    away_goals_avg = foot_model.predict(pd.DataFrame(data={'team': awayTeam,
                                                            'opponent': homeTeam,'home':0},
                                                      index=[1])).values[0]
    team_pred = [[poisson.pmf(i, team_avg) for i in range(0, max_goals+1)] for team_avg in [home_goals_avg, away_goals_avg]]
    return(np.outer(np.array(team_pred[0]), np.array(team_pred[1])))

simulate_match(poisson_model, 'Barcelona', 'Sociedad', max_goals=3)

Al ejecutar la función, esta nos retornará una matriz que simplemente muestra la probabilidad de que Barcelona (filas de la matriz) y Sociedad (columnas de matriz) anoten un número específico de goles. A lo largo de la diagonal, ambos equipos anotan el mismo número de goles.

Afortunadamente, podemos usar funciones básicas de manipulación de matrices para realizar estos cálculos.

bar_soc = simulate_match(poisson_model, "Barcelona", "Sociedad", max_goals=10)
# Victoria del Barcelona
np.sum(np.tril(bar_soc, -1))
# Empate
np.sum(np.diag(bar_soc))
# Victoria del Sociedad
np.sum(np.triu(bar_soc, 1))

Gracias por leer esta entrada, y si fue de tu agrado, no olvides compartir. Puedes leer aqui la parte 1 y aqui la parte 2.