Customer Churn Prediction Model

Build an ML pipeline to predict telecom customer churn with scikit-learn.

Introduction

30 Second Summary

Every subscription service you use tracks whether you're likely to cancel. Streaming platforms, phone carriers, and software companies all use machine learning models to predict which customers are about to leave.

In this project, you will build a classification model that predicts customer churn for a telecom company. You will load a real-world dataset, preprocess it, train a classifier, and evaluate its performance with industry-standard metrics.

What You'll Build

A complete ML classification pipeline in Google Colab that takes raw customer data and outputs churn predictions with measurable accuracy.

By the end of this project, you'll have:

  • A Google Colab notebook containing your full ML pipeline from data loading to evaluation.
  • A trained logistic regression model that predicts whether a customer will churn.
  • A classification report and confusion matrix showing your model's precision, recall, and F1-score.
  • Secret Mission: Train a Random Forest classifier and compare its performance against your logistic regression model.

Are there any prerequisites?

None. This guide includes setup for all required tools. You only need a Google account to access Google Colab, which is free to use.

Set Up Google Colab and Load the Dataset

Every ML project starts with two things: a workspace to write code in and data to work with. Google Colab gives you a free, browser-based Python environment with all the ML libraries pre-installed.

In this step, you'll create a Colab notebook, import the libraries you need, and load a real-world telecom dataset with over 7,000 customer records.

In this step, get ready to:
  • Create a Google Colab notebook.
  • Import machine learning libraries.
  • Load and explore the customer churn dataset.
Open Google Colab and create a notebook

Google Colab is a hosted Jupyter Notebook service that requires no setup. It comes with pandas, scikit-learn, and matplotlib already installed, so you can jump straight into coding.

  • Open your browser and navigate to colab.research.google.com.
  • Sign in with your Google account.
  • Click New notebook to create a blank notebook.
  • Click the notebook title at the top left (it says Untitled0.ipynb by default) and rename it to Customer Churn Prediction.

What is Google Colab?

Google Colab is a free, cloud-based notebook environment built on top of Jupyter. It lets you write and run Python code directly in your browser without installing anything on your computer.

Notebooks are organized into cells. You type code into a cell and run it by pressing Shift+Enter or clicking the play button. Each cell's output appears directly below it.

Import libraries and load the dataset

Before you can work with data, you need to import the Python libraries that provide the tools for data manipulation, visualization, and machine learning.

  • In the first code cell of your notebook, paste the following code to import all required libraries:
# Cell 1: Import Libraries
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import classification_report, confusion_matrix, accuracy_score

What does this code do?

  • pandas handles data loading and manipulation (think spreadsheets in Python).
  • numpy provides math operations on arrays of numbers.
  • matplotlib.pyplot creates charts and visualizations.
  • The sklearn imports bring in tools for splitting data, scaling features, training a model, and measuring how well it performs.
  • Run the cell by pressing Shift+Enter.
  • You should see the cell complete with no output (a small green checkmark appears to the left of the cell).

Seeing an import error?

All of these libraries come pre-installed in Google Colab. If you see an error, make sure you are running the notebook in Colab (not a local Jupyter installation). Double-check that you copied the code exactly as shown above.

help me debug an import error in Google Colab

Now that your libraries are ready, you can load the dataset. The Telco Customer Churn dataset is hosted on GitHub, so you can load it directly from a URL.

  • Click the + Code button to create a new cell below, then paste this code:
# Cell 2: Load the Dataset
url = "https://raw.githubusercontent.com/IBM/telco-customer-churn-on-icp4d/master/data/Telco-Customer-Churn.csv"
df = pd.read_csv(url)
print(f"Dataset shape: {df.shape}")
print(f"\nFirst 5 rows:")
df.head()

What does this code do?

  • url stores the direct link to the CSV file hosted on IBM's GitHub repository.
  • pd.read_csv(url) downloads the file and loads it into a DataFrame (a table-like structure in pandas).
  • df.shape prints the number of rows and columns.
  • df.head() displays the first 5 rows so you can preview the data.
  • Run the cell by pressing Shift+Enter.
  • You should see the dataset shape printed (7043 rows and 21 columns) followed by a table showing the first 5 rows of customer data.

