Projeto de Ciência de Dados: Diagnóstico de Câncer de Mama (Wisconsin)¶

Integrantes¶

  • Taimisson de Carvalho Schardosim

Links do Projeto¶

  • Dataset Original (UCI): Breast Cancer Wisconsin (Dianostic)
  • Descrição dos Atributos: Wisconsin Diagnostic Breast Cancer (WDBC)
  • Apresentação (Slides): Link do Slides (Conta ASAV / UNISINOS)
  • Vídeo de Apresentação: Vídeo de Apresentação do Grau B
In [ ]:
%pip install pandas numpy matplotlib seaborn scikit-learn --quiet ucimlrepo

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
from sklearn.preprocessing import StandardScaler
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score, precision_score, recall_score, f1_score, confusion_matrix, classification_report
from sklearn.linear_model import LogisticRegression
from sklearn.ensemble import RandomForestClassifier
from sklearn.pipeline import Pipeline
import warnings
from ucimlrepo import fetch_ucirepo
warnings.filterwarnings('ignore')
[notice] A new release of pip available: 22.3.1 -> 25.3
[notice] To update, run: python.exe -m pip install --upgrade pip
Note: you may need to restart the kernel to use updated packages.

Introdução e Contexto¶

O câncer de mama é uma das principais causas de mortalidade entre mulheres em todo o mundo. A detecção precoce é essencial para aumentar as chances de sucesso no tratamento. O Wisconsin Diagnostic Breast Cancer (WDBC) é um conjunto de dados amplamente utilizado em pesquisas de aprendizado de máquina para diferenciar tumores malignos de benignos.

Os dados são obtidos a partir de imagens digitalizadas de aspirados por agulha fina (FNA) de massas mamárias. Para cada núcleo celular, dez características são calculadas, como raio, textura, perímetro, área, suavidade, compacidade, concavidade, pontos côncavos, simetria e dimensão fractal. Em seguida, são computadas a média, o erro padrão (standard error) e o valor "pior" (maior valor entre as três medições) dessas características, totalizando 30 atributos numéricos para cada amostra.

Ao analisar essas características, algoritmos de machine learning podem aprender padrões que distinguem tumores malignos (câncer) dos benignos (não câncer), contribuindo para sistemas de apoio à decisão médica. Este projeto segue um pipeline completo de ciência de dados, desde a obtenção do dataset até a avaliação dos modelos de classificação.

In [ ]:
breast_cancer_wisconsin_diagnostic = fetch_ucirepo(id=17) 
  
X_temp = breast_cancer_wisconsin_diagnostic.data.features 
y_temp = breast_cancer_wisconsin_diagnostic.data.targets 
  
print(breast_cancer_wisconsin_diagnostic.metadata) 
print(breast_cancer_wisconsin_diagnostic.variables)

# combinando em um unico df pra analise
df = X_temp.copy()
df['diagnosis'] = y_temp.values.ravel()

# renomeando as colunas pra ficar mais descritivo
# o padrão é: 1 = mean, 2 = se (standard error), 3 = worst
rename_dict = {
    'radius1': 'radius_mean', 'texture1': 'texture_mean', 'perimeter1': 'perimeter_mean',
    'area1': 'area_mean', 'smoothness1': 'smoothness_mean', 'compactness1': 'compactness_mean',
    'concavity1': 'concavity_mean', 'concave_points1': 'concave_points_mean', 
    'symmetry1': 'symmetry_mean', 'fractal_dimension1': 'fractal_dimension_mean',
    'radius2': 'radius_se', 'texture2': 'texture_se', 'perimeter2': 'perimeter_se',
    'area2': 'area_se', 'smoothness2': 'smoothness_se', 'compactness2': 'compactness_se',
    'concavity2': 'concavity_se', 'concave_points2': 'concave_points_se',
    'symmetry2': 'symmetry_se', 'fractal_dimension2': 'fractal_dimension_se',
    'radius3': 'radius_worst', 'texture3': 'texture_worst', 'perimeter3': 'perimeter_worst',
    'area3': 'area_worst', 'smoothness3': 'smoothness_worst', 'compactness3': 'compactness_worst',
    'concavity3': 'concavity_worst', 'concave_points3': 'concave_points_worst',
    'symmetry3': 'symmetry_worst', 'fractal_dimension3': 'fractal_dimension_worst'
}
df.rename(columns=rename_dict, inplace=True)

# verificar os dados
df_shape = df.shape
df_head = df.head()
print(f"\nDimensões do dataset: {df_shape}")

