Skip to content

KNN

Introdução

O objetivo desta etapa foi aplicar o algoritmo K-Nearest Neighbors (KNN) sobre a base de dados de partidas de futebol, utilizando as bibliotecas pandas, numpy, matplotlib e scikit-learn.
Diferente da árvore de decisão, o KNN classifica novas observações com base na proximidade de exemplos já conhecidos.
A proposta é avaliar como variáveis como estádio, público, posse de bola, passes e chances criadas podem auxiliar na previsão do resultado da partida (class).
Foram desenvolvidas duas abordagens: uma implementação manual, para consolidar a compreensão do funcionamento do método, e outra com a biblioteca scikit-learn, permitindo comparação de resultados e visualização da fronteira de decisão por meio do PCA.

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).

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:13.230886 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:13.304750 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:13.487655 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:13.667832 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:13.802619 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:13.962736 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:14.148779 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:14.307195 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:14.437516 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:14.574685 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:14.709058 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.
stadium class attendance Home Team Away Team home_possessions away_possessions home_pass away_pass home_chances away_chances
8 0 52395 1 10 70.6 29.4 91.1 79.3 3 0
7 1 59475 2 11 53.6 46.4 87.1 84.6 1 0
12 0 0 13 6 60.7 39.3 88.9 82.3 1 0
12 2 31395 13 11 64 36 81.3 73.7 1 0
2 0 53018 5 19 63.7 36.3 86.8 74.1 4 0
7 0 0 2 25 64.5 35.5 89.1 79.1 1 0
14 1 25043 11 9 48.9 51.1 81.1 80.1 0 5
4 2 27010 22 1 28.5 71.5 75.4 90.5 0 4
10 0 17080 9 16 68.9 31.1 84.7 68.2 0 1
9 1 0 17 5 41.9 58.1 74.7 83.4 3 4
17 0 32231 12 14 66.6 33.4 88.5 74.6 1 0
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
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
20th November 2021 5:30pm Anfield h 53,092 5 4 2 0 62.5 37.5 19 5 9 3 3 0 7 2 88.4 81 4 0 6 1 2 8 57.1 33.3 50 50 3 5 15 12 2 0 0 0 https://www.skysports.com/football/liverpool-vs-arsenal/stats/446401
1/11/2020 4:30pm Old Trafford a 0 3 0 2 1 53.3 46.7 8 7 2 2 3 5 3 0 85.1 85.6 0 0 6 3 4 0 70 75 66.7 33.3 1 3 12 12 3 3 0 0 https://www.skysports.com/football/manchester-united-vs-arsenal/stats/428902
16/01/2021 3:00pm London Stadium h 0 14 1 23 0 44.8 55.2 15 10 4 2 6 3 5 5 72.8 73.8 0 0 7 4 5 1 62.5 46.2 54.4 45.6 2 2 12 9 2 1 0 0 https://www.skysports.com/football/west-ham-united-vs-burnley/stats/429025
21st May 2023 2:00pm Amex Stadium h 31,507 6 3 20 1 63 37 26 5 8 1 11 3 7 1 91.7 79.6 1 1 4 3 1 1 69.2 62.5 73.3 26.7 0 5 8 9 2 5 0 0 https://www.skysports.com/football/brighton-and-hove-albion-vs-southampton/464996
25th April 2022 8:00pm Selhurst Park d 25,357 11 0 19 0 52.5 47.5 17 9 7 2 6 4 4 3 75.2 69.1 0 0 6 3 1 1 53.8 58.6 57.8 42.2 2 7 12 13 2 2 0 0 https://www.skysports.com/football/crystal-palace-vs-leeds-united/stats/446623
3rd October 2021 2:00pm Tottenham Hotspur Stadium h 53,076 8 2 7 1 56.3 43.7 17 14 8 3 6 5 3 6 79.7 71.1 3 1 5 8 0 1 57.1 57.1 48.8 51.2 2 6 11 14 2 1 0 0 https://www.skysports.com/football/tottenham-hotspur-vs-aston-villa/stats/446355
12th December 2021 2:00pm The King Power Stadium h 31,959 18 4 4 0 47.3 52.7 8 12 5 3 2 3 1 6 79.3 79.8 2 0 4 6 0 1 65 61.5 46.7 53.3 3 1 9 16 2 3 0 0 https://www.skysports.com/football/leicester-city-vs-newcastle-united/stats/446444
21st November 2021 2:00pm Etihad Stadium h 52,571 1 3 17 0 73.3 22.7 17 4 7 1 6 0 4 3 93.7 69.6 4 0 7 1 0 2 46.7 38.9 75 25 1 4 5 7 1 1 0 0 https://www.skysports.com/football/manchester-city-vs-everton/stats/446402
26/12/2020 8:00pm Bramall Lane a 0 25 0 17 1 43.8 56.2 10 7 2 3 5 3 3 1 72.9 79.1 1 1 6 3 0 2 50 76.9 38.8 61.2 2 1 11 10 2 4 0 0 https://www.skysports.com/football/sheffield-united-vs-everton/stats/428984
12/3/2021 8:00pm St James' Park, Newcastle d 0 4 1 7 1 49 51 12 15 3 6 4 4 5 5 71.9 76.2 1 1 2 2 0 4 65 50 44 56 6 3 10 10 2 3 0 0 https://www.skysports.com/football/newcastle-united-vs-aston-villa/stats/429114

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.

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