Seeing a connection error?

This usually means the GitHub URL is unreachable. Make sure your internet connection is stable and that you copied the full URL without any extra spaces or line breaks.

help me fix a connection error when loading a CSV from a URL in pandas

Explore the dataset structure

Before building a model, you need to understand what data you're working with. Exploring column types, value ranges, and the balance between churned and non-churned customers helps you plan the right preprocessing steps.

  • Create a new code cell by clicking + Code, then paste this code:
# Cell 3: Explore the Data
print(df.info())
print(f"\nBasic statistics:")
print(df.describe())
print(f"\nChurn distribution:")
print(df['Churn'].value_counts())

What does this code do?

  • df.info() shows every column's name, data type, and how many non-null values it has.
  • df.describe() gives basic statistics (mean, min, max, standard deviation) for numeric columns.
  • df['Churn'].value_counts() counts how many customers churned (Yes) versus stayed (No), which tells you if the dataset is balanced or skewed.
  • Run the cell by pressing Shift+Enter.
  • You should see three sections of output: a column listing with data types, a statistics table for numeric columns, and a count showing roughly 5,174 non-churned customers and 1,869 churned customers.

Seeing a NameError for df?

This means Cell 2 did not run successfully. Go back to the cell where you loaded the dataset and run it again. Cells must be run in order because each one depends on the variables created by previous cells.

help me fix a NameError in my Colab notebook

✔️ Awesome, I've got everything!

Great. Double-check that you have saved your notebook (Colab auto-saves to Google Drive, but you can also press Ctrl+S or Cmd+S).

ⓧ I'd like to double check the full code

Here is everything you should have across your three cells so far:

# Cell 1: Import Libraries
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import classification_report, confusion_matrix, accuracy_score

# Cell 2: Load the Dataset
url = "https://raw.githubusercontent.com/IBM/telco-customer-churn-on-icp4d/master/data/Telco-Customer-Churn.csv"
df = pd.read_csv(url)
print(f"Dataset shape: {df.shape}")
print(f"\nFirst 5 rows:")
df.head()

# Cell 3: Explore the Data
print(df.info())
print(f"\nBasic statistics:")
print(df.describe())
print(f"\nChurn distribution:")
print(df['Churn'].value_counts())

Your workspace is ready and you have a clear picture of the data you're working with. Next up, you'll clean the dataset and visualize churn patterns to uncover which features matter most.

Explore and Clean the Data

In the previous step, you loaded the Telco Customer Churn dataset and got a first look at its structure. You know there are 7,043 rows and 21 columns. Now it's time to dig deeper.

Real-world datasets almost always have quality issues that will break your model or produce misleading results. In this step, you'll fix a hidden data quality problem, visualize how many customers actually churned, and explore which features correlate with churn.

In this step, get ready to:
  • Clean the TotalCharges column by converting empty strings to numeric values.
  • Visualize the churn distribution with a bar chart.
  • Explore key features to understand their relationship with churn.
Handle missing values in TotalCharges

The TotalCharges column looks numeric, but it actually contains empty strings for customers who just signed up and haven't been charged yet. These empty strings will cause errors if you try to use them in calculations or model training.

  • Create a new code cell and convert TotalCharges to numeric values by pasting this code:
# Cell 4: Clean the Data
# TotalCharges has empty strings that need to be converted
df['TotalCharges'] = pd.to_numeric(df['TotalCharges'], errors='coerce')
# Fill NaN values with 0 (these are new customers with 0 tenure)
df['TotalCharges'] = df['TotalCharges'].fillna(0)
print(f"Missing values after cleaning:\n{df.isnull().sum().sum()}")

What does this code do?

pd.to_numeric(df['TotalCharges'], errors='coerce') attempts to convert every value in the column to a number. The errors='coerce' part tells pandas to turn any value it can't convert (like an empty string) into NaN instead of crashing.

.fillna(0) replaces those NaN values with 0. This makes sense because these are new customers with 0 tenure who haven't been charged anything yet.

  • Run the cell by pressing Shift+Enter.
  • You should see Missing values after cleaning: 0 printed in the output. This confirms all missing values have been handled.

