Skip to content

Exercicio 2 KNN

Esse exercicio é baseado no dataset Crash Car do Kaggle. O objetivo é analisar a base de dados, limpá-la e no final construir um modelo KNN

Etapa 1 - Analise e instalação dos dados

O dataset foi carregado a partir de um arquivo Excel, contendo informações como tipo de colisão, tipo de lesão, dia da semana, fatores primários do acidente, data, hora e localização.

Carregando os dados

Formato do dataset: (53943, 11)

import pandas as pd
# Ler o arquivo corretamente (apenas )
df = pd.read_excel("docs/arvore-decisao/crashcar.xlsx")

# Visualização inicial da base de dados
print('Formato do dataset:', df.shape)

O dataset possui 53943 linha e 11 colunas.

As bibiliotecas utilizadas

    import numpy as np
    import pandas as pd
    import matplotlib.pyplot as plt
    import seaborn as sns
    from io import StringIO

    from sklearn.datasets import make_classification
    from sklearn.preprocessing import StandardScaler
    from sklearn.model_selection import train_test_split
    from sklearn.neighbors import KNeighborsClassifier
    from sklearn.metrics import accuracy_score, classification_report, confusion_matrix

Explicação dos tipos de dados

Year : int64

Month : int64

Day : int64

Weekend? : object

Hour : float64

Collision Type : object

Injury Type : object

Primary Factor : object

Reported_Location : object

Latitude : float64

Longitude : float64

    for col in df:
    print(col, ":", df[col].dtype, "\n")

Explicação

Int64: Dados numéricos inteiros, como Year, Month, Day, Hour e Latitude.
Object: Dados categóricos ou textuais, como Collision Type, Injury Type, Weekend?, Primary Factor e Time.
FLoat64: Dados numéricos com casas decimais, como Longitude, Latitude e Hora.

Estatisticas descritivas dos numéricos

Formato do dataset: (53943, 11) Estatísticas das colunas numéricas:

Year Month Day Hour Latitude
52972 2003 9 1 500.0 39.129395
27402 2009 12 5 1300.0 39.155688
3958 2015 11 2 1300.0 39.165562
40878 2006 4 5 1400.0 0.000000
8384 2014 10 4 1200.0 0.000000
53166 2003 3 3 500.0 39.099918
38421 2006 10 4 1500.0 39.169584
49319 2003 6 2 2100.0 39.339296
27797 2009 7 6 2300.0 39.139344
12492 2013 10 3 500.0 0.000000
# Estatísticas descritivas das colunas numéricas
print('Estatísticas das colunas numéricas:')
print(df[['Year', 'Month', 'Day', 'Hour', 'Latitude']].describe())

Visualização dos dados

2025-09-26T12:32:49.923390 image/svg+xml Matplotlib v3.10.6, https://matplotlib.org/ 2025-09-26T12:32:49.988094 image/svg+xml Matplotlib v3.10.6, https://matplotlib.org/

Visualizações: Foram criados gráficos de barras para o número de acidentes por tipo de veículo e por gravidade (fatal/não fatal), facilitando a compreensão dos dados.

Exploração inicial dos dados

import matplotlib.pyplot as plt
import pandas as pd
df = pd.read_excel("docs/arvore-decisao/crashcar.xlsx")

# Visualização inicial da base de dados
print(f'Formato do dataset: {df.shape}<br>')

# Explicação de tipo de dado em cada coluna
for col in df:
    print(df[col].head(1), "<br>")
print('Valores nulos por coluna:')
print(df.isnull().sum(), "<br>")


################################################
# Estatísticas descritivas das colunas numéricas
print('Estatísticas das colunas numéricas:')
print(df[['Year', 'Month', 'Day', 'Hour', 'Latitude']].describe())

Etapa 2

Normalização de dados categóricos em numéricos