df_head
{'uci_id': 17, 'name': 'Breast Cancer Wisconsin (Diagnostic)', 'repository_url': 'https://archive.ics.uci.edu/dataset/17/breast+cancer+wisconsin+diagnostic', 'data_url': 'https://archive.ics.uci.edu/static/public/17/data.csv', 'abstract': 'Diagnostic Wisconsin Breast Cancer Database.', 'area': 'Health and Medicine', 'tasks': ['Classification'], 'characteristics': ['Multivariate'], 'num_instances': 569, 'num_features': 30, 'feature_types': ['Real'], 'demographics': [], 'target_col': ['Diagnosis'], 'index_col': ['ID'], 'has_missing_values': 'no', 'missing_values_symbol': None, 'year_of_dataset_creation': 1993, 'last_updated': 'Fri Nov 03 2023', 'dataset_doi': '10.24432/C5DW2B', 'creators': ['William Wolberg', 'Olvi Mangasarian', 'Nick Street', 'W. Street'], 'intro_paper': {'ID': 230, 'type': 'NATIVE', 'title': 'Nuclear feature extraction for breast tumor diagnosis', 'authors': 'W. Street, W. Wolberg, O. Mangasarian', 'venue': 'Electronic imaging', 'year': 1993, 'journal': None, 'DOI': '10.1117/12.148698', 'URL': 'https://www.semanticscholar.org/paper/53f0fbb425bc14468eb3bf96b2e1d41ba8087f36', 'sha': None, 'corpus': None, 'arxiv': None, 'mag': None, 'acl': None, 'pmid': None, 'pmcid': None}, 'additional_info': {'summary': 'Features are computed from a digitized image of a fine needle aspirate (FNA) of a breast mass.  They describe characteristics of the cell nuclei present in the image. A few of the images can be found at http://www.cs.wisc.edu/~street/images/\r\n\r\nSeparating plane described above was obtained using Multisurface Method-Tree (MSM-T) [K. P. Bennett, "Decision Tree Construction Via Linear Programming." Proceedings of the 4th Midwest Artificial Intelligence and Cognitive Science Society, pp. 97-101, 1992], a classification method which uses linear programming to construct a decision tree.  Relevant features were selected using an exhaustive search in the space of 1-4 features and 1-3 separating planes.\r\n\r\nThe actual linear program used to obtain the separating plane in the 3-dimensional space is that described in: [K. P. Bennett and O. L. Mangasarian: "Robust Linear Programming Discrimination of Two Linearly Inseparable Sets", Optimization Methods and Software 1, 1992, 23-34].\r\n\r\nThis database is also available through the UW CS ftp server:\r\nftp ftp.cs.wisc.edu\r\ncd math-prog/cpo-dataset/machine-learn/WDBC/', 'purpose': None, 'funded_by': None, 'instances_represent': None, 'recommended_data_splits': None, 'sensitive_data': None, 'preprocessing_description': None, 'variable_info': '1) ID number\r\n2) Diagnosis (M = malignant, B = benign)\r\n3-32)\r\n\r\nTen real-valued features are computed for each cell nucleus:\r\n\r\n\ta) radius (mean of distances from center to points on the perimeter)\r\n\tb) texture (standard deviation of gray-scale values)\r\n\tc) perimeter\r\n\td) area\r\n\te) smoothness (local variation in radius lengths)\r\n\tf) compactness (perimeter^2 / area - 1.0)\r\n\tg) concavity (severity of concave portions of the contour)\r\n\th) concave points (number of concave portions of the contour)\r\n\ti) symmetry \r\n\tj) fractal dimension ("coastline approximation" - 1)', 'citation': None}}
                  name     role         type demographic description units  \
0                   ID       ID  Categorical        None        None  None   
1            Diagnosis   Target  Categorical        None        None  None   
2              radius1  Feature   Continuous        None        None  None   
3             texture1  Feature   Continuous        None        None  None   
4           perimeter1  Feature   Continuous        None        None  None   
5                area1  Feature   Continuous        None        None  None   
6          smoothness1  Feature   Continuous        None        None  None   
7         compactness1  Feature   Continuous        None        None  None   
8           concavity1  Feature   Continuous        None        None  None   
9      concave_points1  Feature   Continuous        None        None  None   
10           symmetry1  Feature   Continuous        None        None  None   
11  fractal_dimension1  Feature   Continuous        None        None  None   
12             radius2  Feature   Continuous        None        None  None   
13            texture2  Feature   Continuous        None        None  None   
14          perimeter2  Feature   Continuous        None        None  None   
15               area2  Feature   Continuous        None        None  None   
16         smoothness2  Feature   Continuous        None        None  None   
17        compactness2  Feature   Continuous        None        None  None   
18          concavity2  Feature   Continuous        None        None  None   
19     concave_points2  Feature   Continuous        None        None  None   
20           symmetry2  Feature   Continuous        None        None  None   
21  fractal_dimension2  Feature   Continuous        None        None  None   
22             radius3  Feature   Continuous        None        None  None   
23            texture3  Feature   Continuous        None        None  None   
24          perimeter3  Feature   Continuous        None        None  None   
25               area3  Feature   Continuous        None        None  None   
26         smoothness3  Feature   Continuous        None        None  None   
27        compactness3  Feature   Continuous        None        None  None   
28          concavity3  Feature   Continuous        None        None  None   
29     concave_points3  Feature   Continuous        None        None  None   
30           symmetry3  Feature   Continuous        None        None  None   
31  fractal_dimension3  Feature   Continuous        None        None  None   

   missing_values  
