Skip to content

Projeto de Predição de Doenças Cardiovasculares

Este projeto tem como objetivo aplicar técnicas de Machine Learning para prever a presença de doenças cardiovasculares a partir de variáveis clínicas. O desenvolvimento segue etapas bem definidas, cada uma com critérios e pontuação, conforme especificado na rubrica do projeto.


Etapas do Projeto

1. Exploração dos Dados

Descrição do Conjunto de Dados

O dataset utilizado foi obtido no Kaggle e reúne informações provenientes de cinco bases distintas do UCI Machine Learning Repository.
Após a remoção de duplicatas, o conjunto final contém 918 observações e 12 variáveis, sendo 11 atributos preditores e 1 variável alvo (HeartDisease).

Variáveis

  • Age: idade do paciente (anos)
  • Sex: sexo (M = Masculino, F = Feminino)
  • ChestPainType: tipo de dor no peito
  • TA: Angina Típica
  • ATA: Angina Atípica
  • NAP: Dor Não-Anginosa
  • ASY: Assintomático
  • RestingBP: pressão arterial em repouso (mm Hg)
  • Cholesterol: colesterol sérico (mg/dl)
  • FastingBS: glicemia em jejum (>120 mg/dl = 1, caso contrário = 0)
  • RestingECG: resultados do eletrocardiograma em repouso
  • MaxHR: frequência cardíaca máxima atingida
  • ExerciseAngina: angina induzida por exercício (Y/N)
  • Oldpeak: depressão do segmento ST
  • ST_Slope: inclinação do segmento ST (Up, Flat, Down)
  • HeartDisease: variável alvo (0 = normal, 1 = presença de doença)

Estatísticas Descritivas e Visualizações

  • Idade: varia entre ~28 e 77 anos, com média em torno de 53 anos.
2025-09-02T14:08:34.942896 image/svg+xml Matplotlib v3.10.6, https://matplotlib.org/
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from io import BytesIO

# carregar dataset
df = pd.read_csv("https://raw.githubusercontent.com/bligui/Machine-Learning-Ana/refs/heads/main/dados/heart.csv")

# tratar dados
df.fillna(df.median(numeric_only=True), inplace=True)
df = df.drop_duplicates()


# --- PLOT ---
fig, ax = plt.subplots(1, 1, figsize=(8, 5))

# histograma normalizado
ax.hist(df["Age"], bins=20, color="pink", edgecolor="lightcoral", alpha=0.6, density=True)

# curva de densidade (gaussiana simples)
x_vals = np.linspace(df["Age"].min(), df["Age"].max(), 200)
mean = df["Age"].mean()
std = df["Age"].std()
pdf = (1 / (std * np.sqrt(2 * np.pi))) * np.exp(-0.5 * ((x_vals - mean) / std) ** 2)

ax.plot(x_vals, pdf, color="darkred", linewidth=2, label="Curva de densidade")

ax.set_title("Distribuição da Idade")
ax.set_xlabel("Idade")
ax.set_ylabel("Densidade")
ax.grid(axis="y", linestyle="--", alpha=0.6)
ax.legend()

buffer = BytesIO()
plt.savefig(buffer, format="svg", bbox_inches="tight")
buffer.seek(0)
print(buffer.getvalue().decode("utf-8"))
  • Sexo: há predominância do sexo masculino no conjunto.
2025-09-02T14:08:35.124643 image/svg+xml Matplotlib v3.10.6, https://matplotlib.org/
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from io import BytesIO

# carregar dataset
df = pd.read_csv("https://raw.githubusercontent.com/bligui/Machine-Learning-Ana/refs/heads/main/dados/heart.csv")

# tratar dados
df.fillna(df.median(numeric_only=True), inplace=True)
df = df.drop_duplicates()


# --- PLOT: Distribuição por Sexo ---
fig, ax = plt.subplots(1, 1, figsize=(6, 4))

counts = df["Sex"].value_counts().sort_index()
ax.bar(counts.index, counts.values, color=["pink", "skyblue"], edgecolor="lightcoral")

ax.set_title("Distribuição por Sexo")
ax.set_xlabel("Sexo (F=0, M=1)")
ax.set_ylabel("Contagem")
ax.set_xticks([0, 1])
ax.set_xticklabels(["Feminino", "Masculino"])
ax.grid(axis="y", linestyle="--", alpha=0.6)

