Skip to content

Árvore de Decisão

Introdução

O objetivo deste roteiro é utilizar as bibliotecas pandas, numpy, matplotlib e scikit-learn, além da base de dados escolhida no Kagle, para treinar e avaliar um algoritmo de árvore de decisão.
A proposta é verificar como variáveis como estádio, público, posse de bola, passes e chances criadas influenciam no resultado final da partida (class), permitindo explorar o uso de modelos de Machine Learning no contexto esportivo.

Base de dados

A base de dados utilizada neste projeto contém informações de partidas de futebol, totalizando 1140 linhas e 40 colunas. Entre as variáveis estão posse de bola, número de passes e chances criadas.
A variável alvo escolhida é a coluna class, que indica o resultado da partida (vitória do mandante, empate ou vitória do visitante), sendo o objeto da classificação pelo algoritmo de árvore de decisão.

Exploração dos Dados

A seguir foi feita uma análise do significado e composição de cada coluna presente na base, com a finalidade de identificar possíveis problemas a serem tratados posteriormente. As visualizações e estatísticas descritivas ajudam a compreender a natureza dos dados e orientar decisões de pré-processamento e modelagem.

A coluna class é a variável alvo do projeto e representa o resultado da partida: vitória do mandante ("h"), empate ("d") ou vitória do visitante ("a"). Trata-se de uma variável categórica com três possíveis valores, sendo o objeto de classificação do modelo de árvore de decisão. A análise exploratória dessa coluna é essencial para observar o balanceamento do conjunto de dados, isto é, se há proporções semelhantes ou discrepantes entre os resultados possíveis.

2025-10-28T14:01:06.406686 image/svg+xml Matplotlib v3.10.7, https://matplotlib.org/

A coluna attendance representa o público presente em cada partida, um indicador de contexto do jogo que pode refletir fatores como mando de campo, relevância do confronto e engajamento da torcida. Em bases de futebol, o público tende a variar bastante entre estádios e rodadas, podendo apresentar assimetria (jogos muito cheios em arenas grandes) e valores atípicos (clássicos, finais).

Do ponto de vista analítico, é uma variável contínua útil para observar a distribuição de torcedores ao longo das partidas e investigar relações com o resultado (class).

2025-10-28T14:01:06.480373 image/svg+xml Matplotlib v3.10.7, https://matplotlib.org/

A coluna stadium identifica o estádio onde a partida foi disputada. Trata-se de uma variável categórica, associada ao contexto e à capacidade do local, podendo refletir fatores como mando de campo, perfil da torcida, relevo do gramado e até particularidades logísticas. Na análise exploratória, é útil observar a frequência de jogos por estádio, verificando balanceamento da amostra (quais arenas têm mais/menos partidas) e possíveis vieses (ex.: concentração em poucos estádios). Relações mais profundas com o resultado (class) podem ser investigadas em etapas seguintes, mas aqui focamos em entender a composição dessa variável no conjunto de dados.

2025-10-28T14:01:06.665323 image/svg+xml Matplotlib v3.10.7, https://matplotlib.org/

A coluna home_possessions representa a porcentagem de posse de bola do time mandante em cada partida. Trata-se de uma variável numérica contínua, normalmente variando entre 30% e 70% na maioria dos jogos, podendo indicar estilos de jogo (times que mantêm a bola ou que jogam mais reativamente). A análise exploratória dessa variável permite observar a distribuição da posse de bola entre os mandantes, identificar valores atípicos e comparar a média do controle de jogo ao longo das rodadas. Posteriormente, poderá ser interessante relacionar essa posse com o resultado final (class) para verificar padrões.

2025-10-28T14:01:06.847952 image/svg+xml Matplotlib v3.10.7, https://matplotlib.org/

A coluna away_possessions representa a porcentagem de posse de bola do time visitante em cada partida. Por ser uma variável numérica contínua, sua distribuição ajuda a observar o comportamento dos visitantes em termos de controle de jogo, identificar valores atípicos e comparar a tendência média de posse fora de casa. Em etapas posteriores, pode ser relacionada ao resultado (class) para investigar padrões de desempenho como visitante.

