Skip to content

Exploração de Dados

Descrição da base de dados e código de exploração

O câncer de mama é o tipo de câncer mais comum entre mulheres em todo o mundo, responsável por aproximadamente 25% de todos os casos e afetando milhões de pessoas todos os anos. Ele se desenvolve quando células da mama começam a crescer de forma descontrolada, formando tumores que podem ser identificados por exames de imagem (raios-X) ou detectados como nódulos.

O principal desafio no diagnóstico é diferenciar corretamente os tumores malignos (cancerosos) dos benignos (não cancerosos). O objetivo deste projeto é desenvolver um modelo de classificação supervisionada capaz de prever, com base em atributos numéricos das células, se um tumor é maligno ou benigno, e estabelecer um diagnóstico confiável.

Sobre o Dataset

Total de registros: 569 amostras

Variável alvo: diagnosis (M = maligno, B = benigno)

Número de variáveis preditoras: 30 atributos numéricos relacionados ao tamanho, textura, formato e concavidade das células.

id diagnosis radius_mean texture_mean perimeter_mean area_mean smoothness_mean compactness_mean concavity_mean concave points_mean symmetry_mean fractal_dimension_mean radius_se texture_se perimeter_se area_se smoothness_se compactness_se concavity_se concave points_se symmetry_se fractal_dimension_se radius_worst texture_worst perimeter_worst area_worst smoothness_worst compactness_worst concavity_worst concave points_worst symmetry_worst fractal_dimension_worst
87930 B 12.47 18.6 81.09 481.9 0.09965 0.1058 0.08005 0.03821 0.1925 0.06373 0.3961 1.044 2.497 30.29 0.006953 0.01911 0.02701 0.01037 0.01782 0.003586 14.97 24.64 96.05 677.9 0.1426 0.2378 0.2671 0.1015 0.3014 0.0875
859575 M 18.94 21.31 123.6 1130 0.09009 0.1029 0.108 0.07951 0.1582 0.05461 0.7888 0.7975 5.486 96.05 0.004444 0.01652 0.02269 0.0137 0.01386 0.001698 24.86 26.58 165.9 1866 0.1193 0.2336 0.2687 0.1789 0.2551 0.06589
8670 M 15.46 19.48 101.7 748.9 0.1092 0.1223 0.1466 0.08087 0.1931 0.05796 0.4743 0.7859 3.094 48.31 0.00624 0.01484 0.02813 0.01093 0.01397 0.002461 19.26 26 124.9 1156 0.1546 0.2394 0.3791 0.1514 0.2837 0.08019
907915 B 12.4 17.68 81.47 467.8 0.1054 0.1316 0.07741 0.02799 0.1811 0.07102 0.1767 1.46 2.204 15.43 0.01 0.03295 0.04861 0.01167 0.02187 0.006005 12.88 22.91 89.61 515.8 0.145 0.2629 0.2403 0.0737 0.2556 0.09359
921385 B 11.54 14.44 74.65 402.9 0.09984 0.112 0.06737 0.02594 0.1818 0.06782 0.2784 1.768 1.628 20.86 0.01215 0.04112 0.05553 0.01494 0.0184 0.005512 12.26 19.68 78.78 457.8 0.1345 0.2118 0.1797 0.06918 0.2329 0.08134
927241 M 20.6 29.33 140.1 1265 0.1178 0.277 0.3514 0.152 0.2397 0.07016 0.726 1.595 5.772 86.22 0.006522 0.06158 0.07117 0.01664 0.02324 0.006185 25.74 39.42 184.6 1821 0.165 0.8681 0.9387 0.265 0.4087 0.124
9012000 M 22.01 21.9 147.2 1482 0.1063 0.1954 0.2448 0.1501 0.1824 0.0614 1.008 0.6999 7.561 130.2 0.003978 0.02821 0.03576 0.01471 0.01518 0.003796 27.66 25.8 195 2227 0.1294 0.3885 0.4756 0.2432 0.2741 0.08574
853201 M 17.57 15.05 115 955.1 0.09847 0.1157 0.09875 0.07953 0.1739 0.06149 0.6003 0.8225 4.655 61.1 0.005627 0.03033 0.03407 0.01354 0.01925 0.003742 20.01 19.52 134.9 1227 0.1255 0.2812 0.2489 0.1456 0.2756 0.07919
8611161 B 13.34 15.86 86.49 520 0.1078 0.1535 0.1169 0.06987 0.1942 0.06902 0.286 1.016 1.535 12.96 0.006794 0.03575 0.0398 0.01383 0.02134 0.004603 15.53 23.19 96.66 614.9 0.1536 0.4791 0.4858 0.1708 0.3527 0.1016
911673 B 13.9 16.62 88.97 599.4 0.06828 0.05319 0.02224 0.01339 0.1813 0.05536 0.1555 0.5762 1.392 14.03 0.003308 0.01315 0.009904 0.004832 0.01316 0.002095 15.14 21.8 101.2 718.9 0.09384 0.2006 0.1384 0.06222 0.2679 0.07698