# salvar em buffer como SVG
buffer = BytesIO()
plt.savefig(buffer, format="svg", bbox_inches="tight")
buffer.seek(0)
print(buffer.getvalue().decode("utf-8"))
  • Colesterol: grande variabilidade, com valores fora da faixa esperada em alguns casos.
2025-09-02T14:08:35.248432 image/svg+xml Matplotlib v3.10.6, https://matplotlib.org/
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from io import BytesIO

# carregar dataset
df = pd.read_csv("https://raw.githubusercontent.com/bligui/Machine-Learning-Ana/refs/heads/main/dados/heart.csv")

# tratar dados
df.fillna(df.median(numeric_only=True), inplace=True)
df = df.drop_duplicates()


# --- PLOT: Boxplot do Colesterol ---
fig, ax = plt.subplots(figsize=(7, 5))

bp = ax.boxplot(df["Cholesterol"], patch_artist=True, widths=0.5)

# customização do estilo
for box in bp["boxes"]:
    box.set(facecolor="pink", edgecolor="lightcoral", linewidth=1.2)
for whisker in bp["whiskers"]:
    whisker.set(color="lightcoral", linewidth=1.2)
for cap in bp["caps"]:
    cap.set(color="lightcoral", linewidth=1.2)
for median in bp["medians"]:
    median.set(color="darkred", linewidth=1.5)

ax.set_title("Boxplot do Colesterol")
ax.set_xlabel("Colesterol (mg/dl)")
ax.set_xticks([])  # remove eixo X categórico, já que é uma única variável
ax.grid(axis="y", linestyle="--", alpha=0.6)

# salvar em buffer como SVG
buffer = BytesIO()
plt.savefig(buffer, format="svg", bbox_inches="tight")
buffer.seek(0)
print(buffer.getvalue().decode("utf-8"))
  • Pressão Arterial em Repouso: média próxima de 130 mm Hg, condizente com casos de hipertensão.
2025-09-02T14:08:35.374637 image/svg+xml Matplotlib v3.10.6, https://matplotlib.org/
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from io import BytesIO

# carregar dataset
df = pd.read_csv("https://raw.githubusercontent.com/bligui/Machine-Learning-Ana/refs/heads/main/dados/heart.csv")

# tratar dados
df.fillna(df.median(numeric_only=True), inplace=True)
df = df.drop_duplicates()


# --- PLOT: Boxplot da Pressão Arterial em Repouso ---
fig, ax = plt.subplots(figsize=(7, 5))

bp = ax.boxplot(df["RestingBP"], patch_artist=True, widths=0.5)

# customização
for box in bp["boxes"]:
    box.set(facecolor="pink", edgecolor="lightcoral", linewidth=1.2)
for whisker in bp["whiskers"]:
    whisker.set(color="lightcoral", linewidth=1.2)
for cap in bp["caps"]:
    cap.set(color="lightcoral", linewidth=1.2)
for median in bp["medians"]:
    median.set(color="darkred", linewidth=1.5)

ax.set_title("Boxplot da Pressão Arterial em Repouso")
ax.set_xlabel("Pressão Arterial (mm Hg)")
ax.set_xticks([])  # remove eixo X categórico
ax.grid(axis="y", linestyle="--", alpha=0.6)

# salvar em buffer como SVG
buffer = BytesIO()
plt.savefig(buffer, format="svg", bbox_inches="tight")
buffer.seek(0)
print(buffer.getvalue().decode("utf-8"))
  • MaxHR: varia entre 60 e 202, indicando ampla faixa de condicionamento físico.
2025-09-02T14:08:35.539541 image/svg+xml Matplotlib v3.10.6, https://matplotlib.org/
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from io import BytesIO

# carregar dataset
df = pd.read_csv("https://raw.githubusercontent.com/bligui/Machine-Learning-Ana/refs/heads/main/dados/heart.csv")

# tratar dados
df.fillna(df.median(numeric_only=True), inplace=True)
df = df.drop_duplicates()


# --- PLOT: Histograma + Curva de Densidade ---
fig, ax = plt.subplots(figsize=(8, 5))

