# This Python 3 environment comes with many helpful analytics libraries installed
# It is defined by the kaggle/python Docker image: https://github.com/kaggle/docker-python
# For example, here's several helpful packages to load

import numpy as np # linear algebra
import pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)

# Input data files are available in the read-only "../input/" directory
# For example, running this (by clicking run or pressing Shift+Enter) will list all files under the input directory

import os
for dirname, _, filenames in os.walk('/kaggle/input'):
    for filename in filenames:
        print(os.path.join(dirname, filename))

# You can write up to 20GB to the current directory (/kaggle/working/) that gets preserved as output when you create a version using "Save & Run All" 
# You can also write temporary files to /kaggle/temp/, but they won't be saved outside of the current session
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
sns.set(style="ticks", color_codes=True)
plt.rcParams['figure.figsize'] = (8,5)
plt.rcParams['figure.dpi'] = 150
"""
# Prevendo resultados do Brasileirão

A ideia desse notebook é criar um modelo preditivo para jogos do campeonato brasileiro. Com apenas esse dataset, pretendo criar as features necessárias para testar alguns classificadores. Acredito que seja complicado obter bons resultados, até pelo paper que uso como base, onde um trabalho acadêmico conseguiu chegar apenas em 54% de taxa de acerto no campeonato holandês, mas de qualquer forma pode ser interessante para analisar a relação entre algumas features e o vencedor de partidas esportivas.

Além disso, é uma boa forma de testar minhas habilidades de manipulação de dataframe.

Usei como fonte de estudo para análise esportiva o paper 'Predicting The Dutch Football Competition Using Public Data: A Machine Learning Approach', de Niek Tax e Yme Joustra. Vou tentar aplicar um modelo de Feature Engineering parecido e testar se há muitas diferenças para modelos preditivos do futebol brasileiro para o holandês.

Pretendo testar os seguintes modelos: Logistic Regression, Naive Bayes Classifier, Multilayer Perceptron e Random Forest. A ideia é avaliá-los e comparar qual está tendo o melhor resultado pro futebol brasileiro.
"""
"""
# Limpeza dos Dados

Primeiro, vou selecionar apenas as colunas que vão ter alguma utilidade, retirando Horário e Dia. Também vou selecionar jogos do Brasileirão a partir de 2003, primeira temporada dos pontos corridos.
"""
df = pd.read_csv('/kaggle/input/campeonato-brasileiro-de-futebol/campeonato-brasileiro-full.csv')
df.head()
df.isnull().sum()
df.drop(['Horário', 'Dia'], axis=1, inplace = True)
df.head()
df.dtypes
df['Data'] = pd.to_datetime(df['Data'])
df = df[df['Data'].dt.year >= 2003]
"""
Outro ponto importante é trocar o nome do vencedor do jogo para 0 (vitória do mandate), 1 (vitória do visitante) ou 2 (empate)
"""
def change_winner_name(row):
    if (row['Vencedor'] == row['Clube 1']):
        return 0
    elif (row['Vencedor'] == row['Clube 2']):
        return 1
    else:
        return 2