Nesta seção foi implementado o algoritmo KNN de forma manual, a partir do zero, para consolidar o entendimento do funcionamento do método.
A implementação considera a distância euclidiana entre os pontos, identifica os vizinhos mais próximos e atribui a classe com maior frequência.
Esse exercício é importante para compreender a lógica por trás do KNN antes de utilizar bibliotecas prontas.

Acurácia (KNN k=5): 0.51

import numpy as np
import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score
from sklearn.preprocessing import StandardScaler

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

y_map = {"h": 0, "d": 1, "a": 2}
y = df["class"].astype(str).str.strip().map(y_map).astype(int)

num_cols = [
    "attendance",
    "home_possessions", "away_possessions",
    "home_pass", "away_pass",
    "home_chances", "away_chances",
]
cat_cols = ["stadium", "Home Team", "Away Team"]

df["attendance"] = (
    df["attendance"]
      .astype(str)
      .str.replace(r"[^0-9]", "", regex=True)
      .replace("", "0")
      .astype(float)
)

for c in num_cols:
    df[c] = pd.to_numeric(df[c], errors="coerce")
    df[c] = df[c].fillna(df[c].median())

X_cat = pd.get_dummies(
    df[cat_cols].astype(str).apply(lambda s: s.str.strip()),
    drop_first=False, dtype=int
)
X_num = df[num_cols].copy()

scaler = StandardScaler()
X_num[num_cols] = scaler.fit_transform(X_num[num_cols])

X = pd.concat([X_num, X_cat], axis=1).values

X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.20, random_state=42, stratify=y
)

class KNNClassifier:
    def __init__(self, k=5):
        self.k = k

    def fit(self, X, y):
        self.X_train = X
        self.y_train = np.array(y)

    def predict(self, X):
        return np.array([self._predict(x) for x in X])

    def _predict(self, x):
        distances = np.sqrt(((self.X_train - x) ** 2).sum(axis=1))
        k_idx = np.argsort(distances)[:self.k]
        k_labels = self.y_train[k_idx]
        vals, counts = np.unique(k_labels, return_counts=True)
        return vals[np.argmax(counts)]

knn = KNNClassifier(k=5)
knn.fit(X_train, y_train)
y_pred = knn.predict(X_test)

acc = accuracy_score(y_test, y_pred)
print(f"Acurácia (KNN k={knn.k}): {acc:.2f}")

Usando Scikit-Learn