# histograma normalizado
ax.hist(df["MaxHR"], bins=20, color="pink", edgecolor="lightcoral", alpha=0.6, density=True)

# curva de densidade gaussiana
x_vals = np.linspace(df["MaxHR"].min(), df["MaxHR"].max(), 200)
mean = df["MaxHR"].mean()
std = df["MaxHR"].std()
pdf = (1 / (std * np.sqrt(2 * np.pi))) * np.exp(-0.5 * ((x_vals - mean) / std) ** 2)
ax.plot(x_vals, pdf, color="darkred", linewidth=2, label="Curva de densidade")

ax.set_title("Distribuição da Frequência Cardíaca Máxima (MaxHR)")
ax.set_xlabel("MaxHR")
ax.set_ylabel("Frequência")
ax.grid(axis="y", linestyle="--", alpha=0.6)
ax.legend()

# salvar em buffer como SVG
buffer = BytesIO()
plt.savefig(buffer, format="svg", bbox_inches="tight")
buffer.seek(0)
print(buffer.getvalue().decode("utf-8"))
  • Distribuição da Variável Alvo (HeartDisease): aproximadamente 55% dos pacientes apresentam diagnóstico positivo, o que gera uma base relativamente balanceada para treinamento.
2025-09-02T14:08:35.705388 image/svg+xml Matplotlib v3.10.6, https://matplotlib.org/
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from io import BytesIO

# carregar dataset
df = pd.read_csv("https://raw.githubusercontent.com/bligui/Machine-Learning-Ana/refs/heads/main/dados/heart.csv")

# tratar dados
df.fillna(df.median(numeric_only=True), inplace=True)
df = df.drop_duplicates()

# --- PLOT: Distribuição da Variável Alvo ---
fig, ax = plt.subplots(figsize=(6, 4))

counts = df["HeartDisease"].value_counts().sort_index()
ax.bar(counts.index, counts.values, color=["pink", "lightcoral"], edgecolor="darkred")

ax.set_title("Distribuição da Variável Alvo (HeartDisease)")
ax.set_xlabel("HeartDisease (0 = Não, 1 = Sim)")
ax.set_ylabel("Contagem")
ax.set_xticks([0, 1])
ax.set_xticklabels(["Não", "Sim"])
ax.grid(axis="y", linestyle="--", alpha=0.6)

# salvar em buffer como SVG
buffer = BytesIO()
plt.savefig(buffer, format="svg", bbox_inches="tight")
buffer.seek(0)
print(buffer.getvalue().decode("utf-8"))

Conclusões

  • Distribuição da idade mostra maior concentração entre 45 e 60 anos.
  • Proporção por sexo evidencia predominância masculina.
  • Boxplots de colesterol e pressão arterial revelam a presença de outliers que devem ser tratados no pré-processamento.
  • Relação entre ChestPainType e HeartDisease indica que pacientes assintomáticos (ASY) têm maior probabilidade de diagnóstico positivo.
2025-09-02T14:08:35.855863 image/svg+xml Matplotlib v3.10.6, https://matplotlib.org/
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from io import BytesIO

# carregar dataset
df = pd.read_csv("https://raw.githubusercontent.com/bligui/Machine-Learning-Ana/refs/heads/main/dados/heart.csv")

# tratar dados
df.fillna(df.median(numeric_only=True), inplace=True)
df = df.drop_duplicates()

# --- PLOT: ChestPainType vs HeartDisease ---
fig, ax = plt.subplots(figsize=(8, 5))

# contar combinações
counts = df.groupby(["ChestPainType", "HeartDisease"]).size().unstack(fill_value=0)

# pegar as classes de HeartDisease dinamicamente (0 e 1)
cols = counts.columns.tolist()

# posições das barras
x = np.arange(len(counts.index))
width = 0.35

# barras
bars1 = ax.bar(x - width/2, counts[cols[0]], width, label="Não", color="pink", edgecolor="darkred")
bars2 = ax.bar(x + width/2, counts[cols[1]], width, label="Sim", color="salmon", edgecolor="darkred")