2025-10-28T14:01:06.984253 image/svg+xml Matplotlib v3.10.7, https://matplotlib.org/

A coluna Home Team identifica o time mandante na partida. Embora esteja codificada numericamente no dataset, sua natureza é categórica (IDs de times). Na análise exploratória, é útil observar a frequência de jogos por mandante, verificando o balanceamento da amostra entre os times que atuam em casa e possíveis concentrações. Relações com o resultado (class) podem ser exploradas depois; aqui focamos em entender a composição dessa variável.

2025-10-28T14:01:07.148154 image/svg+xml Matplotlib v3.10.7, https://matplotlib.org/

A coluna Away Team identifica o time visitante na partida. Apesar de codificada como número, sua natureza é categórica (IDs de times). Na análise exploratória, observar a frequência de jogos por visitante ajuda a avaliar o balanceamento da amostra e possíveis concentrações de partidas em determinados clubes.

2025-10-28T14:01:07.335485 image/svg+xml Matplotlib v3.10.7, https://matplotlib.org/

A coluna home_pass indica o número de passes realizados pelo time mandante em cada partida. É uma variável numérica contínua que ajuda a caracterizar o estilo de jogo do mandante, podendo variar bastante entre equipes mais ou menos dependentes da posse de bola. Na análise exploratória, observar a distribuição dos passes permite identificar médias, dispersão e valores atípicos.

2025-10-28T14:01:07.493622 image/svg+xml Matplotlib v3.10.7, https://matplotlib.org/

A coluna away_pass indica o número de passes realizados pelo time visitante em cada partida. Sendo uma variável numérica contínua, sua distribuição mostra como os visitantes se comportam em termos de construção de jogadas e controle de posse fora de casa. A análise exploratória ajuda a entender a média de passes, variações entre os jogos e eventuais valores extremos.

2025-10-28T14:01:07.682552 image/svg+xml Matplotlib v3.10.7, https://matplotlib.org/

A coluna home_chances representa a quantidade de chances de gol criadas pelo time mandante durante a partida. É uma variável numérica discreta que indica o nível de ofensividade da equipe jogando em casa. A análise exploratória permite identificar a frequência de jogos com poucas ou muitas oportunidades e verificar a dispersão desse tipo de estatística.

2025-10-28T14:01:07.816271 image/svg+xml Matplotlib v3.10.7, https://matplotlib.org/

A coluna away_chances indica a quantidade de chances de gol criadas pelo time visitante durante a partida. É uma variável numérica discreta que ajuda a compreender a ofensividade dos times jogando fora de casa. A análise exploratória mostra como os visitantes se comportam em termos de criação de oportunidades, permitindo identificar padrões de equilíbrio ou diferenças marcantes em relação aos mandantes.

2025-10-28T14:01:07.952453 image/svg+xml Matplotlib v3.10.7, https://matplotlib.org/

Pré-processamento

Após a exploração inicial da base, foram aplicados procedimentos de pré-processamento para preparar os dados para o treinamento do modelo.
Entre as etapas realizadas estão:

  • Conversão de tipos: variáveis originalmente em texto com valores numéricos (como attendance) foram transformadas em formato numérico.
  • Tratamento de valores categóricos: colunas como stadium, Home Team e Away Team foram mantidas como categóricas, sendo posteriormente convertidas em variáveis numéricas por meio de técnicas de codificação.
  • Remoção de colunas irrelevantes ou redundantes: colunas de identificação e de tempo (date, clock, links), bem como estatísticas que representam vazamento de informação do resultado (ex.: Goals Home, Away Goals), foram descartadas do conjunto de treino.
  • Separação entre features e target: as variáveis explicativas (X) foram definidas a partir de aproximadamente dez colunas relevantes da base, enquanto a variável alvo (y) é a coluna class.
  • Divisão em treino e teste: o conjunto de dados foi dividido em duas partes, garantindo estratificação do alvo para manter o equilíbrio das classes.

Com essas etapas, os dados foram organizados de forma consistente, reduzindo ruídos e preparando a base para a etapa seguinte: o treinamento do modelo de árvore de decisão.

