Heart Disease Risk Prediction App
Build and deploy a clinical ML model that predicts heart disease risk.
Introduction
30 Second Summary
Every year, doctors assess millions of patients for heart disease risk. They gather biomarkers like cholesterol levels, blood pressure readings, and ECG results, then weigh those numbers against decades of clinical experience. What if software could do that assessment instantly and consistently?
In this project, you will build a complete heart disease risk prediction platform. You will train a machine learning model on real clinical data, serve it through a REST API, and deploy an interactive dashboard where a clinician can enter patient data and receive an instant risk score.
What You'll Build
A full-stack medical machine learning product that takes raw patient biomarkers and returns a heart disease risk prediction with the key contributing factors explained.
By the end of this project, you'll have:
- A trained classification model that predicts heart disease risk from 13 clinical features like cholesterol, resting blood pressure, and exercise test results.
- A FastAPI REST API with /predict and /health endpoints and auto-generated Swagger documentation.
- A live Streamlit clinical dashboard deployed for free on Streamlit Community Cloud, complete with patient input forms and risk visualization.
- Secret Mission: Add SHAP-based individual patient explanations showing exactly which biomarkers are driving each prediction.
Are there any prerequisites?
This project assumes you are comfortable with Python fundamentals and have a basic understanding of machine learning concepts (what a model is, what training means). Familiarity with pandas for data manipulation will also help you move through the early steps faster.
Before We Start
You're building a heart disease risk prediction SaaS product. This is the type of clinical decision support tool that health-tech companies sell to hospitals.
Before diving into the data science, you need a clean development environment. In this step, you'll create a project folder, set up an isolated virtual environment, and install all the libraries the project needs.
In this step, get ready to:
- Create a project folder with an isolated Python virtual environment.
- Install FastAPI, Streamlit, scikit-learn, and supporting libraries.
- Verify that all libraries import correctly in a Python shell.
Create your project folder and virtual environment
A virtual environment gives your project its own isolated set of Python packages. This prevents version conflicts between different projects on your machine.
- Press Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
- Type Terminal (macOS) or Command Prompt (Windows) 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
- Create a virtual environment inside the project folder by running:
python3 -m venv venv
What does this command do?
python3 -m venv venv tells Python to create a virtual environment in a folder called venv. This folder holds a private copy of the Python interpreter and a clean package directory just for this project.
- 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 beginning of your terminal prompt. This confirms the virtual environment is active.
Don't see (venv) in your prompt?
Make sure you ran the activation command from inside the heart-disease-predictor folder. The venv subfolder must exist in your current directory.
On Windows, if you see an execution policy error, run Set-ExecutionPolicy -Scope CurrentUser -ExecutionPolicy RemoteSigned in PowerShell first, then retry the activation.
Help me activate my Python virtual environment
- Open the project folder in VS Code by running:
code .
If code . doesn't work
Open VS Code manually, then click File in the top menu bar and select Open Folder. Navigate to your Desktop and select the heart-disease-predictor folder.
Install the project dependencies
With your virtual environment active, every package you install stays contained in this project. Nothing touches your system-wide Python.
- Install all required libraries by running this command:
pip install "fastapi[standard]==0.136.3" streamlit==1.57.0 scikit-learn==1.8.0 pandas matplotlib joblib
What are these libraries?
- FastAPI is a modern Python web framework for building APIs. The [standard] extra includes Uvicorn, the server that runs your API.
- Streamlit lets you build interactive web dashboards in pure Python.
- scikit-learn provides the machine learning algorithms you'll train.
- pandas handles data loading and manipulation.
- matplotlib creates charts and visualizations.
- joblib saves your trained model to a file so other programs can load it.
The installation may take a minute or two depending on your internet speed. You should see progress bars for each package being downloaded.
Seeing installation errors?
Make sure (venv) is still showing in your terminal prompt. If it disappeared, reactivate the virtual environment before retrying the install.
If you see a permissions error, confirm you're installing inside the virtual environment and not your system Python.
Help me fix my pip install errors
Verify your installations
A quick import test confirms every library installed correctly. If any import fails, you'll catch it now instead of in the middle of building your model.
- Open a Python shell by running:
python3
- Import each library and print a success message by entering these lines:
import fastapi
import streamlit
import sklearn
import pandas
import matplotlib
import joblib
print("All libraries imported successfully!")
What should I see?
If everything installed correctly, you'll see All libraries imported successfully! printed with no error messages above it. Each import line should run silently.
- Exit the Python shell by running:
exit()
Seeing a ModuleNotFoundError?
Exit the Python shell with exit(), confirm (venv) is in your prompt, and rerun the pip install command from the previous substep.
If you opened a new terminal window since activating, you need to reactivate the virtual environment first.
Help me fix my ModuleNotFoundError
Your environment is ready. Next up, you'll load real clinical data and explore the patterns inside it.
Explore and Prepare the Clinical Dataset
Your environment is set up and ready to go. Now it is time to get your hands on real patient data.
Before you can train a model to predict heart disease, you need to understand the data you are working with. What patterns exist in the clinical measurements? Are there missing values that could trip up your model? This step walks you through loading a real clinical dataset, exploring its structure, and cleaning it for machine learning.
In this step, get ready to:
- Load the UCI Cleveland Heart Disease dataset into a Jupyter Notebook.
- Perform exploratory data analysis with visualizations.
- Clean and encode the dataset for machine learning.
Load the UCI Cleveland Heart Disease dataset
The UCI Machine Learning Repository hosts one of the most-cited heart disease datasets in research. It contains 303 patient records from the Cleveland Clinic, each with 13 clinical features and a diagnosis label.
- Open the Command Palette by pressing Cmd+Shift+P (macOS) or Ctrl+Shift+P (Windows).
- Type Create: New Jupyter Notebook and select it from the list.
- Save the notebook as heart_disease_eda.ipynb inside your heart-disease-predictor folder using File > Save As....
- Click Select Kernel in the top right corner of the notebook.
- Choose the Python environment from your virtual environment.
What is a Jupyter Notebook?
A Jupyter Notebook lets you write and run Python code in small chunks called cells. Each cell can be executed independently, which makes it perfect for data exploration where you want to see results as you go.
- Add the following code to the first cell of your notebook to import libraries and load the dataset:
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
# Column names for the 14 attributes used in published experiments
column_names = [
'age', 'sex', 'cp', 'trestbps', 'chol', 'fbs',
'restecg', 'thalach', 'exang', 'oldpeak', 'slope',
'ca', 'thal', 'target'
]
# Load the Cleveland Heart Disease dataset from UCI ML Repository
url = "http://archive.ics.uci.edu/ml/machine-learning-databases/heart-disease/processed.cleveland.data"
df = pd.read_csv(url, names=column_names, na_values='?')
# Check the shape and first few rows
print(f"Dataset shape: {df.shape}")
df.head()
What does this code do?
- column_names defines the 14 clinical attributes (13 features plus 1 target) that the Cleveland dataset uses.
- pd.read_csv(url, names=column_names, na_values='?') downloads the data directly from UCI and tells pandas to treat ? characters as missing values.
- The dataset has 303 rows (patients) and 14 columns.
- Run the cell by pressing Shift+Enter.
You should see Dataset shape: (303, 14) printed, followed by a table showing the first five patient records.
Seeing a connection error?
- Make sure you have an active internet connection. The dataset is loaded directly from the UCI website.
- If your kernel is not connected, click Select Kernel again and choose your project's virtual environment.
Still stuck? Why can't my notebook connect to the UCI dataset?
Perform exploratory data analysis
Before cleaning data, you need to understand what you are working with. EDA reveals class imbalances, correlations between features, and data quality issues.
- Add a new cell and paste the following code to check for missing values and plot the target distribution:
# Check for missing values
print("Missing values per column:")
print(df.isnull().sum())
print(f"\nTotal missing values: {df.isnull().sum().sum()}")
# Plot raw target distribution (0 = no disease, 1-4 = disease stages)
plt.figure(figsize=(6, 4))
df['target'].value_counts().sort_index().plot(kind='bar', color='steelblue')
plt.title('Heart Disease Class Distribution (Raw)')
plt.xlabel('Disease Stage (0 = none, 1-4 = present)')
plt.ylabel('Number of Patients')
plt.tight_layout()
plt.show()
What does this code do?
- df.isnull().sum() counts missing values in each column. The dataset has 6 missing values in the ca and thal columns (marked as ? in the raw file).
- The bar chart shows how many patients fall into each disease stage. Stage 0 means no disease. Stages 1 through 4 represent increasing severity.
- Run the cell with Shift+Enter.
You should see a count of missing values (4 in ca, 2 in thal) and a bar chart showing the distribution across disease stages.
- Add another new cell and paste this code to visualize feature correlations:
# Correlation heatmap of all features
plt.figure(figsize=(12, 8))
corr = df.corr()
im = plt.imshow(corr, cmap='coolwarm', vmin=-1, vmax=1)
plt.colorbar(im)
plt.xticks(range(len(corr.columns)), corr.columns, rotation=45, ha='right')
plt.yticks(range(len(corr.columns)), corr.columns)
plt.title('Feature Correlation Heatmap')
plt.tight_layout()
plt.show()
What does this code do?
A correlation heatmap shows how strongly each pair of features relates to each other. Red cells indicate positive correlation (both increase together). Blue cells indicate negative correlation (one increases while the other decreases).
Look for strong correlations with the target column. Features like cp (chest pain type) and thalach (max heart rate) tend to show notable correlations with disease presence.
- Run the cell with Shift+Enter.
You should see a color-coded grid. Diagonal cells are always dark red (a feature correlates perfectly with itself).
Heatmap looks squished or labels overlap?
- Make sure your notebook output area is wide enough. You can drag the edge of the VS Code panel to give it more space.
- The plt.tight_layout() call prevents most overlap issues. If labels still cut off, try increasing the figsize width value.
Apply clinical feature engineering
The raw dataset has missing values and a multi-class target (0 through 4). For a binary classifier that predicts "disease present or not," you need to clean the gaps and collapse the target into two values.
- Add a new cell and paste this code to handle missing values, binarize the target, and ensure proper column types:
# Fill missing values in 'ca' and 'thal' with the most frequent value (mode)
df['ca'] = df['ca'].fillna(df['ca'].mode()[0])
df['thal'] = df['thal'].fillna(df['thal'].mode()[0])
print(f"Missing values remaining: {df.isnull().sum().sum()}")
# Binarize the target: 0 = no disease, 1 = disease present (stages 1-4)
df['target'] = (df['target'] > 0).astype(int)
print("\nBinarized target distribution:")
print(df['target'].value_counts())
# Ensure categorical columns are proper integer type
categorical_cols = ['sex', 'cp', 'fbs', 'restecg', 'exang', 'slope', 'ca', 'thal']
for col in categorical_cols:
df[col] = df[col].astype(int)
print(f"\nFinal dataset shape: {df.shape}")
print(df.dtypes)
What does this code do?
- fillna(df['ca'].mode()[0]) replaces missing values with the most common value in that column. This is a safe strategy for small numbers of missing values in categorical data.
- (df['target'] > 0).astype(int) converts the five-class target (0, 1, 2, 3, 4) into a binary column. Any value above 0 becomes 1 (disease present).
- Casting categorical columns to int ensures that scikit-learn treats them as numeric inputs rather than objects.
- Run the cell with Shift+Enter.
You should see Missing values remaining: 0 and a target distribution showing the split between 0 (no disease) and 1 (disease present).
- Add a final cell to save the cleaned dataset:
# Save the cleaned dataset for use in later steps
df.to_csv('heart_clean.csv', index=False)
print("Saved heart_clean.csv")
print(f"Final shape: {df.shape} (13 features + 1 target)")
Why save to CSV?
Saving the cleaned data to heart_clean.csv means your API and dashboard can load the prepared data without re-running the entire notebook. It also locks in your preprocessing decisions so the model training step produces consistent results.
- Run the cell with Shift+Enter.
- Confirm the file exists by running this in a new cell:
import os
print(os.listdir('.'))
You should see heart_clean.csv in the output list.
Don't see heart_clean.csv in the file list?
- Make sure you ran all previous cells in order. The save cell depends on the df variable created in the first cell.
- Check that your notebook's working directory matches your project folder. Run os.getcwd() in a cell to confirm you are inside heart-disease-predictor.
Still having issues? Help me find where my notebook is saving files.
- Save your notebook with Cmd+S (macOS) or Ctrl+S (Windows).
✔️ Awesome, I've got everything!
Great. Double check you have saved both your notebook and the heart_clean.csv file in your heart-disease-predictor folder.
ⓧ I'd like to double check the full code
Here is the complete notebook code across all cells:
# Cell 1: Import libraries and load dataset
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
# Column names for the 14 attributes used in published experiments
column_names = [
'age', 'sex', 'cp', 'trestbps', 'chol', 'fbs',
'restecg', 'thalach', 'exang', 'oldpeak', 'slope',
'ca', 'thal', 'target'
]
# Load the Cleveland Heart Disease dataset from UCI ML Repository
url = "http://archive.ics.uci.edu/ml/machine-learning-databases/heart-disease/processed.cleveland.data"
df = pd.read_csv(url, names=column_names, na_values='?')
# Check the shape and first few rows
print(f"Dataset shape: {df.shape}")
df.head()
# Cell 2: Check missing values and plot target distribution
print("Missing values per column:")
print(df.isnull().sum())
print(f"\nTotal missing values: {df.isnull().sum().sum()}")
# Plot raw target distribution (0 = no disease, 1-4 = disease stages)
plt.figure(figsize=(6, 4))
df['target'].value_counts().sort_index().plot(kind='bar', color='steelblue')
plt.title('Heart Disease Class Distribution (Raw)')
plt.xlabel('Disease Stage (0 = none, 1-4 = present)')
plt.ylabel('Number of Patients')
plt.tight_layout()
plt.show()
# Cell 3: Correlation heatmap
plt.figure(figsize=(12, 8))
corr = df.corr()
im = plt.imshow(corr, cmap='coolwarm', vmin=-1, vmax=1)
plt.colorbar(im)
plt.xticks(range(len(corr.columns)), corr.columns, rotation=45, ha='right')
plt.yticks(range(len(corr.columns)), corr.columns)
plt.title('Feature Correlation Heatmap')
plt.tight_layout()
plt.show()
# Cell 4: Feature engineering - handle missing values, binarize target, encode types
df['ca'] = df['ca'].fillna(df['ca'].mode()[0])
df['thal'] = df['thal'].fillna(df['thal'].mode()[0])
print(f"Missing values remaining: {df.isnull().sum().sum()}")
# Binarize the target: 0 = no disease, 1 = disease present (stages 1-4)
df['target'] = (df['target'] > 0).astype(int)
print("\nBinarized target distribution:")
print(df['target'].value_counts())
# Ensure categorical columns are proper integer type
categorical_cols = ['sex', 'cp', 'fbs', 'restecg', 'exang', 'slope', 'ca', 'thal']
for col in categorical_cols:
df[col] = df[col].astype(int)
print(f"\nFinal dataset shape: {df.shape}")
print(df.dtypes)
# Cell 5: Save cleaned dataset
df.to_csv('heart_clean.csv', index=False)
print("Saved heart_clean.csv")
print(f"Final shape: {df.shape} (13 features + 1 target)")
You now have a clean, well-understood dataset ready for model training. Next up, you will train multiple classifiers and pick the best one based on medical-relevant metrics.
Train and Evaluate the Prediction Model
Your cleaned dataset is ready with 303 patient records and 13 clinical features encoded for machine learning. Now it is time to find out which algorithm best predicts heart disease from these biomarkers.
In clinical settings, a missed diagnosis (false negative) can be life-threatening. You will train three classifiers and evaluate them with metrics that reflect this medical reality, then save the winner for production use.
In this step, get ready to:
- Split data into stratified train/test sets.
- Train and compare Logistic Regression, Random Forest, and Gradient Boosting classifiers.
- Evaluate with ROC-AUC, precision, recall, and confusion matrices, then serialize the best model.
Load the data and create a stratified train/test split
A stratified split ensures that both the training and test sets have the same proportion of heart disease patients as the full dataset. Without this, a random split on 303 records could accidentally stack one group with more positive cases, skewing your evaluation.
- In VS Code, open the heart-disease-predictor folder in the Explorer sidebar.
- Create a new file called heart_disease_model.ipynb by clicking the New File icon next to the folder name.
- Add a new code cell and paste the following imports and data-loading code:
import pandas as pd
import numpy as np
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression
from sklearn.ensemble import RandomForestClassifier, GradientBoostingClassifier
from sklearn.metrics import roc_auc_score, classification_report, ConfusionMatrixDisplay
import matplotlib.pyplot as plt
import joblib
# Load the cleaned dataset from the previous step
df = pd.read_csv('heart_clean.csv')
# Separate features (X) from the target label (y)
X = df.drop('target', axis=1)
y = df['target']
print(f"Dataset: {X.shape[0]} patients, {X.shape[1]} features")
What does this code do?
This cell loads your cleaned CSV and separates it into two pieces. X holds the 13 clinical feature columns (age, cholesterol, blood pressure, etc.). y holds the binary target (0 = no disease, 1 = disease present).
- Run the cell by pressing Shift+Enter.
- You should see output confirming 303 patients and 13 features.
- Add a new code cell below and paste the train/test split code:
# Split with stratification to maintain class balance in both sets
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42, stratify=y
)
print(f"Training set: {X_train.shape[0]} patients")
print(f"Test set: {X_test.shape[0]} patients")
print(f"Disease rate in training: {y_train.mean():.2%}")
print(f"Disease rate in test: {y_test.mean():.2%}")
What does stratify=y do?
The stratify=y parameter forces both the training and test sets to have the same ratio of disease vs. no-disease cases as the full dataset. This is critical for medical datasets where class imbalance can distort evaluation metrics.
The test_size=0.2 reserves 20% of patients (about 61 records) for unbiased evaluation. The remaining 80% is used for training.
- Run the cell. You should see roughly 242 training patients and 61 test patients, with nearly identical disease rates in both sets.
Seeing a FileNotFoundError?
Make sure heart_clean.csv is in the same folder as your new notebook. Check the Explorer sidebar to confirm the file exists in heart-disease-predictor.
Help me fix a FileNotFoundError when loading my CSV in Jupyter
Train and compare three classifiers
No single algorithm is guaranteed to perform best on every dataset. By training three fundamentally different classifiers, you can compare how each handles the clinical feature relationships.
Why these three classifiers?
Logistic Regression is a linear model that gives you an interpretable baseline. If it performs well, the relationship between features and disease risk is mostly linear.
Random Forest builds many decision trees and averages their votes, capturing non-linear patterns without overfitting easily. Gradient Boosting builds trees sequentially, with each tree correcting the previous one's mistakes. It often achieves the highest accuracy but can overfit small datasets.
- Add a new code cell and paste the model training code:
# Define three classifiers for comparison
models = {
'Logistic Regression': LogisticRegression(max_iter=1000, random_state=42),
'Random Forest': RandomForestClassifier(n_estimators=100, random_state=42),
'Gradient Boosting': GradientBoostingClassifier(n_estimators=100, random_state=42)
}
# Train each model on the training data
for name, model in models.items():
model.fit(X_train, y_train)
print(f"{name} trained successfully")
What does this code do?
The dictionary stores each classifier with a descriptive name as the key. The loop calls .fit() on each one, which trains the model on the 242 training patients.
The random_state=42 ensures reproducible results. The max_iter=1000 on Logistic Regression gives the solver enough iterations to converge on this dataset.
- Run the cell. You should see three "trained successfully" messages.
Seeing a ConvergenceWarning?
If Logistic Regression shows a convergence warning, increase max_iter to 2000. This gives the solver more time to find the optimal weights.
Help me fix a ConvergenceWarning in my Logistic Regression model
- Add a new code cell and paste the evaluation code:
# Evaluate each model with medical-relevant metrics
results = {}
for name, model in models.items():
y_pred = model.predict(X_test)
y_prob = model.predict_proba(X_test)[:, 1]
# ROC-AUC measures overall ranking quality
auc = roc_auc_score(y_test, y_prob)
results[name] = auc
print(f"\n{'='*50}")
print(f"{name} (ROC-AUC: {auc:.3f})")
print(f"{'='*50}")
# Precision, recall, and F1 for each class
print(classification_report(y_test, y_pred))
Why these metrics matter in medicine
ROC-AUC (Area Under the ROC Curve) measures how well the model separates disease from no-disease across all possible thresholds. A score of 1.0 is perfect. A score of 0.5 is random guessing.
Recall (sensitivity) is critical here. It measures what percentage of actual disease cases the model catches. A low recall means patients with heart disease slip through undiagnosed. Precision measures how many of the "disease" predictions are correct, reducing unnecessary follow-up procedures.
- Run the cell. You should see a classification report for each model showing precision, recall, and F1-score for both classes, plus the ROC-AUC score in the header.
- Add a new code cell and paste the confusion matrix visualization code:
# Visualize confusion matrices side by side
fig, axes = plt.subplots(1, 3, figsize=(15, 4))
for ax, (name, model) in zip(axes, models.items()):
ConfusionMatrixDisplay.from_estimator(model, X_test, y_test, ax=ax)
ax.set_title(name)
plt.tight_layout()
plt.show()
How to read the confusion matrices
Each matrix shows four quadrants. Top-left is true negatives (correctly identified healthy patients). Bottom-right is true positives (correctly identified disease). Top-right is false positives (healthy patients flagged as sick). Bottom-left is false negatives (sick patients missed).
In a clinical tool, the bottom-left number is the most dangerous. Those are patients with heart disease who the model failed to catch.
- Run the cell. You should see three confusion matrix heatmaps displayed side by side.
Plots not showing up?
Make sure you are running the notebook with the correct kernel. Click the kernel name in the top-right corner of the notebook and select the Python interpreter from your virtual environment.
Help me display matplotlib plots in my Jupyter notebook in VS Code
Select the best model and serialize it with joblib
Model serialization saves your trained model to a file so the API and dashboard can load it without retraining. The joblib library is recommended by scikit-learn for this purpose because it efficiently handles the large numpy arrays inside trained models.
- Add a new code cell and paste the model selection and serialization code:
# Select the best model based on ROC-AUC score
best_model_name = max(results, key=results.get)
best_model = models[best_model_name]
print(f"Best model: {best_model_name} (ROC-AUC: {results[best_model_name]:.3f})")
# Serialize the best model for production use
joblib.dump(best_model, 'heart_model.pkl')
print(f"Model saved to heart_model.pkl")
What does joblib.dump do?
The joblib.dump() function writes the entire trained model object (including learned weights, tree structures, and hyperparameters) to a .pkl file. Later, your FastAPI server and Streamlit dashboard will call joblib.load('heart_model.pkl') to get the model back, ready to make predictions instantly without retraining.
- Run the cell. You should see the best model name printed along with its ROC-AUC score, and a confirmation that it was saved.
- Save the notebook by pressing Cmd+S (macOS) or Ctrl+S (Windows).
- Confirm the model file exists by checking the Explorer sidebar. You should see heart_model.pkl in your heart-disease-predictor folder.
Don't see heart_model.pkl in the sidebar?
Make sure you ran all cells in order from top to bottom. If you ran cells out of order, the best_model variable might not exist yet. Try clicking Run All at the top of the notebook to re-execute everything in sequence.
Help me figure out why my model file isn't being created
✔️ Awesome, I've got everything!
Great. Double check you have saved heart_disease_model.ipynb and that heart_model.pkl appears in your project folder.
ⓧ I'd like to double check the full code
Here is the full heart_disease_model.ipynb notebook code. Each section below represents one cell:
import pandas as pd
import numpy as np
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression
from sklearn.ensemble import RandomForestClassifier, GradientBoostingClassifier
from sklearn.metrics import roc_auc_score, classification_report, ConfusionMatrixDisplay
import matplotlib.pyplot as plt
import joblib
# Load the cleaned dataset from the previous step
df = pd.read_csv('heart_clean.csv')
# Separate features (X) from the target label (y)
X = df.drop('target', axis=1)
y = df['target']
print(f"Dataset: {X.shape[0]} patients, {X.shape[1]} features")
# Split with stratification to maintain class balance in both sets
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42, stratify=y
)
print(f"Training set: {X_train.shape[0]} patients")
print(f"Test set: {X_test.shape[0]} patients")
print(f"Disease rate in training: {y_train.mean():.2%}")
print(f"Disease rate in test: {y_test.mean():.2%}")
# Define three classifiers for comparison
models = {
'Logistic Regression': LogisticRegression(max_iter=1000, random_state=42),
'Random Forest': RandomForestClassifier(n_estimators=100, random_state=42),
'Gradient Boosting': GradientBoostingClassifier(n_estimators=100, random_state=42)
}
# Train each model on the training data
for name, model in models.items():
model.fit(X_train, y_train)
print(f"{name} trained successfully")
# Evaluate each model with medical-relevant metrics
results = {}
for name, model in models.items():
y_pred = model.predict(X_test)
y_prob = model.predict_proba(X_test)[:, 1]
# ROC-AUC measures overall ranking quality
auc = roc_auc_score(y_test, y_prob)
results[name] = auc
print(f"\n{'='*50}")
print(f"{name} (ROC-AUC: {auc:.3f})")
print(f"{'='*50}")
# Precision, recall, and F1 for each class
print(classification_report(y_test, y_pred))
# Visualize confusion matrices side by side
fig, axes = plt.subplots(1, 3, figsize=(15, 4))
for ax, (name, model) in zip(axes, models.items()):
ConfusionMatrixDisplay.from_estimator(model, X_test, y_test, ax=ax)
ax.set_title(name)
plt.tight_layout()
plt.show()
# Select the best model based on ROC-AUC score
best_model_name = max(results, key=results.get)
best_model = models[best_model_name]
print(f"Best model: {best_model_name} (ROC-AUC: {results[best_model_name]:.3f})")
# Serialize the best model for production use
joblib.dump(best_model, 'heart_model.pkl')
print(f"Model saved to heart_model.pkl")
Your trained model is now saved and ready to serve predictions. Next up, you will wrap it in a FastAPI REST API so any application can send patient data and receive a risk score back.
Build the FastAPI Prediction API
Your model is trained, evaluated, and saved as a .pkl file. Now you need a way for other applications to use it.
In this step, you will wrap your model in a FastAPI application that accepts patient data as JSON and returns a risk prediction. This is how real health-tech companies serve ML models to hospital systems.
In this step, get ready to:
- Create a FastAPI app with a Pydantic input schema matching the 13 clinical features.
- Implement /health and /predict endpoints that load your serialized model.
- Test the API locally using the auto-generated Swagger UI.
Create the FastAPI app with a Pydantic input schema
A REST API needs to know what shape of data to expect. Pydantic models define that shape, and FastAPI uses them to automatically validate incoming requests and generate documentation.
- In VS Code, create a new file called api.py inside your heart-disease-predictor folder.
- Add the imports, patient input schema, and app initialization by pasting the following code:
from fastapi import FastAPI
from pydantic import BaseModel
import joblib
import numpy as np
import pandas as pd
# Define the expected shape of patient input data
class PatientFeatures(BaseModel):
age: int
sex: int
cp: int
trestbps: int
chol: int
fbs: int
restecg: int
thalach: int
exang: int
oldpeak: float
slope: int
ca: int
thal: int
# Create the FastAPI app
app = FastAPI()
What does this code do?
The imports bring in FastAPI for the web framework, BaseModel from Pydantic for data validation, joblib for loading your saved model, and numpy/pandas for data manipulation.
The PatientFeatures class defines exactly which fields a prediction request must include. Each of the 13 fields matches a column from your training data. FastAPI will reject any request that is missing a field or sends the wrong type.
What are these clinical feature names?
- cp = chest pain type. trestbps = resting blood pressure. chol = serum cholesterol.
- fbs = fasting blood sugar > 120 mg/dl. restecg = resting ECG results.
- thalach = max heart rate achieved. exang = exercise-induced angina.
- oldpeak = ST depression induced by exercise. slope = slope of the peak exercise ST segment.
- ca = number of major vessels colored by fluoroscopy. thal = thalassemia type.
- Save api.py.
Seeing a red underline on imports?
Make sure your virtual environment is selected as the Python interpreter in VS Code. Press Cmd+Shift+P (macOS) or Ctrl+Shift+P (Windows), type Python: Select Interpreter, and choose the interpreter from your venv folder.
Help me fix import errors in my FastAPI file
Implement the /health and /predict endpoints
The app skeleton is ready. Now you need the actual endpoints that handle requests.
- In api.py, add the following code below app = FastAPI():
# Health check endpoint
@app.get("/health")
def health_check():
return {"status": "ok"}
# Prediction endpoint
@app.post("/predict")
def predict(patient: PatientFeatures):
# Load the trained model
model = joblib.load("heart_model.pkl")
# Convert patient data to a DataFrame
data = pd.DataFrame([patient.model_dump()])
# Make prediction and get probability
prediction = model.predict(data)[0]
probability = model.predict_proba(data)[0][1]
return {
"prediction": int(prediction),
"risk_probability": float(probability)
}
What do these endpoints do?
/health is a simple GET endpoint that returns {"status": "ok"}. Production systems use health checks to verify the API is running before sending real traffic.
/predict is a POST endpoint that receives patient data as JSON. It loads heart_model.pkl, converts the input into a DataFrame (the format your model expects), then returns both a binary prediction (0 = no disease, 1 = disease) and the probability score.
Why load the model inside the endpoint?
Loading the model on each request keeps this tutorial simple and ensures changes to heart_model.pkl are picked up immediately. In a production system, you would load the model once at startup for better performance.
- Save api.py.
Seeing a warning on model_dump?
The model_dump() method is from Pydantic v2, which ships with FastAPI 0.136.3. If you see an error about this method not existing, your FastAPI version may be outdated. Reinstall with pip install "fastapi[standard]==0.136.3".
Help me fix a model_dump error in my FastAPI predict endpoint
✔️ Awesome, I've got everything!
Great. Double check you've saved your file before moving on to testing.
ⓧ I'd like to double check the full code
Here is the complete api.py file. Make sure yours matches:
from fastapi import FastAPI
from pydantic import BaseModel
import joblib
import numpy as np
import pandas as pd
# Define the expected shape of patient input data
class PatientFeatures(BaseModel):
age: int
sex: int
cp: int
trestbps: int
chol: int
fbs: int
restecg: int
thalach: int
exang: int
oldpeak: float
slope: int
ca: int
thal: int
# Create the FastAPI app
app = FastAPI()
# Health check endpoint
@app.get("/health")
def health_check():
return {"status": "ok"}
# Prediction endpoint
@app.post("/predict")
def predict(patient: PatientFeatures):
# Load the trained model
model = joblib.load("heart_model.pkl")
# Convert patient data to a DataFrame
data = pd.DataFrame([patient.model_dump()])
# Make prediction and get probability
prediction = model.predict(data)[0]
probability = model.predict_proba(data)[0][1]
return {
"prediction": int(prediction),
"risk_probability": float(probability)
}
Test the API locally with Swagger UI
FastAPI automatically generates interactive documentation for your API. You can use it to send test requests without writing any client code.
- Start the API server by running this command in your terminal:
uvicorn api:app --reload
What does this command do?
- uvicorn is the server that runs your FastAPI app.
- api:app tells uvicorn to look in api.py for the variable called app.
- --reload makes the server restart automatically whenever you save a change to your code.
You should see output indicating the server is running at http://127.0.0.1:8000.
- Open your browser and navigate to http://127.0.0.1:8000/docs.
You should see the Swagger UI page with your two endpoints listed: /health and /predict.
- Click on the POST /predict endpoint to expand it.
- Click Try it out.
- Replace the example JSON body with this sample patient data:
{
"age": 55,
"sex": 1,
"cp": 2,
"trestbps": 130,
"chol": 250,
"fbs": 0,
"restecg": 1,
"thalach": 150,
"exang": 0,
"oldpeak": 1.5,
"slope": 1,
"ca": 0,
"thal": 2
}
- Click Execute.
You should see a JSON response with a prediction value (0 or 1) and a risk_probability value (a decimal between 0 and 1).
Seeing a 500 Internal Server Error?
Check that heart_model.pkl is in the same folder as api.py. The endpoint loads the model from the current working directory.
Also check your terminal for a traceback. A common cause is a column mismatch between the JSON fields and the features your model was trained on.
Help me debug my /predict endpoint returning a 500 error
- Also try the GET /health endpoint by expanding it, clicking Try it out, and clicking Execute. You should see {"status": "ok"} in the response.
Your prediction API is live and serving results. Next up, you will build a Streamlit dashboard so clinicians can interact with the model through a visual interface instead of raw JSON.
Build the Streamlit Clinical Dashboard
Your FastAPI prediction API works. A clinician can send patient data to the /predict endpoint and get a risk score back.
But asking a doctor to craft JSON payloads is not a product. In this step, you will build the user-facing layer: a Streamlit dashboard where a clinician fills in patient biomarkers through a form and instantly sees a risk prediction with visualizations.
In this step, get ready to:
- Create a multi-section dashboard with a patient input sidebar and results display.
- Connect the dashboard directly to the trained model via joblib.
- Add a risk gauge visualization and a feature importance bar chart.
Create the dashboard layout and sidebar inputs
Streamlit apps are plain Python scripts that run top to bottom. The sidebar is where the clinician enters patient data. The main panel displays results.
- Create a new file called app.py in your heart-disease-predictor folder.
- Add the following code to set up the page layout and all 13 clinical input widgets in the sidebar:
import streamlit as st
import joblib
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
# Configure the page layout
st.set_page_config(page_title="Heart Disease Risk Predictor", layout="wide")
st.title("Heart Disease Risk Prediction")
st.write("Enter patient clinical data in the sidebar to receive a risk assessment.")
# Sidebar inputs for all 13 clinical features
st.sidebar.header("Patient Clinical Data")
age = st.sidebar.number_input("Age", min_value=1, max_value=120, value=55)
sex = st.sidebar.selectbox("Sex", options=[0, 1],
format_func=lambda x: "Female" if x == 0 else "Male")
cp = st.sidebar.selectbox("Chest Pain Type (cp)", options=[0, 1, 2, 3],
format_func=lambda x: ["Typical Angina", "Atypical Angina",
"Non-anginal Pain", "Asymptomatic"][x])
trestbps = st.sidebar.number_input("Resting Blood Pressure (trestbps)",
min_value=80, max_value=200, value=120)
chol = st.sidebar.number_input("Serum Cholesterol mg/dl (chol)",
min_value=100, max_value=600, value=200)
fbs = st.sidebar.selectbox("Fasting Blood Sugar > 120 mg/dl (fbs)",
options=[0, 1], format_func=lambda x: "No" if x == 0 else "Yes")
restecg = st.sidebar.selectbox("Resting ECG Results (restecg)",
options=[0, 1, 2], format_func=lambda x: ["Normal",
"ST-T Abnormality", "Left Ventricular Hypertrophy"][x])
thalach = st.sidebar.number_input("Max Heart Rate Achieved (thalach)",
min_value=60, max_value=220, value=150)
exang = st.sidebar.selectbox("Exercise Induced Angina (exang)",
options=[0, 1], format_func=lambda x: "No" if x == 0 else "Yes")
oldpeak = st.sidebar.number_input("ST Depression (oldpeak)",
min_value=0.0, max_value=7.0, value=1.0, step=0.1)
slope = st.sidebar.selectbox("Slope of Peak Exercise ST (slope)",
options=[0, 1, 2],
format_func=lambda x: ["Upsloping", "Flat", "Downsloping"][x])
ca = st.sidebar.number_input("Number of Major Vessels (ca)",
min_value=0, max_value=3, value=0)
thal = st.sidebar.selectbox("Thalassemia (thal)", options=[1, 2, 3],
format_func=lambda x: ["Normal", "Fixed Defect",
"Reversible Defect"][x - 1])
What does this code do?
The first lines import everything the dashboard needs: Streamlit for the UI, joblib to load the model, pandas and numpy for data handling, and matplotlib for charts.
Each st.sidebar widget maps to one of the 13 UCI Heart Disease features. The format_func parameter translates numeric codes (like 0 and 1) into human-readable labels so a clinician sees "Female" instead of "0".
- Save app.py.
- Launch the dashboard by running this command in your terminal:
streamlit run app.py
- A browser tab opens automatically at http://localhost:8501. You should see the title "Heart Disease Risk Prediction" and a sidebar on the left with all 13 input fields.
Why load the model directly instead of calling the API?
For Streamlit Community Cloud deployment, the dashboard loads heart_model.pkl directly via joblib. This avoids needing a separate paid API hosting service. The FastAPI component you built teaches API design principles and can be run locally for testing.
Don't see the dashboard?
Make sure your virtual environment is active. If the port is already in use, Streamlit will prompt you to use a different one.
If you see a ModuleNotFoundError, confirm you installed Streamlit in your active virtual environment.
help me debug why my Streamlit app won't launch
Connect the dashboard to the model and display predictions
The sidebar collects input, but doesn't do anything with it yet. Now you will wire up the model so it runs a prediction every time the clinician adjusts a value.
- In app.py, add the following code below the last sidebar input (thal):
# Create a DataFrame from the sidebar inputs
patient_data = pd.DataFrame({
'age': [age], 'sex': [sex], 'cp': [cp],
'trestbps': [trestbps], 'chol': [chol], 'fbs': [fbs],
'restecg': [restecg], 'thalach': [thalach], 'exang': [exang],
'oldpeak': [oldpeak], 'slope': [slope], 'ca': [ca], 'thal': [thal]
})
# Load the trained model and run prediction
model = joblib.load('heart_model.pkl')
prediction = model.predict(patient_data)[0]
probability = model.predict_proba(patient_data)[0]
# Extract the risk probability for class 1 (disease present)
risk_probability = probability[1]
risk_label = "High Risk" if prediction == 1 else "Low Risk"
# Display the prediction result
st.subheader("Prediction Result")
col1, col2 = st.columns(2)
col1.metric("Risk Assessment", risk_label)
col2.metric("Risk Probability", f"{risk_probability:.1%}")
What does this code do?
The code packs all 13 sidebar values into a single-row DataFrame that matches the shape the model was trained on.
It loads heart_model.pkl and calls both predict (binary 0 or 1) and predict_proba (probability for each class). The st.metric widgets display the result as a clean label and percentage.
- Save app.py and return to your browser.
- Streamlit auto-detects the file change. Click Rerun if prompted. You should see a "Prediction Result" section with two metrics showing the Risk Assessment ("Low Risk" or "High Risk") and a Risk Probability percentage.
Seeing a KeyError or shape mismatch?
Make sure the column names in your DataFrame exactly match the feature names your model was trained on. The order matters too. Check that the dictionary keys match: age, sex, cp, trestbps, chol, fbs, restecg, thalach, exang, oldpeak, slope, ca, thal.
help me fix a feature mismatch between my Streamlit sidebar inputs and my trained model
Add a risk gauge and feature importance chart
A number alone is hard to interpret. Visualizations give the clinician an instant read on severity and show which biomarkers matter most for this patient's risk profile.
- In app.py, add the following code below the col2.metric line:
# Risk gauge visualization
st.subheader("Risk Gauge")
fig1, ax1 = plt.subplots(figsize=(6, 3))
ax1.barh([0], [risk_probability],
color='red' if prediction == 1 else 'green', height=0.3)
ax1.set_xlim(0, 1)
ax1.set_yticks([])
ax1.set_xlabel("Risk Probability")
ax1.set_title("Heart Disease Risk Level")
ax1.axvline(x=0.5, color='black', linestyle='--', label='Threshold')
ax1.legend()
plt.tight_layout()
st.pyplot(fig1)
# Feature importance bar chart
st.subheader("Feature Importance")
if hasattr(model, 'feature_importances_'):
importances = model.feature_importances_
elif hasattr(model, 'coef_'):
importances = np.abs(model.coef_[0])
else:
importances = np.zeros(13)
feature_names = ['age', 'sex', 'cp', 'trestbps', 'chol', 'fbs',
'restecg', 'thalach', 'exang', 'oldpeak', 'slope', 'ca', 'thal']
fig2, ax2 = plt.subplots(figsize=(8, 5))
sorted_idx = np.argsort(importances)
ax2.barh([feature_names[i] for i in sorted_idx],
importances[sorted_idx], color='steelblue')
ax2.set_xlabel("Importance")
ax2.set_title("Feature Importance for Heart Disease Prediction")
plt.tight_layout()
st.pyplot(fig2)
What does this code do?
The risk gauge draws a horizontal bar from 0 to the predicted probability. A red bar means high risk. A green bar means low risk. The dashed line at 0.5 marks the decision threshold.
The feature importance chart checks whether the model exposes feature_importances_ (tree-based models) or coef_ (linear models), then plots them sorted from least to most important. This lets the clinician see which biomarkers drive the prediction.
- Save app.py and return to your browser.
- Click Rerun if prompted. You should see a Risk Gauge bar (red or green) and a Feature Importance horizontal bar chart below the prediction results.
- Try changing the sidebar values (e.g., set Age to 70 and Cholesterol to 350). The prediction and charts update immediately.
Charts not appearing?
Confirm you imported matplotlib.pyplot as plt at the top of app.py. If you see a blank space where the chart should be, check that you are calling st.pyplot(fig1) and st.pyplot(fig2) after each figure.
help me debug why my matplotlib charts aren't displaying in Streamlit
✔️ Awesome, I've got everything!
Great. Double check you have saved app.py and all three sections (sidebar, prediction, visualizations) are working in your browser.
ⓧ I'd like to double check the full code
Here is the complete app.py file:
import streamlit as st
import joblib
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
# Configure the page layout
st.set_page_config(page_title="Heart Disease Risk Predictor", layout="wide")
st.title("Heart Disease Risk Prediction")
st.write("Enter patient clinical data in the sidebar to receive a risk assessment.")
# Sidebar inputs for all 13 clinical features
st.sidebar.header("Patient Clinical Data")
age = st.sidebar.number_input("Age", min_value=1, max_value=120, value=55)
sex = st.sidebar.selectbox("Sex", options=[0, 1],
format_func=lambda x: "Female" if x == 0 else "Male")
cp = st.sidebar.selectbox("Chest Pain Type (cp)", options=[0, 1, 2, 3],
format_func=lambda x: ["Typical Angina", "Atypical Angina",
"Non-anginal Pain", "Asymptomatic"][x])
trestbps = st.sidebar.number_input("Resting Blood Pressure (trestbps)",
min_value=80, max_value=200, value=120)
chol = st.sidebar.number_input("Serum Cholesterol mg/dl (chol)",
min_value=100, max_value=600, value=200)
fbs = st.sidebar.selectbox("Fasting Blood Sugar > 120 mg/dl (fbs)",
options=[0, 1], format_func=lambda x: "No" if x == 0 else "Yes")
restecg = st.sidebar.selectbox("Resting ECG Results (restecg)",
options=[0, 1, 2], format_func=lambda x: ["Normal",
"ST-T Abnormality", "Left Ventricular Hypertrophy"][x])
thalach = st.sidebar.number_input("Max Heart Rate Achieved (thalach)",
min_value=60, max_value=220, value=150)
exang = st.sidebar.selectbox("Exercise Induced Angina (exang)",
options=[0, 1], format_func=lambda x: "No" if x == 0 else "Yes")
oldpeak = st.sidebar.number_input("ST Depression (oldpeak)",
min_value=0.0, max_value=7.0, value=1.0, step=0.1)
slope = st.sidebar.selectbox("Slope of Peak Exercise ST (slope)",
options=[0, 1, 2],
format_func=lambda x: ["Upsloping", "Flat", "Downsloping"][x])
ca = st.sidebar.number_input("Number of Major Vessels (ca)",
min_value=0, max_value=3, value=0)
thal = st.sidebar.selectbox("Thalassemia (thal)", options=[1, 2, 3],
format_func=lambda x: ["Normal", "Fixed Defect",
"Reversible Defect"][x - 1])
# Create a DataFrame from the sidebar inputs
patient_data = pd.DataFrame({
'age': [age], 'sex': [sex], 'cp': [cp],
'trestbps': [trestbps], 'chol': [chol], 'fbs': [fbs],
'restecg': [restecg], 'thalach': [thalach], 'exang': [exang],
'oldpeak': [oldpeak], 'slope': [slope], 'ca': [ca], 'thal': [thal]
})
# Load the trained model and run prediction
model = joblib.load('heart_model.pkl')
prediction = model.predict(patient_data)[0]
probability = model.predict_proba(patient_data)[0]
# Extract the risk probability for class 1 (disease present)
risk_probability = probability[1]
risk_label = "High Risk" if prediction == 1 else "Low Risk"
# Display the prediction result
st.subheader("Prediction Result")
col1, col2 = st.columns(2)
col1.metric("Risk Assessment", risk_label)
col2.metric("Risk Probability", f"{risk_probability:.1%}")
# Risk gauge visualization
st.subheader("Risk Gauge")
fig1, ax1 = plt.subplots(figsize=(6, 3))
ax1.barh([0], [risk_probability],
color='red' if prediction == 1 else 'green', height=0.3)
ax1.set_xlim(0, 1)
ax1.set_yticks([])
ax1.set_xlabel("Risk Probability")
ax1.set_title("Heart Disease Risk Level")
ax1.axvline(x=0.5, color='black', linestyle='--', label='Threshold')
ax1.legend()
plt.tight_layout()
st.pyplot(fig1)
# Feature importance bar chart
st.subheader("Feature Importance")
if hasattr(model, 'feature_importances_'):
importances = model.feature_importances_
elif hasattr(model, 'coef_'):
importances = np.abs(model.coef_[0])
else:
importances = np.zeros(13)
feature_names = ['age', 'sex', 'cp', 'trestbps', 'chol', 'fbs',
'restecg', 'thalach', 'exang', 'oldpeak', 'slope', 'ca', 'thal']
fig2, ax2 = plt.subplots(figsize=(8, 5))
sorted_idx = np.argsort(importances)
ax2.barh([feature_names[i] for i in sorted_idx],
importances[sorted_idx], color='steelblue')
ax2.set_xlabel("Importance")
ax2.set_title("Feature Importance for Heart Disease Prediction")
plt.tight_layout()
st.pyplot(fig2)
Now create a requirements.txt file so your project dependencies are documented for deployment.
- Create requirements.txt in your heart-disease-predictor folder with the following content:
fastapi
uvicorn
streamlit
pandas
numpy
matplotlib
scikit-learn
joblib
What is requirements.txt for?
This file tells deployment platforms (like Streamlit Community Cloud) which packages to install. Every library your project imports needs to be listed here.
- Save requirements.txt.
- Confirm both files exist by running:
ls
- You should see app.py and requirements.txt alongside your existing files (api.py, heart_model.pkl, etc.).
Your clinical dashboard is live on your local machine. Next up, you will push it to GitHub and deploy it to Streamlit Community Cloud so anyone can access it from a URL.
Deploy to Streamlit Community Cloud
Your dashboard works locally, but a clinical tool only has value when clinicians can reach it from any browser.
In this step, you will push your project to GitHub and deploy it on Streamlit Community Cloud. The result is a public URL anyone can visit to enter patient data and receive a heart disease risk prediction.
In this step, get ready to:
- Push all project files to a public GitHub repository.
- Deploy the Streamlit dashboard to Streamlit Community Cloud.
- Test the live app with a sample patient prediction.
Push your project to GitHub
Streamlit Community Cloud deploys directly from a GitHub repository. Every time you push a change, the live app updates automatically.
- Open your browser and go to github.com.
- Click the + icon in the top-right corner, then select New repository.
- Enter heart-disease-predictor as the repository name.
- Make sure Public is selected.
- Leave all other options unchecked (no README, no .gitignore, no license).
- Click Create repository.
GitHub will show a quick setup page with commands. You will use these in your terminal next.
- Switch back to your terminal in VS Code (make sure you are inside the heart-disease-predictor folder).
- Initialize a Git repository and push your code by running these commands:
git init
git add .
git commit -m "Initial commit: heart disease prediction platform"
git branch -M main
git remote add origin https://github.com/[[GITHUB_USERNAME="your-github-username"]]/heart-disease-predictor.git
git push -u origin main
What do these commands do?
- git init turns your project folder into a Git repository.
- git add . stages every file in the folder for the commit.
- git commit saves a snapshot of all staged files with a message.
- git branch -M main renames the default branch to main.
- git remote add origin links your local repo to the GitHub repo you just created.
- git push -u origin main uploads all your files to GitHub.
- Refresh your GitHub repository page in the browser.
- You should see all your project files listed: app.py, api.py, heart_model.pkl, heart_clean.csv, requirements.txt, and your notebook files.
Push failed or asked for credentials?
- If Git asks for a username and password, GitHub no longer accepts passwords for HTTPS pushes. Create a Personal Access Token in your GitHub settings under Developer settings and use it as your password.
- If you see a "remote already exists" error, you may have initialized Git earlier. Run git remote set-url origin followed by your repo URL to update the remote.
Why can't I push my code to GitHub?
Deploy on Streamlit Community Cloud
Streamlit Community Cloud is a free hosting platform that connects directly to your GitHub repo. It reads your requirements.txt and installs dependencies automatically.
- Open share.streamlit.io in your browser.
- Click Continue to sign-in.
- Click Continue with GitHub.
- Enter your GitHub credentials and follow the authentication prompts.
- If prompted, click Authorize streamlit to grant access to your public repositories.
What is Streamlit Community Cloud?
Streamlit Community Cloud is a free deployment platform for unlimited public Streamlit apps. It reads your GitHub repo, installs your Python dependencies from requirements.txt, and serves your app at a public .streamlit.app URL.
Once you are signed in to your workspace, you can create your app.
- In the upper-right corner, click Create app.
- When asked if you already have an app, click Yup, I have an app.
- In the Repository field, select or type your heart-disease-predictor repository.
- In the Branch field, enter main.
- In the Main file path field, enter app.py.
- Click Deploy.
Streamlit Community Cloud will now build your app. You can watch the logs on the right side of the screen as dependencies install.
How long does deployment take?
Most apps deploy within a few minutes. Your app has several dependencies (scikit-learn, pandas, matplotlib) so the first build may take 3-5 minutes.
Subsequent pushes update almost instantly because dependencies are cached.
Seeing a build error?
- The most common cause is a missing or malformed requirements.txt. Make sure it exists in the root of your repo and lists all your Python dependencies.
- If you see a ModuleNotFoundError in the logs, the missing package needs to be added to requirements.txt. Commit and push the fix, and Streamlit will automatically rebuild.
- If the app crashes on startup, check that heart_model.pkl was committed to the repo. Binary files are sometimes excluded by a .gitignore.
Why is my Streamlit deployment failing?
Test the live app
Once the build completes, your app opens automatically at its public URL.
- Wait for the app to finish loading in your browser.
- In the sidebar, enter sample patient data (for example: age 55, male, chest pain type 2, resting BP 140, cholesterol 250).
- Adjust the remaining inputs and wait for the prediction to update.
- Verify that the risk gauge and feature importance chart render correctly with a prediction result.
You should see a risk score displayed along with a gauge visualization and a bar chart showing which features contributed most to the prediction.
- Copy the app URL from your browser address bar. This is your shareable link.
Share your app with others
Since you deployed from a public GitHub repository, your app is public by default. Anyone with the URL can access it and try a prediction.
App shows an error after loading?
- If the app shows a FileNotFoundError for heart_model.pkl, confirm that the .pkl file is committed and visible in your GitHub repo.
- If the sidebar inputs appear but predictions fail, check the Streamlit logs by clicking Manage app in the lower-right corner of the deployed app.
Help me debug my deployed Streamlit app that shows errors at runtime.
Your heart disease prediction platform is now live on the internet. Anyone with the URL can enter patient biomarkers and receive an instant risk assessment. Next up, the Secret Mission challenges you to add explainability with SHAP.
Secret mission
Add SHAP-Based Patient Explanations
Your dashboard shows a risk score, but clinicians need to know why. Add SHAP (SHapley Additive exPlanations) to show exactly which biomarkers are driving each prediction. Instead of just showing high risk, display something like elevated serum cholesterol contributed +12% to risk score.
Clean Up Your Resources
Clean Up Your Resources
Decide whether to keep your resources running, pause them to come back later, or delete them entirely.
This project runs locally and deploys to Streamlit Community Cloud for free, so there are no ongoing costs.
Resources you used:
- Local project folder heart-disease-predictor on your Desktop.
- GitHub repository heart-disease-predictor.
- Streamlit Community Cloud app deployment.
Keep everything running
No action needed. Choose this if you're still actively building or want to keep your live Streamlit app accessible.
- Your local project folder stays on your Desktop.
- Your Streamlit Community Cloud app remains live and free.
- Your GitHub repo stays available for future updates.
Pause - I'll come back to this later
Shut down any running local processes to free up memory. Your files, GitHub repo, and Streamlit app stay intact.
- If you have a local Streamlit or FastAPI server running, press Ctrl+C in the terminal to stop it.
- If your virtual environment is active, deactivate it by running:
deactivate
- Your Streamlit Community Cloud app stays deployed at no cost. No need to pause it.
Delete - I don't want to use this again
Remove all project resources and start fresh. This cannot be undone.
Delete your local project folder
- Delete the heart-disease-predictor folder from your Desktop by running:
macOS
cd ~/Desktop
rm -rf heart-disease-predictor
Windows
cd ~\Desktop
rmdir /s /q heart-disease-predictor
Delete your Streamlit Community Cloud app
- Go to your Streamlit Community Cloud dashboard at share.streamlit.io.
- Find your heart-disease-predictor app and open its settings menu.
- Select the option to delete the app.
Delete your GitHub repository
- Go to your heart-disease-predictor repository on GitHub.
- Open the repository's Settings tab.
- Scroll to the bottom of the page to find the danger zone where you can delete the repository.
- Confirm the deletion by typing the repository name when prompted.
That's a Wrap!
That's a Wrap!
You just shipped a medical ML product from raw clinical data to a live deployment. A clinician can now enter patient biomarkers into your app and receive an instant heart disease risk prediction with the key contributing factors explained.
Here's what you accomplished:
- Trained and evaluated multiple classifiers on real patient data from the UCI Cleveland Heart Disease dataset, using medical-relevant metrics like ROC-AUC and precision-recall.
- Built a production-grade FastAPI REST API with automatic Swagger documentation for serving heart disease predictions.
- Deployed a clinician-facing Streamlit dashboard with risk gauge visualization and feature importance charts to Streamlit Community Cloud for free.
- Added SHAP-based individual patient explanations showing exactly which biomarkers drive each prediction for clinical transparency.
Ready to quiz yourself?