# adicionar valores de contagem acima das barras
for bar in list(bars1) + list(bars2):
    height = bar.get_height()
    ax.text(bar.get_x() + bar.get_width()/2, height + 0.5, str(int(height)),
            ha="center", va="bottom", fontsize=9)

ax.set_title("Relação entre ChestPainType e HeartDisease")
ax.set_xlabel("Tipo de Dor no Peito")
ax.set_ylabel("Contagem")
ax.set_xticks(x)
ax.set_xticklabels(counts.index)  # usa os nomes originais (TA, ATA, NAP, ASY)
ax.legend(title="HeartDisease")
ax.grid(axis="y", linestyle="--", alpha=0.6)

# salvar em buffer como SVG
buffer = BytesIO()
plt.savefig(buffer, format="svg", bbox_inches="tight")
buffer.seek(0)
print(buffer.getvalue().decode("utf-8"))

2. Pré-processamento

Limpeza dos Dados

Antes de qualquer análise ou modelagem, é essencial garantir que os dados estejam consistentes e utilizáveis.
Neste passo, foram realizadas duas etapas principais:

  • Tratamento de valores ausentes: substituímos os valores nulos nas colunas numéricas pela mediana, pois ela é menos sensível a outliers do que a média.
  • Detecção de outliers: utilizamos o Z-Score para identificar valores que se desviam muito da distribuição normal dos dados. Embora os outliers não tenham sido removidos nesta etapa, sua identificação é fundamental para entender possíveis distorções.

Número de outliers detectados: 19

Age Sex ChestPainType RestingBP Cholesterol FastingBS RestingECG MaxHR ExerciseAngina Oldpeak ST_Slope HeartDisease
63 F ATA 140 195 0 Normal 179 N 0 Up 0
53 M NAP 145 518 0 Normal 130 N 0 Flat 1
65 M ASY 160 0 1 ST 122 N 1.2 Flat 1
56 M ASY 130 0 0 LVH 122 Y 1 Flat 1
54 M ATA 108 309 0 Normal 156 N 0 Up 0
67 M ASY 125 254 1 Normal 163 N 0.2 Flat 1
56 M ASY 120 0 0 ST 148 N 0 Flat 1
69 M NAP 142 271 0 LVH 126 N 0.3 Up 0
46 M TA 140 272 1 Normal 175 N 2 Flat 1
58 M ASY 120 0 0 LVH 106 Y 1.5 Down 1
import pandas as pd
import numpy as np

def preprocess(df):
    df.fillna(df.median(numeric_only=True), inplace=True)
    num_df = df.select_dtypes(include=[np.number])
    z_scores = np.abs((num_df - num_df.mean()) / num_df.std())
    outliers = (z_scores > 3).sum().sum()
    # imprime só o número (sem quebrar a tabela depois)
    print(f"**Número de outliers detectados:** {outliers}\n")
    return df

df = pd.read_csv("https://raw.githubusercontent.com/bligui/Machine-Learning-Ana/refs/heads/main/dados/heart.csv")
df = preprocess(df)

# imprime tabela em markdown (sem texto junto)
print(df.sample(n=10, random_state=42).to_markdown(index=False))

Codificação de Variáveis Categóricas

Os algoritmos de Machine Learning trabalham apenas com valores numéricos. Portanto, variáveis categóricas como "Sex", "ChestPainType", "RestingECG", "ExerciseAngina" e "ST_Slope" foram convertidas em números usando mapeamento direto.

Exemplos:
- "M"1 e "F"0
- "ASY"3, "NAP"2, "ATA"1, "TA"0

Essa etapa é essencial para que o modelo consiga interpretar as categorias sem perder o significado original.

Age Sex ChestPainType RestingBP Cholesterol FastingBS RestingECG MaxHR ExerciseAngina Oldpeak ST_Slope HeartDisease
63 0 1 140 195 0 0 179 0 0 0 0
53 1 2 145 518 0 0 130 0 0 1 1
65 1 3 160 0 1 1 122 0 1.2 1 1
56 1 3 130 0 0 2 122 1 1 1 1
54 1 1 108 309 0 0 156 0 0 0 0
67 1 3 125 254 1 0 163 0 0.2 1 1
56 1 3 120 0 0 1 148 0 0 1 1
69 1 2 142 271 0 2 126 0 0.3 0 0
46 1 0 140 272 1 0 175 0 2 1 1
58 1 3 120 0 0 2 106 1 1.5 2 1
import pandas as pd
import numpy as np