Seeing a KeyError or unexpected output?

  • Make sure you ran Cell 2 first. The df variable must already exist from loading the dataset.
  • Check that the column name is exactly TotalCharges with a capital T and C.
  • help me debug my TotalCharges cleaning code
Visualize the churn distribution

Before training a model, you need to understand how balanced your target variable is. If almost no customers churn, the model might just predict "no churn" for everyone and still get high accuracy. A bar chart makes this balance immediately visible.

  • Create a new code cell and build a bar chart of churn counts by pasting this code:
# Cell 5: Visualize Churn Distribution
churn_counts = df['Churn'].value_counts()
plt.figure(figsize=(6, 4))
plt.bar(churn_counts.index, churn_counts.values, color=['steelblue', 'coral'])
plt.title('Customer Churn Distribution')
plt.xlabel('Churn')
plt.ylabel('Count')
plt.tight_layout()
plt.show()

What does this code do?

  • value_counts() counts how many customers fall into each category ("Yes" for churned, "No" for stayed).
  • plt.bar() creates a bar chart. The steelblue bar represents customers who stayed. The coral bar represents customers who left.
  • plt.tight_layout() adjusts spacing so labels don't get cut off.
  • Run the cell by pressing Shift+Enter.
  • You should see a bar chart with "Customer Churn Distribution" as the title. The "No" bar (steelblue) should be significantly taller than the "Yes" bar (coral), showing that most customers did not churn.

What is class imbalance?

Your chart shows that roughly 73% of customers stayed and 27% churned. This is called class imbalance. It matters because a lazy model could just predict "No" for every customer and still be 73% accurate. That's why you'll use metrics like precision and recall later, not just accuracy.

No chart appearing?

  • Make sure you ran Cell 1 first. The import matplotlib.pyplot as plt line must have executed successfully.
  • Check that Cell 4 ran without errors. The df['Churn'] column must exist.
  • help me fix my matplotlib chart in Colab
Explore key features and their relationship with churn

Now you know how many customers churned. The next question is: what makes churned customers different from those who stayed? Looking at averages and distributions for key features gives you intuition about what the model will learn.

  • Create a new code cell and explore feature relationships by pasting this code:
# Cell 6: Explore Key Features
print("Average Monthly Charges by Churn Status:")
print(df.groupby('Churn')['MonthlyCharges'].mean())
print("\nAverage Tenure by Churn Status:")
print(df.groupby('Churn')['tenure'].mean())
print("\nChurn Rate by Contract Type:")
print(df.groupby('Contract')['Churn'].value_counts(normalize=True))

What does this code do?

  • groupby('Churn')['MonthlyCharges'].mean() splits customers into churned and non-churned groups, then calculates the average monthly bill for each group.
  • groupby('Churn')['tenure'].mean() does the same for tenure (how many months the customer has been with the company).
  • groupby('Contract')['Churn'].value_counts(normalize=True) shows the proportion of customers who churned within each contract type (month-to-month, one year, two year).
  • Run the cell by pressing Shift+Enter.
  • You should see three printed summaries. Churned customers tend to have higher monthly charges, shorter tenure, and month-to-month contracts.

Why do these patterns matter?

These summaries reveal signals your model will pick up on. Customers who pay more per month, have shorter relationships with the company, and are on flexible contracts are more likely to leave. Your logistic regression model will learn these patterns from the data automatically.

Seeing unexpected values or errors?

  • If you see an error about MonthlyCharges or tenure not being found, check that column names are capitalized exactly as shown.
  • Make sure all previous cells (1 through 4) ran successfully. Each cell depends on the ones before it.
  • help me debug my groupby code in pandas

✔️ Awesome, I've got everything!

Great. Double check that your notebook is saved by pressing Ctrl+S (Windows) or Cmd+S (macOS).

ⓧ I'd like to double check the full code

Here is the complete code for all cells up to this point. Compare each cell in your notebook against this reference.

# Cell 1: Import Libraries
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import classification_report, confusion_matrix, accuracy_score

# Cell 2: Load the Dataset
url = "https://raw.githubusercontent.com/IBM/telco-customer-churn-on-icp4d/master/data/Telco-Customer-Churn.csv"
df = pd.read_csv(url)
print(f"Dataset shape: {df.shape}")
print(f"\nFirst 5 rows:")
df.head()