Pré processamento

Explicação dos processos realizados no pré-processamento

Antes do treinamento do modelo, foi realizado um pré-processamento para garantir a qualidade e consistência dos dados:

Remoção de colunas irrelevantes – A coluna id foi descartada, pois não contribui para o aprendizado do modelo.

Tratamento de valores ausentes – Foram encontrados valores faltantes em algumas variáveis (concavity_worst e concave points_worst). Esses valores foram preenchidos utilizando a mediana.

Codificação de variáveis categóricas – A variável alvo diagnosis foi transformada em valores numéricos por meio de Label Encoding (M = 1, B = 0), permitindo sua utilização pelo algoritmo de aprendizado.

Todos os processos de pré-processamento feitos no algoritmo de árvore de decisão foram feitos também no algoritmo de KNN.

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


#carregamento da base
df = pd.read_csv('https://raw.githubusercontent.com/MariaLuizazz/MACHINE-LEARNING-PESSOAL/refs/heads/main/dados/breast-cancer.csv')


#pré processamento
#remoção da coluna id pois é irrelevante para o modelo
df = df.drop(columns=['id'])

#conversão de letra para número
label_encoder = LabelEncoder()  
df['diagnosis'] = label_encoder.fit_transform(df['diagnosis'])

#features escolhidas, todas menos diagnosis e id
X = df.drop(columns=['diagnosis'])
y = df['diagnosis']

#imputação com mediana de valores ausentes nas features concavity_worts e concavity points_worst
df['concavity_mean'].fillna(df['concavity_mean'].median(), inplace=True)
df['concave points_mean'].fillna(df['concave points_mean'].median(), inplace=True)

print(df.sample(n=10, random_state=42).to_markdown(index=False))
diagnosis radius_mean texture_mean perimeter_mean area_mean smoothness_mean compactness_mean concavity_mean concave points_mean symmetry_mean fractal_dimension_mean radius_se texture_se perimeter_se area_se smoothness_se compactness_se concavity_se concave points_se symmetry_se fractal_dimension_se radius_worst texture_worst perimeter_worst area_worst smoothness_worst compactness_worst concavity_worst concave points_worst symmetry_worst fractal_dimension_worst
0 12.47 18.6 81.09 481.9 0.09965 0.1058 0.08005 0.03821 0.1925 0.06373 0.3961 1.044 2.497 30.29 0.006953 0.01911 0.02701 0.01037 0.01782 0.003586 14.97 24.64 96.05 677.9 0.1426 0.2378 0.2671 0.1015 0.3014 0.0875
1 18.94 21.31 123.6 1130 0.09009 0.1029 0.108 0.07951 0.1582 0.05461 0.7888 0.7975 5.486 96.05 0.004444 0.01652 0.02269 0.0137 0.01386 0.001698 24.86 26.58 165.9 1866 0.1193 0.2336 0.2687 0.1789 0.2551 0.06589
1 15.46 19.48 101.7 748.9 0.1092 0.1223 0.1466 0.08087 0.1931 0.05796 0.4743 0.7859 3.094 48.31 0.00624 0.01484 0.02813 0.01093 0.01397 0.002461 19.26 26 124.9 1156 0.1546 0.2394 0.3791 0.1514 0.2837 0.08019
0 12.4 17.68 81.47 467.8 0.1054 0.1316 0.07741 0.02799 0.1811 0.07102 0.1767 1.46 2.204 15.43 0.01 0.03295 0.04861 0.01167 0.02187 0.006005 12.88 22.91 89.61 515.8 0.145 0.2629 0.2403 0.0737 0.2556 0.09359
0 11.54 14.44 74.65 402.9 0.09984 0.112 0.06737 0.02594 0.1818 0.06782 0.2784 1.768 1.628 20.86 0.01215 0.04112 0.05553 0.01494 0.0184 0.005512 12.26 19.68 78.78 457.8 0.1345 0.2118 0.1797 0.06918 0.2329 0.08134
1 20.6 29.33 140.1 1265 0.1178 0.277 0.3514 0.152 0.2397 0.07016 0.726 1.595 5.772 86.22 0.006522 0.06158 0.07117 0.01664 0.02324 0.006185 25.74 39.42 184.6 1821 0.165 0.8681 0.9387 0.265 0.4087 0.124
1 22.01 21.9 147.2 1482 0.1063 0.1954 0.2448 0.1501 0.1824 0.0614 1.008 0.6999 7.561 130.2 0.003978 0.02821 0.03576 0.01471 0.01518 0.003796 27.66 25.8 195 2227 0.1294 0.3885 0.4756 0.2432 0.2741 0.08574
1 17.57 15.05 115 955.1 0.09847 0.1157 0.09875 0.07953 0.1739 0.06149 0.6003 0.8225 4.655 61.1 0.005627 0.03033 0.03407 0.01354 0.01925 0.003742 20.01 19.52 134.9 1227 0.1255 0.2812 0.2489 0.1456 0.2756 0.07919
0 13.34 15.86 86.49 520 0.1078 0.1535 0.1169 0.06987 0.1942 0.06902 0.286 1.016 1.535 12.96 0.006794 0.03575 0.0398 0.01383 0.02134 0.004603 15.53 23.19 96.66 614.9 0.1536 0.4791 0.4858 0.1708 0.3527 0.1016
0 13.9 16.62 88.97 599.4 0.06828 0.05319 0.02224 0.01339 0.1813 0.05536 0.1555 0.5762 1.392 14.03 0.003308 0.01315 0.009904 0.004832 0.01316 0.002095 15.14 21.8 101.2 718.9 0.09384 0.2006 0.1384 0.06222 0.2679 0.07698