0              no  
1              no  
2              no  
3              no  
4              no  
5              no  
6              no  
7              no  
8              no  
9              no  
10             no  
11             no  
12             no  
13             no  
14             no  
15             no  
16             no  
17             no  
18             no  
19             no  
20             no  
21             no  
22             no  
23             no  
24             no  
25             no  
26             no  
27             no  
28             no  
29             no  
30             no  
31             no  

Dimensões do dataset: (569, 31)
Out[ ]:
radius_mean texture_mean perimeter_mean area_mean smoothness_mean compactness_mean concavity_mean concave_points_mean symmetry_mean fractal_dimension_mean ... texture_worst perimeter_worst area_worst smoothness_worst compactness_worst concavity_worst concave_points_worst symmetry_worst fractal_dimension_worst diagnosis
0 17.99 10.38 122.80 1001.0 0.11840 0.27760 0.3001 0.14710 0.2419 0.07871 ... 17.33 184.60 2019.0 0.1622 0.6656 0.7119 0.2654 0.4601 0.11890 M
1 20.57 17.77 132.90 1326.0 0.08474 0.07864 0.0869 0.07017 0.1812 0.05667 ... 23.41 158.80 1956.0 0.1238 0.1866 0.2416 0.1860 0.2750 0.08902 M
2 19.69 21.25 130.00 1203.0 0.10960 0.15990 0.1974 0.12790 0.2069 0.05999 ... 25.53 152.50 1709.0 0.1444 0.4245 0.4504 0.2430 0.3613 0.08758 M
3 11.42 20.38 77.58 386.1 0.14250 0.28390 0.2414 0.10520 0.2597 0.09744 ... 26.50 98.87 567.7 0.2098 0.8663 0.6869 0.2575 0.6638 0.17300 M
4 20.29 14.34 135.10 1297.0 0.10030 0.13280 0.1980 0.10430 0.1809 0.05883 ... 16.67 152.20 1575.0 0.1374 0.2050 0.4000 0.1625 0.2364 0.07678 M

5 rows × 31 columns

Análise Exploratória dos Dados¶

Nesta seção exploramos as dimensões, tipos de dados e estatísticas descritivas do conjunto. O dataset possui 569 amostras e 32 colunas (uma coluna de identificação, uma de diagnóstico e 30 atributos numéricos). A variável diagnosis possui duas classes: M (maligno) e B (benigno).

Também verificamos as principais estatísticas descritivas para compreender a distribuição dos atributos.

In [ ]:
# to´ps de dados e resumo estatístico
print("\n=== Tipos de Dados ===")
print(df.dtypes.head())

print("\n=== Estatísticas Descritivas ===")
describe = df.describe().transpose()
describe
=== Tipos de Dados ===
radius_mean        float64
texture_mean       float64
perimeter_mean     float64
area_mean          float64
smoothness_mean    float64
dtype: object