# Cell 3: Explore the Data
print(df.info())
print(f"\nBasic statistics:")
print(df.describe())
print(f"\nChurn distribution:")
print(df['Churn'].value_counts())

# Cell 4: Clean the Data
# TotalCharges has empty strings that need to be converted
df['TotalCharges'] = pd.to_numeric(df['TotalCharges'], errors='coerce')
# Fill NaN values with 0 (these are new customers with 0 tenure)
df['TotalCharges'] = df['TotalCharges'].fillna(0)
print(f"Missing values after cleaning:\n{df.isnull().sum().sum()}")

# Cell 5: Visualize Churn Distribution
churn_counts = df['Churn'].value_counts()
plt.figure(figsize=(6, 4))
plt.bar(churn_counts.index, churn_counts.values, color=['steelblue', 'coral'])
plt.title('Customer Churn Distribution')
plt.xlabel('Churn')
plt.ylabel('Count')
plt.tight_layout()
plt.show()

# Cell 6: Explore Key Features
print("Average Monthly Charges by Churn Status:")
print(df.groupby('Churn')['MonthlyCharges'].mean())
print("\nAverage Tenure by Churn Status:")
print(df.groupby('Churn')['tenure'].mean())
print("\nChurn Rate by Contract Type:")
print(df.groupby('Contract')['Churn'].value_counts(normalize=True))

Your data is clean and you have a solid understanding of what makes customers churn. Next up, you'll transform these features into a format your model can actually learn from.

Preprocess Features for Model Training

Your data is clean, and you understand the patterns behind churn. However, machine learning models cannot read text like "Yes" or "Month-to-month." They need numbers.

In this step, you will convert all text-based columns into numeric values, split the data into training and test sets, and scale the features so they are ready for your model.

In this step, get ready to:
  • Encode the target variable and drop non-predictive columns.
  • Convert categorical text features into numeric columns.
  • Split data into training and test sets, then scale numeric features.
Encode the target variable and drop the customer ID

The Churn column currently holds "Yes" and "No" strings. Your model needs this as 1 (churned) and 0 (stayed). The customerID column is a unique identifier that has no predictive power, so it needs to go.

  • Create a new code cell in your notebook and add the following code to encode the target and drop the ID column:
# Cell 7: Preprocess - Encode Target and Drop ID
df['Churn'] = df['Churn'].map({'Yes': 1, 'No': 0})
df = df.drop('customerID', axis=1)

What does this code do?

.map({'Yes': 1, 'No': 0}) replaces every "Yes" with 1 and every "No" with 0 in the Churn column. This is called label encoding for the target variable.

.drop('customerID', axis=1) removes the ID column because it is just a random string assigned to each customer. Leaving it in would confuse the model into thinking the ID pattern matters.

  • Run the cell by pressing Shift+Enter.
  • The cell runs silently with no output. This is expected because you are transforming data in place without a print statement.

Seeing a KeyError?

If you see an error mentioning Churn or customerID, you may have run this cell twice. Running it a second time tries to drop a column that no longer exists. Go to Runtime > Run all to re-run all cells from the top.

Help me fix a KeyError when encoding my Churn column

Encode categorical features into numeric columns

Your dataset still has columns like Contract, InternetService, and PaymentMethod that contain text values. You need to convert these into numbers using a technique called one-hot encoding.

  • Create a new code cell and encode all categorical columns by running:
# Cell 8: Encode Categorical Features
df_encoded = pd.get_dummies(df, drop_first=True)
print(f"Shape after encoding: {df_encoded.shape}")
print(f"Columns: {list(df_encoded.columns[:10])}...")

What does get_dummies do?

pd.get_dummies() takes every text column and creates a new binary (0 or 1) column for each unique value. For example, a Contract column with values "Month-to-month", "One year", and "Two year" becomes two columns: Contract_One year and Contract_Two year.

The drop_first=True parameter drops one category per column to avoid redundancy. If a customer is not "One year" and not "Two year", they must be "Month-to-month." The model can figure that out without a dedicated column.

  • Run the cell. You should see the new shape of your DataFrame (many more columns than before) and a preview of the first 10 column names.

