E-Commerce Churn Predictor App
Train a Random Forest model and deploy a live churn prediction app.
Introduction
30 Second Summary
Every online store loses customers. Some quietly stop ordering, and by the time anyone notices, they're already gone. What if you could predict which customers are about to leave before it happens?
In this project, you will build a machine learning model that predicts whether an e-commerce customer is likely to churn. You'll wrap it in an interactive web app and deploy it for free so anyone can try it.
What You'll Build
You type in a customer's details, click Predict, and instantly see whether they're likely to leave along with a confidence percentage. Then you share the live URL with a recruiter or friend and watch them try it themselves.
By the end of this project, you'll have:
- An instant churn prediction system where you input any customer's details and get a stay-or-leave verdict with a confidence percentage.
- A trained Random Forest model that crushes a Logistic Regression baseline on imbalanced data, proving why the right algorithm matters.
- A live, shareable URL powered by Streamlit and backed by a clean GitHub repository that proves you can ship ML products end-to-end.
- Secret Mission: Add feature importance visualization and risk tier classification to your dashboard.
Are there any prerequisites?
You'll need Python 3.11+ installed, a GitHub account, and VS Code with the Jupyter extension. No prior machine learning experience is required.
Before We Start
Before We Start...
Before we dive in, let's lock in what you're about to build and why it matters.
Set Up Your Project
Every machine learning project starts with a clean workspace. Without the right libraries and folder structure, you cannot load data, train models, or build the prediction app.
In this step, you will create a dedicated project folder, install the ML packages you need, and download the dataset that powers the churn predictor.
In this step, get ready to:
- Create a project folder and Python virtual environment.
- Install machine learning and data visualization packages.
- Download the churn dataset and create a Jupyter notebook.
Create your project folder and virtual environment
A virtual environment keeps your project's packages separate from your system Python. This prevents version conflicts with other projects on your machine.
- Press Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
- Type Terminal (macOS) or PowerShell (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 churn-predictor
cd churn-predictor
- Confirm the folder exists by running:
ls
The folder is empty for now. Next, open it in VS Code.
- Press Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
- Type VS Code and press Enter to open it.
- Click File in the top menu bar.
- Click Open Folder.
- Navigate to your Desktop, select the churn-predictor folder, then click Open.
You should see churn-predictor in the VS Code sidebar with no files inside yet.
- Open the integrated terminal by pressing Ctrl+` (both macOS and Windows).
Now create and activate your virtual environment. The commands differ by operating system.
macOS
- Create and activate your virtual environment by running these commands:
python3 -m venv venv
source venv/bin/activate
Windows
- Create and activate your virtual environment by running these commands:
python -m venv venv
.\venv\Scripts\activate
Your terminal prompt now shows (venv) at the beginning. This confirms the virtual environment is active.
Don't see (venv) in your prompt?
On Windows, you may get a script execution policy error. Run Set-ExecutionPolicy -Scope CurrentUser RemoteSigned in PowerShell, then try the activate command again.
On macOS, make sure you typed source before the path. Without it, the command silently fails.
help me activate my Python virtual environment
Install the required packages
Your churn predictor uses scikit-learn for model training, pandas for data manipulation, and Streamlit for the web app. Installing them all now means you will not hit missing-package errors later.
- Install all project packages by running this command:
pip install scikit-learn==1.9.0 imbalanced-learn==0.14.2 pandas==3.0.5 matplotlib==3.11.1 seaborn==0.13.2 streamlit==1.59.1 jupyter
What does this command do?
This installs seven packages with pinned version numbers. Pinning versions ensures your project works the same way every time, regardless of when someone runs it.
- scikit-learn: machine learning models and evaluation metrics.
- imbalanced-learn: SMOTE and other techniques for handling class imbalance.
- pandas: data loading and manipulation.
- matplotlib and seaborn: data visualization.
- streamlit: the framework for building your prediction web app.
- jupyter: lets you run interactive notebooks for data exploration.
The install takes a minute or two. You will see progress bars as each package downloads.
Before you verify, what version number do you expect to see printed?
- Confirm the install succeeded by running:
python -c "import sklearn; print(sklearn.__version__)"
You should see 1.9.0 printed. This confirms scikit-learn installed at the correct version.
Seeing ModuleNotFoundError?
Make sure your terminal still shows (venv) in the prompt. If it disappeared, re-activate the virtual environment and run the pip install again.
If you see a version other than 1.9.0, run pip install scikit-learn==1.9.0 to force the correct version.
help me fix my scikit-learn installation
Download the dataset and create project files
The model needs real customer data to learn from. You will download an e-commerce churn dataset from Kaggle that contains 11 features about customer behavior.
- Open this link in your browser: https://www.kaggle.com/datasets/samuelsemaya/e-commerce-customer-churn
- Sign in to your Kaggle account (or create a free one if you do not have one yet).
- Click the Download button near the top of the page to download the ZIP file.
- Unzip the downloaded file.
- Move the ecommerce_customer_churn.csv file into your churn-predictor folder on the Desktop.
Finding your project folder
Your churn-predictor folder lives on your Desktop. Drag the CSV file from your Downloads folder directly into the churn-predictor folder in VS Code's sidebar.
Now create the Jupyter notebook where you will explore the data and train models.
- In VS Code, press Cmd+Shift+P (macOS) or Ctrl+Shift+P (Windows) to open the Command Palette.
- Type Jupyter: Create New Jupyter Notebook and select it from the list.
- A new untitled notebook opens. Press Cmd+S (macOS) or Ctrl+S (Windows) to save it.
- Name the file churn_analysis.ipynb and save it in the churn-predictor root folder.
- Confirm your project files are in place by running this in the integrated terminal:
ls
You should see churn_analysis.ipynb, ecommerce_customer_churn.csv, and the venv folder listed.
Missing the CSV file?
Make sure you moved the file into the churn-predictor folder on your Desktop, not into a subfolder. The file name should be exactly ecommerce_customer_churn.csv.
If the Kaggle download gave you a ZIP inside a ZIP, extract both layers until you see the raw CSV file.
help me find my downloaded dataset file
Your workspace is ready. Next up, you will load the dataset into your notebook, visualize the class imbalance, and train your first baseline model.
Explore the Data and Train a Baseline
Your environment is set up and the dataset is waiting. Before jumping into a complex model, you need to understand the shape of your data and identify any hidden problems that could sabotage predictions.
In this step, you will load the e-commerce churn dataset, visualize the class imbalance problem, and train a Logistic Regression baseline. The baseline will reveal exactly why a naive approach fails when the data is lopsided.
In this step, get ready to:
- Load and visualize the dataset to spot class imbalance.
- Preprocess categorical features and split the data for modeling.
- Train a Logistic Regression baseline and observe its failure on churners.
Load and explore the dataset
Every ML project starts with understanding the raw data. You need to know how many rows you have, whether any values are missing, and how the target variable is distributed.
- In your churn_analysis.ipynb notebook, add a new cell with the following imports:
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
from sklearn.preprocessing import StandardScaler, LabelEncoder
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import classification_report, roc_auc_score
What does this code do?
These imports bring in everything you need for this step. pandas handles data loading and manipulation. matplotlib and seaborn create visualizations.
The sklearn imports give you preprocessing tools, a train/test splitter, the baseline model, and evaluation metrics.
- Run the cell by pressing Shift+Enter.
- Add a new cell below and load the dataset:
# Load the dataset
df = pd.read_csv("ecommerce_customer_churn.csv")
# Check the dataset dimensions and structure
print(f"Dataset shape: {df.shape}")
print(f"\nMissing values:\n{df.isnull().sum()}")
print(f"\nChurn distribution:\n{df['Churn'].value_counts(normalize=True)}")
What does this code do?
- df.shape tells you how many rows (customers) and columns (features) the dataset has.
- df.isnull().sum() reveals any missing values per column that could break your model later.
- value_counts(normalize=True) shows the percentage split between churners and non-churners. This is where the imbalance becomes visible.
- Run the cell with Shift+Enter.
You should see roughly 83% of customers stayed (class 0) and only about 17% churned (class 1). That lopsided split is the class imbalance problem.
Seeing a FileNotFoundError?
- Make sure ecommerce_customer_churn.csv is in the same folder as your notebook (the churn-predictor root).
- Check that the filename matches exactly, including underscores and the .csv extension.
Why is my CSV not loading?
- Add a new cell and visualize the class imbalance with a bar chart:
# Visualize the class imbalance
sns.countplot(data=df, x="Churn")
plt.title("Churn Distribution")
plt.xlabel("Churn (0 = Stayed, 1 = Churned)")
plt.ylabel("Count")
plt.show()
What does this chart show?
The bar for class 0 (stayed) towers over class 1 (churned). This visual confirms the numbers: your model will see far more examples of happy customers than churners.
A model trained on this imbalanced data will learn to predict "stayed" almost every time, because that is the safe bet for overall accuracy. It will miss the churners you actually care about.
- Run the cell with Shift+Enter. You should see a bar chart with two bars where the left bar (0) is dramatically taller than the right bar (1).
Preprocess and split the data
Machine learning models need numeric inputs. Two of the columns in this dataset are categorical text values that need encoding before the model can use them.
- Add a new cell to encode the categorical columns:
# Encode categorical columns as numbers
label_encoders = {}
categorical_cols = ["PreferedOrderCat", "MaritalStatus"]
for col in categorical_cols:
le = LabelEncoder()
df[col] = le.fit_transform(df[col])
label_encoders[col] = le
print(f"Encoded columns: {categorical_cols}")
What does this code do?
LabelEncoder converts text categories (like "Laptop", "Phone", "Single", "Married") into numbers (0, 1, 2...). The encoders are stored in a dictionary so you can reuse them later when making predictions on new data.
- Run the cell. You should see Encoded columns: ['PreferedOrderCat', 'MaritalStatus'] printed.
- Add a new cell to separate features from the target and split into training/test sets:
# Separate features and target
X = df.drop("Churn", axis=1)
y = df["Churn"]
# 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, stratify=y
)
# Scale features for consistent model input
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)
X_test_scaled = scaler.transform(X_test)
print(f"Training set: {X_train_scaled.shape[0]} samples")
print(f"Test set: {X_test_scaled.shape[0]} samples")
What does this code do?
- X holds all feature columns. y holds the Churn target column (what you are trying to predict).
- train_test_split reserves 20% of the data for testing. stratify=y ensures both sets maintain the same 83/17 churn ratio.
- StandardScaler normalizes features so columns with large ranges (like cashback amount) do not dominate smaller ones (like satisfaction score).
- Run the cell. You should see the number of training and test samples printed (roughly 4,500 training and 1,100 test samples).
Seeing a KeyError on 'Churn'?
Make sure you ran the cells in order from top to bottom. If you re-ran the encoding cell after dropping columns, the DataFrame may have changed. Restart the kernel and run all cells from the top.
Help me fix a KeyError in my churn notebook.
Train the Logistic Regression baseline
With the data prepared, it is time to train a simple baseline model. Logistic Regression is fast and interpretable, making it a standard first-pass model for binary classification problems.
Before you run the next cell, think about this: only 17% of customers actually churn. What do you expect the model's ability to catch churners (recall on class 1) to look like?
- Add a new cell and train the Logistic Regression baseline:
# Train the Logistic Regression baseline
print("--- Logistic Regression (Baseline) ---")
lr_model = LogisticRegression(random_state=42, max_iter=1000)
lr_model.fit(X_train_scaled, y_train)
y_pred_lr = lr_model.predict(X_test_scaled)
print(classification_report(y_test, y_pred_lr))
print(f"ROC-AUC: {roc_auc_score(y_test, lr_model.predict_proba(X_test_scaled)[:, 1]):.4f}")
- Run the cell with Shift+Enter.
Look at the classification report output. Find the recall value for class 1 (the churn row). It should be around 0.50 to 0.60, meaning the model misses nearly half of all churners.
Why is recall so low for churners?
The model saw five times more "stayed" examples than "churned" examples during training. It learned that predicting "stayed" is usually correct. That overall accuracy looks fine, but the model fails at the one job you actually care about: catching customers who are about to leave.
This is exactly the problem you will fix in the next step using SMOTE and a more powerful model.
Seeing a convergence warning?
The max_iter=1000 parameter should prevent this. If you still see a convergence warning, the model still produced results. The classification report will still display. The warning does not block your progress.
Why is my LogisticRegression showing a convergence warning?
✔️ Awesome, I've got everything!
Great. Double check you have saved your notebook with all six cells.
ⓧ I'd like to double check the full code
Scroll through your notebook and confirm you have these cells in order:
- Cell 1: Imports (pandas, matplotlib, seaborn, sklearn).
- Cell 2: Load CSV and print shape, missing values, churn distribution.
- Cell 3: Seaborn countplot of the Churn column.
- Cell 4: LabelEncoder loop for PreferedOrderCat and MaritalStatus.
- Cell 5: X/y split, train_test_split, StandardScaler.
- Cell 6: LogisticRegression trained and evaluated with classification_report and roc_auc_score.
Each cell should run without errors when executed from top to bottom.
Your baseline proves the problem: Logistic Regression on imbalanced data misses nearly half the churners. Next up, you will fix that imbalance with SMOTE and unleash a Random Forest that dramatically outperforms this baseline.
Fix the Imbalance and Build a Random Forest
Your Logistic Regression baseline from the last step exposed a critical problem. The model misses nearly half the customers who actually churn because only ~17% of the dataset represents churners.
In this step, you'll fix the class imbalance using SMOTE (Synthetic Minority Over-sampling Technique), which creates synthetic samples so the model learns what churners look like. Then you'll train a Random Forest that captures non-linear patterns Logistic Regression misses entirely.
In this step, get ready to:
- Apply SMOTE to balance the training data.
- Train a Random Forest on the balanced data and compare results.
- Save the trained model and preprocessors for deployment.
Apply SMOTE to the training data
SMOTE generates synthetic examples of the minority class (churners) by interpolating between existing minority samples. This gives the model enough examples of both classes to learn from.
- In your churn_analysis.ipynb notebook, create a new cell below your Logistic Regression results and paste this code:
from imblearn.over_sampling import SMOTE
# Check class distribution before balancing
print(f"Before SMOTE: {pd.Series(y_train).value_counts().to_dict()}")
# Create synthetic samples for the minority class
smote = SMOTE(random_state=42)
X_train_resampled, y_train_resampled = smote.fit_resample(X_train_scaled, y_train)
# Verify the classes are now balanced
print(f"After SMOTE: {pd.Series(y_train_resampled).value_counts().to_dict()}")
What does this code do?
SMOTE(random_state=42) creates a resampler that generates synthetic minority-class samples by interpolating between existing churner data points.
fit_resample() analyzes the training data, creates synthetic churn samples, and returns balanced arrays where both classes have equal representation. The test set stays untouched so evaluation reflects real-world proportions.
- Run the cell.
- You should see the "Before" distribution heavily skewed (roughly 83% class 0, 17% class 1) and the "After" distribution showing equal counts for both classes.
Seeing a NameError for X_train_scaled?
Make sure you ran all cells from Step 2 in order before this one. Jupyter notebooks require cells to be executed sequentially for variables to exist in memory.
Try clicking Kernel then Restart & Run All to re-execute everything from the top.
Help me fix my SMOTE cell error in Jupyter.
Why SMOTE instead of class_weight?
scikit-learn offers a class_weight="balanced" parameter that adjusts loss weights without creating new data. SMOTE is more widely applicable because it teaches the transferable concept of synthetic oversampling. It appears frequently in data science interviews and works with any classifier, not just those supporting class weights.
Train a Random Forest and compare
Random Forest is an ensemble of decision trees. Each tree sees a random subset of features and data, then they vote together on the final prediction. This captures non-linear patterns that a single Logistic Regression line cannot.
- Create a new cell and paste this code to train the Random Forest on your balanced data:
from sklearn.ensemble import RandomForestClassifier
# Train Random Forest on the balanced data
rf_model = RandomForestClassifier(n_estimators=100, random_state=42)
rf_model.fit(X_train_resampled, y_train_resampled)
# Evaluate on the original (imbalanced) test set
y_pred_rf = rf_model.predict(X_test_scaled)
print("--- Random Forest (with SMOTE) ---")
print(classification_report(y_test, y_pred_rf))
print(f"ROC-AUC: {roc_auc_score(y_test, rf_model.predict_proba(X_test_scaled)[:, 1]):.4f}")
What does this code do?
- RandomForestClassifier(n_estimators=100) builds an ensemble of 100 decision trees that vote together.
- The model trains on X_train_resampled (the SMOTE-balanced data) but evaluates on the original X_test_scaled so the metrics reflect real-world class proportions.
- classification_report shows precision, recall, and F1-score per class. roc_auc_score measures how well the model separates churners from non-churners across all probability thresholds.
Before you run this cell, think about what you expect. Will the Random Forest's recall on the churn class (class 1) be higher or lower than the Logistic Regression's ~0.50-0.60?
- Run the cell now.
You should see recall for the churn class (class 1) jump to approximately 0.80 or higher. The ROC-AUC should be above 0.90. Compare this to your Logistic Regression results above. The Random Forest trained on SMOTE-balanced data catches far more churners.
Recall still looks low?
Make sure your Random Forest trains on X_train_resampled and y_train_resampled (the SMOTE output), not X_train_scaled and y_train (the original imbalanced data).
Help me debug why my Random Forest recall is still low.
Save the trained model and preprocessors
Your trained model lives only in notebook memory right now. To use it in a web app later, you need to serialize it to disk. joblib saves Python objects as .pkl files that can be loaded anywhere.
- Create a new cell and save all three objects your app will need:
import joblib
# Save the trained model and preprocessing objects
joblib.dump(rf_model, "model.pkl")
joblib.dump(scaler, "scaler.pkl")
joblib.dump(label_encoders, "label_encoders.pkl")
print("Model, scaler, and label encoders saved successfully.")
Why save the scaler and label encoders too?
The model expects inputs in the exact same format it was trained on. The StandardScaler remembers the mean and standard deviation of each feature, and the LabelEncoders remember which category maps to which number. Without these, new predictions would use unscaled or wrongly encoded values and produce garbage results.
- Run the cell. You should see the success message printed.
- Confirm the files exist by running this in a new cell:
import os
print([f for f in os.listdir(".") if f.endswith(".pkl")])
- You should see ['model.pkl', 'scaler.pkl', 'label_encoders.pkl'] (order may vary).
Now let's consolidate all your notebook work into a standalone Python script. This makes your training pipeline reproducible without needing to run notebook cells in order.
- Create a new file called train_model.py in your churn-predictor folder.
- Paste the imports, data loading, and preprocessing section:
import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler, LabelEncoder
from sklearn.linear_model import LogisticRegression
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import classification_report, roc_auc_score
from imblearn.over_sampling import SMOTE
import joblib
df = pd.read_csv("ecommerce_customer_churn.csv")
print(f"Dataset shape: {df.shape}")
print(f"Churn distribution:\n{df['Churn'].value_counts(normalize=True)}")
label_encoders = {}
categorical_cols = ["PreferedOrderCat", "MaritalStatus"]
for col in categorical_cols:
le = LabelEncoder()
df[col] = le.fit_transform(df[col])
label_encoders[col] = le
X = df.drop("Churn", axis=1)
y = df["Churn"]
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42, stratify=y
)
What does this section do?
This is the same code you wrote in your notebook across Steps 2 and 3. It loads the CSV, encodes the two categorical columns, separates features from the target, and creates the 80/20 stratified train/test split. Having it in a script means anyone can reproduce your model with one command.
- Below the code you just pasted, add the model training and saving section:
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)
X_test_scaled = scaler.transform(X_test)
print("\n--- Logistic Regression (Baseline) ---")
lr_model = LogisticRegression(random_state=42, max_iter=1000)
lr_model.fit(X_train_scaled, y_train)
y_pred_lr = lr_model.predict(X_test_scaled)
print(classification_report(y_test, y_pred_lr))
print(f"ROC-AUC: {roc_auc_score(y_test, lr_model.predict_proba(X_test_scaled)[:, 1]):.4f}")
smote = SMOTE(random_state=42)
X_train_resampled, y_train_resampled = smote.fit_resample(X_train_scaled, y_train)
print(f"\nAfter SMOTE - Class distribution: {pd.Series(y_train_resampled).value_counts().to_dict()}")
print("\n--- Random Forest (with SMOTE) ---")
rf_model = RandomForestClassifier(n_estimators=100, random_state=42)
rf_model.fit(X_train_resampled, y_train_resampled)
y_pred_rf = rf_model.predict(X_test_scaled)
print(classification_report(y_test, y_pred_rf))
print(f"ROC-AUC: {roc_auc_score(y_test, rf_model.predict_proba(X_test_scaled)[:, 1]):.4f}")
joblib.dump(rf_model, "model.pkl")
joblib.dump(scaler, "scaler.pkl")
joblib.dump(label_encoders, "label_encoders.pkl")
print("\nModel, scaler, and label encoders saved successfully.")
What does this section do?
This section scales the features, trains both models (Logistic Regression baseline and the SMOTE-enhanced Random Forest), prints their metrics side by side, and saves the three objects your web app will need: the trained Random Forest, the fitted scaler, and the label encoder dictionary.
- Save train_model.py.
- Test your standalone script by running it in the terminal:
python train_model.py
You should see the dataset shape, churn distribution, both classification reports printed side by side, and the final success message confirming all three files are saved.
Seeing a ModuleNotFoundError for imblearn?
Make sure your virtual environment is activated. You should see (venv) at the start of your terminal prompt. If not, activate it with .\venv\Scripts\activate (Windows) or source venv/bin/activate (macOS/Linux).
Help me fix my imblearn import error when running train_model.py.
✔️ Awesome, I've got everything!
Great. Make sure you have saved both churn_analysis.ipynb and train_model.py.
ⓧ I'd like to double check the full code
Your train_model.py should contain both chunks above in order. Scroll up and confirm:
- Chunk 1: imports through train_test_split() (8 imports, data loading, encoding, X/y split, train/test split).
- Chunk 2: scaling through joblib.dump() (StandardScaler, LR baseline, SMOTE, Random Forest, and saving all three .pkl files).
Your notebook should have the three new cells: SMOTE application, Random Forest training, and joblib save.
Your Random Forest now catches the majority of churners that the baseline missed completely. Next up, you'll wrap this model in a Streamlit web app so anyone can enter customer details and get an instant churn prediction.
Build the Streamlit Prediction App
Your Random Forest model is trained, evaluated, and saved as a set of .pkl files. Right now those files are only usable through a Python script that you run in a terminal.
Streamlit turns those files into an interactive web app. In this step, you'll build a form where anyone can enter customer details and get an instant churn prediction with a confidence percentage.
In this step, get ready to:
- Create the Streamlit app UI with input widgets for all 10 customer features.
- Add prediction logic that encodes inputs, scales them, and returns a churn probability.
- Create a deployment-ready requirements.txt and test the app locally.
Create app.py with the UI layout
Streamlit apps are plain Python scripts. When you run one, Streamlit starts a local web server and renders your widgets in the browser automatically.
- In VS Code, create a new file called app.py inside your churn-predictor folder.
- Add the imports, model loading, title, and first column of input widgets by pasting this code:
import streamlit as st
import numpy as np
import joblib
# Load the trained model and preprocessing tools
model = joblib.load("model.pkl")
scaler = joblib.load("scaler.pkl")
label_encoders = joblib.load("label_encoders.pkl")
# App title and description
st.title("E-Commerce Customer Churn Predictor")
st.write("Enter customer details to predict churn risk.")
# Create a two-column layout for input widgets
col1, col2 = st.columns(2)
with col1:
tenure = st.number_input("Tenure (months)", min_value=0, max_value=72, value=12)
warehouse_to_home = st.number_input("Warehouse to Home (km)", min_value=1, max_value=200, value=15)
num_devices = st.number_input("Devices Registered", min_value=1, max_value=10, value=3)
preferred_order_cat = st.selectbox(
"Preferred Order Category",
label_encoders["PreferedOrderCat"].classes_.tolist(),
)
satisfaction_score = st.slider("Satisfaction Score", min_value=1, max_value=5, value=3)
What does this code do?
The first three lines load the model, scaler, and label encoders you saved in the previous step. These are the "brains" of the app.
st.columns(2) splits the page into two side-by-side columns so the form doesn't stretch too tall. Each widget (number_input, selectbox, slider) collects one feature value from the user.
- Save app.py.
- Launch the app by running this command in your terminal:
streamlit run app.py
Streamlit will start a local server and automatically open your browser to localhost:8501. You should see the title "E-Commerce Customer Churn Predictor" and the left column with five input widgets.
What is Streamlit doing?
Streamlit re-runs your entire app.py script from top to bottom every time you interact with a widget or save the file. This means you can keep the server running and just save your changes to see updates instantly.
Browser didn't open?
If your browser didn't open automatically, navigate to http://localhost:8501 manually. Make sure your virtual environment is still activated in the terminal where you ran the command.
If you see a "ModuleNotFoundError", confirm you installed all packages in Step 1. Run pip install streamlit again if needed.
Why isn't my Streamlit app loading in the browser?
Now add the second column of widgets. Paste this code directly below the with col1: block (after the satisfaction_score line) in app.py:
with col2:
marital_status = st.selectbox(
"Marital Status",
label_encoders["MaritalStatus"].classes_.tolist(),
)
num_address = st.number_input("Number of Addresses", min_value=1, max_value=20, value=3)
complaint = st.selectbox("Complaint in Last Month", ["No", "Yes"])
days_since_last_order = st.number_input("Days Since Last Order", min_value=0, max_value=60, value=5)
cashback_amount = st.number_input("Cashback Amount ($)", min_value=0.0, max_value=500.0, value=150.0)
What does this code do?
The second column captures the remaining five features: marital status, number of addresses, whether the customer complained recently, days since their last order, and their cashback amount. Together with column one, the form now covers all 10 features the model expects.
- Save app.py.
- Switch back to your browser. Streamlit auto-reloads the page. You should now see both columns side by side, with five widgets in each.
Only seeing one column?
Make sure the with col2: block is at the same indentation level as with col1: (not nested inside it). Both blocks should be peers under the same col1, col2 = st.columns(2) line.
Why are my Streamlit columns not displaying correctly?
Add prediction logic
The form collects raw user input, but the model expects encoded and scaled numbers. You need a button that transforms the inputs and feeds them to the model.
- Add the prediction button and logic at the bottom of app.py (below both with col1: and with col2: blocks) by pasting this code:
if st.button("Predict Churn Risk"):
# Encode categorical inputs using the saved LabelEncoders
complaint_val = 1 if complaint == "Yes" else 0
preferred_cat_encoded = label_encoders["PreferedOrderCat"].transform([preferred_order_cat])[0]
marital_encoded = label_encoders["MaritalStatus"].transform([marital_status])[0]
# Assemble all features in the same order the model was trained on
features = np.array([[
tenure,
warehouse_to_home,
num_devices,
preferred_cat_encoded,
satisfaction_score,
marital_encoded,
num_address,
complaint_val,
days_since_last_order,
cashback_amount,
]])
# Scale features and make prediction
features_scaled = scaler.transform(features)
prediction = model.predict(features_scaled)[0]
probability = model.predict_proba(features_scaled)[0][1]
# Display the prediction result
st.subheader("Prediction Result")
if prediction == 1:
st.error(f"This customer is likely to CHURN (Probability: {probability:.1%})")
else:
st.success(f"This customer is likely to STAY (Churn Probability: {probability:.1%})")
st.metric("Churn Probability", f"{probability:.1%}")
What does this code do?
When the user clicks the button, the code first encodes the two categorical inputs (PreferedOrderCat and MaritalStatus) using the same LabelEncoders from training. The complaint field is converted from "Yes"/"No" to 1/0 manually.
All 10 values are assembled into a numpy array in the exact order the model was trained on. The scaler normalizes the values, then model.predict() returns the class (0 or 1) and model.predict_proba() returns the probability. A red error box appears for churn, and a green success box for stay.
- Save app.py.
- Switch back to your browser. You should see a Predict Churn Risk button below the input columns.
- Leave the default values as they are and click Predict Churn Risk.
You should see a green "likely to STAY" message with a churn probability percentage displayed below it.
Try a high-risk customer
Set Tenure to 1, Satisfaction Score to 1, Complaint to "Yes", and Days Since Last Order to 30. Click predict again. The result should flip to a red "likely to CHURN" message with a much higher probability.
Seeing a ValueError or KeyError?
A ValueError about array shape usually means the features are not in the correct order or one is missing. Double-check that your features = np.array([[...]]) list has exactly 10 values in the order shown above.
A KeyError on PreferedOrderCat means the dictionary key doesn't match. Note the spelling is "Prefered" (one r), matching the original dataset column name.
Help me debug my Streamlit churn prediction button.
Create requirements.txt for deployment
For deployment, Streamlit Community Cloud only needs to know which packages your app imports at runtime. Your app uses streamlit, numpy, and joblib. Both numpy and joblib are dependencies of scikit-learn, so you only need to list two packages.
- Create a new file called requirements.txt in your churn-predictor folder and paste this content:
streamlit==1.59.1
scikit-learn==1.9.0
Why only two packages?
Your app imports streamlit, numpy, and joblib. Since numpy and joblib are both installed automatically as dependencies of scikit-learn, listing just these two keeps the deployment lightweight and fast to build.
- Save requirements.txt.
- Confirm the app is still running in your browser at localhost:8501. If you stopped it earlier, restart it by running:
streamlit run app.py
✔️ Awesome, I've got everything!
Great. Double check you've saved both app.py and requirements.txt.
ⓧ I'd like to double check the full code
Your app.py should contain these sections in order. Scroll up and confirm each is present:
- Imports (streamlit, numpy, joblib) and model/scaler/encoder loading.
- Title, description, and col1, col2 = st.columns(2).
- with col1: block with 5 widgets (tenure, warehouse_to_home, num_devices, preferred_order_cat, satisfaction_score).
- with col2: block with 5 widgets (marital_status, num_address, complaint, days_since_last_order, cashback_amount).
- if st.button("Predict Churn Risk"): block with encoding, scaling, prediction, and display logic.
Your requirements.txt should contain exactly two lines:
streamlit==1.59.1
scikit-learn==1.9.0
Before you move on, predict: what will happen if you change the Satisfaction Score to 1 and Complaint to "Yes", then click predict?
Try it. The probability should jump significantly higher, and the result should flip to a red churn warning. Your model is responding to the features it learned are most predictive during training.
Your churn predictor is alive and working locally. Next up, you'll push it to GitHub and deploy it on Streamlit Community Cloud so anyone with the link can use it.
Deploy to GitHub and Streamlit Community Cloud
Your churn predictor runs beautifully on your machine. The problem is that nobody else can see it.
In this step, you'll push your project to GitHub and deploy the app on Streamlit Community Cloud. The result is a public URL you can share with recruiters, friends, or anyone who wants to see your ML app in action.
In this step, get ready to:
- Push your project to a GitHub repository.
- Deploy the app to Streamlit Community Cloud with a shareable public URL.
Create a .gitignore and initialize your git repository
Before you commit your code, you need to tell Git which files to ignore. Your virtual environment and cache files are large and machine-specific.
- In VS Code, create a new file called .gitignore in the root of your churn-predictor folder.
- Paste the following content into the file:
venv/
__pycache__/
.ipynb_checkpoints/
*.pyc
What does this file do?
A .gitignore file tells Git to skip certain files when tracking changes. The venv/ folder alone contains thousands of files that anyone can recreate by running pip install.
Cache folders like __pycache__/ and .ipynb_checkpoints/ are auto-generated by Python and Jupyter. They never belong in a repository.
- Save .gitignore.
✔️ Awesome, I've got everything!
Great. Make sure the file is saved before moving on.
ⓧ I'd like to double check the full code
venv/
__pycache__/
.ipynb_checkpoints/
*.pyc
Now initialize a Git repository and make your first commit.
- Initialize a Git repo, stage all files, and commit by running these commands in your terminal:
git init
git add .
git commit -m "Initial commit: churn prediction app"
- You should see output confirming Git created the repository and committed your files. It will list the number of files changed and insertions.
Seeing 'git: command not found'?
Git may not be installed on your system. Download it from git-scm.com and install it. After installation, close and reopen your terminal before trying again.
Why can't my terminal find git?
Push to GitHub
Your code is committed locally. Now you need a remote repository on GitHub so Streamlit Community Cloud can access it.
- Open your browser and go to github.com/new to create a new repository.
- Name the repository churn-predictor.
- Leave it set to Public (required for free Streamlit deployment).
- Do NOT check any boxes for README, .gitignore, or license. You already have your own files.
- Click Create repository.
GitHub shows setup instructions with a URL for your new repository. Copy the HTTPS URL.
- Connect your local repo to GitHub and push your code by running these commands:
git remote add origin [[YOUR_REPO_URL="https://github.com/YOUR_USERNAME/churn-predictor.git"]]
git branch -M main
git push -u origin main
What do these commands do?
- git remote add origin links your local repo to the GitHub repository you just created.
- git branch -M main renames your default branch to main (the standard name GitHub expects).
- git push -u origin main uploads all your committed files to GitHub.
- You should see progress output ending with a line like Branch 'main' set up to track remote branch 'main' from 'origin'.
- Refresh your GitHub repository page in the browser. You should see all your project files listed: app.py, train_model.py, requirements.txt, model.pkl, and the rest.
Push failed or asking for credentials?
If Git asks for a username and password, GitHub no longer accepts passwords for HTTPS pushes. You need a Personal Access Token (PAT). Go to GitHub Settings > Developer settings > Personal access tokens to generate one, then use it as your password.
Help me push my code to GitHub
Deploy on Streamlit Community Cloud
Your code is on GitHub. Now you can deploy it with one click on Streamlit Community Cloud, which hosts Python apps for free from public repositories.
- Open share.streamlit.io in your browser.
- Click Continue with GitHub to sign in with your GitHub account.
- If prompted, authorize Streamlit to access your GitHub repositories.
Once signed in, you land on your Streamlit workspace.
- Click Create app.
- In the Repository field, select your churn-predictor repository.
- Set the Branch to main.
- Set the Main file path to app.py.
- Click Deploy.
What happens during deployment?
Streamlit Community Cloud pulls your code from GitHub, installs the packages listed in requirements.txt, and starts your app. This usually takes 2-3 minutes on the first deploy.
Because your requirements.txt only lists streamlit and scikit-learn, the build is fast. The app.py file only uses numpy and joblib beyond those, which are dependencies of scikit-learn.
- Wait for the build to finish. You will see a spinning indicator followed by your app loading in the browser.
Before you test it, what do you expect to see when the app loads? Will it look different from your local version?
- Once the app loads, enter some sample customer details (for example: Tenure 2, Satisfaction Score 1, Complaint Yes).
- Click Predict Churn Risk.
You should see a churn prediction with a probability percentage. The app works exactly like your local version, but now it has a public URL anyone can visit.
Where is my app URL?
Your app's public URL appears in the browser address bar. It follows the pattern https://your-app-name.streamlit.app. Copy this URL and save it. This is what you share with others.
App not loading or showing an error?
The most common issue is a missing file in your GitHub repo. Check that model.pkl, scaler.pkl, and label_encoders.pkl are all visible in your GitHub repository. If they are missing, your .gitignore might be excluding them.
Also confirm your requirements.txt is in the root of the repo (not inside a subfolder). Streamlit Cloud reads it from the repo root to install dependencies.
Help me debug my Streamlit deployment
Secret mission
Add Feature Importance and Risk Tiers
Your churn predictor says yes or no, but employers want to see WHY. Add a feature importance chart that reveals the top drivers of churn, and color-coded risk tiers that turn raw probability into actionable business recommendations.
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 entirely on free services and local files, so there are no ongoing costs.
Resources you used:
- Streamlit Community Cloud app (free tier, no charges).
- GitHub repository (free).
- Local churn-predictor folder (virtual environment, trained model files, notebook, dataset).
Keep everything running
No action needed. Choose this if you plan to continue improving your churn predictor or want to keep the live URL active for your portfolio.
- Your Streamlit Community Cloud app stays live indefinitely on the free tier. Anyone with the link can use it.
- Your GitHub repository remains accessible at its current URL.
- Your local churn-predictor folder stays on your Desktop with the virtual environment and model files intact.
Pause - I'll come back to this later
Put your local environment to sleep and let Streamlit handle the rest. Your files and repo stay intact for when you return.
- Your Streamlit app automatically sleeps after a period of inactivity. It wakes up when someone visits the URL. No manual action needed.
- Deactivate your virtual environment by running:
deactivate
- Your GitHub repository stays as-is. No changes needed.
- When you come back, reactivate the virtual environment and pick up where you left off.
Delete - I don't want to use this again
Remove all project resources and start fresh. Follow these steps to fully clean up.
- Delete your Streamlit Community Cloud app:
- Go to share.streamlit.io and find your churn predictor app.
- Click the overflow icon (three vertical dots) next to your app.
- Click Delete.
- Enter the required confirmation string and click Delete again.
- Delete your GitHub repository:
- Open your repository on GitHub and click Settings.
- Scroll down to the Danger Zone section.
- Click Delete this repository.
- Click I want to delete this repository.
- Click I have read and understand these effects.
- Type your repository name to confirm, then click Delete this repository.
- Delete the local project folder:
macOS
- Run this command in your terminal:
rm -rf ~/Desktop/churn-predictor
Windows
- Run this command in your terminal:
rmdir /s /q "$HOME\Desktop\churn-predictor"
This removes the virtual environment, model files, notebook, dataset, and all project code from your machine.
That's a Wrap!
That's a Wrap!
Nice work! You just built and deployed a complete machine learning system that predicts e-commerce customer churn in real time.
You've learned how to:
- Train a Random Forest model that outperforms a Logistic Regression baseline by solving class imbalance with SMOTE.
- Build an interactive Streamlit web app that takes customer details and returns a live churn prediction with probability scores.
- Deploy a portfolio-ready ML project to Streamlit Community Cloud with a shareable public URL backed by a clean GitHub repository.
- Secret Mission: Added feature importance visualization and risk tier classification to make your predictions explainable and actionable for business teams.
Ready to quiz yourself?