Probabilidade E Estatística Para Previsão de Resultados Esportivos
Tags: python, datascience, statistics, football
Published: 2020-08-11 20:13:42

### Considerações Iniciais
Usaremos conceitos matemáticos como *Valor Esperado* e *Distribuição de Probabilidade* nesse artigo, se você não conhece muito sobre esses conceitos, é possível que você ainda entenda tudo que está sendo feito no artigo, mas se você deseja aprender mais sobre os conteúdos, indico o site *Khan Academy* nos módulos sobre [distribuição de probabilidade](https://pt.khanacademy.org/math/statistics-probability/random-variables-stats-library/random-variables-discrete/v/discrete-probability-distribution) e [Média - Valor Esperado](https://pt.khanacademy.org/math/statistics-probability/random-variables-stats-library/random-variables-discrete/v/expected-value-of-a-discrete-random-variable), são vídeos curtos e bastante explicativos sobre o conteúdo.

## Introdução ao Projeto
Nesse projeto, vamos usar probabilidade e estatística para prever resultados de partidas de futebol. Para isso, vamos usar *Python* e sua biblioteca *Numpy*, acompanhados de conceitos de probabilidade e estatística.

Vamos realizar o seguinte processo, faremos a leitura de um arquivo contendo todos os resultados de partidas do AFC Ajax na liga holandesa de futebol (Eredivisie) na temporada de 18/19 e iremos, a cada rodada, realizar uma previsão de resultado da próxima partida do time, essa previsão consistirá na Esperança (Valor Esperado) de gols feitos pelo clube e na Esperança de gols sofridos.

### O Que Acontece Se Usarmos Adivinhação Com Valores Aleatórios?
Para futura comparação, analisaremos o que aconteceria se tentássemos adivinhar os resultados das partidas usando valores aleatórios.

Considerando que o Ajax pode marcar de 0 (número mínimo de gols marcados em uma partida nos nossos dados) a 8 (número máximo registrado pelo Ajax em uma partida nos nossos dados) gols, ou seja, um total de 9 possibilidades de resultados, e sofrer de 0 (mínimo registrado) a 6 (máximo registrado) gols, um total de 7 possibilidades. Temos que a probabilidade de acertar uma previsão de rodada é acertar o número de gols marcados e de gols sofridos, então escolhendo valores aleatórios no intervalo essa é.
{% katex %}
P(golsMarcados) = \frac{1}{9} \newline
P(golsSofridos) = \frac{1}{7} \newline
P(umaRodada) = P(golsMarcados) * P(golsSofridos) = \frac{1}{9}*  \frac{1}{7} =  \frac{1}{63} = 0,0159
{% endkatex %}

A liga holandesa possui 34 rodadas, não faremos previsão para a primeira rodada, pois não temos nenhum dado anterior que nos ajude a calcular uma previsão. Então, considerando que temos 33 rodadas para tentar acertar pelo menos um resultado, multiplicaremos 33 pela probabilidade de uma rodada, que nos fornece um valor de 0,5238 resultados certos. O que significa que sem artifícios de probabilidade, usando valores aleatórios, é esperado que acertemos o resultado de menos de uma rodada das 33 analisadas. Já para o número de gols marcados temos um valor esperado de acertos de 3,6667 (33 * 1/9) e para o de gols sofridos 4,7143 (33 * 1/7).

Vamos então tentar melhorar esses valores (que são **bem** baixos) usando matemática e programação.

## Implementação Do Projeto

Para criarmos nosso projeto, primeiro, vamos criar nosso arquivo de resultados, esse arquivo terá uma formatação específica e será escrito como:
```
golsmarcados,golssofridos
```
Por exemplo, se o Ajax marcou 4 gols e sofreu 2 em uma partida de uma rodada teremos no arquivo:
```
4,2
```
Esse arquivo será salvo como `resultados.txt`, e está disponível no [repositório do projeto](https://gitlab.com/veronicamars73/probabilidade-para-previsao-de-resultados/-/blob/master/resultados.txt).

Agora vamos começar a produzir nosso programa, iniciaremos importando as bibliotecas que usaremos.

```python
import numpy as np
```

Depois abriremos nosso arquivo de resultados.
```python
# Abrindo o arquivo com os resultados
"""
o arquivo está formatado da seguinte forma:
se o ajax ganhou fazendo 3 gols e sofrendo 1 - em casa ou fora de casa -
nosso arquivo registrará a rodada como:
3,1
"""
arquivo = open("resultados.txt", "r")
```

Após a abertura, vamos inserir o conteúdo do arquivo em uma lista chamada `resultado_rodadas` usando uma *list comprehension*, que é uma forma de definição, criação e manutenção de listas no python. Com essa ferramenta, podemos criar um iterador e preencher listas com uma só linha.

Já no fim da iteração, fecharemos o arquivo (`resultados.txt`) que foi aberto no início do nosso programa.

```python
# Inicializando nossa lista de resultados
resultado_rodadas = []

# O for vai percorrer cada linha do arquivo
for linha in arquivo:
    """
    A próxima linha irá adicionar os resultados na nossa lista,
    dentro dos parênteses temos uma 'list comprehension' que
    realiza o mesmo que o seguinte trecho de código:
    list = []
    for x in l.split(","):
        list.append(int(x))
    results.append(list)
    """
    resultado_rodadas.append([int(x) for x in linha.split(",")])

# Fecharemos o arquivo
arquivo.close()

```

Agora daremos início à análise dos dados obtidos. Primeiramente, inicializaremos algumas variáveis que armazenaram nossos dados formatados.

```python
# Agora iremos trabalhar com os dados que obtemos rodada a rodada

# Inicializaremos nossas listas de gols marcados e gols sofridos
gols_marcados = []
gols_sofridos = []

# Também calcularemos nosso número de acertos de resultados para controlarmos quão boa é nossa previsão
acertos_rodada = 0
acertos_gols_marcados = 0
acertos_gols_sofridos = 0
```
Iremos, então, percorrer toda a lista `resultado_rodadas` separando os valores que ela contém em gols marcados e sofridos e depois calculando o valor esperado de cada uma dessas categorias para calcularmos uma previsão para a próxima rodada.

Além disso, obteremos a frequência de cada número de gols, isto é, quantas vezes o time marcou 0 gol, 1 gol, 2 gols e assim por diante. Faremos o mesmo com os gols sofridos. Com a frequência de cada gol, vamos ter os dados necessário para calcular nosso valor esperado.

Por exemplo, podemos ter uma frequência como a representada no gráfico abaixo (*Essa não é a frequência real dos dados*).

![Gráfico de Exemplificação de como funcionará nosso dicionário de frequência](https://dev-to-uploads.s3.amazonaws.com/i/we3o542ka64unq3chfgk.png)
*Gráfico de Exemplificação de como funcionará nosso dicionário de frequência*

Para separar os gols escreveremos:

```python
"""
Percorreremos nossa lista de resultados por rodada e calcularemos
o valor esperado de gols marcados e sofridos para cada rodada faremos
uma previsão com esses valores e depois iremos conferir se esses valores 
correspondem ao resultado que ocorreu na rodada
"""
for rodada in range(len(resultado_rodadas)):
    gols_marcados.append(resultado_rodadas[rodada][0])
    gols_sofridos.append(resultado_rodadas[rodada][1])

    # Agora iremos obter a frequência do número de gols registrados até o momento
    num_gols, freq_num_gols = np.unique(gols_marcados, return_counts=True)
    # Por questão de organização transformaremos nossos valores em um dicionário do tipo 'gols':frequencia
    dic_gols_marcados = dict(zip(num_gols, freq_num_gols))

    # Faremos o mesmo com os gols sofridos
    num_gols, freq_num_gols = np.unique(gols_sofridos, return_counts=True)
    # Por questão de organização transformaremos nossos valores em um dicionário do tipo 'gols':frequencia
    dic_gols_sofridos = dict(zip(num_gols, freq_num_gols))
```

Após isso, calcularemos a esperança/valor esperado dos gols, isto é, os valores que são esperados na próxima rodada considerando os valores das rodadas anteriores. Para calcularmos tal valor iremos multiplicar todos os valores do dicionário (número de gols marcados) por sua probabilidade de ocorrência (Frequência dividida pelo número de rodadas).

```python
    # Agora calcularemos a esperança dos gols 
    esperanca_marcados=0
    for gol in dic_gols_marcados.keys():
        esperanca_marcados += gol*(dic_gols_marcados[gol]/len(gols_marcados))

    esperanca_sofridos=0 
    for gol in dic_gols_sofridos:
        esperanca_sofridos += gol*(dic_gols_sofridos[gol]/len(gols_sofridos))
```
Depois de calcular nossas esperanças, vamos realizar nossa previsão e compará-las com os resultados reais das rodadas para conferir se acertamos o resultado da partida, o número de gols marcados e o número de gols sofridos com nossa previsão.

```python
    # Depois de calcular nossas esperanças vamos imprimi-las e compará-las com os resultados reais

    # As próximas linhas arredondam nosso resultado para um valor inteiro
    esperanca_marcados = int(np.around(esperanca_marcados))
    esperanca_sofridos = int(np.around(esperanca_sofridos))

    # Se estivermos na última rodada já realizamos nossa previsão
    # na iteração anterior então devemos não devemos imprimir nossa esperança
    if (rodada+1 == len(resultado_rodadas)):
        break
    """
    Agora imprimiremos nosso valor esperado para a rodada que virá
    como listas têm posição inicial no número 0 temos que adicionar
    1 ao valor da rodada para conseguirmos a rodada que está sendo lida atualmente,
    ou seja, temos que adicionar 2 ao numero da `rodada`
    para conseguirmos o valor da próxima rodada.
    """
    print(f'Na {rodada+2} rodada esperamos um resultado de Ajax  {esperanca_marcados} x {esperanca_sofridos} adversário')
    print(f'Na {rodada+2} rodada obtemos um resultado de Ajax  {resultado_rodadas[rodada+1][0]} x {resultado_rodadas[rodada+1][1]} adversário')
    
    # Veremos se acertamos os resultados
    if(esperanca_marcados==resultado_rodadas[rodada+1][0] and esperanca_sofridos==resultado_rodadas[rodada+1][1]):
        acertos_rodada += 1
    if(esperanca_marcados==resultado_rodadas[rodada+1][0]):
        acertos_gols_marcados += 1
    if(esperanca_sofridos==resultado_rodadas[rodada+1][1]):
        acertos_gols_sofridos += 1
```

Depois da execução do for, iremos conferir nossa quantidade de acertos.
```python
# Imprimiremos nossa quantidade de acertos
print("Acertamos {0:1d} previsões de uma rodada completa, isto é, {1:2.2f}%".format(acertos_rodada, (acertos_rodada/33)*100))

print("Acertamos {0:1d} previsões de gols marcados, isto é, {1:2.2f}%".format(acertos_gols_marcados, (acertos_gols_marcados/33)*100))

print("Acertamos {0:1d} previsões de gols sofridos, isto é, {1:2.2f}%".format(acertos_gols_sofridos, (acertos_gols_sofridos/33)*100))
```
A saída do nosso programa será basicamente a seguinte
```text
> Na 2 rodada esperamos um resultado de Ajax  1 x 1 adversário
> Na 2 rodada obtemos um resultado de Ajax  1 x 0 adversário
...
> Na 34 rodada esperamos um resultado de Ajax  3 x 1 adversário
> Na 34 rodada obtemos um resultado de Ajax  4 x 1 adversário
> Acertamos 4 previsões de uma rodada completa, isto é, 12.12%
> Acertamos 7 previsões de gols marcados, isto é, 21.21%
> Acertamos 15 previsões de gols sofridos, isto é, 45.45%
```
Note que acertamos 4 resultados de uma rodada completa, 8 vezes mais que com o uso de valores aleatórios, 7 previsões de gols marcados, 2 vezes mais, e 15 previsões de gols sofridos, 3 vezes mais.

É perceptível que o uso de valor esperado ajudou **bastante** a melhorarmos nosso número de acertos de resultados. Isso representa o quanto conceitos simples de probabilidade, estatística e especialmente a matemática podem ser poderosos na análise de dados.

O programa desenvolvido nesse artigo está disponível no meu [repositório do gitlab](https://gitlab.com/veronicamars73/probabilidade-para-previsao-de-resultados). Espero ter ajudado, se você estiver com algum problema ou dúvida sinta-se convidado a comentar esse post ou [enviar um e-mail para mim](mailto:mendie73@gmail.com) ;).