Shape looks wrong or no new columns appeared?

Make sure Cell 7 ran before this cell. If customerID was not dropped first, it will be treated as a text column and encoded incorrectly, inflating your column count.

Help me debug my get_dummies encoding output

Split the data and scale numeric features

Before training, you need to separate your data into a training set (what the model learns from) and a test set (what you evaluate it against). This prevents the model from simply memorizing the answers.

  • Create a new code cell and split your data into training and test sets by running:
# Cell 9: Split into Features and Target
X = df_encoded.drop('Churn', axis=1)
y = df_encoded['Churn']

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 size: {X_train.shape[0]}")
print(f"Test set size: {X_test.shape[0]}")

What does this code do?

X holds all the feature columns (everything except Churn). y holds the target column (what you want to predict).

train_test_split randomly divides the data so 80% goes to training and 20% goes to testing. stratify=y ensures both sets have the same proportion of churned vs. non-churned customers. random_state=42 makes the split reproducible so you get the same results every time.

  • Run the cell. You should see the training set size (around 5,634) and test set size (around 1,409) printed.

Seeing a NameError for df_encoded?

This means Cell 8 did not run successfully. Go back and run Cell 8 first, then return to this cell.

Help me fix a NameError in my train_test_split cell

Now you need to scale the numeric features. Columns like tenure (0-72) and MonthlyCharges (18-118) have very different ranges. Without scaling, the model would give more weight to larger numbers.

  • Create a new code cell and scale your features by running:
# Cell 10: Scale Numeric Features
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)
X_test_scaled = scaler.transform(X_test)

Why fit_transform on training but only transform on test?

fit_transform() learns the mean and standard deviation from the training data, then applies the transformation. transform() on the test data uses those same learned values.

If you fit the scaler on the test data too, you would leak information about the test set into your preprocessing. This is called data leakage and it makes your evaluation metrics unreliable.

  • Run the cell. It completes silently because scaling transforms the data in memory without printing anything.

Seeing a ValueError about array shapes?

This can happen if you ran cells out of order or re-ran Cell 7 without re-running Cell 8. Use Runtime > Run all to execute all cells from the beginning in order.

Help me fix a ValueError when scaling my features

✔️ Awesome, I've got everything!

Great. Double check that you have Cells 7 through 10 all showing green checkmarks (completed) in your notebook.

ⓧ I'd like to double check the full code

Here is the full notebook code from Cell 1 through Cell 10. Compare your cells against this:

# Cell 1: Import Libraries
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import classification_report, confusion_matrix, accuracy_score

# Cell 2: Load the Dataset
url = "https://raw.githubusercontent.com/IBM/telco-customer-churn-on-icp4d/master/data/Telco-Customer-Churn.csv"
df = pd.read_csv(url)
print(f"Dataset shape: {df.shape}")
print(f"\nFirst 5 rows:")
df.head()

# Cell 3: Explore the Data
print(df.info())
print(f"\nBasic statistics:")
print(df.describe())
print(f"\nChurn distribution:")
print(df['Churn'].value_counts())

# Cell 4: Clean the Data
# TotalCharges has empty strings that need to be converted
df['TotalCharges'] = pd.to_numeric(df['TotalCharges'], errors='coerce')
# Fill NaN values with 0 (these are new customers with 0 tenure)
df['TotalCharges'] = df['TotalCharges'].fillna(0)
print(f"Missing values after cleaning:\n{df.isnull().sum().sum()}")

# Cell 5: Visualize Churn Distribution
churn_counts = df['Churn'].value_counts()
plt.figure(figsize=(6, 4))
plt.bar(churn_counts.index, churn_counts.values, color=['steelblue', 'coral'])
plt.title('Customer Churn Distribution')
plt.xlabel('Churn')
plt.ylabel('Count')
plt.tight_layout()
plt.show()

# Cell 6: Explore Key Features
print("Average Monthly Charges by Churn Status:")
print(df.groupby('Churn')['MonthlyCharges'].mean())
print("\nAverage Tenure by Churn Status:")
print(df.groupby('Churn')['tenure'].mean())
print("\nChurn Rate by Contract Type:")
print(df.groupby('Contract')['Churn'].value_counts(normalize=True))

