Predicción de los resultados de fútbol para las clasificatorias sudamericanas para el mundial de Qatar 2022
En esta oportunidad, desarrollé una simple solución al problema de predicción de resultados de fútbol modelando los goles convertidos por las selecciones nacionales pertenecientes a Conmebol. Los registros se seleccionaron usando la técnica de Webscrapping y comprenden los resultados entre las clasificatorias para el mundial de Francia 1998 y Qatar 2022.
Nota del autor: Este modelo es una simple aplicación de modelos estadísticos, en la que no se realizó la validación de supuestos, y tiene un enfoque más académico. Hay técnicas más precisas que necesitan mayor información (posesión de balón, pases realizados,..)
A días de las últimos partidos de fútbol para las clasificatorias Conmebol para la copa del mundo de Qatar 2022, busqué una técnica sencilla para predecir los partidos de las últimas 2 fechas y el partido pendiente entre Brasil y Argentina. Para este análisis se utilizaron los resultados de todos los partidos clasificatorios desde el mundial de Francia 1998, siendo esta la primera clasificatoria en la que empezó el sistema de partidos de todos contra todos. El foco es modelar los goles convertidos mediante un modelo lineal general generalizado (GLM) asumiendo que tienen una distribución Poisson; usando como variables independientesel equipo los equipos jugadores y la situación de localia .
La extracción de información se realizó usando técnicas de Webscrapping. La información se obtuvo de la biblioteca libre Wikipedia s para las clasificatorias de los mundiales de Francia, 1998, Korea - Japon, 2002, Alemania, 2006, Sudafrica, 2010, Brazil, 2014, Rusia, 2018 y Qatar, 2022.
El script Scrapping_conmebol.py contiene los detalles de la extracción de información y tienen el siguiente formato.
import Scrapping_conmebol as scrap
database = scrap.database
database.sample(7)
date | World Cup Qualif | Team_home | Team_away | Goals_home | Goals_away |
---|---|---|---|---|---|
1997-04-02 | 1998 | Bolivia | Argentina | 2.0 | 1.0 |
2009-04-01 | 2010 | Chile | Uruguay | 0.0 | 0.0 |
2000-11-15 | 2002 | Paraguay | Peru | 5.0 | 1.0 |
2022-03-29 | 2022 | Venezuela | Colombia | NaN | NaN |
1997-07-20 | 1998 | Bolivia | Uruguay | 1.0 | 0.0 |
2001-03-28 | 2002 | Ecuador | Brazil | 1.0 | 0.0 |
2017-10-05 | 2018 | Venezuela | Uruguay | 0.0 | 0.0 |
No es necesaria una limpieza de datos, aunque es necesario extraer los 11 partidos que aún no se realizan.
date | World Cup Qualif | Team_home | Team_away | Goals_home | Goals_away |
---|---|---|---|---|---|
2021-09-05 | 2022 | Brazil | Argentina | NaN | NaN |
2022-03-24 | 2022 | Uruguay | Peru | NaN | NaN |
2022-03-24 | 2022 | Colombia | Bolivia | NaN | NaN |
2022-03-24 | 2022 | Brazil | Chile | NaN | NaN |
2022-03-24 | 2022 | Paraguay | Ecuador | NaN | NaN |
2022-03-25 | 2022 | Argentina | Venezuela | NaN | NaN |
2022-03-29 | 2022 | Peru | Paraguay | NaN | NaN |
2022-03-29 | 2022 | Venezuela | Colombia | NaN | NaN |
2022-03-29 | 2022 | Bolivia | Brazil | NaN | NaN |
2022-03-29 | 2022 | Chile | Uruguay | NaN | NaN |
2022-03-29 | 2022 | Ecuador | Argentina | NaN | NaN |
El objetivo es predecir estos partidos usando información de los partidos anteriores.
En el conjunto de datos existen 6 variables, y son las siguientes:
>>>
<class 'pandas.core.frame.DataFrame'>
Int64Index: 583 entries, 0 to 583
Data columns (total 6 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 date 583 non-null datetime64[ns]
1 World Cup Qualif 583 non-null int64
2 Team_home 583 non-null object
3 Team_away 583 non-null object
4 Goals_home 583 non-null float64
5 Goals_away 583 non-null float64
dtypes: datetime64[ns](1), float64(2), int64(1), object(2)
memory usage: 31.9+ KB
Existen 583 registros (partidos) que van a ser utilizados en la construcción del modelo. Antes del modelo vamos a analizar detalladamente los goles convertidos en los partidos que se encuentran en el período de análisis.
Goals_home | Goals_away | |
---|---|---|
count | 583.000000 | 583.000000 |
mean | 1.675815 | 0.943396 |
std | 1.395703 | 1.039703 |
min | 0.000000 | 0.000000 |
25% | 1.000000 | 0.000000 |
50% | 1.000000 | 1.000000 |
75% | 2.500000 | 1.000000 |
max | 6.000000 | 6.000000 |
Se puede concluir que en promedio el equipo de local convierte más goles que el equipo visitante. El máximo de goles convertidos es 6. Para mayor detalle se utiliza un gráfico de frecuencias para los goles tanto de visita como de local.
Se observa que menor a 2 goles, la mayor frecuencia está en los equipos visitantes, mientras que para una cantidad mayor a de 1 gol hay una frecuencia más favorable del equipo local.
Al ser un valor discreto con mayor frecuencia en los primeros valores y decayendo fuertemente a medida que la cantidad de goles convertidos aumenta, es plausible asumir que estos conllevan una distribución Poisson y mediante estimación de máxima verosimilitud, se estima el parámetro con el promedio de goles convertidos.
En el caso de los goles de local es estimó un y para los goles de visita es de
, y visualmente no se observa un mal ajuste de la distribución en ambos casos.
Por la característica de los datos, se va a utilizar la librería statsmodels
y utilizar un modelo lineal generalizado (GLM) con función de enlace Poisson
La instalación de las liberías necesarias para aplicar este modelo en Python tiene la sintaxis
import statsmodels.api as sm
import statsmodels.formula.api as smf
La intención no es modelar un muestra de entrenamiento y prueba, sino tratar de predecir una pequeña cantidad de partidos faltantes. Por lo mismo utilizaremos toda nuestra data para estimar los parámetros.
Se transforman los datos de la siguiente forma
goals = pd.concat([df[['Team_home','Team_away','Goals_home']].assign(Home=1).
rename(columns={'Team_home':'Team', 'Team_away':'Opponent','Goals_home':'goals'}),
df[['Team_home','Team_away','Goals_away']].assign(Home=0).
rename(columns={'Team_away':'Team', 'Team_home':'Opponent','Goals_away':'goals'})])
goals.sample(7)
>>>
Team Opponent Goals Home
Bolivia Ecuador 0 0
Uruguay Ecuador 0 0
Colombia Brazil 0 1
Peru Venezuela 1 0
Argentina Venezuela 3 0
donde Team es el equipo objetivo, Opponent es el equipo contrincante, Goals son los goles convertidos por Team y Home es la variable dummy que identifica si Team jugó de local (1) o visita (0).
El output en de la estimación del modelo es la siguiente
poisson_model = smf.glm(formula="goals ~ Home + Team + Opponent",
data=goals, family=sm.families.Poisson()).fit()
poisson_model.summary()
>>>
Generalized Linear Model Regression Results
Dep. Variable: goals No. Observations: 1166
Model: GLM Df Residuals: 1146
Model Family: Poisson Df Model: 19
Link Function: Log Scale: 1.0000
Method: IRLS Log-Likelihood: -1640.3
Date: Mon, 21 Mar 2022 Deviance: 1319.4
Time: 18:21:44 Pearson chi2: 1.17e+03
No. Iterations: 5 Pseudo R-squ. (CS): 0.2221
Covariance Type: nonrobust
coef std err z P>|z| [0.025 0.975]
Intercept -0.2571 0.130 -1.983 0.047 -0.511 -0.003
Home 0.5751 0.053 10.787 0.000 0.471 0.680
Team[T.Bolivia] -0.2707 0.112 -2.407 0.016 -0.491 -0.050
Team[T.Brazil] 0.1975 0.105 1.880 0.060 -0.008 0.404
Team[T.Chile] -0.0950 0.106 -0.899 0.369 -0.302 0.112
Team[T.Colombia] -0.2866 0.110 -2.597 0.009 -0.503 -0.070
Team[T.Ecuador] -0.1685 0.107 -1.572 0.116 -0.379 0.042
Team[T.Paraguay] -0.2856 0.111 -2.571 0.010 -0.503 -0.068
Team[T.Peru] -0.3964 0.115 -3.435 0.001 -0.623 -0.170
Team[T.Uruguay] -0.1424 0.107 -1.334 0.182 -0.352 0.067
Team[T.Venezuela] -0.4407 0.118 -3.733 0.000 -0.672 -0.209
Opponent[T.Bolivia] 0.7567 0.119 6.338 0.000 0.523 0.991
Opponent[T.Brazil] -0.2042 0.162 -1.260 0.208 -0.522 0.113
Opponent[T.Chile] 0.4241 0.127 3.339 0.001 0.175 0.673
Opponent[T.Colombia] 0.0572 0.136 0.420 0.675 -0.210 0.324
Opponent[T.Ecuador] 0.3121 0.129 2.413 0.016 0.059 0.566
Opponent[T.Paraguay] 0.3568 0.128 2.790 0.005 0.106 0.607
Opponent[T.Peru] 0.4972 0.124 4.001 0.000 0.254 0.741
Opponent[T.Uruguay] 0.3353 0.129 2.600 0.009 0.083 0.588
Opponent[T.Venezuela] 0.7269 0.119 6.085 0.000 0.493 0.961
De los tests individuales, podemos observar que con mayor fuerza los parámetros de Colombia y Brasil como visitante y Chile de local son los que con mayor fuerza tienen poca significación en el modelo (mayores p-valores). también observamos el test de Wald para la validación de cada parámetro.
poisson_model.wald_test_terms()
>>>
<class 'statsmodels.stats.contrast.WaldTestResults'>
chi2 P>chi2 df constraint
Intercept 3.9311 0.04740 1
Team 49.5467 1.311e-07 9
Opponent 107.8602 4.007e-19 9
home 116.3672 3.950e-27 1
Con un nivel de 5% rechazamos que todos los parámetros asociados al modelo de regresión tiene un parámetro significativamente nulo. Con esta validación, se procede a la predición de los partidos faltantes.
Para ls predicción, vamos a rellenar los vacios nulos de nuestros conjunto de datos database
definiendo la función match_results
; en este proceso la predicción se redondea a su entero más cercano. Para los partidos restantes tenemos los resultados.
def match_results(Team, Opponent):
home_res = np.round(poisson_model.predict(
pd.DataFrame(data={'Team': Team, 'Opponent': Opponent,'home':1},index=[1])),0)
away_res = np.round(poisson_model.predict(
pd.DataFrame(data={'Team': Opponent, 'Opponent': Team,'home':0},index=[1])),0)
database.loc[(database.Team_home==Team)&(database.Team_away==Opponent)&
(database.index.isin(index_na)),'Goals_home'] = home_res.values
database.loc[(database.Team_home==Team)&(database.Team_away==Opponent)&
(database.index.isin(index_na)),'Goals_away'] = away_res.values
return print(f'{Team} {home_res.values[0]:.0f} - {away_res.values[0]:.0f} {Opponent}')
match_results('Brazil', 'Argentina')
match_results('Uruguay', 'Peru')
match_results('Colombia', 'Bolivia')
match_results('Brazil', 'Chile')
match_results('Paraguay', 'Ecuador')
match_results('Argentina', 'Venezuela')
match_results('Peru', 'Paraguay')
match_results('Venezuela', 'Colombia')
match_results('Bolivia', 'Brazil')
match_results('Chile', 'Uruguay')
match_results('Ecuador', 'Argentina')
>>>
Brazil 2 - 1 Argentina
Uruguay 2 - 1 Peru
Colombia 2 - 1 Bolivia
Brazil 3 - 1 Chile
Paraguay 1 - 1 Ecuador
Argentina 3 - 0 Venezuela
Peru 1 - 1 Paraguay
Venezuela 1 - 1 Colombia
Bolivia 1 - 2 Brazil
Chile 2 - 1 Uruguay
Ecuador 1 - 1 Argentina
De los resultados más atractivos, tenemos que Uruguay vence a Perú como local, Ecuador empata con Paraguay de visita, Perú empata con Paraguay de local y Chile vence a Uruguay en condición de local.
Con la predicción de todos los resultados de las clasificatorias para Qatar 2022, podemos rearmar el cuadro de posiciones para determinar que equipos clasifican al mundial de fútbol.
Pos | Team | Points | Goals dif |
---|---|---|---|
1 | Brazil | 48 | 31 |
2 | Argentina | 39 | 18 |
3 | Ecuador | 27 | 10 |
4 | Uruguay | 25 | -3 |
5 | Chile | 22 | -2 |
6 | Peru | 22 | -5 |
7 | Colombia | 21 | -2 |
8 | Bolivia | 15 | -14 |
9 | Paraguay | 15 | -14 |
10 | Venezuela | 11 | -19 |
Tenemos que Brasil, Argentina, Ecuador y Uruguay clasifican de manera directa al mundial, quedando Chile en el repechaje. Se observa que tanto Chile como Perú tienen la misma cantidad de puntos, aunque Chile tiene una diferencia de goles mayor, siendo esto determinante para obtener el 5° lugar.
Una alternativa de análisis es dar mayor importancia a los registros más actuales, ponderando pesos mayores a los partidos de la actual clasificatoria y menos importancia a los partidos más antiguos.