df['Vencedor'] = df.apply(change_winner_name, axis=1)
df.drop(['Clube 1 Estado', 'Clube 2 Estado', 'Estado Clube Vencedor'], axis=1, inplace = True)
df.reset_index(drop=True, inplace = True)
df.head()
df['Rodada'] = [int(x.split('ª')[0]) for x in df['Rodada'].values]
df['Clube 1'] = [x.lower() for x in df['Clube 1'].values]
df['Clube 2'] = [x.lower() for x in df['Clube 2'].values]
df.head()
df.shape
"""
# Feature Engineering

O processo mais importante para obter um modelo viável será o de Feature Engineering. A ideia vai ser obter o máximo possível de features por meio desse dataset, e depois reduzir usando o PCA (para os modelos de Logistic Regression e Naive Bayes), ou usando os meios de redução dos próprios modelos (caso das árvores).
"""
"""
Como foi apresentado no paper, irei propor as seguintes features:

- número de vitórias do time na temporada
- número de derrotas do time na temporada
- número de empates do time na temporada
- número de vitórias do time em casa (%)
- número de vitórias do time fora de casa (%)
- número de empates do time em casa (%)
- número de empates do time fora de casa (%)
- número de derrotas do time em casa (%)
- número de derrotas do time fora de casa (%)
- sequência de vitórias em casa do time mandante
- sequência sem vitórias em casa do time mandante
- sequência de vitórias fora de casa do time visitante
- sequência sem vitórias fora de casa do time visitante
- o time jogou ou não a última temporada
- dias desde o último jogo 
- gols marcados essa temporada jogando em casa
- gols sofridos essa temporada jogando em casa
- gols marcados essa temporada jogando fora
- gols sofridos essa temporada jogando fora
- encontros entre os dois time em que o clube que está como mandante ganhou em qualquer estádio
- encontros entre os dois time em que o clube que está como visitante ganhou em qualquer estádio

Acredito que o modelo ficaria melhor com mais algumas features: dados especifícios de jogadores (autores de gols e assistências), informações sobre troca de comando técnico, informações sobre as distâncias entre as cidades e possivelmente dados de casas de apostas. Porém, vou focar no que pode ser obtido a partir desse dataset.

Também vou deixar a maioria das features separadas para jogos em casa e jogos fora, primeiro, a complexidade do código aumenta bastante ao unir ambos os dados. Também é válido separar para aumentar a participação do efeito mandante no modelo, que creio que seja de bastante impacto no futebol brasileiro.}

Para qualquer uma dessas features, é necessário fazer a análise por temporada. Por isso, a primeira coisa a se fazer é criar essa coluna:
"""
df['Temporada'] = df['Data'].dt.year
df['Temporada']
"""
## Número de vitórias, derrotas e empates

Essas 3 features precisam ser obtidas tanto para o time mandante quanto para o visitante.
"""
pd.options.mode.chained_assignment = None

temporadas = df['Temporada'].value_counts().index.sort_values().values

df['Wins Home Team'] = df['Clube 1']
df['Wins Away Team'] = df['Clube 1']

df['Loss Home Team'] = df['Clube 1']
df['Loss Away Team'] = df['Clube 1']

df['Draw Home Team'] = df['Clube 1']
df['Draw Away Team'] = df['Clube 1']

for temporada in  temporadas:
    dft = df[df['Temporada'] == temporada]
    
    for index, row in dft.iterrows():
        df_home = dft[(dft['Rodada'] < row['Rodada']) & (dft['Clube 1'] == row['Clube 1'])]
        c1_wins = 0
        c1_loss = 0
        c1_draw = 0
        for index2, row2 in df_home.iterrows():
            if row2['Vencedor'] == 0:
                c1_wins+=1
            elif row2['Vencedor'] == 1:
                c1_loss+=1
            elif row2['Vencedor'] == 2:
                c1_draw+=1

        df_away = dft[(dft['Rodada'] < row['Rodada']) & (dft['Clube 2'] == row['Clube 1'])]
        for index2, row2 in df_away.iterrows():
            if row2['Vencedor'] == 1:
                c1_wins+=1
            elif row2['Vencedor'] == 0:
                c1_loss+=1
            elif row2['Vencedor'] == 2:
                c1_draw+=1


        df_home = dft[(dft['Rodada'] < row['Rodada']) & (dft['Clube 1'] == row['Clube 2'])]
        c2_wins = 0
        c2_loss = 0
        c2_draw = 0
        for index2, row2 in df_home.iterrows():
            if row2['Vencedor'] == 0:
                c2_wins+=1
            elif row2['Vencedor'] == 1:
                c2_loss+=1
            elif row2['Vencedor'] == 2:
                c2_draw+=1

        df_away = dft[(dft['Rodada'] < row['Rodada']) & (dft['Clube 2'] == row['Clube 2'])]
        for index2, row2 in df_away.iterrows():
            if row2['Vencedor'] == 1:
                c2_wins+=1
            elif row2['Vencedor'] == 0:
                c2_loss+=1
            elif row2['Vencedor'] == 2:
                c2_draw+=1


        df.at[index, 'Wins Home Team'] = c1_wins
        df.at[index, 'Wins Away Team'] = c2_wins
        
        df.at[index, 'Loss Home Team'] = c1_loss
        df.at[index, 'Loss Away Team'] = c2_loss
        
        df.at[index, 'Draw Home Team'] = c1_draw
        df.at[index, 'Draw Away Team'] = c2_draw