=== Estatísticas Descritivas ===
Out[ ]:
count mean std min 25% 50% 75% max
radius_mean 569.0 14.127292 3.524049 6.981000 11.700000 13.370000 15.780000 28.11000
texture_mean 569.0 19.289649 4.301036 9.710000 16.170000 18.840000 21.800000 39.28000
perimeter_mean 569.0 91.969033 24.298981 43.790000 75.170000 86.240000 104.100000 188.50000
area_mean 569.0 654.889104 351.914129 143.500000 420.300000 551.100000 782.700000 2501.00000
smoothness_mean 569.0 0.096360 0.014064 0.052630 0.086370 0.095870 0.105300 0.16340
compactness_mean 569.0 0.104341 0.052813 0.019380 0.064920 0.092630 0.130400 0.34540
concavity_mean 569.0 0.088799 0.079720 0.000000 0.029560 0.061540 0.130700 0.42680
concave_points_mean 569.0 0.048919 0.038803 0.000000 0.020310 0.033500 0.074000 0.20120
symmetry_mean 569.0 0.181162 0.027414 0.106000 0.161900 0.179200 0.195700 0.30400
fractal_dimension_mean 569.0 0.062798 0.007060 0.049960 0.057700 0.061540 0.066120 0.09744
radius_se 569.0 0.405172 0.277313 0.111500 0.232400 0.324200 0.478900 2.87300
texture_se 569.0 1.216853 0.551648 0.360200 0.833900 1.108000 1.474000 4.88500
perimeter_se 569.0 2.866059 2.021855 0.757000 1.606000 2.287000 3.357000 21.98000
area_se 569.0 40.337079 45.491006 6.802000 17.850000 24.530000 45.190000 542.20000
smoothness_se 569.0 0.007041 0.003003 0.001713 0.005169 0.006380 0.008146 0.03113
compactness_se 569.0 0.025478 0.017908 0.002252 0.013080 0.020450 0.032450 0.13540
concavity_se 569.0 0.031894 0.030186 0.000000 0.015090 0.025890 0.042050 0.39600
concave_points_se 569.0 0.011796 0.006170 0.000000 0.007638 0.010930 0.014710 0.05279
symmetry_se 569.0 0.020542 0.008266 0.007882 0.015160 0.018730 0.023480 0.07895
fractal_dimension_se 569.0 0.003795 0.002646 0.000895 0.002248 0.003187 0.004558 0.02984
radius_worst 569.0 16.269190 4.833242 7.930000 13.010000 14.970000 18.790000 36.04000
texture_worst 569.0 25.677223 6.146258 12.020000 21.080000 25.410000 29.720000 49.54000
perimeter_worst 569.0 107.261213 33.602542 50.410000 84.110000 97.660000 125.400000 251.20000
area_worst 569.0 880.583128 569.356993 185.200000 515.300000 686.500000 1084.000000 4254.00000
smoothness_worst 569.0 0.132369 0.022832 0.071170 0.116600 0.131300 0.146000 0.22260
compactness_worst 569.0 0.254265 0.157336 0.027290 0.147200 0.211900 0.339100 1.05800
concavity_worst 569.0 0.272188 0.208624 0.000000 0.114500 0.226700 0.382900 1.25200
concave_points_worst 569.0 0.114606 0.065732 0.000000 0.064930 0.099930 0.161400 0.29100
symmetry_worst 569.0 0.290076 0.061867 0.156500 0.250400 0.282200 0.317900 0.66380
fractal_dimension_worst 569.0 0.083946 0.018061 0.055040 0.071460 0.080040 0.092080 0.20750
In [ ]:
# distribuição da variável alvo
print("\n=== Distribuição de Diagnósticos ===")
diagnosis_counts = df['diagnosis'].value_counts()
print(f"Contagem:\n{diagnosis_counts}")

# config visual
sns.set_style("whitegrid")
plt.figure(figsize=(18, 6))

# mapear pra garantir q o gráfico mostre nomes legíveis (Benigno/Maligno)
label_map = {'M': 'Maligno', 'B': 'Benigno', 1: 'Maligno', 0: 'Benigno'}
color_map = {'Maligno': '#e74c3c', 'Benigno': '#3498db'} # Vermelho e Azul

# Preparando labels e cores baseados nos dados atuais
plot_labels = [label_map.get(x, str(x)) for x in diagnosis_counts.index]
plot_colors = [color_map.get(lbl, '#95a5a6') for lbl in plot_labels]

# 1. graph de Barras pra contagem
plt.subplot(1, 3, 1)
ax = sns.barplot(x=plot_labels, y=diagnosis_counts.values, palette=plot_colors)
plt.title('Contagem de Casos por Diagnóstico', fontsize=14, pad=15)
plt.xlabel('Diagnóstico', fontsize=12)
plt.ylabel('Número de Pacientes', fontsize=12)
plt.ylim(0, max(diagnosis_counts)*1.15)
for i, v in enumerate(diagnosis_counts.values):
    ax.text(i, v + 5, f"{v}", ha='center', va='bottom', fontsize=12, fontweight='bold')
sns.despine()

# 2. graph de Rosca pra proporção
plt.subplot(1, 3, 2)
plt.pie(diagnosis_counts, labels=plot_labels, autopct='%1.1f%%', 
        colors=plot_colors, startangle=90, pctdistance=0.85, explode=[0.05]*len(diagnosis_counts), 
        textprops={'fontsize': 12, 'weight': 'bold'})
centre_circle = plt.Circle((0,0),0.70,fc='white')
fig = plt.gcf()
fig.gca().add_artist(centre_circle)
plt.title('Proporção de Diagnósticos', fontsize=14, pad=15)

# 3. graph de Barras Horizontal com os dois juntos 
plt.subplot(1, 3, 3)
ax2 = sns.barplot(x=diagnosis_counts.values, y=plot_labels, palette=plot_colors, orient='h')
plt.title('Distribuição Detalhada', fontsize=14, pad=15)
plt.xlabel('Quantidade', fontsize=12)
plt.xlim(0, max(diagnosis_counts)*1.25)
total = sum(diagnosis_counts)
for i, v in enumerate(diagnosis_counts.values):
    ax2.text(v + 3, i, f"{v} ({v/total:.1%})", va='center', fontsize=11, fontweight='bold')