Nesta seção foi implementado o algoritmo KNN de forma manual, a partir do zero, para consolidar o entendimento do funcionamento do método.
A implementação considera a distância euclidiana entre os pontos, identifica os vizinhos mais próximos e atribui a classe com maior frequência.
Esse exercício é importante para compreender a lógica por trás do KNN antes de utilizar bibliotecas prontas.

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

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from io import StringIO
import seaborn as sns
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.decomposition import PCA
from sklearn.neighbors import KNeighborsClassifier
from sklearn.metrics import accuracy_score

# 1) Carregar sua base
df = pd.read_csv("./src/mydata.csv")

# 2) Alvo (class): h=mandante, d=empate, a=visitante
y_map = {"h": 0, "d": 1, "a": 2}
y = df["class"].astype(str).str.strip().map(y_map).astype(int)

# 3) Seleção de features (≈10, sem vazamento)
num_cols = [
    "attendance",
    "home_possessions", "away_possessions",
    "home_pass", "away_pass",
    "home_chances", "away_chances",
]
cat_cols = ["stadium", "Home Team", "Away Team"]

# 4) Limpezas mínimas para numéricas (ex.: attendance vem como texto com pontuação)
df["attendance"] = (
    df["attendance"]
      .astype(str)
      .str.replace(r"[^0-9]", "", regex=True)
      .replace("", "0")
      .astype(float)
)

for c in num_cols:
    df[c] = pd.to_numeric(df[c], errors="coerce")
    df[c] = df[c].fillna(df[c].median())

# 5) One-hot para categóricas; escala para numéricas
X_cat = pd.get_dummies(
    df[cat_cols].astype(str).apply(lambda s: s.str.strip()),
    drop_first=False, dtype=int
)
X_num = df[num_cols].copy()

scaler = StandardScaler()
X_num[num_cols] = scaler.fit_transform(X_num[num_cols])

X = pd.concat([X_num, X_cat], axis=1).values

# 6) Split (mesma estrutura)
X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.2, random_state=42, stratify=y
)

# 7) PCA só para visualização (2D)
pca = PCA(n_components=2)
X_train_2d = pca.fit_transform(X_train)
X_test_2d = pca.transform(X_test)

# 8) KNN (baseline igual)
knn = KNeighborsClassifier(n_neighbors=5)
knn.fit(X_train_2d, y_train)
predictions = knn.predict(X_test_2d)
print(f"Accuracy: {accuracy_score(y_test, predictions):.2f}")

# 9) Fronteira de decisão (no espaço 2D do PCA)
plt.figure(figsize=(12, 10))
h = 0.05
x_min, x_max = X_train_2d[:, 0].min() - 1, X_train_2d[:, 0].max() + 1
y_min, y_max = X_train_2d[:, 1].min() - 1, X_train_2d[:, 1].max() + 1
xx, yy = np.meshgrid(np.arange(x_min, x_max, h), np.arange(y_min, y_max, h))

Z = knn.predict(np.c_[xx.ravel(), yy.ravel()])
Z = Z.reshape(xx.shape)

plt.contourf(xx, yy, Z, cmap=plt.cm.RdYlBu, alpha=0.3)
sns.scatterplot(x=X_train_2d[:, 0], y=X_train_2d[:, 1], hue=y_train,
                palette="deep", s=100, edgecolor="k", alpha=0.8, legend="full")

plt.xlabel("PCA 1")
plt.ylabel("PCA 2")
plt.title("KNN Decision Boundary (Football Matches)")

buffer = StringIO()
plt.savefig(buffer, format="svg", transparent=True)
print(buffer.getvalue())

Avaliação do Modelo

Após o treinamento do algoritmo KNN, o modelo foi avaliado no conjunto de teste, obtendo resultados de acurácia em torno de 0.42 (usando scikit-learn) e 0.51 (na implementação manual).
Esses valores mostram que o modelo consegue acertar parte das previsões, mas ainda apresenta uma performance limitada.