stadium class attendance Home Team Away Team home_possessions away_possessions home_pass away_pass home_chances away_chances
6 1 36173 19 14 63.3 36.7 79.4 62.5 1 0
7 1 59475 2 11 53.6 46.4 87.1 84.6 1 0
24 1 41830 7 1 27.7 72.3 70.7 88.2 0 4
25 0 10305 15 10 42.7 57.3 76.6 85.5 2 0
16 1 30157 20 8 50.4 49.6 84.9 83.8 2 2
12 2 34624 13 2 37.4 62.6 79.6 89.3 0 1
11 1 0 14 11 51.9 48.1 85.6 77.5 0 2
12 1 0 13 8 54.8 45.2 85.6 78.8 1 0
24 2 41400 7 1 36 64 80.8 89.5 1 1
11 1 59949 14 4 47 53 75.8 78.7 1 1
6 2 36405 19 4 63 37 79 67.1 1 1
import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn import tree
from sklearn.metrics import accuracy_score
import matplotlib.pyplot as plt
from io import StringIO
from sklearn.preprocessing import LabelEncoder

label_encoder = LabelEncoder()

df = pd.read_csv("./src/mydata.csv")

# Excluir as colunas não desejadas
df = df.drop(columns= ["date", "clock", "links", "Goals Home", "Away Goals", "home_shots", "away_shots", "home_on", "away_on",
                        "home_off", "away_off", "home_blocked", "away_blocked", "home_corners", "away_corners", 
                        "home_offside", "away_offside", "home_tackles", "away_tackles", "home_duels", "away_duels",
                        "home_saves", "away_saves", "home_fouls", "away_fouls", "home_yellow", "away_yellow",
                        "home_red", "away_red"])

# Label encoding dos estadios em texto
df["stadium"] = label_encoder.fit_transform(df["stadium"])

# Transformar resultado do jogo times em números
df["class"] = df["class"].replace({'h':0, 'd': 1, 'a':2})

# Transforma o públido de string para número
df["attendance"] = df["attendance"].str.replace(',', '').astype(int)

print(df.sample(frac=.01).to_markdown(index=False))
date clock stadium class attendance Home Team Goals Home Away Team Away Goals home_possessions away_possessions home_shots away_shots home_on away_on home_off away_off home_blocked away_blocked home_pass away_pass home_chances away_chances home_corners away_corners home_offside away_offside home_tackles away_tackles home_duels away_duels home_saves away_saves home_fouls away_fouls home_yellow away_yellow home_red away_red links
19th May 2022 8:00pm Villa Park d 40,468 7 1 23 1 71.3 28.7 22 10 9 5 7 2 6 3 90.1 68.4 0 1 13 6 1 0 62.5 54.5 48.3 51.7 3 8 11 20 0 1 0 1 https://www.skysports.com/football/aston-villa-vs-burnley/stats/446458
18th October 2021 8:00pm Emirates Stadium d 59,475 2 2 11 2 53.6 46.4 17 9 6 6 7 3 4 0 87.1 84.6 1 0 6 4 0 3 52.9 72.7 28.6 71.4 4 4 7 4 1 1 0 0 https://www.skysports.com/football/arsenal-vs-crystal-palace/stats/446358
10th April 2022 4:30pm Etihad Stadium d 53,197 1 2 5 2 55.1 44.9 11 6 5 4 4 2 2 0 85.9 80.6 2 3 4 1 5 2 35.3 66.7 50 50 2 2 9 11 1 4 0 0 https://www.skysports.com/football/manchester-city-vs-liverpool/stats/446603
17/01/2021 7:15pm Etihad Stadium h 0 1 4 11 0 72.2 27.8 13 2 6 0 3 1 4 1 90.4 72.2 1 0 11 1 0 0 55.6 60 53.8 46.2 0 2 9 8 0 0 0 0 https://www.skysports.com/football/manchester-city-vs-crystal-palace/stats/429023
14th May 2023 4:30pm Emirates Stadium a 60,139 2 0 6 3 40.9 59.1 14 12 2 6 8 5 4 1 76.7 83.4 1 2 5 2 1 4 30 60 42.1 57.9 3 2 13 17 1 2 0 0 https://www.skysports.com/football/arsenal-vs-brighton-and-hove-albion/464985
6/12/2020 2:00pm Bramall Lane a 0 25 1 18 2 29.7 70.3 4 13 1 4 2 4 1 5 66.9 85.4 0 1 6 5 4 4 25 66.7 54.8 45.2 2 0 13 6 1 3 0 0 https://www.skysports.com/football/sheffield-united-vs-leicester-city/stats/428943
25/04/2021 12:00pm Molineux a 0 13 0 23 4 59.2 40.8 12 14 2 7 5 6 5 1 77.8 71.7 0 3 8 5 2 2 68.8 66.7 47.6 52.4 3 2 8 9 1 2 0 0 https://www.skysports.com/football/wolverhampton-wanderers-vs-burnley/stats/429166
19th February 2023 2:00pm Old Trafford h 73,578 3 3 18 0 56.9 43.1 26 19 8 3 9 9 9 7 84.6 81.1 6 2 6 6 0 1 69.6 53.8 69.6 30.4 3 5 9 9 0 2 0 0 https://www.skysports.com/football/manchester-united-vs-leicester-city/464870
19th October 2022 7:30pm Anfield h 53,346 5 1 14 0 53.8 46.2 22 8 7 3 8 4 7 1 85.2 80.8 1 4 8 3 1 0 61.5 53.3 61.1 38.9 3 6 9 8 0 0 0 0 https://www.skysports.com/football/liverpool-vs-west-ham-united/464752
12th December 2021 4:30pm Selhurst Park h 24,066 11 3 17 1 61.2 38.8 17 12 6 6 5 1 6 5 83.4 70.3 1 1 8 2 1 0 80 64 65.9 34.1 5 3 15 14 0 2 0 0 https://www.skysports.com/football/crystal-palace-vs-everton/stats/446443
7th May 2022 5:30pm Amex Stadium h 31,637 6 4 3 0 42 58 17 15 6 5 7 7 4 3 78.6 84.6 3 1 7 6 3 3 81.8 84.6 40.9 59.1 5 1 13 9 0 2 0 0 https://www.skysports.com/football/brighton-and-hove-albion-vs-manchester-united/stats/446640