sns.despine(left=True)

plt.tight_layout()
plt.show()
=== Distribuição de Diagnósticos ===
Contagem:
diagnosis
B    357
M    212
Name: count, dtype: int64
No description has been provided for this image
In [ ]:
# histogramas com distribuição por features 
# aqui eu escolhi as 5 das 10 features com "mean" apenas para visualizar 
selected_features = ['radius_mean', 'texture_mean', 'perimeter_mean', 'area_mean', 'smoothness_mean']
plt.figure(figsize=(14, 8))
for i, feat in enumerate(selected_features):
    plt.subplot(2, 3, i + 1)
    sns.histplot(data=df, x=feat, hue='diagnosis', kde=True, element='step', stat='density', common_norm=False)
    plt.title(f'Distribuição de {feat}')
plt.tight_layout()
plt.show()

# boxplot pra comparar as classes (Benigno vs Maligno)
plt.figure(figsize=(14, 6))
data_melted = df[selected_features + ['diagnosis']].melt(id_vars='diagnosis', var_name='Feature', value_name='Value')
# normalizando para melhor visualização
from sklearn.preprocessing import StandardScaler
scaler = StandardScaler()
data_normalized = df[selected_features].copy()
data_normalized[selected_features] = scaler.fit_transform(data_normalized[selected_features])
data_normalized['diagnosis'] = df['diagnosis'].values
data_melted_norm = data_normalized.melt(id_vars='diagnosis', var_name='Feature', value_name='Value')

sns.boxplot(x='Feature', y='Value', hue='diagnosis', data=data_melted_norm, palette='Set2')
plt.xticks(rotation=45)
plt.title('Boxplot: Comparação das Variáveis entre Benigno (B) e Maligno (M)')
plt.legend(title='Diagnóstico', loc='upper right') 
plt.grid(True, alpha=0.3)
plt.tight_layout()
plt.show()
    
# Scatterplot pra visualizar separabilidade em 2D
plt.figure(figsize=(10, 6))
sns.scatterplot(x='radius_mean', y='texture_mean', hue='diagnosis', data=df, palette='coolwarm', alpha=0.7, s=60)
plt.title('Gráfico de Dispersão: Raio Médio vs Textura Média')
plt.legend(title='Diagnóstico', loc='upper right') 
plt.grid(True, alpha=0.3)
plt.show()

# Matriz de correlação entre as features q escolhi (sem diagnosis por enquanto)
plt.figure(figsize=(10,8))
sns.heatmap(df[selected_features].corr(), annot=True, cmap='coolwarm', fmt='.2f')
plt.title('Correlação entre Features Selecionadas')
plt.tight_layout()
plt.show()
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image

Tratamento de Dados, Classificação de Variáveis e Seleção de Atributos¶

Nesta etapa, vou reaizar:

  • Verificação de valores nulos ou inconsistentes: De acordo com a documentação, o dataset não possui valores ausentes. Ainda assim, verificamos nulos e duplicatas.
  • Classificação das variáveis: Todos os 30 atributos são numéricos contínuos. A variável diagnosis é categórica binária.
  • Seleção de atributos relevantes: Utilizamos a correlação e a importância das features para identificar quais variáveis contribuem mais para o modelo.
In [ ]:
# Conversão da variável-alvo para numérica (M=1, B=0)
df['diagnosis'] = df['diagnosis'].map({'M': 1, 'B': 0})
print("Variável 'diagnosis' convertida: M → 1 (Maligno), B → 0 (Benigno)")
print(f"Distribuição após conversão:\n{df['diagnosis'].value_counts().sort_index()}")
Variável 'diagnosis' convertida: M → 1 (Maligno), B → 0 (Benigno)
Distribuição após conversão:
diagnosis
0    357
1    212
Name: count, dtype: int64
In [ ]:
# Agora que diagnosis é numérico, dá pra descobrir quais variáveis mais influenciam o diagnóstico
# Método 1: Correlação Linear 

# Calculando a correlação de TODAS as features com a variável alvo 'diagnosis'
correlation_with_target = df.corr()['diagnosis'].drop('diagnosis').sort_values(ascending=False)

print("=== Top 10 Variáveis mais correlacionadas com Malignidade (Correlação Positiva) ===")
print(correlation_with_target.head(10))

# Visualizando as correlações mais fortes
plt.figure(figsize=(12, 8))
# Pegamos as 15 com maior correlação absoluta (seja positiva ou negativa)
top_correlations = correlation_with_target.abs().sort_values(ascending=False).head(15)
# Recuperamos os valores originais (com sinal) para essas 15
top_features_names = top_correlations.index
top_features_values = correlation_with_target[top_features_names]

