Predict Heart Disease with ML
Train and compare three classifiers on real clinical data to predict heart disease.
Introduction
30 Second Summary
Heart disease is the leading cause of death worldwide. What if a machine learning model could help doctors predict which patients are at risk, using data they already collect?
In this project, you will build a complete classification pipeline that predicts heart disease from real clinical data. You will train and compare three different classifiers, then generate research-ready visualizations of the results.
What You'll Build
A Python script that loads real patient data, trains three machine learning classifiers, evaluates their performance, and produces publication-quality charts.
By the end of this project, you'll have:
- A working ML classification pipeline that predicts heart disease from 13 clinical features.
- A side-by-side comparison of three classifiers (Logistic Regression, Random Forest, and K-Nearest Neighbors) with accuracy, precision, recall, and F1 scores.
- Research-ready visualizations including confusion matrix heatmaps and a feature importance chart.
- Secret Mission: Add k-fold cross-validation and export a formatted results table ready for a research paper.
Are there any prerequisites?
You should have basic Python knowledge (variables, loops, and functions). No prior machine learning experience is needed.
Set Up Python 3 and Your Project Environment
Your machine learning project relies on four Python libraries that require Python 3.11 or higher. Your existing Python 2.7 from university won't work with them.
In this step, you'll install Python 3.13, create an isolated project environment, and install everything you need to start building.
In this step, get ready to:
- Install Python 3.13.
- Create a project folder and virtual environment.
- Install scikit-learn, pandas, matplotlib, and seaborn.
Install Python 3.13
Python 3.13 is the runtime that powers every library in this project. You need to install it fresh because your existing Python 2.7 is too old.
- Open the Python downloads page in your browser.
- Click the big Download Python 3.13.x button at the top of the page. The page detects your operating system automatically.
- Run the downloaded installer.
Windows
- On the first screen, check the box labelled Add python.exe to PATH at the bottom of the window.
- Click Install Now.
Do not skip the PATH checkbox
Without this checkbox, your terminal will not be able to find the python command. If you already clicked past it, re-run the installer, select Modify, and check the box.
macOS
- Double-click the downloaded .pkg file to launch the installer.
- Follow the prompts and click Install. Python installs to /usr/local/bin/python3 automatically.
- Use python3 instead of python for all commands in this project until you activate the virtual environment.
Linux users
Follow the same steps as macOS. If the downloads page does not auto-detect your OS, scroll down and grab the source tarball or use your distro's package manager.
Once the installer finishes, verify that Python 3.13 is available.
- In VS Code, open a new terminal by clicking Terminal in the top menu bar.
- Click New Terminal.
- Check your Python version by running this command:
python --version
macOS and Linux users
Run python3 --version instead. The python command on macOS often still points to the system's older Python.
✔️ I see Python 3.13.x
Python 3.13 is installed and ready. Your existing Python 2.7 is still on your system, but the python command now points to 3.13.
ⓧ I see Python 2.7.x or an older version
Your terminal is still pointing to the old Python 2.7. This usually means the PATH wasn't updated.
- Close the terminal completely and open a new one in VS Code. PATH changes only take effect in new terminal sessions.
- If you still see 2.7, re-run the Python installer. Select Modify and make sure Add python.exe to PATH is checked.
- macOS users: try running python3 --version instead. If that shows 3.13, use python3 for all commands until you activate the virtual environment.
ⓧ Command not found
Python is not on your system PATH. The installer either did not add it, or you need a fresh terminal session.
- Close the current terminal and open a fresh one via Terminal then New Terminal.
- If that does not work, re-run the Python installer from python.org. Check the Add python.exe to PATH box on the first screen, then click Install Now.
- After reinstalling, close VS Code entirely and reopen it so the new PATH is picked up.
Create a project folder and virtual environment
A virtual environment gives your project its own isolated set of packages. Without one, packages install globally and can conflict between projects.
- 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-ml
cd heart-disease-ml
Your terminal prompt now shows heart-disease-ml as the current directory.
- Create and activate a virtual environment by running these commands:
macOS
python3 -m venv venv
source venv/bin/activate
Linux users
Use the same commands shown here. They work identically on Linux.
Windows
python -m venv venv
venv\Scripts\activate
Your terminal prompt should now show (venv) at the beginning. This confirms the virtual environment is active.
What is a virtual environment?
A virtual environment is a self-contained Python installation inside your project folder. Any packages you install while the environment is active only exist inside this project.
This keeps your machine learning libraries separate from other Python projects on your computer. If you close your terminal later, re-activate the environment before running your script.
Don't see (venv) in your prompt?
- Make sure you ran the activation command for your operating system. Windows uses venv\Scripts\activate while macOS and Linux use source venv/bin/activate.
- On Windows, if you see an error about running scripts being disabled, run this in PowerShell first:
- Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser and then try the activation command again.
- Confirm you are inside the heart-disease-ml folder. The venv folder must exist here for activation to work.
Help me activate my Python virtual environment.
Install the required Python packages
With your virtual environment active, you can now install the four libraries your project needs.
- Install all four packages by running this command:
pip install scikit-learn==1.9.0 pandas==3.0.3 matplotlib==3.11.0 seaborn==0.13.2
What are these packages?
- scikit-learn provides the machine learning algorithms you'll train (Logistic Regression, Random Forest, KNN).
- pandas loads and manipulates your dataset as a table (called a DataFrame).
- matplotlib creates the charts and plots you'll save as image files.
- seaborn builds on top of matplotlib to make heatmaps and styled visualizations easier.
The install may take a minute or two. You'll see progress bars as each package downloads.
- Verify the installation by running this command:
python -c "import sklearn; print(sklearn.__version__)"
You should see 1.9.0 printed in the terminal.
Install failing or import error?
- Make sure you see (venv) in your terminal prompt. If it is missing, pip will try to install packages globally and may fail with a permissions error.
- If you see a network timeout, check your internet connection and try the pip install command again. Package downloads can occasionally time out on slow connections.
- If the import command returns an error about sklearn not being found, confirm that pip install completed without errors. Look for a "Successfully installed" message in the output.
Help me fix my scikit-learn installation.
Your environment is ready. Next up, you'll load real patient data and start exploring it.
Load and Explore the Heart Disease Dataset
Your Python environment is ready. All the packages you need are installed and your virtual environment is keeping everything organized.
Now it's time to work with real patient data. The UCI Heart Disease Cleveland dataset contains records from 303 patients, each with 13 clinical measurements like age, cholesterol, and resting blood pressure. In this step, you'll load this dataset, clean it up, and take a first visual look at the patterns inside.
In this step, get ready to:
- Load the UCI Heart Disease Cleveland dataset into a pandas DataFrame.
- Clean the data by handling missing values and preparing the target column.
- Create a correlation heatmap to visualize relationships between features.
Create the main script file and load the dataset
Every machine learning project starts with data. You'll create a single Python script that loads the Cleveland heart disease dataset directly from the UCI Machine Learning Repository over the internet.
- In VS Code, press Cmd+N (macOS) or Ctrl+N (Windows) to create a new file.
- Press Cmd+S (macOS) or Ctrl+S (Windows) to save the file.
- Name the file heart_disease_ml.py and save it inside your heart-disease-ml folder.
- Add the following imports 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, confusion_matrix
What do these imports do?
- pandas loads and manipulates data in table form (like a spreadsheet).
- numpy provides numerical operations you'll use throughout the project.
- matplotlib and seaborn create charts and visualizations.
- The sklearn imports bring in tools for data preprocessing, model training, and evaluation. You'll use these in the next steps.
You won't see any output yet. The next chunk uses these imports to load the dataset.
- Load the dataset and print initial statistics by adding this code below your imports in heart_disease_ml.py:
# ============================================================
# STEP 2: Load and Explore the Heart Disease Dataset
# ============================================================
# Define column names for the UCI Heart Disease Cleveland dataset
column_names = [
'age', 'sex', 'cp', 'trestbps', 'chol', 'fbs', 'restecg',
'thalach', 'exang', 'oldpeak', 'slope', 'ca', 'thal', 'target'
]
# Load the dataset from the UCI repository
url = "https://archive.ics.uci.edu/ml/machine-learning-databases/heart-disease/processed.cleveland.data"
df = pd.read_csv(url, names=column_names, na_values='?')
print("=== Dataset Shape ===")
print(df.shape)
print("\n=== First 5 Rows ===")
print(df.head())
print("\n=== Data Types ===")
print(df.dtypes)
print("\n=== Summary Statistics ===")
print(df.describe())
What does this code do?
- column_names defines the 14 column headers for the dataset. The raw file has no header row, so you provide them manually.
- pd.read_csv() downloads the dataset directly from the UCI repository URL and loads it into a DataFrame called df.
- na_values='?' tells pandas to treat any ? characters in the raw data as missing values instead of regular text.
- The four print() calls give you a quick overview of how many rows and columns exist, what the first few rows look like, each column's data type, and basic statistics like averages and ranges.
- Save heart_disease_ml.py.
- Run the script in your VS Code terminal by running:
python heart_disease_ml.py
- You should see === Dataset Shape === followed by (303, 14), then the first 5 rows of patient data, data types for each column, and summary statistics.
Seeing a connection error?
The script downloads data from the internet. Make sure you have an active internet connection.
If you see ModuleNotFoundError, check that your virtual environment is activated. Your terminal prompt should start with (venv).
Help me debug my heart disease ML script
Explore the data
Real-world datasets are rarely perfect. The Cleveland dataset uses ? characters to represent missing values in some patient records. You also need to convert the target column into a simple yes-or-no format for classification.
- Handle missing values and prepare the target column by adding this code below the last print(df.describe()) line in heart_disease_ml.py:
# Check for missing values
print("\n=== Missing Values ===")
print(df.isnull().sum())
# Drop rows with missing values
df = df.dropna()
print(f"\nDataset shape after dropping missing values: {df.shape}")
# Convert target to binary: 0 = no disease, 1 = disease present
df['target'] = (df['target'] > 0).astype(int)
print("\n=== Target Distribution ===")
print(df['target'].value_counts())
What does this code do?
- df.isnull().sum() counts how many missing values exist in each column. The ca and thal columns have a few missing entries.
- df.dropna() removes any row with missing data. This drops about 6 rows, leaving approximately 297 clean patient records.
- The original target column contains values 0 through 4 (different severity levels). (df['target'] > 0).astype(int) converts this to binary: 0 means no heart disease, 1 means heart disease is present.
- value_counts() shows how many patients fall into each category. This tells you whether the dataset is balanced or skewed toward one outcome.
Why convert to binary?
The original target values (0 through 4) represent different stages of heart disease. Published research on this dataset focuses on a simpler question: does the patient have heart disease or not? Converting to binary (0 vs 1) matches how this dataset is used in ML research papers.
- Save heart_disease_ml.py and run the script again:
python heart_disease_ml.py
- Scroll down past the earlier output. You should now see a === Missing Values === section showing counts per column, the cleaned dataset shape (approximately (297, 14)), and a === Target Distribution === section showing counts for 0 and 1.
Not seeing the missing values output?
Make sure you added the new code below the existing print(df.describe()) line, not above it or inside another block.
Check that you saved the file before running the script. Unsaved changes won't be included when Python executes the file.
Help me debug my data cleaning code
Visualize the data distribution
A correlation heatmap shows how strongly each pair of features is related. This is your first tool for spotting which clinical measurements might be most useful for predicting heart disease.
- Create and save the correlation heatmap by adding this code at the bottom of heart_disease_ml.py:
# Create correlation heatmap
plt.figure(figsize=(12, 10))
sns.heatmap(df.corr(), annot=True, fmt='.1f', cmap='coolwarm', center=0)
plt.title('Feature Correlation Heatmap')
plt.tight_layout()
plt.savefig('correlation_heatmap.png', dpi=150)
plt.close()
print("\nCorrelation heatmap saved to correlation_heatmap.png")
What does this code do?
- df.corr() calculates the correlation between every pair of columns. Values close to 1 or -1 mean a strong relationship. Values near 0 mean little connection.
- sns.heatmap() from seaborn turns the correlation table into a color-coded grid. annot=True prints the actual numbers on each cell.
- plt.savefig() saves the chart as a PNG image file instead of displaying it in a window. plt.close() frees the memory used by the figure.
- Save heart_disease_ml.py and run the script again:
python heart_disease_ml.py
- At the bottom of the output, you should see Correlation heatmap saved to correlation_heatmap.png.
- Confirm the image file was created by running:
ls
- You should see correlation_heatmap.png and heart_disease_ml.py listed in the output.
Don't see the heatmap file?
Make sure you saved the file before running the script. The heatmap code must come after the data cleaning code, since it uses the cleaned DataFrame.
If you see a warning about FutureWarning from df.corr(), you can safely ignore it. The heatmap should still be saved.
Help me fix my correlation heatmap code
✔️ Awesome, I've got everything!
Great. Double check you've saved heart_disease_ml.py before moving on.
ⓧ I'd like to double check the full code
Here is the complete heart_disease_ml.py file as it should look at the end of this step. Compare it with yours line by line.
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, confusion_matrix
# ============================================================
# STEP 2: Load and Explore the Heart Disease Dataset
# ============================================================
# Define column names for the UCI Heart Disease Cleveland dataset
column_names = [
'age', 'sex', 'cp', 'trestbps', 'chol', 'fbs', 'restecg',
'thalach', 'exang', 'oldpeak', 'slope', 'ca', 'thal', 'target'
]
# Load the dataset from the UCI repository
url = "https://archive.ics.uci.edu/ml/machine-learning-databases/heart-disease/processed.cleveland.data"
df = pd.read_csv(url, names=column_names, na_values='?')
print("=== Dataset Shape ===")
print(df.shape)
print("\n=== First 5 Rows ===")
print(df.head())
print("\n=== Data Types ===")
print(df.dtypes)
print("\n=== Summary Statistics ===")
print(df.describe())
# Check for missing values
print("\n=== Missing Values ===")
print(df.isnull().sum())
# Drop rows with missing values
df = df.dropna()
print(f"\nDataset shape after dropping missing values: {df.shape}")
# Convert target to binary: 0 = no disease, 1 = disease present
df['target'] = (df['target'] > 0).astype(int)
print("\n=== Target Distribution ===")
print(df['target'].value_counts())
# Create correlation heatmap
plt.figure(figsize=(12, 10))
sns.heatmap(df.corr(), annot=True, fmt='.1f', cmap='coolwarm', center=0)
plt.title('Feature Correlation Heatmap')
plt.tight_layout()
plt.savefig('correlation_heatmap.png', dpi=150)
plt.close()
print("\nCorrelation heatmap saved to correlation_heatmap.png")
Your data is loaded, cleaned, and you've had your first visual look at the relationships between features. Next up, you'll preprocess this data for machine learning by splitting it into training and test sets and scaling the features.
Preprocess the Data and Prepare for Training
In the last step, you loaded the UCI Heart Disease dataset and explored its structure. Now it's time to prepare this data so machine learning models can actually learn from it.
Different features in the dataset have wildly different scales. Age ranges from 29 to 77, while fbs (fasting blood sugar) is just 0 or 1. Without preprocessing, models can give unfair weight to features with larger numbers.
In this step, get ready to:
- Separate the dataset into features (X) and target (y).
- Split the data into training and test sets, then scale the features for consistent model training.
Separate features and target
Machine learning models need their inputs and outputs separated. The 13 clinical columns (age, cholesterol, blood pressure, etc.) are the features the model learns from. The target column is what the model tries to predict.
- In heart_disease_ml.py, add this code at the bottom of the file to separate the features from the target:
# ============================================================
# STEP 3: Preprocess the Data
# ============================================================
# Separate features and target
X = df.drop('target', axis=1)
y = df['target']
print(f"\nFeatures shape: {X.shape}")
print(f"Target shape: {y.shape}")
What does this code do?
- X = df.drop('target', axis=1) creates a new DataFrame with all columns except the target. These 13 columns are the features your model will learn from.
- y = df['target'] pulls out just the target column. This is what the model will try to predict: 0 for no disease, 1 for disease present.
- The print statements confirm that X and y have matching row counts and that X has 13 feature columns.
- Save heart_disease_ml.py.
- Run the script to see the new output:
python heart_disease_ml.py
At the bottom of the output, you should see Features shape: showing 13 columns and Target shape: showing a matching number of rows.
Shapes don't look right?
- Make sure you added the new code after the existing correlation heatmap code, not inside it.
- Check that you typed 'target' with the same spelling and lowercase as in the column_names list.
- Why aren't my X and y shapes matching?
Split into training and test sets, then scale features
You need to hold back some data for testing so the model is evaluated on data it has never seen. Feature scaling then ensures all features contribute equally, regardless of their original range.
- Add this code below the previous block to split the data into training and test sets:
# Split into training and test sets
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42
)
print(f"\nTraining set: {X_train.shape}")
print(f"Test set: {X_test.shape}")
What does this code do?
- test_size=0.2 reserves 20% of the data for testing. The remaining 80% is used for training.
- random_state=42 ensures the split is reproducible. Anyone running this script gets the exact same train/test split.
- The four variables (X_train, X_test, y_train, y_test) hold the features and targets for each subset.
- Save heart_disease_ml.py and run the script again.
Near the bottom of the output, you should see Training set: and Test set: shapes. The training set should have approximately 80% of the rows and the test set approximately 20%.
Seeing an error about train_test_split?
- Confirm that the import line from sklearn.model_selection import train_test_split exists at the top of your file. It was added in Step 2.
- Make sure this code is placed after the block that creates X and y. If the split runs before the features exist, you'll get a NameError.
- Why is my train_test_split throwing an error?
- Add this code below the train/test split to scale the features:
# Scale features
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)
X_test_scaled = scaler.transform(X_test)
What does this code do?
- StandardScaler transforms each feature so it has a mean of 0 and a standard deviation of 1. This puts all features on the same scale.
- fit_transform on the training data calculates the scaling parameters (mean and standard deviation) and applies them in one step.
- transform on the test data applies the same parameters calculated from the training data. The test data is never used to calculate scaling values.
What is data leakage?
Data leakage happens when information from the test set accidentally influences the training process. If you fit the scaler on the entire dataset (including test data), the training data "learns" about the test set's distribution.
This gives artificially high accuracy that won't hold up on truly new data. Fitting only on training data prevents this and is a critical best practice in ML research.
- Save heart_disease_ml.py.
These lines don't produce printed output. You'll see the scaled data in action when it trains models in the next step.
Seeing a NameError for StandardScaler?
- Confirm that the import line from sklearn.preprocessing import StandardScaler exists at the top of your file.
- Check that X_train is defined before this block. The scaler needs the training data to exist first.
- Why am I getting a NameError for StandardScaler?
✔️ Awesome, I've got everything!
Great. Double check you've saved heart_disease_ml.py before moving on.
ⓧ I'd like to double check the full code
Here is the complete heart_disease_ml.py file as it should look at the end of this step.
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, confusion_matrix
# ============================================================
# STEP 2: Load and Explore the Heart Disease Dataset
# ============================================================
# Define column names for the UCI Heart Disease Cleveland dataset
column_names = [
'age', 'sex', 'cp', 'trestbps', 'chol', 'fbs', 'restecg',
'thalach', 'exang', 'oldpeak', 'slope', 'ca', 'thal', 'target'
]
# Load the dataset from the UCI repository
url = "https://archive.ics.uci.edu/ml/machine-learning-databases/heart-disease/processed.cleveland.data"
df = pd.read_csv(url, names=column_names, na_values='?')
print("=== Dataset Shape ===")
print(df.shape)
print("\n=== First 5 Rows ===")
print(df.head())
print("\n=== Data Types ===")
print(df.dtypes)
print("\n=== Summary Statistics ===")
print(df.describe())
# Check for missing values
print("\n=== Missing Values ===")
print(df.isnull().sum())
# Drop rows with missing values
df = df.dropna()
print(f"\nDataset shape after dropping missing values: {df.shape}")
# Convert target to binary: 0 = no disease, 1 = disease present
df['target'] = (df['target'] > 0).astype(int)
print("\n=== Target Distribution ===")
print(df['target'].value_counts())
# Create correlation heatmap
plt.figure(figsize=(12, 10))
sns.heatmap(df.corr(), annot=True, fmt='.1f', cmap='coolwarm', center=0)
plt.title('Feature Correlation Heatmap')
plt.tight_layout()
plt.savefig('correlation_heatmap.png', dpi=150)
plt.close()
print("\nCorrelation heatmap saved to correlation_heatmap.png")
# ============================================================
# STEP 3: Preprocess the Data
# ============================================================
# Separate features and target
X = df.drop('target', axis=1)
y = df['target']
print(f"\nFeatures shape: {X.shape}")
print(f"Target shape: {y.shape}")
# Split into training and test sets
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42
)
print(f"\nTraining set: {X_train.shape}")
print(f"Test set: {X_test.shape}")
# Scale features
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)
X_test_scaled = scaler.transform(X_test)
Your data is cleaned, split, and scaled. Next up, you'll train three different classifiers on this prepared data and compare which one predicts heart disease best.
Train Three Classifiers and Compare Results
Your data is cleaned, split, and scaled. Now comes the exciting part: training machine learning models to actually predict heart disease from patient data.
You will train three fundamentally different classifiers on the same dataset. Comparing multiple models on identical data is standard practice in ML research because no single algorithm is guaranteed to perform best on every problem.
In this step, get ready to:
- Train three different classifiers on the scaled training data.
- Evaluate each model with classification reports and confusion matrices.
- Generate a feature importance chart showing which clinical features matter most.
Train the three models
Each of the three classifiers you will train uses a different strategy to make predictions. Logistic Regression draws a linear boundary between classes. Random Forest builds many decision trees and combines their votes. K-Nearest Neighbors classifies a patient based on the most similar patients in the training data.
- In heart_disease_ml.py, add the following code at the very end of the file, below the line X_test_scaled = scaler.transform(X_test):
# ============================================================
# STEP 4: Train Three Classifiers and Compare Results
# ============================================================
# Create and train models
models = {
'Logistic Regression': LogisticRegression(max_iter=1000, random_state=42),
'Random Forest': RandomForestClassifier(n_estimators=100, random_state=42),
'K-Nearest Neighbors': KNeighborsClassifier(n_neighbors=5)
}
What does this code do?
- models is a dictionary that stores all three classifiers, keyed by name. This makes it easy to loop through them later for training and evaluation.
- max_iter=1000 gives Logistic Regression enough iterations to converge on a solution. random_state=42 ensures reproducible results across runs.
- n_estimators=100 tells Random Forest to build 100 decision trees. More trees generally improve accuracy.
- n_neighbors=5 means KNN looks at the 5 most similar patients when classifying a new one.
You won't see any output yet. The next chunk uses this dictionary to train and evaluate each model.
Evaluate each model
Now you will loop through all three models, train each one on the scaled training data, and immediately evaluate its predictions. The loop also plots a confusion matrix heatmap for each model so you can see exactly where it gets predictions right and wrong.
- Add this code directly below the models dictionary you just created:
for name, model in models.items():
model.fit(X_train_scaled, y_train)
y_pred = model.predict(X_test_scaled)
print(f"\n{'='*50}")
print(f"{name}")
print('='*50)
print(classification_report(y_test, y_pred))
# Plot confusion matrix
cm = confusion_matrix(y_test, y_pred)
plt.figure(figsize=(6, 5))
sns.heatmap(cm, annot=True, fmt='d', cmap='Blues',
xticklabels=['No Disease', 'Disease'],
yticklabels=['No Disease', 'Disease'])
plt.title(f'Confusion Matrix - {name}')
plt.ylabel('Actual')
plt.xlabel('Predicted')
plt.tight_layout()
filename = f"confusion_matrix_{name.lower().replace(' ', '_')}.png"
plt.savefig(filename, dpi=150)
plt.close()
print(f"Confusion matrix saved to {filename}")
What does this code do?
The loop iterates through each model in the models dictionary and performs three things for each one:
- model.fit() trains the model on the scaled training data, and model.predict() generates predictions on the test set.
- classification_report() prints precision, recall, F1-score, and accuracy for each model. These metrics tell you how well the model distinguishes disease from no disease.
- confusion_matrix() shows a 2x2 grid of correct vs. incorrect predictions, plotted as a heatmap. Each cell shows how many patients the model classified into each category.
- Save heart_disease_ml.py.
- Run the script in your terminal by running:
python heart_disease_ml.py
- You should see three classification reports printed in the terminal, one for each model. Each report shows precision, recall, F1-score, and accuracy values.
- You should also see three new PNG files in your heart-disease-ml folder: confusion_matrix_logistic_regression.png, confusion_matrix_random_forest.png, and confusion_matrix_k-nearest_neighbors.png.
Not seeing classification reports?
- Make sure your virtual environment is still active. You should see (venv) at the start of your terminal prompt.
- Check that the for loop is indented correctly. Every line inside the loop must be indented by one level (4 spaces) from the for keyword.
- Confirm the models dictionary is defined before the for loop, with no typos in the class names.
Still stuck? Ask for help.
What is a confusion matrix?
A confusion matrix is a 2x2 table that breaks predictions into four categories: true positives (correctly predicted disease), true negatives (correctly predicted no disease), false positives (predicted disease but the patient is healthy), and false negatives (predicted no disease but the patient is sick).
In medicine, false negatives are especially dangerous. A model that misses a patient with heart disease is far worse than one that occasionally flags a healthy patient for further testing.
Visualize feature importance
Random Forest models track how much each feature contributes to their predictions. This is called feature importance. Visualizing it tells you which clinical measurements matter most for predicting heart disease.
- Add this code at the very end of heart_disease_ml.py, below the for loop:
# Plot feature importance for Random Forest
rf_model = models['Random Forest']
feature_importance = pd.Series(
rf_model.feature_importances_, index=X.columns
).sort_values(ascending=True)
plt.figure(figsize=(10, 6))
feature_importance.plot(kind='barh', color='steelblue')
plt.title('Feature Importance (Random Forest)')
plt.xlabel('Importance')
plt.ylabel('Feature')
plt.tight_layout()
plt.savefig('feature_importance.png', dpi=150)
plt.close()
print("\nFeature importance chart saved to feature_importance.png")
What does this code do?
- rf_model.feature_importances_ returns an array of scores showing how much each feature contributed to the Random Forest's predictions.
- The scores are wrapped in a pd.Series and sorted in ascending order so the most important features appear at the top of the horizontal bar chart.
- The chart is saved as feature_importance.png so you can include it in presentations or research papers.
- Save heart_disease_ml.py and run the script again:
python heart_disease_ml.py
- You should see a new line at the end of the terminal output: Feature importance chart saved to feature_importance.png.
- Open feature_importance.png from the heart-disease-ml folder. You should see a horizontal bar chart with the 13 clinical features ranked by importance.
Not seeing the feature importance chart?
- Make sure the feature importance code is placed outside the for loop, not indented inside it. It should be at the same indentation level as the for keyword itself.
- Check that you wrote models['Random Forest'] with exact capitalization and spacing. Dictionary keys are case-sensitive.
Still stuck? Ask for help.
✔️ Awesome, I've got everything!
Great. Double check you have saved heart_disease_ml.py and all five PNG files are in your heart-disease-ml folder.
ⓧ I'd like to double check the full code
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, confusion_matrix
# ============================================================
# STEP 2: Load and Explore the Heart Disease Dataset
# ============================================================
# Define column names for the UCI Heart Disease Cleveland dataset
column_names = [
'age', 'sex', 'cp', 'trestbps', 'chol', 'fbs', 'restecg',
'thalach', 'exang', 'oldpeak', 'slope', 'ca', 'thal', 'target'
]
# Load the dataset from the UCI repository
url = "https://archive.ics.uci.edu/ml/machine-learning-databases/heart-disease/processed.cleveland.data"
df = pd.read_csv(url, names=column_names, na_values='?')
print("=== Dataset Shape ===")
print(df.shape)
print("\n=== First 5 Rows ===")
print(df.head())
print("\n=== Data Types ===")
print(df.dtypes)
print("\n=== Summary Statistics ===")
print(df.describe())
# Check for missing values
print("\n=== Missing Values ===")
print(df.isnull().sum())
# Drop rows with missing values
df = df.dropna()
print(f"\nDataset shape after dropping missing values: {df.shape}")
# Convert target to binary: 0 = no disease, 1 = disease present
df['target'] = (df['target'] > 0).astype(int)
print("\n=== Target Distribution ===")
print(df['target'].value_counts())
# Create correlation heatmap
plt.figure(figsize=(12, 10))
sns.heatmap(df.corr(), annot=True, fmt='.1f', cmap='coolwarm', center=0)
plt.title('Feature Correlation Heatmap')
plt.tight_layout()
plt.savefig('correlation_heatmap.png', dpi=150)
plt.close()
print("\nCorrelation heatmap saved to correlation_heatmap.png")
# ============================================================
# STEP 3: Preprocess the Data
# ============================================================
# Separate features and target
X = df.drop('target', axis=1)
y = df['target']
print(f"\nFeatures shape: {X.shape}")
print(f"Target shape: {y.shape}")
# Split into training and test sets
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42
)
print(f"\nTraining set: {X_train.shape}")
print(f"Test set: {X_test.shape}")
# Scale features
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)
X_test_scaled = scaler.transform(X_test)
# ============================================================
# STEP 4: Train Three Classifiers and Compare Results
# ============================================================
# Create and train models
models = {
'Logistic Regression': LogisticRegression(max_iter=1000, random_state=42),
'Random Forest': RandomForestClassifier(n_estimators=100, random_state=42),
'K-Nearest Neighbors': KNeighborsClassifier(n_neighbors=5)
}
for name, model in models.items():
model.fit(X_train_scaled, y_train)
y_pred = model.predict(X_test_scaled)
print(f"\n{'='*50}")
print(f"{name}")
print('='*50)
print(classification_report(y_test, y_pred))
# Plot confusion matrix
cm = confusion_matrix(y_test, y_pred)
plt.figure(figsize=(6, 5))
sns.heatmap(cm, annot=True, fmt='d', cmap='Blues',
xticklabels=['No Disease', 'Disease'],
yticklabels=['No Disease', 'Disease'])
plt.title(f'Confusion Matrix - {name}')
plt.ylabel('Actual')
plt.xlabel('Predicted')
plt.tight_layout()
filename = f"confusion_matrix_{name.lower().replace(' ', '_')}.png"
plt.savefig(filename, dpi=150)
plt.close()
print(f"Confusion matrix saved to {filename}")
# Plot feature importance for Random Forest
rf_model = models['Random Forest']
feature_importance = pd.Series(
rf_model.feature_importances_, index=X.columns
).sort_values(ascending=True)
plt.figure(figsize=(10, 6))
feature_importance.plot(kind='barh', color='steelblue')
plt.title('Feature Importance (Random Forest)')
plt.xlabel('Importance')
plt.ylabel('Feature')
plt.tight_layout()
plt.savefig('feature_importance.png', dpi=150)
plt.close()
print("\nFeature importance chart saved to feature_importance.png")
Secret mission
Add Cross-Validation and Build a Research-Ready Results Table
Your pipeline already compares three classifiers, but the evaluation relies on a single train/test split. In this mission, you will add k-fold cross-validation to produce more reliable accuracy scores. You will also build a formatted results table ready for a research paper.
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 running, pause them to come back later, or delete them entirely.
Resources you used:
- heart-disease-ml project folder on your Desktop (contains heart_disease_ml.py, generated PNG images, results_table.csv, and the venv virtual environment).
- Python 3.13 installation.
Keep everything running
No action needed. Choose this if you plan to keep experimenting with your machine learning pipeline or want to try new classifiers on the dataset.
- Your project files, generated visualizations, and Python installation remain on your machine.
- To reactivate your virtual environment later, navigate to your heart-disease-ml folder and run venv\Scripts\activate (Windows) or source venv/bin/activate (macOS/Linux).
Pause - I'll come back to this later
Deactivate your virtual environment to free up your terminal session while keeping all project files intact for later.
- If your virtual environment is still active (you see (venv) in your terminal prompt), deactivate it by running:
deactivate
- Close VS Code when you are done for now.
- When you come back, reactivate the virtual environment by navigating to your heart-disease-ml folder and running venv\Scripts\activate (Windows) or source venv/bin/activate (macOS/Linux).
Delete - I don't want to use this again
Remove all project files and start fresh.
- If your virtual environment is still active (you see (venv) in your terminal prompt), deactivate it first by running:
deactivate
- Delete your heart-disease-ml project folder. This removes your script, all generated images, the CSV file, and the virtual environment.
macOS
- Run this command in your terminal:
rm -rf ~/Desktop/heart-disease-ml
Windows
- Run this command in Command Prompt:
rmdir /s /q "%USERPROFILE%\Desktop\heart-disease-ml"
- To uninstall Python 3.13, open Settings on your computer.
- Select Apps, then Installed apps.
- Search for Python 3.13, click the three-dot menu next to it, and select Uninstall.
Nice Work!
Nice Work!
You just built a complete machine learning classification pipeline that predicts heart disease from real clinical data. This is the same methodology used in published research papers.
Here is what you accomplished:
- Loaded, cleaned, and preprocessed the UCI Heart Disease Cleveland dataset (303 patients, 13 clinical features) with proper feature scaling and train/test splitting to prevent data leakage.
- Trained and compared three classifiers on the same dataset: Logistic Regression, Random Forest, and K-Nearest Neighbors. You evaluated each using accuracy, precision, recall, F1-score, and confusion matrices.
- Generated publication-quality visualizations including a correlation heatmap, confusion matrix heatmaps for each model, and a feature importance chart showing which clinical features matter most.
- Secret Mission: Added 10-fold cross-validation and exported a research-ready results comparison table to results_table.csv.
Ready to quiz yourself?