# Cell 7: Preprocess - Encode Target and Drop ID
df['Churn'] = df['Churn'].map({'Yes': 1, 'No': 0})
df = df.drop('customerID', axis=1)

# Cell 8: Encode Categorical Features
df_encoded = pd.get_dummies(df, drop_first=True)
print(f"Shape after encoding: {df_encoded.shape}")
print(f"Columns: {list(df_encoded.columns[:10])}...")

# Cell 9: Split into Features and Target
X = df_encoded.drop('Churn', axis=1)
y = df_encoded['Churn']

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 size: {X_train.shape[0]}")
print(f"Test set size: {X_test.shape[0]}")

# Cell 10: Scale Numeric Features
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)
X_test_scaled = scaler.transform(X_test)

Your data is fully preprocessed. The features are encoded, split, and scaled. Next up, you will train your first machine learning model and see how well it predicts customer churn.

Train and Evaluate a Logistic Regression Model

Your data is preprocessed and split into training and test sets. This is the step where all that preparation pays off.

Logistic regression is the standard starting point for binary classification problems. It trains fast, produces interpretable results, and gives you a solid baseline to compare future models against.

In this step, get ready to:
  • Train a logistic regression model on your preprocessed training data.
  • Evaluate model performance with precision, recall, and F1-score.
  • Visualize predictions with a confusion matrix heatmap.
Train the logistic regression model

Training a model means feeding it your labeled training data so it can learn the patterns that distinguish churned customers from loyal ones.

  • Create a new code cell in your Colab notebook and add the following code to train your model:
# Cell 11: Train Logistic Regression
model = LogisticRegression(max_iter=1000, random_state=42)
model.fit(X_train_scaled, y_train)
print("Model trained successfully!")

What does this code do?

LogisticRegression(max_iter=1000, random_state=42) creates the model object. max_iter=1000 gives the algorithm enough iterations to converge on a solution. random_state=42 ensures you get reproducible results every time you run the cell.

model.fit(X_train_scaled, y_train) is where the actual learning happens. The model examines the scaled training features alongside the churn labels and learns which patterns predict churn.

  • Run the cell by pressing the play button or pressing Shift+Enter.
  • You should see Model trained successfully! printed below the cell.

Seeing a ConvergenceWarning?

If you see a warning about convergence, make sure you have max_iter=1000 in your LogisticRegression call. The default of 100 iterations is sometimes not enough for this dataset.

Also confirm that you ran Cell 10 (the scaling cell) before this one. Unscaled features make convergence much harder.

Help me fix a ConvergenceWarning in my logistic regression model

Generate predictions and evaluate with a classification report

A trained model is only useful if you know how well it performs on data it has never seen before. The test set you held back earlier is perfect for this.

  • Create a new code cell and add the following code to generate predictions and print evaluation metrics:
# Cell 12: Evaluate the Model
y_pred = model.predict(X_test_scaled)
print(f"Accuracy: {accuracy_score(y_test, y_pred):.4f}")
print(f"\nClassification Report:")
print(classification_report(y_test, y_pred, target_names=['No Churn', 'Churn']))

What does this code do?

model.predict(X_test_scaled) feeds the test data through the trained model. Each row gets a predicted label of 0 (no churn) or 1 (churn).

accuracy_score tells you what percentage of predictions were correct overall. The classification_report breaks performance down by class, showing precision (of all predicted churns, how many actually churned), recall (of all actual churns, how many did the model catch), and F1-score (the balance between precision and recall).

  • Run the cell.
  • You should see an accuracy score followed by a table with precision, recall, and F1-score columns for both the No Churn and Churn classes.

How do I read the classification report?

Focus on the Churn row. In a churn prediction scenario, recall for the Churn class is especially important. A low recall means the model is missing customers who are about to leave.

You will likely see that accuracy is around 80%, but recall for the Churn class is lower. This is common with imbalanced datasets where one class (No Churn) has far more examples.

Seeing a NameError?