def preprocess(df):
    # Preencher valores ausentes
    df.fillna(df.median(numeric_only=True), inplace=True)

    # Detectar outliers (Z-Score)
    num_df = df.select_dtypes(include=[np.number])
    z_scores = np.abs((num_df - num_df.mean()) / num_df.std())
    outliers = (z_scores > 3).sum().sum()

    # Encoding categórico
    df["Sex"] = df["Sex"].map({"M": 1, "F": 0})
    df["ChestPainType"] = df["ChestPainType"].map({"TA": 0, "ATA": 1, "NAP": 2, "ASY": 3})
    df["RestingECG"] = df["RestingECG"].map({"Normal": 0, "ST": 1, "LVH": 2})
    df["ExerciseAngina"] = df["ExerciseAngina"].map({"Y": 1, "N": 0})
    df["ST_Slope"] = df["ST_Slope"].map({"Up": 0, "Flat": 1, "Down": 2})

    return df

# Carregar dataset
df = pd.read_csv("https://raw.githubusercontent.com/bligui/Machine-Learning-Ana/refs/heads/main/dados/heart.csv")

# Pré-processamento
df = preprocess(df)

# Exibir amostra (markdown)
print(df.sample(n=10, random_state=42).to_markdown(index=False))

Normalização e Padronização

Depois da limpeza e codificação, aplicamos técnicas de reescalonamento dos dados numéricos:

  • Min-Max Normalization: transforma os valores de cada coluna para o intervalo [0, 1]. Essa técnica é útil quando queremos preservar a proporção entre valores, mas garantir que todos estejam no mesmo intervalo.
  • Standardization (Z-Score): transforma os dados para que tenham média 0 e desvio padrão 1, centralizando a distribuição. Essa técnica é mais adequada quando os dados seguem (ou se aproximam de) uma distribuição normal.

Ambas as técnicas têm como objetivo evitar que variáveis com escalas diferentes dominem o aprendizado dos algoritmos de Machine Learning.

Standardized (Z-score)

Age Sex ChestPainType RestingBP Cholesterol FastingBS RestingECG MaxHR ExerciseAngina Oldpeak ST_Slope HeartDisease
1.00599 -1.93711 -1.34435 0.410685 -0.034736 -0.551041 -0.748772 1.65711 -0.823108 -0.831979 -1.05154 -1.11251
-0.0541624 0.515671 -0.270275 0.680749 2.91816 -0.551041 -0.748772 -0.26745 -0.823108 -0.831979 0.595753 0.897891
1.21802 0.515671 0.803804 1.49094 -1.81744 1.81277 0.491973 -0.581664 -0.823108 0.293123 0.595753 0.897891
0.263883 0.515671 0.803804 -0.129442 -1.81744 -0.551041 1.73272 -0.581664 1.21358 0.105606 0.595753 0.897891
0.0518527 0.515671 -1.34435 -1.31772 1.00746 -0.551041 -0.748772 0.753746 -0.823108 -0.831979 -1.05154 -1.11251
1.43005 0.515671 0.803804 -0.399506 0.504648 1.81277 -0.748772 1.02868 -0.823108 -0.644462 0.595753 0.897891
0.263883 0.515671 0.803804 -0.66957 -1.81744 -0.551041 0.491973 0.439532 -0.823108 -0.831979 0.595753 0.897891
1.64208 0.515671 -0.270275 0.51871 0.660063 -0.551041 1.73272 -0.424557 -0.823108 -0.550703 -1.05154 -1.11251
-0.796268 0.515671 -2.41843 0.410685 0.669205 1.81277 -0.748772 1.50001 -0.823108 1.04319 0.595753 0.897891
0.475913 0.515671 0.803804 -0.66957 -1.81744 -0.551041 1.73272 -1.21009 1.21358 0.574398 2.24305 0.897891

Min-Max Normalized