sns.barplot(x=top_features_values.values, y=top_features_values.index, palette='coolwarm')
plt.title('Top 15 Variáveis com Maior Influência no Diagnóstico (Correlação)', fontsize=14)
plt.xlabel('Correlação com Diagnosis (1 = Maligno, 0 = Benigno)')
plt.axvline(0, color='black', linewidth=0.5)
plt.grid(axis='x', alpha=0.3)
plt.show()

# Matriz de correlação focada nas Top 10
plt.figure(figsize=(10, 8))
top_10_features = correlation_with_target.head(10).index.tolist()
sns.heatmap(df[top_10_features + ['diagnosis']].corr(), annot=True, cmap='coolwarm', fmt='.2f')
plt.title('Matriz de Correlação: Top 10 Features vs Diagnosis')
plt.tight_layout()
plt.show()
=== Top 10 Variáveis mais correlacionadas com Malignidade (Correlação Positiva) ===
concave_points_worst    0.793566
perimeter_worst         0.782914
concave_points_mean     0.776614
radius_worst            0.776454
perimeter_mean          0.742636
area_worst              0.733825
radius_mean             0.730029
area_mean               0.708984
concavity_mean          0.696360
concavity_worst         0.659610
Name: diagnosis, dtype: float64
No description has been provided for this image
No description has been provided for this image
In [ ]:
print('=== ETAPA 6: Tratamento de Dados Inválidos ou Inconsistentes ===')
print('Valores nulos em cada coluna:')
nulos = df.isnull().sum()
if nulos.sum() == 0:
    print("Nenhum valor nulo encontrado no dataset.")
else:
    print(nulos[nulos > 0])

print(f"\n=== ETAPA 7: Tratamento de Dados Faltantes ===")
print(f"Número de linhas duplicadas: {df.duplicated().sum()}")
if df.duplicated().sum() == 0:
    print("Nenhuma duplicata encontrada.")

print('\n=== ETAPA 9: Classificação das Variáveis ===')
numerical_features = [col for col in df.columns if col != 'diagnosis']
categorical_features = ['diagnosis']

print(f"Variáveis Numéricas Contínuas ({len(numerical_features)}): {numerical_features[:5]}... (primeiras 5)")
print(f"Variável Categórica Binária: {categorical_features}")
print(f"  - 0 = Benigno (B)")
print(f"  - 1 = Maligno (M)")

print('\n=== ETAPA 10: Separação entre Atributos Preditores (X) e Variável-Alvo (y) ===')
X = df.drop(columns=['diagnosis'])
y = df['diagnosis']
print(f"Shape de X (features): {X.shape}")
print(f"Shape de y (target): {y.shape}")
print(f"Distribuição da variável alvo:\n{y.value_counts()}")

print('\n=== ETAPA 8: Seleção de Atributos Relevantes ===')
# calcula correlação absoluta
corr_matrix = X.corr().abs()
upper = corr_matrix.where(np.triu(np.ones(corr_matrix.shape), k=1).astype(bool))

# selecionando features com correlação acima de um limiar de 0,95
threshold = 0.95
to_drop = [column for column in upper.columns if any(upper[column] > threshold)]

print(f"Atributos altamente correlacionados a serem removidos (threshold={threshold}):")
print(to_drop if to_drop else "Nenhum atributo removido com este threshold.")

# remov features correlacionadas
X_reduced = X.drop(columns=to_drop)

# exibindo dimensões após redução
print(f'\nDimensão original de X: {X.shape}')
print(f'Dimensão reduzida de X: {X_reduced.shape}')
print(f'Features removidas: {len(to_drop)}')
=== ETAPA 6: Tratamento de Dados Inválidos ou Inconsistentes ===
Valores nulos em cada coluna:
Nenhum valor nulo encontrado no dataset.

=== ETAPA 7: Tratamento de Dados Faltantes ===
Número de linhas duplicadas: 0
Nenhuma duplicata encontrada.

=== ETAPA 9: Classificação das Variáveis ===
Variáveis Numéricas Contínuas (30): ['radius_mean', 'texture_mean', 'perimeter_mean', 'area_mean', 'smoothness_mean']... (primeiras 5)
Variável Categórica Binária: ['diagnosis']
  - 0 = Benigno (B)
  - 1 = Maligno (M)

=== ETAPA 10: Separação entre Atributos Preditores (X) e Variável-Alvo (y) ===
Shape de X (features): (569, 30)
Shape de y (target): (569,)
Distribuição da variável alvo:
diagnosis
0    357
1    212
Name: count, dtype: int64