If you see an error mentioning that model or X_test_scaled is not defined, run all previous cells in order first. Colab cells must be executed sequentially for variables to carry over.

Help me fix a NameError in my Colab notebook

Visualize the confusion matrix

A confusion matrix shows you exactly where your model gets confused. It breaks predictions into four categories: true positives, true negatives, false positives, and false negatives.

  • Create a new code cell and add the following code to display a confusion matrix heatmap:
# Cell 13: Confusion Matrix Visualization
cm = confusion_matrix(y_test, y_pred)
plt.figure(figsize=(6, 5))
plt.imshow(cm, interpolation='nearest', cmap='Blues')
plt.title('Confusion Matrix')
plt.colorbar()
tick_marks = [0, 1]
plt.xticks(tick_marks, ['No Churn', 'Churn'])
plt.yticks(tick_marks, ['No Churn', 'Churn'])

for i in range(2):
    for j in range(2):
        plt.text(j, i, str(cm[i, j]), ha='center', va='center',
                 color='white' if cm[i, j] > cm.max() / 2 else 'black',
                 fontsize=16)

plt.xlabel('Predicted')
plt.ylabel('Actual')
plt.tight_layout()
plt.show()

What does this code do?

confusion_matrix(y_test, y_pred) computes a 2x2 grid comparing actual labels against predicted labels. Each cell contains a count of how many test samples fell into that combination.

The plt.imshow() call renders this grid as a heatmap. Darker blue squares represent higher counts. The nested loop prints the actual numbers inside each square so you can read exact values at a glance.

  • Run the cell.
  • You should see a blue heatmap with four quadrants. The top-left and bottom-right squares represent correct predictions. The top-right and bottom-left squares represent mistakes.

How do I read the confusion matrix?

  • Top-left (True Negatives): customers who did not churn, correctly predicted as No Churn.
  • Top-right (False Positives): customers who did not churn, incorrectly predicted as Churn.
  • Bottom-left (False Negatives): customers who churned, but the model missed them.
  • Bottom-right (True Positives): customers who churned, correctly identified by the model.

Not seeing the heatmap?

Make sure plt.show() is the last line of the cell. If you accidentally deleted it, the plot may not render.

Also confirm that Cell 12 ran successfully before this one. The variable y_pred must exist for the confusion matrix to compute.

Help me display my confusion matrix heatmap in Colab

✔️ Awesome, I've got everything!

Great. Double check that you have saved your notebook with all 13 cells executed.

ⓧ I'd like to double check the full code

Here is the full notebook code from Cell 1 through Cell 13. Compare each cell against yours to spot any differences.

# Cell 1: Import Libraries
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import classification_report, confusion_matrix, accuracy_score

# Cell 2: Load the Dataset
url = "https://raw.githubusercontent.com/IBM/telco-customer-churn-on-icp4d/master/data/Telco-Customer-Churn.csv"
df = pd.read_csv(url)
print(f"Dataset shape: {df.shape}")
print(f"\nFirst 5 rows:")
df.head()

# Cell 3: Explore the Data
print(df.info())
print(f"\nBasic statistics:")
print(df.describe())
print(f"\nChurn distribution:")
print(df['Churn'].value_counts())

# Cell 4: Clean the Data
# TotalCharges has empty strings that need to be converted
df['TotalCharges'] = pd.to_numeric(df['TotalCharges'], errors='coerce')
# Fill NaN values with 0 (these are new customers with 0 tenure)
df['TotalCharges'] = df['TotalCharges'].fillna(0)
print(f"Missing values after cleaning:\n{df.isnull().sum().sum()}")

# Cell 5: Visualize Churn Distribution
churn_counts = df['Churn'].value_counts()
plt.figure(figsize=(6, 4))
plt.bar(churn_counts.index, churn_counts.values, color=['steelblue', 'coral'])
plt.title('Customer Churn Distribution')
plt.xlabel('Churn')
plt.ylabel('Count')
plt.tight_layout()
plt.show()

# Cell 6: Explore Key Features
print("Average Monthly Charges by Churn Status:")
print(df.groupby('Churn')['MonthlyCharges'].mean())
print("\nAverage Tenure by Churn Status:")
print(df.groupby('Churn')['tenure'].mean())
print("\nChurn Rate by Contract Type:")
print(df.groupby('Contract')['Churn'].value_counts(normalize=True))