Divisão dos Dados

Com a base pré-processada, realizou-se a divisão entre conjuntos de treinamento e teste.
O objetivo dessa etapa é garantir que o modelo seja avaliado em dados que ele nunca viu durante o treinamento, permitindo uma medida mais confiável de sua capacidade de generalização.

Foi utilizada a função train_test_split da biblioteca scikit-learn, com os seguintes critérios:
- 70% dos dados destinados ao treinamento, para que o modelo aprenda os padrões da base;
- 30% dos dados destinados ao teste, para avaliar o desempenho em novos exemplos;
- Estratificação pelo alvo (class), garantindo que a proporção entre vitórias do mandante, empates e vitórias do visitante fosse mantida em ambos os conjuntos;
- Random State fixado, assegurando reprodutibilidade na divisão.

Dessa forma, o conjunto de treinamento foi usado para ajustar os parâmetros da árvore de decisão, enquanto o conjunto de teste serviu para medir a precisão e robustez do modelo.

import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn import tree
from sklearn.metrics import accuracy_score
import matplotlib.pyplot as plt
from io import StringIO
from sklearn.preprocessing import LabelEncoder

label_encoder = LabelEncoder()

df = pd.read_csv("./src/mydata.csv")

# Excluir as colunas não desejadas
df = df.drop(columns= ["date", "clock", "links", "Goals Home", "Away Goals", "home_shots", "away_shots", "home_on", "away_on",
                        "home_off", "away_off", "home_blocked", "away_blocked", "home_corners", "away_corners", 
                        "home_offside", "away_offside", "home_tackles", "away_tackles", "home_duels", "away_duels",
                        "home_saves", "away_saves", "home_fouls", "away_fouls", "home_yellow", "away_yellow",
                        "home_red", "away_red"])

# Label encoding dos estadios em texto
df["stadium"] = label_encoder.fit_transform(df["stadium"])

# Transformar resultado do jogo times em números
df["class"] = df["class"].replace({'h':0, 'd': 1, 'a':2})