=== ETAPA 8: Seleção de Atributos Relevantes ===
Atributos altamente correlacionados a serem removidos (threshold=0.95):
['perimeter_mean', 'area_mean', 'perimeter_se', 'area_se', 'radius_worst', 'perimeter_worst', 'area_worst']

Dimensão original de X: (569, 30)
Dimensão reduzida de X: (569, 23)
Features removidas: 7
In [ ]:
print('=== ETAPA 13: Divisão dos Dados em Conjunto de Treino e Teste ===')
X_train, X_test, y_train, y_test = train_test_split(X_reduced, y, test_size=0.2, random_state=42, stratify=y)
print(f"Tamanho do conjunto de treino: {X_train.shape[0]} amostras")
print(f"Tamanho do conjunto de teste: {X_test.shape[0]} amostras")
print(f"Proporção de classes no treino:\n{y_train.value_counts(normalize=True)}")
print(f"Proporção de classes no teste:\n{y_test.value_counts(normalize=True)}")

print('\n=== ETAPA 14 e 15: Treinamento e Avaliação dos Modelos ===\n')

models = {
    'Regressão Logística': LogisticRegression(max_iter=10000),
    'Random Forest': RandomForestClassifier(n_estimators=200, random_state=42)
}

results = {}
trained_pipelines = {}  # Armazena os modelos treinados

for name, model in models.items():
    # Pipeline com padronização (StandardScaler) e classificador
    pipeline = Pipeline([
        ('scaler', StandardScaler()),
        ('classifier', model)
    ])
    pipeline.fit(X_train, y_train)
    y_pred = pipeline.predict(X_test)
    
    # Salva o pipeline treinado
    trained_pipelines[name] = pipeline
    
    # Métricas de avaliação
    acc = accuracy_score(y_test, y_pred)
    prec = precision_score(y_test, y_pred)
    rec = recall_score(y_test, y_pred)
    f1 = f1_score(y_test, y_pred)
    results[name] = {'Acurácia': acc, 'Precisão': prec, 'Recall': rec, 'F1-score': f1}
    
    print(f"\n{'='*50}")
    print(f"== {name} ==")
    print(f"{'='*50}")
    print(classification_report(y_test, y_pred, target_names=['Benigno','Maligno']))
    
    # Matriz de Confusão
    cm = confusion_matrix(y_test, y_pred)
    plt.figure(figsize=(6, 5))
    sns.heatmap(cm, annot=True, fmt='d', cmap='Blues', cbar=False)
    plt.title(f'Matriz de Confusão - {name}')
    plt.xlabel('Classe Predita')
    plt.ylabel('Classe Verdadeira')
    plt.tight_layout()
    plt.show()

# Compilando resultados em DataFrame
print('\n=== RESUMO DAS MÉTRICAS ===')
results_df = pd.DataFrame(results).T
results_df
=== ETAPA 13: Divisão dos Dados em Conjunto de Treino e Teste ===
Tamanho do conjunto de treino: 455 amostras
Tamanho do conjunto de teste: 114 amostras
Proporção de classes no treino:
diagnosis
0    0.626374
1    0.373626
Name: proportion, dtype: float64
Proporção de classes no teste:
diagnosis
0    0.631579
1    0.368421
Name: proportion, dtype: float64

=== ETAPA 14 e 15: Treinamento e Avaliação dos Modelos ===


==================================================
== Regressão Logística ==
==================================================
              precision    recall  f1-score   support

     Benigno       0.96      0.99      0.97        72
     Maligno       0.97      0.93      0.95        42

    accuracy                           0.96       114
   macro avg       0.97      0.96      0.96       114
weighted avg       0.97      0.96      0.96       114

No description has been provided for this image
==================================================
== Random Forest ==
==================================================
              precision    recall  f1-score   support

     Benigno       0.93      0.99      0.96        72
     Maligno       0.97      0.88      0.93        42

    accuracy                           0.95       114
   macro avg       0.95      0.93      0.94       114
weighted avg       0.95      0.95      0.95       114

No description has been provided for this image
=== RESUMO DAS MÉTRICAS ===
Out[ ]:
Acurácia Precisão Recall F1-score
Regressão Logística 0.964912 0.975000 0.928571 0.95122
Random Forest 0.947368 0.973684 0.880952 0.92500
In [ ]:
# Análise de Importância das Features (Random Forest)
print('=== Importância dos Atributos (Feature Importance) ===\n')

# Reutilizando o Random Forest já treinado
rf_pipeline = trained_pipelines['Random Forest']
rf = rf_pipeline.named_steps['classifier']
importances = rf.feature_importances_
feat_importance = pd.Series(importances, index=X_reduced.columns).sort_values(ascending=False)

# Top 10 features mais importantes
top_features = feat_importance.head(10)