# Cell 7: Preprocess - Encode Target and Drop ID
df['Churn'] = df['Churn'].map({'Yes': 1, 'No': 0})
df = df.drop('customerID', axis=1)

# Cell 8: Encode Categorical Features
df_encoded = pd.get_dummies(df, drop_first=True)
print(f"Shape after encoding: {df_encoded.shape}")
print(f"Columns: {list(df_encoded.columns[:10])}...")

# Cell 9: Split into Features and Target
X = df_encoded.drop('Churn', axis=1)
y = df_encoded['Churn']

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 size: {X_train.shape[0]}")
print(f"Test set size: {X_test.shape[0]}")

# Cell 10: Scale Numeric Features
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)
X_test_scaled = scaler.transform(X_test)

# Cell 11: Train Logistic Regression
model = LogisticRegression(max_iter=1000, random_state=42)
model.fit(X_train_scaled, y_train)
print("Model trained successfully!")

# Cell 12: Evaluate the Model
y_pred = model.predict(X_test_scaled)
print(f"Accuracy: {accuracy_score(y_test, y_pred):.4f}")
print(f"\nClassification Report:")
print(classification_report(y_test, y_pred, target_names=['No Churn', 'Churn']))

# Cell 13: Confusion Matrix Visualization
cm = confusion_matrix(y_test, y_pred)
plt.figure(figsize=(6, 5))
plt.imshow(cm, interpolation='nearest', cmap='Blues')
plt.title('Confusion Matrix')
plt.colorbar()
tick_marks = [0, 1]
plt.xticks(tick_marks, ['No Churn', 'Churn'])
plt.yticks(tick_marks, ['No Churn', 'Churn'])

for i in range(2):
    for j in range(2):
        plt.text(j, i, str(cm[i, j]), ha='center', va='center',
                 color='white' if cm[i, j] > cm.max() / 2 else 'black',
                 fontsize=16)

plt.xlabel('Predicted')
plt.ylabel('Actual')
plt.tight_layout()
plt.show()

Secret mission

Compare with a Random Forest Classifier

Your logistic regression model works, but is it the best you can do? Train a Random Forest classifier on the same data and compare the two models head-to-head. This is how ML engineers make model selection decisions in the real world.

Clean Up Your Resources

Clean Up Your Resources

This project runs entirely in Google Colab, so there are no ongoing costs. Decide whether to keep your notebook for reference, or delete it entirely.

Resources you used:

  • Google Colab notebook ("Customer Churn Prediction"), stored in your Google Drive.

Keep everything running

No action needed. Choose this if you want to revisit the notebook, tweak the model, or use it as a reference for future ML projects.

  • Your notebook stays in your Google Drive under the Colab Notebooks folder.
  • It takes up minimal storage and costs nothing to keep.

Pause - I'll come back to this later

Since Colab notebooks don't use ongoing compute when closed, there is nothing to pause. Your notebook is already idle once you close the browser tab.

  • Close the Colab browser tab. The runtime disconnects automatically after a period of inactivity.
  • Your notebook remains saved in Google Drive for whenever you return.

Delete - I don't want to use this again

Remove the notebook from your Google Drive to start fresh.

  • Open Google Drive at drive.google.com.
  • Navigate to the Colab Notebooks folder.
  • Right-click on the "Customer Churn Prediction" notebook file.
  • Select Move to trash.
  • Empty your trash to permanently delete the file.

That's a Wrap!

That's a Wrap!

Nice work! You just built your first end-to-end machine learning classification pipeline using scikit-learn and Google Colab. Welcome to the world of ML engineering.

You've learned how to:

  • Built a complete ML pipeline from data loading through evaluation on a real-world telecom dataset with 7,000+ customer records.
  • Preprocessed messy real-world data using categorical encoding and feature scaling techniques to prepare it for model training.
  • Trained a logistic regression classifier and evaluated it with precision, recall, F1-score, and a confusion matrix visualization.
  • Secret Mission: Compared logistic regression against a Random Forest to understand model selection trade-offs.

Ready to quiz yourself?