Divisão dos dados, Treinamento do Modelo e Avaliação do Modelo.

O dataset foi dividido em conjuntos de treino e teste com uma proporção de 80% treino e 20% teste.

O questionamento principal foi: Quais features são mais relevantes para o diagnóstico de câncer de mama de acordo com o que foi fornecido no meu dataset?

Ao analisar o dataset, foi bom lembrar que trata-se da previsão de um diagnóstico, é preciso entender a base e o que eu quero prever. Em uma pesquisa rápida para entender melhor, concluí que: para a escolha das minhas features eu deveria ficar atenta às minhas variáveis mais relevantes.

Se o nódulo é redondo, pequeno e com bordas suaves → mais provável benigno. Se o nódulo é grande, irregular, com bordas cheias de reentrâncias → mais provável maligno. Nódulos malignos costumam ser maiores, com contornos irregulares e não lisos, enquanto benignos tendem a ser mais arredondados e bem delimitados.

Portanto, as variáveis mais importantes do meu dataset para o diagnóstico seriam aquelas que especificam tamanho, formato e textura do nódulo. No caso, as variáveis escolhidas foram: texture_mean e radius_mean.

TESTE 1

Ao analiser o primeiro gráfico, o modelo paresentou sinais de overfitting, realizando uma validação cruzada e matriz de confusão foi possível constatar que de acordo com o modelo sem balancemanto e com k = 3 que:

102: Pacientes saudáveis corretamente diagnosticados

5: Pacientes saudáveis erroneamente diagnosticados com câncer (Falso Positivo)

14: Pacientes com câncer erroneamente diagnosticados como saudáveis (Falso Negativo - GRAVE)

50: Pacientes com câncer corretamente diagnosticados

Com esses erros o gráfico resultou da seguinte forma:

Accuracy: 0.89 2026-01-20T18:12:02.567836 image/svg+xml Matplotlib v3.10.8, https://matplotlib.org/ Validação Cruzada: 0.873 ± 0.037 Matriz de Confusão: [[102 5] [ 14 50]]

import numpy as np
import matplotlib.pyplot as plt
from io import StringIO
from sklearn.datasets import make_classification
from sklearn.model_selection import train_test_split
from sklearn.neighbors import KNeighborsClassifier
from sklearn.metrics import accuracy_score
import seaborn as sns
import pandas as pd
from sklearn.preprocessing import LabelEncoder