Collision Type Collision Type Num
0 2-Car 1
1 2-Car 1
2 2-Car 1
3 2-Car 1
4 2-Car 1
5 2-Car 1
6 2-Car 1
7 1-Car 1
8 2-Car 1
9 1-Car 1
def collision_to_num(collision):
    if collision == '1-Car' or collision == '2-Car' or collision == '3+ Cars':
        return 1
    elif collision == 'Moped/Motorcycle':
        return 2
    elif collision == 'Bus':
        return 3
    elif collision == 'Pedestrian':
        return 4
    elif collision == 'Cyclist':
        return 5
    else:
        return 0
df['Collision Type Num'] = df['Collision Type'].apply(collision_to_num)
Injury Type Injury Type Num
0 No injury/unknown 0
1 No injury/unknown 0
2 Non-incapacitating 0
3 Non-incapacitating 0
4 No injury/unknown 0
5 No injury/unknown 0
6 No injury/unknown 0
7 Incapacitating 0
8 No injury/unknown 0
9 No injury/unknown 0
def injury_to_num(injury):
    if injury == 'Fatal':
        return 1
    else:
        return 0

df['Injury Type Num'] = df['Injury Type'].apply(injury_to_num)
Weekend? Weekend Num
0 Weekday 2
1 Weekday 2
2 Weekend 1
3 Weekend 1
4 Weekend 1
5 Weekday 2
6 Weekday 2
7 Weekday 2
8 Weekend 1
9 Weekend 1
def weekend_to_num(value):
    if str(value).lower() == 'weekend':
        return 1
    elif str(value).lower() == 'weekday':
        return 2
    else:
        return 0
df['Weekend Num'] = df['Weekend?'].apply(weekend_to_num)
Primary Factor Primary Factor Num
0 OTHER (DRIVER) - EXPLAIN IN NARRATIVE 0
1 FOLLOWING TOO CLOSELY 1
2 DISREGARD SIGNAL/REG SIGN 1
3 FAILURE TO YIELD RIGHT OF WAY 1
4 FAILURE TO YIELD RIGHT OF WAY 1
5 FAILURE TO YIELD RIGHT OF WAY 1
6 DRIVER DISTRACTED - EXPLAIN IN NARRATIVE 5
7 ENGINE FAILURE OR DEFECTIVE 3
8 FOLLOWING TOO CLOSELY 1
9 RAN OFF ROAD RIGHT 1


1 = Erros de julgamento do motorista
2 = Velocidade / comportamento arriscado
3 = Falhas mecânicas
4 = Condições da estrada / ambientais
5 = Distrações
6 = Uso de Substâncias
7 = Fatores diversos
8 = Outros

def primary_factor_to_num(factor):
    if factor == 'Erros de julgamento do motorista':
        return 1
    elif factor == 'Velocidade / comportamento arriscado':
        return 2
    elif factor == 'Falhas mecânicas':
        return 3
    elif factor == 'Condições da estrada / ambientais':
        return 4
    elif factor == 'Distrações':
        return 5
    elif factor == 'Uso de Substâncias':
        return 6
    elif factor == 'Fatores diversos':
        return 7
    else:
        return 0  # Outros / não especificado
df['Primary Factor Num'] = df['Primary Factor'].apply(primary_factor_to_num)

Conversão de variáveis categóricas em numéricas: tipo de colisão, tipo de lesão, dia da semana e fator primário foram transformados em variáveis numéricas para facilitar os proximos processos e estabelecer uma normalização.

Limpeza

Tamanho do dataset antes remoção de valores ausentes (53943, 11)
Tamanho do dataset após remoção de valores ausentes (52582, 11)

print(df.shape)
print("Tamanho do dataset antes remoção de valores ausentes")
df = df.dropna()
print(df.shape)
print("Tamanho do dataset após remoção de valores ausentes")

Todos os registros com valores ausentes foram removidos, garantindo a integridade dos dados para o treinamento do modelo

Etapa 3

