How to Build and Deploy a Customer Churn Prediction Model using Scikit-Learn and FastAPI
Customer churn (customers leaving your product or service) is one of the most expensive problems a business faces. Acquiring a new customer usually costs far more than keeping an existing one, so predicting churn early gives teams a real advantage.
In this machine learning deployment tutorial, you will build an end-to-end project: train a customer churn prediction model with Scikit-Learn, save it, and serve it as a production-ready REST API with FastAPI. By the end, you will have a model that any website, CRM or dashboard can call in real time.
What You Will Learn
- Preparing tabular customer data for machine learning
- Building a reusable Scikit-Learn
Pipelinewith preprocessing - Evaluating a classification model
- Serving predictions through a FastAPI endpoint
- Containerizing the API with Docker for cloud deployment
Prerequisites
You need Python 3.10+ and basic familiarity with pandas. Install the dependencies:
pip install pandas scikit-learn fastapi uvicorn joblib
We will use the popular IBM Telco Customer Churn dataset (available on Kaggle). Save it as telco_churn.csv in your project folder.
Step 1: Load and Clean the Data
Real-world data is messy. In this dataset, TotalCharges is stored as text and contains blank values, so we convert it to numeric and drop missing rows.
# train.py
import pandas as pd
df = pd.read_csv("telco_churn.csv")
# Fix TotalCharges and drop rows that couldn't be converted
df["TotalCharges"] = pd.to_numeric(df["TotalCharges"], errors="coerce")
df = df.dropna(subset=["TotalCharges"])
# Target: 1 = churned, 0 = stayed
df["Churn"] = (df["Churn"] == "Yes").astype(int)
NUMERIC = ["tenure", "MonthlyCharges", "TotalCharges"]
CATEGORICAL = ["Contract", "InternetService", "PaymentMethod"]
X = df[NUMERIC + CATEGORICAL]
y = df["Churn"]
We keep a small, meaningful feature set so the API stays simple. Contract type and tenure are among the strongest churn signals in telecom data.
Step 2: Build a Scikit-Learn Pipeline
A Pipeline bundles preprocessing and the model into one object. This is the most important habit for ML model deployment: the exact same transformations run during training and prediction, which prevents training/serving skew.
from sklearn.compose import ColumnTransformer
from sklearn.preprocessing import StandardScaler, OneHotEncoder
from sklearn.pipeline import Pipeline
from sklearn.ensemble import RandomForestClassifier
preprocessor = ColumnTransformer([
("num", StandardScaler(), NUMERIC),
("cat", OneHotEncoder(handle_unknown="ignore"), CATEGORICAL),
])
model = Pipeline([
("preprocess", preprocessor),
("classifier", RandomForestClassifier(
n_estimators=200,
max_depth=8,
class_weight="balanced", # churn data is imbalanced
random_state=42,
)),
])
class_weight="balanced" matters because only about a quarter of customers churn. Without it, the model may simply predict "no churn" for everyone.
Step 3: Train, Evaluate and Save the Model
from sklearn.model_selection import train_test_split
from sklearn.metrics import classification_report, roc_auc_score
import joblib
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, stratify=y, random_state=42
)
model.fit(X_train, y_train)
preds = model.predict(X_test)
probs = model.predict_proba(X_test)[:, 1]
print(classification_report(y_test, preds))
print(f"ROC-AUC: {roc_auc_score(y_test, probs):.3f}")
joblib.dump(model, "churn_model.joblib")
print("Model saved to churn_model.joblib")
Run it with python train.py. Focus on recall for the churn class and ROC-AUC, not just accuracy. Missing a customer who is about to leave usually costs more than a false alarm.
Step 4: Create the FastAPI Prediction Service
FastAPI is fast, uses Python type hints, and generates interactive API documentation automatically, which makes it ideal for a Python machine learning API.
# app.py
from typing import Literal
import joblib
import pandas as pd
from fastapi import FastAPI
from pydantic import BaseModel, Field
app = FastAPI(title="Customer Churn Prediction API", version="1.0")
model = joblib.load("churn_model.joblib")
class Customer(BaseModel):
tenure: int = Field(..., ge=0, example=5)
MonthlyCharges: float = Field(..., gt=0, example=89.5)
TotalCharges: float = Field(..., ge=0, example=447.5)
Contract: Literal["Month-to-month", "One year", "Two year"]
InternetService: Literal["DSL", "Fiber optic", "No"]
PaymentMethod: str = Field(..., example="Electronic check")
@app.get("/")
def health():
return {"status": "ok"}
@app.post("/predict")
def predict(customer: Customer):
data = pd.DataFrame([customer.model_dump()])
prob = float(model.predict_proba(data)[0, 1])
return {
"churn_probability": round(prob, 3),
"will_churn": prob >= 0.5,
"risk_level": "high" if prob >= 0.7 else "medium" if prob >= 0.4 else "low",
}
Pydantic validates every request, so bad inputs (like a negative tenure) are rejected with a clear error before they reach the model.
Step 5: Run and Test the API Locally
uvicorn app:app --reload
Open http://127.0.0.1:8000/docs to see the auto-generated Swagger UI, where you can test predictions in the browser. Or use curl:
curl -X POST http://127.0.0.1:8000/predict \
-H "Content-Type: application/json" \
-d '{"tenure": 3, "MonthlyCharges": 95.0, "TotalCharges": 285.0,
"Contract": "Month-to-month", "InternetService": "Fiber optic",
"PaymentMethod": "Electronic check"}'
A new, month-to-month fiber customer paying by electronic check should come back as high risk.
Step 6: Deploy with Docker
Containerizing your API lets you deploy the ML model to Render, Railway, AWS, Google Cloud Run or Azure with the same setup.
# Dockerfile
FROM python:3.11-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY app.py churn_model.joblib ./
EXPOSE 8000
CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "8000"]
# requirements.txt
pandas
scikit-learn
fastapi
uvicorn
joblib
docker build -t churn-api .
docker run -p 8000:8000 churn-api
Pin the same Scikit-Learn version you trained with, since a saved model may not load correctly in a different version. Push the image to your cloud provider of choice, and your churn model is live.
Next Steps
- Tune hyperparameters with
GridSearchCV - Try gradient boosting (XGBoost or LightGBM) for better ROC-AUC
- Add SHAP explanations so business teams understand why a customer is at risk
- Log predictions and monitor for data drift over time
Conclusion
You have built a complete customer churn prediction workflow: cleaning data, training a Scikit-Learn pipeline, evaluating it with the right metrics, and deploying it as a validated, containerized FastAPI service. This same pattern works for almost any tabular machine learning model you want to put into production.
Want to see more data science and machine learning projects like this in action? Explore my live portfolio at DataPulse, where I share end-to-end projects in Python, machine learning and deep learning.
Top comments (0)