plt.figure(figsize=(12,10))

#carregamento da base
df = pd.read_csv('https://raw.githubusercontent.com/MariaLuizazz/MACHINE-LEARNING-PESSOAL/refs/heads/main/dados/breast-cancer.csv')

#Préprocess
#remoção da coluna id pois é irrelevante para o modelo
df = df.drop(columns=['id'])

#conversão de letra para número
label_encoder = LabelEncoder()  
df['diagnosis'] = label_encoder.fit_transform(df['diagnosis'])


#imputação com mediana de valores ausentes nas features concavity_worts e concavity points_worst
df['concavity_mean'].fillna(df['concavity_mean'].median(), inplace=True)
df['concave points_mean'].fillna(df['concave points_mean'].median(), inplace=True)

#escolha de features
X = df[['radius_mean', 'texture_mean']]
y = df['diagnosis']

#Separação de treino e teste
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=42, stratify=y)

#Treianamento do KNN
knn = KNeighborsClassifier(n_neighbors=3)
knn.fit(X_train, y_train)


#Teste e validação
predictions = knn.predict(X_test)
print(f"Accuracy: {accuracy_score(y_test, predictions):.2f}")


# Mapeia os rótulos: 0 -> Benigno, 1 -> Maligno
labels_map = {0: "Benigno", 1: "Maligno"}
y_labels = y.map(labels_map)


#Preparação para o gráfico da fronteira de decisão(malha de visualização)
h = 0.02
x_min, x_max = X.iloc[:, 0].min() - 1, X.iloc[:, 0].max() + 1
y_min, y_max = X.iloc[:, 1].min() - 1, X.iloc[:, 1].max() + 1
xx, yy = np.meshgrid(np.arange(x_min, x_max, h),np.arange(y_min, y_max, h))

#Prevendo classe em cada ponto
Z = knn.predict(np.c_[xx.ravel(), yy.ravel()])
Z = Z.reshape(xx.shape)

#gráfico final
plt.contourf(xx, yy, Z, cmap=plt.cm.RdYlGn_r, alpha=0.3)
sns.scatterplot(x=X.iloc[:, 0], y=X.iloc[:, 1], hue=y_labels, style=y_labels, palette={'Benigno': 'green', 'Maligno': 'red'}, s=100) #motivooooooo do errroo
plt.xlabel("radius_mean")
plt.ylabel("texture_mean")
plt.title("KNN Decision Boundary (k=3) -  Diagnóstico de Câncer")
plt.legend(title="Diagnóstico")  



#Exibição do gráfico
buffer = StringIO()
plt.savefig(buffer, format="svg", transparent=True)
print(buffer.getvalue())


# 1. Validação Cruzada
from sklearn.model_selection import cross_val_score
scores = cross_val_score(knn, X, y, cv=5)
print(f"Validação Cruzada: {scores.mean():.3f} ± {scores.std():.3f}")


# 3. Matriz de Confusão
from sklearn.metrics import confusion_matrix
print("Matriz de Confusão:")
print(confusion_matrix(y_test, predictions))

TESTE 2

No teste 2 ocorreu aplicação da técnica smote apenas nos dados de treino para balanceamento, sem vazmento de informações para o teste. Realizando uma validação cruzada e matriz de confusão foi possível constatar que de acordo com o modelo co balancemento e com k = 11 que:

O modelo balanceado com K=11 é MAIS SEGURO que a versão anterior, reduzindo os perigosos falsos negativos em 28,6%.

101: Pacientes saudáveis corretamente diagnosticados

6: Pacientes saudáveis erroneamente diagnosticados com câncer (Falso Positivo)

10: Pacientes com câncer erroneamente diagnosticados como saudáveis (Falso Negativo - GRAVE)

54: Pacientes com câncer corretamente diagnosticados

  • Conclusão: a quantidade de falsos negativos cairam, e de positivos aumentou.

Accuracy: 0.91 2026-01-20T18:12:05.302947 image/svg+xml Matplotlib v3.10.8, https://matplotlib.org/ Validação Cruzada: 0.884 ± 0.036 Matriz de Confusão: [[101 6] [ 10 54]]

import numpy as np
import matplotlib.pyplot as plt
from io import StringIO
from sklearn.datasets import make_classification
from sklearn.model_selection import train_test_split
from sklearn.neighbors import KNeighborsClassifier
from sklearn.metrics import accuracy_score
import seaborn as sns
import pandas as pd
from sklearn.preprocessing import LabelEncoder
from imblearn.over_sampling import SMOTE  