Separação em treino e teste / KNN

Distribuição das classes:
Injury Type Num 0 52467 1 115 Name: count, dtype: int64
Proporção de acidentes fatais: 0.0022
Acurácia do modelo: 0.9978

Relatório de Classificação:
precision recall f1-score support

Não Fatal 1.00 1.00 1.00 10494 Fatal 0.00 0.00 0.00 23

accuracy                           1.00     10517

macro avg 0.50 0.50 0.50 10517 weighted avg 1.00 1.00 1.00 10517

    # Selecionar features para o modelo
    features = ['Year', 'Month', 'Day', 'Hour', 'Collision Type Num', 'Weekend Num', 'Primary Factor Num', 'Latitude', 'Longitude']
    X = df[features]
    y = df['Injury Type Num']  # Variável alvo: 1 para fatal, 0 para não fatal

    # Verificar balanceamento das classes
    print("Distribuição das classes:<br>")

    print(y.value_counts())
    print(f"<br>Proporção de acidentes fatais: {y.mean():.4f}<br>")

    # Dividir os dados em treino e teste
    X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42, stratify=y)

    # Normalizar os dados
    scaler = StandardScaler()
    X_train_scaled = scaler.fit_transform(X_train)
    X_test_scaled = scaler.transform(X_test)

    # Implementação do KNN
    class KNNClassifier:
        def __init__(self, k=3):
            self.k = k

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

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

        def _predict(self, x):
            # Calcular distâncias euclidianas
            distances = [np.sqrt(np.sum((x - x_train)**2)) for x_train in self.X_train]
            # Obter índices dos k-vizinhos mais próximos
            k_indices = np.argsort(distances)[:self.k]
            # Obter os rótulos correspondentes
            k_nearest_labels = [self.y_train.iloc[i] for i in k_indices]
            # Retornar a classe majoritária
            most_common = max(set(k_nearest_labels), key=k_nearest_labels.count)
            return most_common

    # Treinar e avaliar o modelo

    knn = KNeighborsClassifier(n_neighbors=5)
    knn.fit(X_train_scaled, y_train)
    predictions = knn.predict(X_test_scaled)
    # Métricas de avaliação
    accuracy = accuracy_score(y_test, predictions)

Matriz

plt.figure(figsize=(6, 5))
plt.imshow(cm, interpolation='nearest', cmap=plt.cm.Blues)
plt.title('Matriz de Confusão')
plt.colorbar()
tick_marks = range(len(cm))
plt.xticks(tick_marks, ['Não Fatal', 'Fatal'])
plt.yticks(tick_marks, ['Não Fatal', 'Fatal'])
plt.xlabel('Predito')
plt.ylabel('Real')

for i in range(cm.shape[0]):
    for j in range(cm.shape[1]):
        plt.text(j, i, str(cm[i, j]), ha='center', va='center', color='black')

plt.tight_layout()
buffer = StringIO()
plt.savefig(buffer, format="svg", transparent=True)
plt.close()
print(buffer.getvalue())
2025-09-26T12:33:18.755716 image/svg+xml Matplotlib v3.10.6, https://matplotlib.org/

Grafico de Fronteira


Proporção de acidentes fatais: 0.0022
Acurácia: 0.95 2025-09-26T12:33:23.722872 image/svg+xml Matplotlib v3.10.6, https://matplotlib.org/

# Visualizar fronteira de decisão
h = 0.02
x_min, x_max = X[:, 0].min() - 1, X[:, 0].max() + 1
y_min, y_max = X[:, 1].min() - 1, X[:, 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)
for label in np.unique(y):
    plt.scatter(X[y == label, 0], X[y == label, 1], label=f"Classe {label}", s=100)
plt.xlabel("Chance de acidente fatal")
plt.ylabel("Chance de acidente não fatal")
plt.title("Fronteira de Decisão KNN ")
plt.legend()

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

print(buffer.getvalue())