# Transforma o públido de string para número
df["attendance"] = df["attendance"].str.replace(',', '').astype(int)

# Variáveis independentes (features)
x = df[[
    "stadium", "attendance",
    "Home Team", "Away Team",
    "home_possessions", "away_possessions",
    "home_pass", "away_pass",
    "home_chances", "away_chances"
]]

# Variável dependente (alvo)
y = df["class"]

x_train, x_test, y_train, y_test = train_test_split(x, y, test_size=0.3, random_state=27, stratify=y)

Treinamento do Modelo

Precisão da Validação: 0.47
Importância das Features:

Feature Importância
7 away_pass 0.160276
6 home_pass 0.152260
2 Home Team 0.116144
1 attendance 0.115863
9 away_chances 0.106053
3 Away Team 0.101649
8 home_chances 0.072009
4 home_possessions 0.069807
0 stadium 0.063873
5 away_possessions 0.042067
2025-10-28T14:01:09.767747 image/svg+xml Matplotlib v3.10.7, https://matplotlib.org/

import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn import tree
from sklearn.metrics import accuracy_score
import matplotlib.pyplot as plt
from io import StringIO
from sklearn.preprocessing import LabelEncoder

label_encoder = LabelEncoder()

df = pd.read_csv("./src/mydata.csv")

# Excluir as colunas não desejadas
df = df.drop(columns= ["date", "clock", "links", "Goals Home", "Away Goals", "home_shots", "away_shots", "home_on", "away_on",
                        "home_off", "away_off", "home_blocked", "away_blocked", "home_corners", "away_corners", 
                        "home_offside", "away_offside", "home_tackles", "away_tackles", "home_duels", "away_duels",
                        "home_saves", "away_saves", "home_fouls", "away_fouls", "home_yellow", "away_yellow",
                        "home_red", "away_red"])

# Label encoding dos estadios em texto
df["stadium"] = label_encoder.fit_transform(df["stadium"])

# Transformar resultado do jogo times em números
df["class"] = df["class"].replace({'h':0, 'd': 1, 'a':2})

# Transforma o públido de string para número
df["attendance"] = df["attendance"].str.replace(',', '').astype(int)

# Variáveis independentes (features)
x = df[[
    "stadium", "attendance",
    "Home Team", "Away Team",
    "home_possessions", "away_possessions",
    "home_pass", "away_pass",
    "home_chances", "away_chances"
]]

# Variável dependente (alvo)
y = df["class"]

x_train, x_test, y_train, y_test = train_test_split(x, y, test_size=0.3, random_state=27, stratify=y)

# Criar e treinar o modelo de árvore de decisão
classifier = tree.DecisionTreeClassifier()
classifier.fit(x_train, y_train)

# Avaliar o modelo
y_pred = classifier.predict(x_test)
accuracy = accuracy_score(y_test, y_pred)
print(f"Precisão da Validação: {accuracy:.2f}")

feature_importance = pd.DataFrame({
    'Feature': classifier.feature_names_in_,
    'Importância': classifier.feature_importances_
})
print("<br>Importância das Features:")
print(feature_importance.sort_values(by='Importância', ascending=False).to_html())

plt.figure(figsize=(20, 10))
tree.plot_tree(classifier, max_depth=5, fontsize=10)

# Para imprimir na página HTML
buffer = StringIO()
plt.savefig(buffer, format="svg")
print(buffer.getvalue())    

Avaliação do Modelo

Após o treinamento da árvore de decisão, o modelo foi avaliado no conjunto de teste, obtendo uma precisão (accuracy) de aproximadamente 0.48.
Esse valor mostra que o modelo acerta menos da metade das previsões, o que indica uma performance limitada.

Contudo, esse resultado deve ser entendido à luz da natureza do problema: prever o resultado de uma partida de futebol é uma tarefa de alta complexidade e incerteza.
Além das estatísticas presentes na base, fatores externos como clima, lesões, decisões de arbitragem, motivação individual e até elementos de acaso influenciam diretamente no desfecho do jogo.
Mesmo com acesso a uma base de dados mais ampla, a previsão exata de partidas continuaria sendo altamente incerta, pois o futebol não é uma ciência exata, mas um evento esportivo com variáveis muitas vezes imprevisíveis.