df.tail()
"""
O número de vitórias por rodada está adicionado. É preciso notar que esse número é em relação a rodada anterior. Por exemplo, o São Paulo entrou na última rodada do brasileiro do ano passado com 16 vitórias, e terminou o campeonato com 17.
"""
"""
## Número de vitórias, derrotas e empates em casa e fora
"""
def find_wins_home(ser):
    wins = [0]
    
    [wins.append(wins[-1]+1) if win==0 else wins.append(wins[-1]) for win in ser.values]
    
    return wins[0:-1]

def find_wins_away(ser):
    wins = [0]
    
    [wins.append(wins[-1]+1) if win==1 else wins.append(wins[-1]) for win in ser.values]
    
    return wins[0:-1]

def find_draws(ser):
    wins = [0]
    
    [wins.append(wins[-1]+1) if win==2 else wins.append(wins[-1]) for win in ser.values]
    
    return wins[0:-1]

df['Wins Home Home Team'] = df.groupby(['Temporada', 'Clube 1'])['Vencedor'].transform(lambda x: find_wins_home(x))
df['Loss Home Home Team'] = df.groupby(['Temporada', 'Clube 1'])['Vencedor'].transform(lambda x: find_wins_away(x))
df['Wins Away Away Team'] = df.groupby(['Temporada', 'Clube 2'])['Vencedor'].transform(lambda x: find_wins_away(x))
df['Loss Away Away Team'] = df.groupby(['Temporada', 'Clube 2'])['Vencedor'].transform(lambda x: find_wins_home(x))
df['Draws Home Home Team'] = df.groupby(['Temporada', 'Clube 1'])['Vencedor'].transform(lambda x: find_draws(x)) 
df['Draws Away Away Team'] = df.groupby(['Temporada', 'Clube 2'])['Vencedor'].transform(lambda x: find_draws(x))
df.tail()
"""
## Sequência de vitórias

Novamente, é necessário que para ambos os casos seja feito a análise pro time da casa quanto para o time visitante. A ideia aqui é analisar os retrospectos do time mandante em casa, e do visitante fora de casa. 
"""
df['Home Wins'] = [1 if x == 0 else 0 for x in df['Vencedor'].values]
df['Away Wins'] = [1 if x == 1 else 0 for x in df['Vencedor'].values]
"""
A coluna 'Home Wins' foi inserida só para facilitar o processo de obter os _streaks_
"""
df.head()
def find_streak(ser):
    streak = [0]
    
    [streak.append(streak[-1]+1) if win==1 else streak.append(0) for win in ser.values]
    
    return streak[0:-1]

df['Home Winning Streak'] = df.groupby(['Temporada', 'Clube 1'])['Home Wins'].transform(lambda x: find_streak(x))
df['Home Losing Streak'] = df.groupby(['Temporada', 'Clube 1'])['Away Wins'].transform(lambda x: find_streak(x))
df['Away Winning Streak'] = df.groupby(['Temporada', 'Clube 2'])['Away Wins'].transform(lambda x: find_streak(x))
df['Away Losing Streak'] = df.groupby(['Temporada', 'Clube 2'])['Home Wins'].transform(lambda x: find_streak(x))
df.tail()
df.drop(['Home Wins', 'Away Wins'], inplace=True, axis=1)
df.head()
"""
As colunas foram adicionadas, basicamente o que elas querem dizer é:

- Home Winning Streak: vitórias seguidas em casa do time mandante
- Home Losing Streak: derrotas seguidas em casa do time mandante
- Away Winning Streak: vitórias seguidas fora de casa do time visitante
- Away Losing Streak: derrotas seguidas fora de casa do time visitante
"""
"""
## Gols marcados e sofridos

Aqui, farei da seguinte forma: computo todos os gols que o time marcou como visitante e como mandante.
"""
def find_goals(ser):
    goals = [0]
    
    [goals.append(goals[-1]+goal) for goal in ser.values]
    
    return goals[0:-1]