plt.figure(figsize=(10,6))
top_features.plot(kind='barh', color='teal')
plt.gca().invert_yaxis()
plt.title('Top 10 Features - Random Forest')
plt.xlabel('Importância Relativa')
plt.ylabel('Atributo')
plt.grid(True, alpha=0.3, axis='x')
plt.tight_layout()
plt.show()

# Exibindo a importância numericamente
print("Top 10 Features:")
print(top_features)
=== Importância dos Atributos (Feature Importance) ===

No description has been provided for this image
Top 10 Features:
concave_points_mean     0.188772
concave_points_worst    0.186250
radius_mean             0.128306
concavity_mean          0.089392
concavity_worst         0.083990
radius_se               0.056054
compactness_worst       0.034914
texture_worst           0.028789
compactness_mean        0.028039
texture_mean            0.021289
dtype: float64
In [ ]:
print('=== Importância das Features na Regressão Logística ===\n')

# Reutilizando a Regressão Logística já treinada
log_reg_pipeline = trained_pipelines['Regressão Logística']
log_reg = log_reg_pipeline.named_steps['classifier']
coefs = log_reg.coef_[0]  # vetor de coeficientes da classe 1

# serie com os coeficientes, indexada pelos nomes das colunas
coef_series = pd.Series(coefs, index=X_reduced.columns)

# vers ordenada por importancia
coef_abs_sorted = coef_series.reindex(coef_series.abs().sort_values(ascending=False).index)

print('Coeficientes (ordenados por |coef|):')
print(coef_abs_sorted)

# opcional: gráfico das top 10 features em módulo
top10_logreg = coef_abs_sorted.head(10)

plt.figure(figsize=(10, 6))
top10_logreg.plot(kind='barh')
plt.gca().invert_yaxis()
plt.title('Top 10 Features (|coef|) - Regressão Logística')
plt.xlabel('Coeficiente (escala padronizada)')
plt.ylabel('Feature')
plt.grid(True, axis='x', alpha=0.3)
plt.tight_layout()
plt.show()
=== Importância das Features na Regressão Logística ===

Coeficientes (ordenados por |coef|):
radius_se                  2.316904
radius_mean                1.772013
texture_worst              1.427858
concave_points_mean        1.343847
symmetry_worst             1.153961
concavity_mean             0.939144
concavity_worst            0.895618
concave_points_worst       0.851972
compactness_se            -0.710962
texture_se                -0.503833
fractal_dimension_mean    -0.501742
symmetry_se               -0.479130
fractal_dimension_se      -0.455980
concave_points_se          0.428322
texture_mean               0.421034
smoothness_worst           0.295889
compactness_mean          -0.268773
concavity_se              -0.245994
symmetry_mean             -0.244083
smoothness_mean            0.238055
smoothness_se              0.217668
compactness_worst         -0.179016
fractal_dimension_worst    0.032173
dtype: float64
No description has been provided for this image

Conclusão¶

Neste projeto, construímos um pipeline completo de ciência de dados para diagnosticar câncer de mama utilizando o conjunto de dados WDBC. O processo incluiu a obtenção dos dados diretamente do repositório da UCI, análise exploratória, tratamento de dados, seleção de atributos, divisão em conjuntos de treino e teste, treinamento de modelos supervisionados e avaliação de desempenho.

Resultados dos Modelos:

A tabela de resultados resume as métricas principais (acurácia, precisão, recall e F1-score) para a Regressão Logística e o Random Forest. O modelo Regressão Logística apresentou desempenho superior, alcançando 96,5% de acurácia e 92,9% de recall, enquanto o Random Forest obteve 94,7% de acurácia e 88,1% de recall.

O recall elevado da Regressão Logística é especialmente crucial em diagnósticos médicos, pois minimiza os falsos negativos (casos malignos classificados como benignos), garantindo que pacientes com câncer sejam corretamente identificados para tratamento. A precisão de 97,5% da Regressão Logística também indica poucos falsos positivos, evitando alarmes desnecessários. Isso demonstra que, para este dataset, modelos lineares podem ser tão ou mais eficazes que modelos complexos quando os dados são bem estruturados.

Atributos Mais Importantes:

A análise de importância das features no Random Forest indicou que concave_points_mean (19,3%), concave_points_worst (18,0%) e radius_mean (11,7%) são as variáveis mais relevantes para a classificação. Essas características estão relacionadas à geometria e concavidade dos núcleos celulares nas imagens FNA, alinhando-se a estudos anteriores que apontam medidas de concavidade e tamanho como fortes indicadores de malignidade. Tais atributos descrevem características morfológicas das células tumorais e ajudam a distinguir tumores malignos de benignos.

Este estudo demonstra o potencial de técnicas de machine learning, especialmente modelos interpretáveis como Regressão Logística, na detecção de câncer de mama, e reforça a importância da disponibilidade de datasets públicos para pesquisa.