Algumas observações complementares ajudam a interpretar esse desempenho: - Baixa capacidade preditiva: a árvore não conseguiu capturar padrões fortes o suficiente para discriminar corretamente as três classes (h, d, a).
- Balanceamento do alvo: embora tenha sido aplicada estratificação, empates tendem a ser mais raros e difíceis de prever, prejudicando a acurácia global.
- Variáveis contextuais: as colunas usadas (como estádio, público, posse de bola, passes e chances) explicam parte do comportamento da partida, mas não determinam o resultado sozinhas.
- Complexidade inerente ao domínio: a imprevisibilidade faz parte da própria essência do futebol, o que limita naturalmente a capacidade de qualquer modelo estatístico atingir altas taxas de acerto.

Portanto, a precisão de 0.48 reflete tanto as limitações do modelo quanto a complexidade do fenômeno analisado. O exercício é válido não apenas para medir desempenho, mas para evidenciar os desafios de aplicar técnicas de Machine Learning em contextos de elevada incerteza como o esporte.

Relatório Final

O projeto teve como objetivo aplicar um algoritmo de árvore de decisão sobre uma base de partidas de futebol, explorando o uso de técnicas de Machine Learning em um contexto esportivo.

A análise foi conduzida em etapas bem definidas:

  • Exploração dos Dados (EDA): foram selecionadas e analisadas cerca de dez variáveis relevantes, entre elas stadium, attendance, Home Team, Away Team, home_possessions, away_possessions, home_pass, away_pass, home_chances e away_chances. A partir de visualizações e estatísticas descritivas, foi possível compreender melhor a natureza de cada coluna e avaliar o balanceamento da variável alvo class, que representa o resultado da partida.

  • Pré-processamento: nesta fase, colunas de identificação e tempo (date, clock, links), bem como estatísticas diretamente ligadas ao resultado (como gols, finalizações e cartões), foram removidas para evitar vazamento de informação. Além disso, foram realizadas conversões de tipos e preparação de variáveis categóricas para futura codificação numérica.

  • Divisão dos Dados: o conjunto foi separado em treino (70%) e teste (30%), com estratificação do alvo para preservar a proporção entre vitórias do mandante, empates e vitórias do visitante. Essa divisão assegurou reprodutibilidade e uma avaliação justa do modelo.

  • Treinamento e Avaliação: a árvore de decisão foi treinada com os dados de treino e avaliada no conjunto de teste. O modelo alcançou uma acurácia de aproximadamente 0.48, valor que indica desempenho limitado, mas esperado dentro do contexto.

A avaliação evidenciou alguns pontos importantes: - O modelo não conseguiu capturar padrões suficientemente fortes, refletindo a alta complexidade da tarefa.
- Empates, por serem menos frequentes, foram mais difíceis de prever, prejudicando a performance global.
- As variáveis selecionadas (contexto e estatísticas gerais) ajudam a caracterizar as partidas, mas não são determinantes para o resultado final.
- O futebol, por sua própria natureza, é altamente imprevisível e sujeito a variáveis externas que não estão presentes no dataset, como clima, arbitragem, lesões e fatores psicológicos.

Conclusão

A experiência demonstrou que, embora seja possível aplicar técnicas de Machine Learning ao domínio esportivo, os resultados precisam ser interpretados com cautela. O desempenho de 0.48 de acurácia não deve ser visto apenas como limitação do modelo, mas também como reflexo da complexidade e da imprevisibilidade inerente ao futebol.

O projeto reforça a importância da análise exploratória, do cuidado no pré-processamento para evitar vazamento de dados e da avaliação crítica dos resultados. Além disso, abre espaço para trabalhos futuros que explorem variáveis adicionais — como forma recente dos times, desempenho de jogadores e fatores externos — e ajustes de hiperparâmetros para buscar melhorias no desempenho do modelo.

Mais do que prever com exatidão os resultados, este trabalho mostra o potencial e os limites do uso de algoritmos de aprendizado de máquina em contextos reais, nos quais a incerteza e a imprevisibilidade são componentes inevitáveis.