df['Goals Scored at Home'] = df.groupby(['Temporada', 'Clube 1'])['Clube 1 Gols'].transform(lambda x: find_goals(x))
df['Goals Conceded at Home'] = df.groupby(['Temporada', 'Clube 1'])['Clube 2 Gols'].transform(lambda x: find_goals(x))

df['Goals Scored Away'] = df.groupby(['Temporada', 'Clube 2'])['Clube 2 Gols'].transform(lambda x: find_goals(x))
df['Goals Conceded Away'] = df.groupby(['Temporada', 'Clube 2'])['Clube 1 Gols'].transform(lambda x: find_goals(x))
df.tail()
"""
## Dias desde o último jogo

Aqui, a ideia é analisar o cansaço. No Brasil, o ideal seria uma feature pegando a distância percorrida pelo clube, mas dada a dificulade e como minha ideia é usar apenas esse dataset, vou colocar só isso.

Como o brasileirão costuma começar na semana ou uma semana depois do estadual, irei colocar para todos os jogos de primeira rodada o intervalo de 5 dias.
"""
import math
temporadas = df['Temporada'].value_counts().index.sort_values().values


df['Days Between'] = 0
df['Days Between Away'] = 0


for temporada in temporadas:
    dft = df[df['Temporada'] == temporada]
    
    for index, row in dft.iterrows():
        dfc = dft[(dft['Data'] <= row['Data']) & ((dft['Clube 1'] == row['Clube 1']) | (dft['Clube 2'] == row['Clube 1']))]
        days_bet = ((dfc['Data'] - dfc['Data'].shift()).dt.days).values[-1]
        if math.isnan(days_bet):
            df.at[index, 'Days Between'] = 5
        else:
            df.at[index, 'Days Between'] = days_bet
            
        dfc = dft[(dft['Data'] <= row['Data']) & ((dft['Clube 1'] == row['Clube 2']) | (dft['Clube 2'] == row['Clube 2']))]
        days_bet_away = ((dfc['Data'] - dfc['Data'].shift()).dt.days).values[-1]
        if math.isnan(days_bet_away):
            df.at[index, 'Days Between Away'] = 5
        else:
            df.at[index, 'Days Between Away'] = days_bet_away
            
            
        
df.tail()
df.head()
"""
## Clubes promovidos

Essa feature visa obter os dados dos times que subiram ou não de divisão. Para todo time promovido nessa temporada (que não jogou temporada passada), essa coluna será igual a 1. Se jogou na Série A no ano anterior, essa coluna será igual a 0. 
"""
temporadas = df['Temporada'].value_counts().index.sort_values().values

df['Is Promoted'] = 0
df['Is Promoted Away'] = 0

for temporada in temporadas[1:]:
    dft = df[df['Temporada'] == temporada]
    
    dfw = df[df['Temporada'] == temporada-1]
    
    for index, row in dft.iterrows():
        last_year_clubs = dfw['Clube 1'].value_counts().index.sort_values().values
        
        home_club = row['Clube 1']
        if home_club in last_year_clubs:
            df.at[index, 'Is Promoted'] = 0
        else:
            df.at[index, 'Is Promoted'] = 1
        
        away_club = row['Clube 2']
        if away_club in last_year_clubs:
            df.at[index, 'Is Promoted Away'] = 0
        else:
            df.at[index, 'Is Promoted Away'] = 1
        
df.tail(20)
"""
## Confrontos diretos

Essa coluna terá as informações dos confrontos entre cada time disputados em qualquer temporada. A ideia é extrair vantagens históricas entre times.
"""
df.head()
df['Home Agnst'] = 0
df['Away Agnst'] = 0
df['Draws Agnst'] = 0

clubs = df['Clube 1'].value_counts().index.sort_values().values

for club1 in clubs:
    for club2 in clubs:
        dfc = df[((df['Clube 1'] == club1) & (df['Clube 2'] == club2)) | ((df['Clube 2'] == club1) & (df['Clube 1'] == club2))]
        win_home = 0
        win_away = 0
        draws = 0
        for index, row in dfc.iterrows():
            df.at[index, 'Home Agnst'] = win_home
            df.at[index, 'Away Agnst'] = win_away
            df.at[index, 'Draws Agnst'] = draws
            
            if row['Vencedor'] == 0:
                win_home +=1
            elif row['Vencedor'] == 1:
                win_away +=1
            else:
                draws+=1
            
            
            
