Build a Heart Disease Predictor
Train and compare ML models to classify heart disease risk from clinical data.
Introduction
30 Second Summary
Heart disease is the leading cause of death worldwide. Doctors and researchers use machine learning to help identify patients at risk before symptoms become critical.
In this project, you will build a heart disease prediction system using Python and scikit-learn that classifies patients as at-risk or healthy based on 11 clinical features.
What You'll Build
A complete machine learning pipeline that loads real patient data, explores it visually, preprocesses it for ML, trains three different classification models, and evaluates them with industry-standard metrics.
By the end of this project, you'll have:
- A working heart disease prediction model trained on 918 real patient records that classifies patients as having heart disease or not.
- A complete Python script demonstrating the end-to-end ML workflow from data loading through exploratory analysis, preprocessing, training, and evaluation.
- A model comparison across three algorithms (Logistic Regression, Random Forest, KNN) with accuracy, precision, recall, and F1-score for each.
- Secret Mission: Add cross-validation and feature importance analysis to make your project interview-ready.
Are there any prerequisites?
You need basic Python knowledge (variables, loops, functions, and lists). You also need a free Kaggle account to download the dataset used in this project.
Set Up Your Python Environment
Machine learning projects depend on several libraries working together. If these libraries conflict with other Python projects on your machine, you'll run into version errors that are difficult to debug.
A virtual environment solves this by creating an isolated space for your project's dependencies. In this step, you'll create that isolated environment, install the ML toolkit, and download the real patient dataset you'll train your model on.
In this step, get ready to:
- Create a project folder and Python virtual environment.
- Install scikit-learn, pandas, matplotlib, and seaborn.
- Download the heart disease dataset and create your Python script file.
Create a project folder and virtual environment
Every Python project should live in its own folder with its own virtual environment. This keeps your ML libraries separate from your system Python and any other projects.
- Press Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
- Type Terminal and press Enter to open it.
- Move to your Desktop by running this command:
cd ~/Desktop
- Create your project folder and move into it by running these commands:
mkdir heart-disease-predictor
cd heart-disease-predictor
- Confirm the folder exists by running:
ls
The output should be empty since you just created the folder. That's expected.
- Create a Python virtual environment by running this command:
python -m venv venv
What is a virtual environment?
A virtual environment is an isolated Python installation inside your project folder. When you install libraries in a virtual environment, they stay contained here and won't conflict with other projects on your machine.
The venv folder it creates contains a copy of the Python interpreter and a clean pip ready for your project's dependencies.
- Confirm the venv folder was created by running:
ls
You should see a venv folder listed in the output.
Don't see the venv folder?
Make sure you typed python -m venv venv exactly as shown. The first venv is the module name, and the second is the folder name you're creating.
If you see a "No module named venv" error, your Python installation may be incomplete. Reinstall Python 3.14.3 from the official Python website with the default options checked.
Help me fix my virtual environment creation error.
- Activate the virtual environment so your terminal uses the isolated Python:
macOS
- Activate the environment by running:
source venv/bin/activate
Windows
- Activate the environment by running:
venv\Scripts\activate
You should see (venv) appear at the start of your terminal prompt. This confirms the virtual environment is active.
Not seeing (venv) in your prompt?
On Windows, if you see an error about scripts being disabled, you need to allow script execution. Run Set-ExecutionPolicy -Scope CurrentUser -ExecutionPolicy RemoteSigned in PowerShell, then try the activation command again.
On macOS, make sure you're in the heart-disease-predictor folder where the venv folder lives.
Help me activate my Python virtual environment.
- Verify pip is available inside the environment by running:
pip --version
You should see a version number and a path that points inside your venv folder. This confirms pip is ready to install packages into your isolated environment.
Install the ML libraries
Your prediction system needs four libraries working together. pandas loads and manipulates the data, scikit-learn trains the models, and matplotlib plus seaborn create the visualizations.
- Install all four libraries in one command by running:
pip install scikit-learn pandas matplotlib seaborn
What's happening during the install?
This command downloads and installs each library plus their dependencies (like numpy and scipy). The install may take a minute or two depending on your internet speed.
You'll see progress bars for each package. As long as you see "Successfully installed" at the end, everything worked.
- Verify all libraries installed correctly by running this quick Python check:
python -c "import sklearn; import pandas; import matplotlib; import seaborn; print('All libraries installed successfully!')"
You should see All libraries installed successfully! printed in your terminal.
Seeing an import error?
Make sure your virtual environment is active. You should see (venv) at the start of your terminal prompt. If it's missing, run the activation command from the previous substep again.
If the install command itself failed, check your internet connection and try running the pip install command again.
Help me fix my library installation error.
Download the dataset and create your script file
You'll use a real clinical dataset with 918 patient records. Each record includes 11 features like age, cholesterol, and chest pain type, plus a diagnosis of whether the patient has heart disease.
- Open the Heart Failure Prediction Dataset page on Kaggle:
- kaggle.com/datasets/fedesoriano/heart-failure-prediction
- Click the Download button near the top of the page. This downloads a zip file called archive.zip.
- Unzip the file. You'll find heart.csv inside.
- Move heart.csv into your heart-disease-predictor folder on your Desktop.
Where is my project folder?
Your project folder is at Desktop/heart-disease-predictor. On macOS, open Finder and navigate to your Desktop. On Windows, open File Explorer and go to your Desktop folder.
Drag or copy the heart.csv file directly into the heart-disease-predictor folder.
- Confirm heart.csv is in the right place by running this in your terminal:
ls
You should see both heart.csv and the venv folder listed.
Don't see heart.csv?
Make sure you moved the CSV file into the heart-disease-predictor folder, not into the venv folder or the Desktop itself. The file must be at the same level as venv.
Also check that you unzipped the downloaded file. The direct download from Kaggle is a .zip archive that needs extracting first.
Help me find where my heart.csv file ended up.
Now open your project folder in VS Code so you can start writing your script.
- Press Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
- Type VS Code and press Enter to open it.
- In VS Code, click File in the top menu bar.
- Click Open Folder.
- Navigate to your Desktop, select the heart-disease-predictor folder, and click Open.
- Create your script file by clicking File then New File.
- Save it immediately with Cmd+S (macOS) or Ctrl+S (Windows).
- Name the file heart_disease_predictor.py and click Save.
You should now see heart_disease_predictor.py open in the VS Code editor with heart.csv visible in the Explorer sidebar on the left.
Trust the workspace
If VS Code shows a banner asking "Do you trust the authors of the files in this folder?", click Yes, I trust the authors. This is your own project folder so it's safe.
Your environment is ready with all the tools you need. Next up, you'll load the heart disease dataset and explore its patterns with visualizations.
Load and Explore the Data
Your environment is set up and your dataset is ready. Now it's time to look at the data before building any models.
Machine learning models are only as good as the data they learn from. In this step, you will load the heart disease dataset into Python, inspect its structure, and create visualizations that reveal patterns in the data.
In this step, get ready to:
- Import ML libraries and load the dataset into a DataFrame.
- Explore target distribution and visualize age patterns with a histogram and correlation heatmap.
- Analyze heart disease rates by chest pain type with a bar chart.
Import libraries and load the data
Every ML project starts by importing the tools you need and loading your data into memory. You will import all the libraries upfront so the script is ready for the full pipeline.
- In heart_disease_predictor.py, add the following import block at the top of the file:
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.linear_model import LogisticRegression
from sklearn.ensemble import RandomForestClassifier
from sklearn.neighbors import KNeighborsClassifier
from sklearn.metrics import classification_report, accuracy_score, ConfusionMatrixDisplay
What do these imports do?
- pandas loads and manipulates tabular data (your CSV).
- numpy handles numerical operations and array math.
- matplotlib and seaborn create data visualizations (histograms, heatmaps, bar charts).
- sklearn modules provide machine learning tools you will use throughout the project (model training, evaluation, preprocessing).
- Below the imports, add the code to load the dataset and print basic information:
# ============================================================
# STEP 1: Load and Explore the Data
# ============================================================
# Load the dataset
df = pd.read_csv("heart.csv")
# Basic info
print("Dataset Shape:", df.shape)
print("\nFirst 5 rows:")
print(df.head())
print("\nData Types:")
print(df.dtypes)
print("\nMissing Values:")
print(df.isnull().sum())
What does this code do?
- pd.read_csv("heart.csv") reads the CSV file into a DataFrame (a table-like structure in pandas).
- df.shape tells you how many rows and columns the dataset has.
- df.head() shows the first 5 rows so you can see what the data looks like.
- df.isnull().sum() checks for missing values in each column. Missing data would need special handling before training.
- Save heart_disease_predictor.py.
- Run the script in your terminal by running:
python heart_disease_predictor.py
- You should see output showing the dataset has 918 rows and 12 columns, followed by the first 5 rows, data types for each column, and 0 missing values across all columns.
Seeing FileNotFoundError?
This means Python cannot find heart.csv in your current working directory. Make sure you are running the script from the same folder where heart.csv is saved.
help me fix a FileNotFoundError when loading heart.csv with pandas
Explore target distribution and visualize patterns
Before training a model, you need to understand what you are predicting. The target column HeartDisease contains 1 (has heart disease) or 0 (no heart disease). Checking its distribution tells you whether the classes are balanced.
- Add the following code below your existing code to check target distribution and create an age histogram:
# Target distribution
print("\nTarget Distribution:")
print(df["HeartDisease"].value_counts())
print(f"\nHeart Disease Rate: {df['HeartDisease'].mean():.2%}")
# Visualization: Age distribution
plt.figure(figsize=(8, 5))
sns.histplot(data=df, x="Age", hue="HeartDisease", kde=True, bins=20)
plt.title("Age Distribution by Heart Disease Status")
plt.xlabel("Age (years)")
plt.ylabel("Count")
plt.legend(["No Disease", "Heart Disease"])
plt.tight_layout()
plt.savefig("age_distribution.png", dpi=100)
plt.close()
print("\nSaved: age_distribution.png")
What does this code do?
value_counts() counts how many patients have heart disease vs not. The .mean() call gives you the percentage of patients with heart disease (since the column is 0s and 1s, the mean equals the rate).
The histogram uses seaborn to plot age distributions split by heart disease status. The kde=True parameter adds a smooth density curve over the bars. The plot is saved as a PNG file so you can include it in a portfolio.
- Next, add the correlation heatmap below your age histogram code:
# Correlation heatmap for numeric features
numeric_cols = df.select_dtypes(include=[np.number]).columns
plt.figure(figsize=(10, 8))
sns.heatmap(df[numeric_cols].corr(), annot=True, cmap="coolwarm", fmt=".2f")
plt.title("Correlation Heatmap - Numeric Features")
plt.tight_layout()
plt.savefig("correlation_heatmap.png", dpi=100)
plt.close()
print("Saved: correlation_heatmap.png")
What is a correlation heatmap?
A correlation heatmap shows how strongly each numeric feature relates to every other feature. Values close to 1 or -1 mean a strong relationship. Values close to 0 mean little connection.
This helps you spot which features might be most useful for predicting heart disease, and whether any features are redundant (highly correlated with each other).
- Save heart_disease_predictor.py and run the script again:
python heart_disease_predictor.py
- You should see the target distribution printed (roughly 508 patients with heart disease and 410 without), plus confirmation that age_distribution.png and correlation_heatmap.png were saved.
Not seeing the PNG files?
Check that plt.savefig() has the correct filename and that you included plt.close() after each plot. Without plt.close(), subsequent plots may draw on top of previous ones.
help me debug why my matplotlib plots are not saving as PNG files
Analyze heart disease by chest pain type
Categorical features (like chest pain type) cannot be plotted on a correlation heatmap. Instead, you can group the data and compare heart disease rates across categories. This reveals which chest pain types are most associated with heart disease.
- Add the following code below your heatmap code to create a bar chart of heart disease rates by chest pain type:
# Heart disease rate by chest pain type
chest_pain_rates = df.groupby("ChestPainType")["HeartDisease"].mean()
plt.figure(figsize=(8, 5))
chest_pain_rates.plot(kind="bar", color=["#2ecc71", "#e74c3c", "#3498db", "#9b59b6"])
plt.title("Heart Disease Rate by Chest Pain Type")
plt.xlabel("Chest Pain Type")
plt.ylabel("Heart Disease Rate")
plt.xticks(rotation=0)
plt.tight_layout()
plt.savefig("chest_pain_analysis.png", dpi=100)
plt.close()
print("Saved: chest_pain_analysis.png")
What do the chest pain types mean?
- ASY = Asymptomatic (no chest pain symptoms).
- ATA = Atypical Angina.
- NAP = Non-Anginal Pain.
- TA = Typical Angina.
The bar chart shows that asymptomatic patients (ASY) have the highest heart disease rate. This is a well-known medical pattern. Patients without obvious chest pain symptoms are often diagnosed later when the disease is more advanced.
- Save heart_disease_predictor.py and run the script one more time:
python heart_disease_predictor.py
- You should see all previous output plus a confirmation that chest_pain_analysis.png was saved.
- Confirm the three PNG files exist in your project folder by running:
ls *.png
- You should see age_distribution.png, chest_pain_analysis.png, and correlation_heatmap.png in the output.
Seeing a UserWarning about legends?
Matplotlib may print a warning about the legend not matching plot handles. This does not affect your saved plot. The visualization still generates correctly.
help me understand a matplotlib UserWarning about legends in my heart disease age distribution plot
✔️ Awesome, I've got everything!
Great. Double check you have saved heart_disease_predictor.py and all three PNG files are in your project folder.
ⓧ I'd like to double check the full code
Here is the full heart_disease_predictor.py so far:
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.linear_model import LogisticRegression
from sklearn.ensemble import RandomForestClassifier
from sklearn.neighbors import KNeighborsClassifier
from sklearn.metrics import classification_report, accuracy_score, ConfusionMatrixDisplay
# ============================================================
# STEP 1: Load and Explore the Data
# ============================================================
# Load the dataset
df = pd.read_csv("heart.csv")
# Basic info
print("Dataset Shape:", df.shape)
print("\nFirst 5 rows:")
print(df.head())
print("\nData Types:")
print(df.dtypes)
print("\nMissing Values:")
print(df.isnull().sum())
# Target distribution
print("\nTarget Distribution:")
print(df["HeartDisease"].value_counts())
print(f"\nHeart Disease Rate: {df['HeartDisease'].mean():.2%}")
# Visualization: Age distribution
plt.figure(figsize=(8, 5))
sns.histplot(data=df, x="Age", hue="HeartDisease", kde=True, bins=20)
plt.title("Age Distribution by Heart Disease Status")
plt.xlabel("Age (years)")
plt.ylabel("Count")
plt.legend(["No Disease", "Heart Disease"])
plt.tight_layout()
plt.savefig("age_distribution.png", dpi=100)
plt.close()
print("\nSaved: age_distribution.png")
# Correlation heatmap for numeric features
numeric_cols = df.select_dtypes(include=[np.number]).columns
plt.figure(figsize=(10, 8))
sns.heatmap(df[numeric_cols].corr(), annot=True, cmap="coolwarm", fmt=".2f")
plt.title("Correlation Heatmap - Numeric Features")
plt.tight_layout()
plt.savefig("correlation_heatmap.png", dpi=100)
plt.close()
print("Saved: correlation_heatmap.png")
# Heart disease rate by chest pain type
chest_pain_rates = df.groupby("ChestPainType")["HeartDisease"].mean()
plt.figure(figsize=(8, 5))
chest_pain_rates.plot(kind="bar", color=["#2ecc71", "#e74c3c", "#3498db", "#9b59b6"])
plt.title("Heart Disease Rate by Chest Pain Type")
plt.xlabel("Chest Pain Type")
plt.ylabel("Heart Disease Rate")
plt.xticks(rotation=0)
plt.tight_layout()
plt.savefig("chest_pain_analysis.png", dpi=100)
plt.close()
print("Saved: chest_pain_analysis.png")
You now have a clear picture of the data. You know the dataset has 918 patients, no missing values, and roughly 55% of patients have heart disease. Next up, you will preprocess this data to make it ready for machine learning models.
Preprocess the Data for ML
Your exploratory analysis revealed the shape of the data and some interesting patterns. Now you need to transform that raw data into a format that machine learning algorithms can actually learn from.
Real-world datasets always have quirks. In this step, you will fix impossible values, convert text categories into numbers, and split the data into training and testing sets so your models can learn without cheating.
In this step, get ready to:
- Fix medically impossible zero values in the Cholesterol and RestingBP columns.
- Encode categorical features into numeric format using one-hot encoding.
- Split data into training and testing sets, then scale features to prevent data leakage.
Handle data quality issues in Cholesterol and RestingBP
The Cholesterol column contains 172 rows with a value of 0. A person cannot have zero cholesterol. These are missing values recorded as zeros, and they would confuse a model if left unchanged.
- In heart_disease_predictor.py, scroll to the bottom of your file (after the last print("Saved: chest_pain_analysis.png") line).
- Add the following code to handle impossible zero values in both columns:
# ============================================================
# STEP 2: Preprocess the Data
# ============================================================
# Handle zero values in Cholesterol (medically impossible)
zero_chol_count = (df["Cholesterol"] == 0).sum()
print(f"\nZero Cholesterol values: {zero_chol_count}")
median_chol = df.loc[df["Cholesterol"] > 0, "Cholesterol"].median()
df.loc[df["Cholesterol"] == 0, "Cholesterol"] = median_chol
print(f"Replaced with median: {median_chol}")
# Handle zero values in RestingBP
zero_bp_count = (df["RestingBP"] == 0).sum()
print(f"Zero RestingBP values: {zero_bp_count}")
if zero_bp_count > 0:
median_bp = df.loc[df["RestingBP"] > 0, "RestingBP"].median()
df.loc[df["RestingBP"] == 0, "RestingBP"] = median_bp
print(f"Replaced with median: {median_bp}")
What does this code do?
This code fixes a real data quality problem in the dataset. Zero values for cholesterol or blood pressure are medically impossible, so they represent missing data that was recorded incorrectly.
- df.loc[df["Cholesterol"] > 0, "Cholesterol"].median() calculates the median using only valid (non-zero) cholesterol values.
- The code then replaces all zero values with that median. This is called median imputation. It preserves the data's overall distribution without introducing outliers.
- The same logic applies to RestingBP, but wrapped in an if check since there is only 1 row affected.
- Save heart_disease_predictor.py.
- Run the script in your terminal. You should see output showing Zero Cholesterol values: 172 followed by the median replacement value.
Seeing a KeyError?
- Check that your column name matches exactly: "Cholesterol" with a capital C. Column names are case-sensitive in pandas.
- Make sure your heart.csv file is in the same folder as your script. The Step 1 code that loads it must run successfully before this section.
Why is my Cholesterol data not being replaced?
Encode categorical features and separate the target
Machine learning algorithms work with numbers, not text labels like "M" or "ATA". You need to convert each categorical column into numeric columns that represent the same information.
- Add the following code below the RestingBP handling section to encode categories and separate features from the target:
# One-hot encode categorical features
categorical_cols = ["Sex", "ChestPainType", "RestingECG", "ExerciseAngina", "ST_Slope"]
df_encoded = pd.get_dummies(df, columns=categorical_cols, drop_first=True)
print(f"\nShape after encoding: {df_encoded.shape}")
print(f"Columns: {list(df_encoded.columns)}")
# Separate features and target
X = df_encoded.drop("HeartDisease", axis=1)
y = df_encoded["HeartDisease"]
What is one-hot encoding?
One-hot encoding converts a single categorical column into multiple binary (0/1) columns. For example, the ChestPainType column with values TA, ATA, NAP, ASY becomes three new columns (one is dropped as the reference category).
The drop_first=True parameter drops one category per feature to avoid redundancy. If a patient is not M, they must be F. The model can infer the dropped category from the remaining columns.
After encoding, X holds all columns except the target. y holds just the HeartDisease column (what the model is learning to predict).
- Save heart_disease_predictor.py and run the script.
- You should see the new shape printed (918 rows, more columns than before) along with a list of the new encoded column names.
Shape looks the same or columns missing?
- Make sure you pass the columns=categorical_cols argument to get_dummies(). Without it, pandas does not know which columns to encode.
- Check that you assigned the result to df_encoded (not back to df). The original DataFrame stays unmodified.
Why aren't my categorical columns being encoded?
Split and scale the data for training
A model needs separate data for learning and testing. If you test on the same data it trained on, you have no idea whether it actually learned patterns or just memorized the answers.
- Add the following code below the feature/target separation to split and scale the data:
# Train-test split (80/20, stratified)
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42, stratify=y
)
print(f"\nTraining set: {X_train.shape[0]} samples")
print(f"Testing set: {X_test.shape[0]} samples")
# Scale numeric features (fit on train only)
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)
X_test_scaled = scaler.transform(X_test)
Why fit the scaler on training data only?
StandardScaler transforms each feature to have a mean of 0 and standard deviation of 1. This helps algorithms that are sensitive to feature scale (like KNN and Logistic Regression).
You call fit_transform() on training data and only transform() on test data. If you fit the scaler on the full dataset, statistics from the test set leak into training. This is called data leakage and it makes your accuracy scores unrealistically high.
What do stratify and random_state do?
- stratify=y ensures both the training and test sets have the same proportion of heart disease cases as the original dataset.
- random_state=42 makes the split reproducible. Anyone who runs your code gets the exact same train/test split.
- Save heart_disease_predictor.py and run the script.
- You should see Training set: 734 samples and Testing set: 184 samples printed in your terminal.
Getting a NameError on X or y?
- Make sure the encoding chunk runs before this one. The X and y variables are created in the previous section.
- Check that you have not accidentally overwritten df_encoded or introduced a typo in the variable name.
Help me debug my train-test split code.
✔️ Awesome, I've got everything!
Great. Double check you have saved your file before moving on.
ⓧ I'd like to double check the full code
Here is the full heart_disease_predictor.py up to this point:
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.linear_model import LogisticRegression
from sklearn.ensemble import RandomForestClassifier
from sklearn.neighbors import KNeighborsClassifier
from sklearn.metrics import classification_report, accuracy_score, ConfusionMatrixDisplay
# ============================================================
# STEP 1: Load and Explore the Data
# ============================================================
# Load the dataset
df = pd.read_csv("heart.csv")
# Basic info
print("Dataset Shape:", df.shape)
print("\nFirst 5 rows:")
print(df.head())
print("\nData Types:")
print(df.dtypes)
print("\nMissing Values:")
print(df.isnull().sum())
# Target distribution
print("\nTarget Distribution:")
print(df["HeartDisease"].value_counts())
print(f"\nHeart Disease Rate: {df['HeartDisease'].mean():.2%}")
# Visualization: Age distribution
plt.figure(figsize=(8, 5))
sns.histplot(data=df, x="Age", hue="HeartDisease", kde=True, bins=20)
plt.title("Age Distribution by Heart Disease Status")
plt.xlabel("Age (years)")
plt.ylabel("Count")
plt.legend(["No Disease", "Heart Disease"])
plt.tight_layout()
plt.savefig("age_distribution.png", dpi=100)
plt.close()
print("\nSaved: age_distribution.png")
# Correlation heatmap for numeric features
numeric_cols = df.select_dtypes(include=[np.number]).columns
plt.figure(figsize=(10, 8))
sns.heatmap(df[numeric_cols].corr(), annot=True, cmap="coolwarm", fmt=".2f")
plt.title("Correlation Heatmap - Numeric Features")
plt.tight_layout()
plt.savefig("correlation_heatmap.png", dpi=100)
plt.close()
print("Saved: correlation_heatmap.png")
# Heart disease rate by chest pain type
chest_pain_rates = df.groupby("ChestPainType")["HeartDisease"].mean()
plt.figure(figsize=(8, 5))
chest_pain_rates.plot(kind="bar", color=["#2ecc71", "#e74c3c", "#3498db", "#9b59b6"])
plt.title("Heart Disease Rate by Chest Pain Type")
plt.xlabel("Chest Pain Type")
plt.ylabel("Heart Disease Rate")
plt.xticks(rotation=0)
plt.tight_layout()
plt.savefig("chest_pain_analysis.png", dpi=100)
plt.close()
print("Saved: chest_pain_analysis.png")
# ============================================================
# STEP 2: Preprocess the Data
# ============================================================
# Handle zero values in Cholesterol (medically impossible)
zero_chol_count = (df["Cholesterol"] == 0).sum()
print(f"\nZero Cholesterol values: {zero_chol_count}")
median_chol = df.loc[df["Cholesterol"] > 0, "Cholesterol"].median()
df.loc[df["Cholesterol"] == 0, "Cholesterol"] = median_chol
print(f"Replaced with median: {median_chol}")
# Handle zero values in RestingBP
zero_bp_count = (df["RestingBP"] == 0).sum()
print(f"Zero RestingBP values: {zero_bp_count}")
if zero_bp_count > 0:
median_bp = df.loc[df["RestingBP"] > 0, "RestingBP"].median()
df.loc[df["RestingBP"] == 0, "RestingBP"] = median_bp
print(f"Replaced with median: {median_bp}")
# One-hot encode categorical features
categorical_cols = ["Sex", "ChestPainType", "RestingECG", "ExerciseAngina", "ST_Slope"]
df_encoded = pd.get_dummies(df, columns=categorical_cols, drop_first=True)
print(f"\nShape after encoding: {df_encoded.shape}")
print(f"Columns: {list(df_encoded.columns)}")
# Separate features and target
X = df_encoded.drop("HeartDisease", axis=1)
y = df_encoded["HeartDisease"]
# Train-test split (80/20, stratified)
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42, stratify=y
)
print(f"\nTraining set: {X_train.shape[0]} samples")
print(f"Testing set: {X_test.shape[0]} samples")
# Scale numeric features (fit on train only)
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)
X_test_scaled = scaler.transform(X_test)
Your data is now clean, encoded, split, and scaled. Next up, you will train three different classification models and see which one predicts heart disease most accurately.
Train and Evaluate Three Models
Your data is cleaned, encoded, and split into training and testing sets. The hard prep work is done.
Now comes the exciting part: training machine learning models to predict heart disease. You will train three different classification algorithms on the same data, then compare their performance to find which one does the best job.
In this step, get ready to:
- Train a Logistic Regression model and evaluate its classification report.
- Train a Random Forest and K-Nearest Neighbors model for comparison.
- Generate a confusion matrix and summary table comparing all three models.
Train a Logistic Regression model
Logistic Regression is one of the most common baseline algorithms for binary classification. Despite its name, it predicts categories (not continuous values) by learning a decision boundary between the two classes.
- In heart_disease_predictor.py, add the following code at the bottom of your file (below the X_test_scaled = scaler.transform(X_test) line):
# ============================================================
# STEP 3: Train and Evaluate Models
# ============================================================
# Model 1: Logistic Regression
lr_model = LogisticRegression(max_iter=1000, random_state=42)
lr_model.fit(X_train_scaled, y_train)
lr_pred = lr_model.predict(X_test_scaled)
print("\n" + "=" * 50)
print("LOGISTIC REGRESSION")
print("=" * 50)
print(f"Accuracy: {accuracy_score(y_test, lr_pred):.4f}")
print(classification_report(y_test, lr_pred, target_names=["No Disease", "Heart Disease"]))
What does this code do?
- LogisticRegression(max_iter=1000, random_state=42) creates the model. max_iter=1000 gives the algorithm enough iterations to converge. random_state=42 ensures reproducible results every time you run the script.
- .fit() trains the model on the training data. .predict() generates predictions on the test data the model has never seen.
- classification_report prints precision, recall, and F1-score for each class. These metrics tell you how well the model identifies both healthy patients and at-risk patients.
- Save heart_disease_predictor.py.
- You won't see output yet. The next substep adds two more models before you run the script.
Train Random Forest and K-Nearest Neighbors
A single model only tells part of the story. By training multiple algorithms on the same data, you can compare their strengths and pick the one best suited for this problem.
- Add the Random Forest model code directly below the Logistic Regression section:
# Model 2: Random Forest
rf_model = RandomForestClassifier(n_estimators=100, random_state=42)
rf_model.fit(X_train_scaled, y_train)
rf_pred = rf_model.predict(X_test_scaled)
print("=" * 50)
print("RANDOM FOREST")
print("=" * 50)
print(f"Accuracy: {accuracy_score(y_test, rf_pred):.4f}")
print(classification_report(y_test, rf_pred, target_names=["No Disease", "Heart Disease"]))
What is a Random Forest?
A Random Forest builds many decision trees (100 in this case, set by n_estimators=100) and takes a vote across all of them. Each tree sees a different random subset of the data, which makes the combined prediction more robust than any single tree.
- Save the file.
- Now add the K-Nearest Neighbors model directly below the Random Forest section:
# Model 3: K-Nearest Neighbors
knn_model = KNeighborsClassifier(n_neighbors=5)
knn_model.fit(X_train_scaled, y_train)
knn_pred = knn_model.predict(X_test_scaled)
print("=" * 50)
print("K-NEAREST NEIGHBORS")
print("=" * 50)
print(f"Accuracy: {accuracy_score(y_test, knn_pred):.4f}")
print(classification_report(y_test, knn_pred, target_names=["No Disease", "Heart Disease"]))
What is K-Nearest Neighbors?
KNN classifies a new patient by finding the 5 most similar patients in the training data (set by n_neighbors=5) and taking a majority vote. If 3 out of 5 neighbors have heart disease, the prediction is "heart disease."
- Save heart_disease_predictor.py.
- Run the script by entering this command in your terminal:
python heart_disease_predictor.py
- Scroll through the terminal output. You should see three separate classification reports labeled LOGISTIC REGRESSION, RANDOM FOREST, and K-NEAREST NEIGHBORS, each showing accuracy, precision, recall, and F1-score.
Not seeing all three reports?
- Make sure each model block is at the same indentation level (no accidental nesting inside the Step 2 code).
- Check that you have a blank line between the LR, RF, and KNN sections. Python won't error on missing blank lines, but it helps you spot misplaced code.
- If you see an indentation or syntax error, double-check that no lines from Step 2 were accidentally deleted.
Help me debug my model training code
Compare models and generate a confusion matrix
Accuracy alone does not tell the full story. A confusion matrix shows exactly where the model gets things right and where it makes mistakes, which is critical in healthcare predictions.
- Add the model comparison and confusion matrix code below the KNN section:
# Model comparison summary
print("\n" + "=" * 50)
print("MODEL COMPARISON SUMMARY")
print("=" * 50)
models = {
"Logistic Regression": lr_pred,
"Random Forest": rf_pred,
"K-Nearest Neighbors": knn_pred,
}
for name, preds in models.items():
acc = accuracy_score(y_test, preds)
print(f"{name:25s} | Accuracy: {acc:.4f}")
# Confusion matrix for best model
best_model_name = max(models, key=lambda k: accuracy_score(y_test, models[k]))
best_preds = models[best_model_name]
print(f"\nBest Model: {best_model_name}")
fig, ax = plt.subplots(figsize=(7, 5))
ConfusionMatrixDisplay.from_predictions(
y_test, best_preds,
display_labels=["No Disease", "Heart Disease"],
cmap="Blues",
ax=ax
)
ax.set_title(f"Confusion Matrix - {best_model_name}")
plt.tight_layout()
plt.savefig("confusion_matrix.png", dpi=100)
plt.close()
print("Saved: confusion_matrix.png")
print("\nDone! Check the generated .png files for visualizations.")
What does this code do?
- The models dictionary maps each model name to its predictions. The loop prints a clean comparison table.
- max(...) automatically identifies the model with the highest accuracy. This means you do not need to manually check which one won.
- ConfusionMatrixDisplay.from_predictions() creates a grid showing true positives, true negatives, false positives, and false negatives. A false negative here means a patient with heart disease was incorrectly classified as healthy.
Why does a confusion matrix matter in healthcare?
In medical predictions, false negatives (missing a sick patient) are far more dangerous than false positives (flagging a healthy patient for more tests). The confusion matrix lets you see exactly how many patients the model would miss. Interviewers love asking about this tradeoff.
- Save heart_disease_predictor.py.
- Run the script again:
python heart_disease_predictor.py
- Scroll to the bottom of the terminal output. You should see a MODEL COMPARISON SUMMARY table listing all three models with their accuracy scores, followed by the name of the best model and a confirmation that confusion_matrix.png was saved.
- Open confusion_matrix.png from your project folder to see the matrix visualization.
Seeing a file not found error for confusion_matrix.png?
- Make sure you are running the script from inside your project folder (the same folder that contains heart.csv). The PNG is saved relative to where you run the command.
- If you see an error about ConfusionMatrixDisplay, confirm your import line at the top of the file includes it: from sklearn.metrics import classification_report, accuracy_score, ConfusionMatrixDisplay.
Help me fix my confusion matrix code
✔️ Awesome, I've got everything!
Great. Double check you have saved your file before moving on.
ⓧ I'd like to double check the full code
Here is the complete heart_disease_predictor.py up to this point:
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.linear_model import LogisticRegression
from sklearn.ensemble import RandomForestClassifier
from sklearn.neighbors import KNeighborsClassifier
from sklearn.metrics import classification_report, accuracy_score, ConfusionMatrixDisplay
# ============================================================
# STEP 1: Load and Explore the Data
# ============================================================
# Load the dataset
df = pd.read_csv("heart.csv")
# Basic info
print("Dataset Shape:", df.shape)
print("\nFirst 5 rows:")
print(df.head())
print("\nData Types:")
print(df.dtypes)
print("\nMissing Values:")
print(df.isnull().sum())
# Target distribution
print("\nTarget Distribution:")
print(df["HeartDisease"].value_counts())
print(f"\nHeart Disease Rate: {df['HeartDisease'].mean():.2%}")
# Visualization: Age distribution
plt.figure(figsize=(8, 5))
sns.histplot(data=df, x="Age", hue="HeartDisease", kde=True, bins=20)
plt.title("Age Distribution by Heart Disease Status")
plt.xlabel("Age (years)")
plt.ylabel("Count")
plt.legend(["No Disease", "Heart Disease"])
plt.tight_layout()
plt.savefig("age_distribution.png", dpi=100)
plt.close()
print("\nSaved: age_distribution.png")
# Correlation heatmap for numeric features
numeric_cols = df.select_dtypes(include=[np.number]).columns
plt.figure(figsize=(10, 8))
sns.heatmap(df[numeric_cols].corr(), annot=True, cmap="coolwarm", fmt=".2f")
plt.title("Correlation Heatmap - Numeric Features")
plt.tight_layout()
plt.savefig("correlation_heatmap.png", dpi=100)
plt.close()
print("Saved: correlation_heatmap.png")
# Heart disease rate by chest pain type
chest_pain_rates = df.groupby("ChestPainType")["HeartDisease"].mean()
plt.figure(figsize=(8, 5))
chest_pain_rates.plot(kind="bar", color=["#2ecc71", "#e74c3c", "#3498db", "#9b59b6"])
plt.title("Heart Disease Rate by Chest Pain Type")
plt.xlabel("Chest Pain Type")
plt.ylabel("Heart Disease Rate")
plt.xticks(rotation=0)
plt.tight_layout()
plt.savefig("chest_pain_analysis.png", dpi=100)
plt.close()
print("Saved: chest_pain_analysis.png")
# ============================================================
# STEP 2: Preprocess the Data
# ============================================================
# Handle zero values in Cholesterol (medically impossible)
zero_chol_count = (df["Cholesterol"] == 0).sum()
print(f"\nZero Cholesterol values: {zero_chol_count}")
median_chol = df.loc[df["Cholesterol"] > 0, "Cholesterol"].median()
df.loc[df["Cholesterol"] == 0, "Cholesterol"] = median_chol
print(f"Replaced with median: {median_chol}")
# Handle zero values in RestingBP
zero_bp_count = (df["RestingBP"] == 0).sum()
print(f"Zero RestingBP values: {zero_bp_count}")
if zero_bp_count > 0:
median_bp = df.loc[df["RestingBP"] > 0, "RestingBP"].median()
df.loc[df["RestingBP"] == 0, "RestingBP"] = median_bp
print(f"Replaced with median: {median_bp}")
# One-hot encode categorical features
categorical_cols = ["Sex", "ChestPainType", "RestingECG", "ExerciseAngina", "ST_Slope"]
df_encoded = pd.get_dummies(df, columns=categorical_cols, drop_first=True)
print(f"\nShape after encoding: {df_encoded.shape}")
print(f"Columns: {list(df_encoded.columns)}")
# Separate features and target
X = df_encoded.drop("HeartDisease", axis=1)
y = df_encoded["HeartDisease"]
# Train-test split (80/20, stratified)
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42, stratify=y
)
print(f"\nTraining set: {X_train.shape[0]} samples")
print(f"Testing set: {X_test.shape[0]} samples")
# Scale numeric features (fit on train only)
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)
X_test_scaled = scaler.transform(X_test)
# ============================================================
# STEP 3: Train and Evaluate Models
# ============================================================
# Model 1: Logistic Regression
lr_model = LogisticRegression(max_iter=1000, random_state=42)
lr_model.fit(X_train_scaled, y_train)
lr_pred = lr_model.predict(X_test_scaled)
print("\n" + "=" * 50)
print("LOGISTIC REGRESSION")
print("=" * 50)
print(f"Accuracy: {accuracy_score(y_test, lr_pred):.4f}")
print(classification_report(y_test, lr_pred, target_names=["No Disease", "Heart Disease"]))
# Model 2: Random Forest
rf_model = RandomForestClassifier(n_estimators=100, random_state=42)
rf_model.fit(X_train_scaled, y_train)
rf_pred = rf_model.predict(X_test_scaled)
print("=" * 50)
print("RANDOM FOREST")
print("=" * 50)
print(f"Accuracy: {accuracy_score(y_test, rf_pred):.4f}")
print(classification_report(y_test, rf_pred, target_names=["No Disease", "Heart Disease"]))
# Model 3: K-Nearest Neighbors
knn_model = KNeighborsClassifier(n_neighbors=5)
knn_model.fit(X_train_scaled, y_train)
knn_pred = knn_model.predict(X_test_scaled)
print("=" * 50)
print("K-NEAREST NEIGHBORS")
print("=" * 50)
print(f"Accuracy: {accuracy_score(y_test, knn_pred):.4f}")
print(classification_report(y_test, knn_pred, target_names=["No Disease", "Heart Disease"]))
# Model comparison summary
print("\n" + "=" * 50)
print("MODEL COMPARISON SUMMARY")
print("=" * 50)
models = {
"Logistic Regression": lr_pred,
"Random Forest": rf_pred,
"K-Nearest Neighbors": knn_pred,
}
for name, preds in models.items():
acc = accuracy_score(y_test, preds)
print(f"{name:25s} | Accuracy: {acc:.4f}")
# Confusion matrix for best model
best_model_name = max(models, key=lambda k: accuracy_score(y_test, models[k]))
best_preds = models[best_model_name]
print(f"\nBest Model: {best_model_name}")
fig, ax = plt.subplots(figsize=(7, 5))
ConfusionMatrixDisplay.from_predictions(
y_test, best_preds,
display_labels=["No Disease", "Heart Disease"],
cmap="Blues",
ax=ax
)
ax.set_title(f"Confusion Matrix - {best_model_name}")
plt.tight_layout()
plt.savefig("confusion_matrix.png", dpi=100)
plt.close()
print("Saved: confusion_matrix.png")
print("\nDone! Check the generated .png files for visualizations.")
You now have three trained models, a clear comparison of their performance, and a confusion matrix showing exactly where the best model succeeds and fails. Next up, the Secret Mission takes this further with cross-validation and feature importance to make your project truly interview-ready.
Secret mission
Cross-Validation and Feature Importance
Your model works on one train/test split. But how do you know that's not a lucky draw? In this mission, you'll add cross-validation to prove your model's consistency, extract feature importances to explain what drives predictions, and build a reusable prediction function for new patients.
Clean Up Your Resources
Clean Up Your Resources
This project runs entirely locally, so there are no ongoing costs. Decide whether to keep your resources for future reference and GitHub upload, or delete them to free up disk space.
Resources you used:
- Virtual environment folder (venv/) containing installed ML packages.
- Project files: heart_disease_predictor.py, heart.csv, and five generated .png visualization files.
Keep everything running
No action needed. Choose this if you plan to push the project to GitHub or continue experimenting with the model.
- Your project folder stays on your Desktop with all files intact.
- The venv/ folder takes up disk space (typically 200-400 MB for the ML libraries) but costs nothing.
- You can re-run heart_disease_predictor.py at any time by activating the virtual environment and running the script.
Pause - I'll come back to this later
Free up disk space by removing the virtual environment while keeping your code and data files. You can recreate it anytime.
- Deactivate your virtual environment by running:
- Delete the venv/ folder to reclaim disk space. On macOS/Linux:
deactivate
rm -rf venv/
On Windows (PowerShell):
deactivate
Remove-Item -Recurse -Force venv\
- Your heart_disease_predictor.py, heart.csv, and .png files stay in place.
- When you come back, recreate the environment with python -m venv venv and reinstall the packages.
Delete - I don't want to use this again
Remove all project resources completely. This deletes your code, data, visualizations, and the virtual environment.
- Deactivate your virtual environment first, then navigate to your Desktop and delete the entire project folder.
- On macOS/Linux, run:
deactivate
cd ~/Desktop
rm -rf heart-disease-predictor
- On Windows (PowerShell), run:
deactivate
cd ~\Desktop
Remove-Item -Recurse -Force heart-disease-predictor
- This removes heart_disease_predictor.py, heart.csv, all .png files, and the venv/ folder.
- Python itself, VS Code, and your Kaggle account remain on your machine. There is nothing else to uninstall.
Nice Work!
Nice Work!
You just built a complete heart disease prediction system using scikit-learn and 918 real patient records. That is a genuine machine learning portfolio piece you can push to GitHub today.
You've learned how to:
- Take a real dataset through the end-to-end ML workflow: loading data, exploring it visually, cleaning quality issues, encoding features, training models, and evaluating results with industry-standard metrics.
- Train and compare three classification algorithms (Logistic Regression, Random Forest, KNN) using proper train/test splitting, stratification, and StandardScaler to prevent data leakage.
- Handle real data quality issues (medically impossible zero values in Cholesterol and RestingBP) and generate professional visualizations including correlation heatmaps and confusion matrices.
- Secret Mission: Added cross-validation for model reliability, feature importance analysis for interpretability, and a reusable prediction function for inference on new patient data.
Ready to quiz yourself?