Esse resultado deve ser interpretado considerando a natureza do problema: prever o resultado de uma partida de futebol é uma tarefa complexa, com alto grau de incerteza.
Mesmo dispondo de estatísticas como posse de bola, passes, chances criadas, estádio e público, o desfecho de um jogo depende também de fatores externos como arbitragem, lesões, clima, motivação e até elementos de acaso.
Portanto, ainda que houvesse mais dados disponíveis, a previsão exata continuaria sendo incerta.

A figura abaixo mostra a fronteira de decisão do KNN no espaço bidimensional reduzido pelo PCA.
As regiões coloridas representam as áreas de influência de cada classe, enquanto os pontos correspondem às partidas reais.

É possível observar uma forte sobreposição entre classes, com pontos de diferentes resultados distribuídos de forma bastante misturada. Isso reforça as limitações do modelo:

  • Baixa capacidade preditiva: as variáveis utilizadas ajudam a descrever o contexto, mas não criam fronteiras claras para separar vitórias, empates e derrotas.
  • Balanceamento do alvo: os empates, menos frequentes, tendem a ser mal classificados, prejudicando a acurácia global.
  • Sensibilidade do algoritmo: o KNN depende fortemente da escala, da escolha de k e da representação dos dados, o que gera variações nos resultados.
  • Complexidade do domínio: a imprevisibilidade do futebol limita naturalmente a precisão que qualquer modelo pode alcançar.

Portanto, as acurácias obtidas refletem tanto as limitações do KNN quanto a complexidade do fenômeno esportivo. Mais do que “acertar resultados”, o exercício evidencia os desafios de aplicar Machine Learning em cenários reais de alta incerteza.

Relatório Final

O projeto teve como objetivo aplicar o algoritmo KNN sobre uma base de partidas de futebol, explorando seu potencial no 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, incluindo stadium, attendance, Home Team, Away Team, home_possessions, away_possessions, home_pass, away_pass, home_chances e away_chances.
  • Pré-processamento: colunas irrelevantes (como IDs e dados de tempo) e estatísticas ligadas diretamente ao resultado (como gols) foram removidas. Variáveis categóricas foram transformadas em numéricas via One-Hot Encoding e variáveis contínuas foram padronizadas.
  • Divisão dos Dados: o conjunto foi separado em treino (70%) e teste (30%), preservando a proporção entre vitórias, empates e derrotas através da estratificação.
  • Treinamento e Avaliação: o KNN foi aplicado em duas versões — manual e com scikit-learn — alcançando acurácias de 0.42–0.51, valores que evidenciam desempenho limitado, mas condizente com a natureza do problema.

A avaliação trouxe alguns aprendizados importantes: - O KNN é sensível à preparação dos dados e à escolha de hiperparâmetros, o que explica diferenças entre implementações.
- As variáveis utilizadas caracterizam parcialmente os jogos, mas não são suficientes para prever com exatidão seus resultados.
- A visualização da fronteira de decisão mostrou que as classes apresentam alta sobreposição, o que dificulta a separação clara.
- O futebol, por ser um evento com forte componente de imprevisibilidade, impõe limites naturais ao desempenho de qualquer modelo.

Conclusão

Assim como na árvore de decisão, a experiência com o KNN reforça a importância de interpretar os resultados com cautela.
A acurácia próxima de 0.5 não deve ser vista apenas como limitação do algoritmo, mas como reflexo da complexidade do domínio esportivo.

O projeto destacou a relevância da análise exploratória, do cuidado com o pré-processamento e da avaliação crítica dos modelos. Além disso, abre espaço para trabalhos futuros que explorem variáveis adicionais (desempenho histórico dos times, estatísticas de jogadores, forma recente, fatores externos) e ajustes de hiperparâmetros do KNN para buscar melhorias.

Mais do que prever com exatidão, este trabalho evidencia o potencial e os limites do uso de algoritmos de aprendizado de máquina em contextos reais, nos quais a incerteza é parte inerente do fenômeno analisado.