Age Sex ChestPainType RestingBP Cholesterol FastingBS RestingECG MaxHR ExerciseAngina Oldpeak ST_Slope HeartDisease
0.714286 0 0.333333 0.7 0.323383 0 0 0.838028 0 0.295455 0 0
0.510204 1 0.666667 0.725 0.859038 0 0 0.492958 0 0.295455 0.5 1
0.755102 1 1 0.8 0 1 0.5 0.43662 0 0.431818 0.5 1
0.571429 1 1 0.65 0 0 1 0.43662 1 0.409091 0.5 1
0.530612 1 0.333333 0.54 0.512438 0 0 0.676056 0 0.295455 0 0
0.795918 1 1 0.625 0.421227 1 0 0.725352 0 0.318182 0.5 1
0.571429 1 1 0.6 0 0 0.5 0.619718 0 0.295455 0.5 1
0.836735 1 0.666667 0.71 0.44942 0 1 0.464789 0 0.329545 0 0
0.367347 1 0 0.7 0.451078 1 0 0.809859 0 0.522727 0.5 1
0.612245 1 1 0.6 0 0 1 0.323944 1 0.465909 1 1

import pandas as pd
import numpy as np

def preprocess(df):
    # Preencher valores ausentes
    df.fillna(df.median(numeric_only=True), inplace=True)

    # Detectar outliers com Z-Score
    num_df = df.select_dtypes(include=[np.number])
    z_scores = np.abs((num_df - num_df.mean()) / num_df.std())
    outliers = (z_scores > 3).sum().sum()

    # Encoding categórico
    df["Sex"] = df["Sex"].map({"M": 1, "F": 0})
    df["ChestPainType"] = df["ChestPainType"].map({"TA": 0, "ATA": 1, "NAP": 2, "ASY": 3})
    df["RestingECG"] = df["RestingECG"].map({"Normal": 0, "ST": 1, "LVH": 2})
    df["ExerciseAngina"] = df["ExerciseAngina"].map({"Y": 1, "N": 0})
    df["ST_Slope"] = df["ST_Slope"].map({"Up": 0, "Flat": 1, "Down": 2})

    return df

# Carregar dataset
df = pd.read_csv("https://raw.githubusercontent.com/bligui/Machine-Learning-Ana/refs/heads/main/dados/heart.csv")

# Pré-processamento
df = preprocess(df)

# Selecionar colunas numéricas
numeric_cols = df.select_dtypes(include=[np.number]).columns

# Min-Max Normalization
df_minmax = df.copy()
df_minmax[numeric_cols] = (df_minmax[numeric_cols] - df_minmax[numeric_cols].min()) / (df_minmax[numeric_cols].max() - df_minmax[numeric_cols].min())

# Standardization (Z-score)
df_standard = df.copy()
df_standard[numeric_cols] = (df_standard[numeric_cols] - df_standard[numeric_cols].mean()) / df_standard[numeric_cols].std()

# Mostrar resultados em tabelas markdown
print("### Min-Max Normalized\n")
print(df_minmax.sample(n=10, random_state=42).to_markdown(index=False))

print("\n### Standardized (Z-score)\n")
print(df_standard.sample(n=10, random_state=42).to_markdown(index=False))

3. Divisão dos Dados

Após o pré-processamento do dataset (preenchimento de valores ausentes, codificação de variáveis categóricas e normalização/padronização), os dados foram divididos em conjuntos de treino e teste.
- Treinamento (70%): utilizado para ajustar os parâmetros do modelo Decision Tree.
- Teste (30%): utilizado para avaliar o desempenho do modelo em dados não vistos.

No código, a divisão foi feita com train_test_split do scikit-learn, garantindo uma separação aleatória, mas reproduzível com random_state=42.

# dividir variáveis
X = df.drop("HeartDisease", axis=1)
y = df["HeartDisease"]

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

4. Treinamento do Modelo

O modelo Decision Tree Classifier foi criado e treinado com os dados de treino (X_train e y_train).
- Cada nó da árvore representa uma decisão baseada em uma feature.
- Cada folha da árvore representa a classe final prevista (0 = No Disease ou 1 = Disease).

O treinamento foi realizado com:

classifier = tree.DecisionTreeClassifier(max_depth=3, random_state=42)
classifier.fit(X_train, y_train)

5. Avaliação do Modelo