df.tail()
"""
Essa feature também poderia levar a diferenciação entre os confrontos diretos em casa e fora, mas deixei assim para evitar muitos casos com zero (times que jogam a Série A pela primeira vez).

Agora, temos as features do modelo e podemos prosseguir com ele. Antes disso, irei analisar a relação das features com o alvo.
"""
"""
# Analisando as Features e criando o Modelo
"""
"""
Primeiro, irei fazer uma análise entre as features do modelo e o vencedor dos jogos.
"""
df.columns
df.shape
target = 'Vencedor'
features = list(df.columns.values[9:])
len(features)
df.dtypes
df[[target]+features] = df[[target]+features].astype(int)
"""
O boxplot de cada uma das features vai servir para verificar quais delas têm pontos muito fora da curva e quais são.
"""
ax = sns.boxplot(x="variable", y="value", data=pd.melt(df[features]))
ax.set_xticklabels(ax.get_xticklabels(),rotation=90)
plt.show()
cols = [target]+features

df_useful = df[cols]
corr = df_useful.corr(method='pearson')

sns.heatmap(corr, xticklabels=corr.columns, yticklabels=corr.columns)
plt.show()
"""
Sem dúvida, é um problema nenhuma coluna ter alta correlação com o resultado. Vamos analisar agora para o caso de gols marcados pelo visitante e mandante na partida.
"""
cols = ['Clube 1 Gols', 'Clube 2 Gols']+features

df_useful = df[cols]
corr = df_useful.corr(method='pearson')

sns.heatmap(corr, xticklabels=corr.columns, yticklabels=corr.columns)
plt.show()
"""
Também não há alta correlação entre as colunas de gols marcados e as features.

## Modelo 1: PCA + Naive Bayes Classifier
Vou aplicar, primeiramente, o modelo PCA para diminuir as dimensões do dataset. Isso permitirá uma análise melhor da base de dados, e também vai traduzir melhor as informações da partida para o modelo.
"""
from sklearn import preprocessing
from sklearn.decomposition import PCA
from sklearn.naive_bayes import GaussianNB
from sklearn.model_selection import train_test_split
X = df[features]
y = df[target]
scaler = preprocessing.StandardScaler()

scaler.fit(X)
X_scaled = scaler.transform(X)
pca = PCA(n_components=5)
pca.fit(X_scaled)
X_pca = pca.transform(X_scaled)

X_pca_df = pd.DataFrame(X_pca, columns=['V1', 'V2', 'V3', 'V4', 'V5'])
X_pca_df.tail()
sns.set(style='ticks', color_codes=True)
sns.pairplot(X_pca_df, kind='reg')
plt.show()
X_train, X_test, y_train, y_test = train_test_split(X_pca_df, y, random_state=0, test_size=0.3)

clf = GaussianNB()

clf.fit(X_train, y_train)
print('Train score: {:.3f}'.format(clf.score(X_train, y_train)))
print('Test score: {:.3f}'.format(clf.score(X_test, y_test)))
from sklearn.metrics import confusion_matrix

y_pred = clf.predict(X_test)

cm = confusion_matrix(y_test, y_pred)

df_cm = pd.DataFrame(cm, index=['Vitória Mandante', 'Vitória Visitante', 'Empate'],
                    columns=['Vitória Mandante', 'Vitória Visitante', 'Empate'])

sns.heatmap(df_cm, annot=True)
plt.show()
proba_df = pd.DataFrame(clf.predict_proba(X_test))
df_r = df.loc[X_test.index,['Clube 1', 'Clube 2', 'Vencedor']].reset_index(drop=True)

df_rp = df_r.merge(proba_df, left_index=True, right_index=True)
df_rp.tail()
"""
## Modelo 2: PCA + MLP
"""
from sklearn.neural_network import MLPClassifier

mlp = MLPClassifier(random_state=42)