plt.figure(figsize=(12,10))

#carregamento da base
df = pd.read_csv('https://raw.githubusercontent.com/MariaLuizazz/MACHINE-LEARNING-PESSOAL/refs/heads/main/dados/breast-cancer.csv')

#Préprocess
#remoção da coluna id pois é irrelevante para o modelo
df = df.drop(columns=['id'])

#conversão de letra para número
label_encoder = LabelEncoder()  
df['diagnosis'] = label_encoder.fit_transform(df['diagnosis'])


#imputação com mediana de valores ausentes nas features concavity_worts e concavity points_worst
df['concavity_mean'].fillna(df['concavity_mean'].median(), inplace=True)
df['concave points_mean'].fillna(df['concave points_mean'].median(), inplace=True)

#escolha de features
# Em vez de usar apenas 2 features
X = df[['radius_mean', 'texture_mean']]
y = df['diagnosis']

#Separação de treino e teste
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=42, stratify=y)


smote = SMOTE(random_state=42)
X_train_balanced, y_train_balanced = smote.fit_resample(X_train, y_train)



#Treianamento do KNN
knn = KNeighborsClassifier(n_neighbors=11)
knn.fit(X_train_balanced, y_train_balanced) 


#Teste e validação
predictions = knn.predict(X_test)
print(f"Accuracy: {accuracy_score(y_test, predictions):.2f}")


# Mapeia os rótulos: 0 -> Benigno, 1 -> Maligno
labels_map = {0: "Benigno", 1: "Maligno"}
y_labels = y.map(labels_map)


#Preparação para o gráfico da fronteira de decisão(malha de visualização)
h = 0.02
x_min, x_max = X.iloc[:, 0].min() - 1, X.iloc[:, 0].max() + 1
y_min, y_max = X.iloc[:, 1].min() - 1, X.iloc[:, 1].max() + 1
xx, yy = np.meshgrid(np.arange(x_min, x_max, h),np.arange(y_min, y_max, h))

#Prevendo classe em cada ponto
Z = knn.predict(np.c_[xx.ravel(), yy.ravel()])
Z = Z.reshape(xx.shape)

#gráfico final
plt.contourf(xx, yy, Z, cmap=plt.cm.RdYlGn_r, alpha=0.3)
sns.scatterplot(x=X.iloc[:, 0], y=X.iloc[:, 1], hue=y_labels, style=y_labels, palette={'Benigno': 'green', 'Maligno': 'red'}, s=100) #motivooooooo do errroo
plt.xlabel("radius_mean")
plt.ylabel("texture_mean")
plt.title("KNN Decision Boundary (k=11) - Diagnóstico de Câncer (Com Balanceamento SMOTE)")
plt.legend(title="Diagnóstico")  



#Exibição do gráfico
buffer = StringIO()
plt.savefig(buffer, format="svg", transparent=True)
print(buffer.getvalue())



# 1. Validação Cruzada
from sklearn.model_selection import cross_val_score
scores = cross_val_score(knn, X, y, cv=5)
print(f"Validação Cruzada: {scores.mean():.3f} ± {scores.std():.3f}")



# 3. Matriz de Confusão
from sklearn.metrics import confusion_matrix
print("Matriz de Confusão:")
print(confusion_matrix(y_test, predictions))

Relatorio final

  • O modelo KNN com k=3 memoriza os dados de treino e usa distância para fazer previsões. Para cada novo tumor, ele encontra os 3 tumores mais similares no conjunto de treino e decide pela maioria, a mesma coisa ocorre com k = 11.

  • Sobre a Avaliação: Após a etapa de treino e teste, o processo entregou uma acurácia de 86% que nos mostra que o modelo acerta 86 em cada 100 previsões.

  • Sobre a Visualização: A fronteira de decisão mostra como o modelo separa tumores benignos de malignos. Áreas coloridas mostram onde o modelo prevê cada classe. Observando o modelo e a acurácia o modelo apresenta sinais de overfitting.

Conclusões - O balanceamento com SMOTE foi crucial para melhorar a detecção de casos malignos - k=11 mostrou-se superior a k=3 para este problema médico - A troca especificidade-sensibilidade foi clinicamente vantajosa - O modelo balanceado é mais seguro para aplicação clínica