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 Pipeline with 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.