mlp.fit(X_train, y_train)
print('Train score: {:.3f}'.format(mlp.score(X_train, y_train)))
print('Test score: {:.3f}'.format(mlp.score(X_test, y_test)))
y_pred = mlp.predict(X_test)

cm = confusion_matrix(y_test, y_pred)

df_cm = pd.DataFrame(cm, index=['Vitória Mandante', 'Vitória Visitante', 'Empate'],
                    columns=['Vitória Mandante', 'Vitória Visitante', 'Empate'])

sns.heatmap(df_cm, annot=True)
plt.show()
proba_df = pd.DataFrame(mlp.predict_proba(X_test))
df_r = df.loc[X_test.index,['Clube 1', 'Clube 2', 'Vencedor']].reset_index(drop=True)

df_rp = df_r.merge(proba_df, left_index=True, right_index=True)
df_rp.tail()
"""
## Modelo 3: PCA + LogReg
"""
pca = PCA(n_components=9)
pca.fit(X_scaled)
X_pca = pca.transform(X_scaled)

X_pca_df = pd.DataFrame(X_pca)

X_train, X_test, y_train, y_test = train_test_split(X_pca_df, y, random_state=0, test_size=0.3)
from sklearn.linear_model import LogisticRegression

logreg = LogisticRegression(C=100, max_iter=100000)

logreg.fit(X_train, y_train)
print('Train score: {:.3f}'.format(logreg.score(X_train, y_train)))
print('Test score: {:.3f}'.format(logreg.score(X_test, y_test)))
y_pred = logreg.predict(X_test)

cm = confusion_matrix(y_test, y_pred)

df_cm = pd.DataFrame(cm, index=['Vitória Mandante', 'Vitória Visitante', 'Empate'],
                    columns=['Vitória Mandante', 'Vitória Visitante', 'Empate'])

sns.heatmap(df_cm, annot=True)
plt.show()
proba_df = pd.DataFrame(logreg.predict_proba(X_test))
df_r = df.loc[X_test.index,['Clube 1', 'Clube 2', 'Vencedor']].reset_index(drop=True)

df_rp = df_r.merge(proba_df, left_index=True, right_index=True)
df_rp.tail()
"""
## Modelo 4: Random Forest
"""
X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=0, test_size=0.3)
from sklearn.ensemble import RandomForestClassifier

forest = RandomForestClassifier(max_features=5, n_estimators=500, random_state=0)

forest.fit(X_train, y_train)
print('Train score: {:.3f}'.format(forest.score(X_train, y_train)))
print('Test score: {:.3f}'.format(forest.score(X_test, y_test)))
y_pred = forest.predict(X_test)

cm = confusion_matrix(y_test, y_pred)

df_cm = pd.DataFrame(cm, index=['Vitória Mandante', 'Vitória Visitante', 'Empate'],
                    columns=['Vitória Mandante', 'Vitória Visitante', 'Empate'])

sns.heatmap(df_cm, annot=True)
plt.show()
proba_df = pd.DataFrame(forest.predict_proba(X_test))
df_r = df.loc[X_test.index,['Clube 1', 'Clube 2', 'Vencedor']].reset_index(drop=True)

df_rp = df_r.merge(proba_df, left_index=True, right_index=True)
df_rp.tail()
"""
# Conclusão

Todos os modelos testados têm um test score muito baixo. Possivelmente, com métricas de avaliação melhor, esse score possa aumentar um pouco, porém, mesmo assim, acho muito difícil que com esse dataset, até criando mais features, o modelo atinja mais de 55% de acertos no test dataset. O próprio paper que usei como base para esse notebook, atinge apenas 54% no futebol holandês (que me parece ser mais previsível que o brasileiro).

Acredito que a grande dificuldade de prever partidas de futebol esteja na presença do empate. Se a classificação for entre vitória do time da casa x não vitória do time da casa, todos os modelos melhoram muito em desempenho. 

Acho que a melhor aplicação desse trabalho é analisando as probabilidades que cada modelo dá, e comparando com as odds de casas de apostas para verificar o quanto eles estão na frente de simples modelos de ML.

Além disso, qualquer ajuda é bem-vinda. Se souberem alguma forma mais eficiente de treinar o modelo, ou qualquer feature que tenha maior impacto, por favor compartilhem!
"""