O modelo treinado foi avaliado utilizando o conjunto de teste (X_test, y_test).

  • Acurácia (Accuracy): proporção de previsões corretas.
  • Visualização da árvore: interpretação das regras de decisão aprendidas pelo modelo.

No código, a avaliação foi feita com:

Validation Accuracy: 0.8333 2025-09-02T14:08:37.489057 image/svg+xml Matplotlib v3.10.6, https://matplotlib.org/

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

from sklearn import tree
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score

def preprocess(df):
    # Preencher valores ausentes
    df.fillna(df.median(numeric_only=True), inplace=True)

    # Detectar outliers com Z-Score
    num_df = df.select_dtypes(include=[np.number])
    z_scores = np.abs((num_df - num_df.mean()) / num_df.std())
    outliers = (z_scores > 3).sum().sum()

    # Encoding categórico
    df["Sex"] = df["Sex"].map({"M": 1, "F": 0})
    df["ChestPainType"] = df["ChestPainType"].map({"TA": 0, "ATA": 1, "NAP": 2, "ASY": 3})
    df["RestingECG"] = df["RestingECG"].map({"Normal": 0, "ST": 1, "LVH": 2})
    df["ExerciseAngina"] = df["ExerciseAngina"].map({"Y": 1, "N": 0})
    df["ST_Slope"] = df["ST_Slope"].map({"Up": 0, "Flat": 1, "Down": 2})

    return df

# Carregar dataset
df = pd.read_csv("https://raw.githubusercontent.com/bligui/Machine-Learning-Ana/refs/heads/main/dados/heart.csv")

# Pré-processamento
df = preprocess(df)

# Selecionar colunas numéricas
numeric_cols = df.select_dtypes(include=[np.number]).columns

# Min-Max Normalization
df_minmax = df.copy()
df_minmax[numeric_cols] = (df_minmax[numeric_cols] - df_minmax[numeric_cols].min()) / (df_minmax[numeric_cols].max() - df_minmax[numeric_cols].min())

# Standardization (Z-score)
df_standard = df.copy()
df_standard[numeric_cols] = (df_standard[numeric_cols] - df_standard[numeric_cols].mean()) / df_standard[numeric_cols].std()

# carregar dataset
df = pd.read_csv("https://raw.githubusercontent.com/bligui/Machine-Learning-Ana/refs/heads/main/dados/heart.csv")
df = preprocess(df)

# dividir variáveis
X = df.drop("HeartDisease", axis=1)
y = df["HeartDisease"]

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

# limitar profundidade da árvore
classifier = tree.DecisionTreeClassifier(max_depth=3, random_state=42)
classifier.fit(X_train, y_train)

# avaliar modelo
y_pred = classifier.predict(X_test)
accuracy = accuracy_score(y_test, y_pred)
print(f"Validation Accuracy: {accuracy:.4f}")

# plotar árvore mais compacta
plt.figure(figsize=(12, 8))
tree.plot_tree(classifier, feature_names=X.columns, class_names=["No Disease", "Disease"], filled=True, fontsize=9)

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

6. Relatório Final

O desenvolvimento do projeto começou pela exploração do conjunto de dados, onde foram observadas características importantes como a predominância do sexo masculino e a maior probabilidade de diagnóstico positivo entre pacientes assintomáticos. Também foi identificada a presença de valores extremos em colesterol e pressão arterial, o que reforçou a necessidade de um bom pré-processamento.

O resultado obtido apresentou uma acurácia em torno de 83%, mostrando que o modelo conseguiu capturar bem os padrões presentes no conjunto de dados. A análise da árvore evidenciou a relevância de variáveis como ChestPainType, ST_Slope e Oldpeak.

Embora os resultados tenham sido positivos, algumas melhorias podem ser consideradas. Entre elas, destacar o uso de mais de um modelo para comparar desempenhos, a aplicação de validação cruzada para obter uma medida mais estável e a análise de métricas além da acurácia, como precisão e recall. Essas ações simples já poderiam aumentar a confiabilidade do modelo e oferecer uma visão mais completa de seu desempenho.

Em resumo, o projeto cumpriu o objetivo de construir um protótipo capaz de prever a presença de doenças cardiovasculares a partir de variáveis clínicas, apresentando bons resultados e mostrando potencial para evoluir em versões futuras.