- Problem Statement
- Overview
- Quick Start
- Workflow
- Impact
- Setup
- Config-Driven Design
- Dependency Management
- Makefile
- Experiment Tracking
- Model Training & Evaluation
- Folder Structure
- License
- Telecom companies lose customers every month without knowing who is about to leave until it's too late.
- Identifying churners early allows businesses to intervene with the right offer before the customer cancels.
- This project predicts which customers are likely to churn, so retention teams can act early instead of reacting late.
- Built an end-to-end churn prediction system on 7,000+ customer records from PostgreSQL using Scikit-learn pipelines, ensuring reproducible training and preventing data leakage.
- Evaluated 7 classification models including a DummyClassifier baseline, selecting Logistic Regression based on recall and PR-AUC given class imbalance, with per-fold metrics tracked in MLflow.
- Optimized the decision threshold via precision-recall curve, targeting ≥90% recall on the churn class while accepting a precision drop as missing a churner outweighs a false retention offer.
- Deployed a Dockerized FastAPI backend on Render with a React frontend on Netlify, pulling the trained model from Hugging Face Hub as a remote artifact store for on-demand risk scoring.
- Choose the setup path that fits your situation :
Path A : You have a PostgreSQL database configured
git clone https://github.com/themrityunjaypathak/ChurnLabs.git
cd ChurnLabs
make install-dev # Install dependencies and setup virtual environment
make ingest # Fetch data from PostgreSQL
make preprocess # Clean and transform the data
make train # Train the model
make api-dev # Start FastAPI backend → http://localhost:8000
make frontend-dev # Start React frontend → http://localhost:5173Path B : You don't have a PostgreSQL database (use local dataset instead)
git clone https://github.com/themrityunjaypathak/ChurnLabs.git
cd ChurnLabs
make install-dev # Install dependencies and setup virtual environmentThen download the dataset from Kaggle and place it at :
data/raw/customer-churn-raw.csv
Then continue :
make preprocess # Clean and transform the data
make train # Train the model
make api-dev # Start FastAPI backend → http://localhost:8000
make frontend-dev # Start React frontend → http://localhost:5173Tip
You can also start the entire application using Docker :
make compose-up-buildOnce the services are running, open the following URLs :
-
React Frontend :
http://localhost:3000 -
FastAPI Backend :
http://localhost:8000
- Achieved 90% recall on the held-out test set, correctly flagging 335 out of 374 churners while missing only 39, with no overfitting between train and test set.
- Accepted a deliberate precision drop from 49% to 43% by tuning the decision threshold from 0.5 to 0.3632, as the cost of a false retention offer is lower than losing a churner.
- First, you need to clone the project from GitHub to your local system.
git clone https://github.com/themrityunjaypathak/ChurnLabs.git- This project uses
uvfor dependency management. uvis a modern Python package manager that is significantly faster thanpip.- Install
uvif not already installed :
pip install uv- Then install all project dependencies :
make install-dev- This step extracts raw customer data from a PostgreSQL database and stores it locally for further processing.
- Run the ingestion pipeline :
make ingest- Connects to a PostgreSQL database using credentials stored in
.env. - Fetches customer churn data required for model training and evaluation.
- Stores raw dataset locally at
data/raw/customer-churn-raw.csv.
- This step transforms the raw dataset into a structured format suitable for model training.
- Run the preprocessing pipeline :
make preprocess- Load Raw Dataset
data/raw/customer-churn-raw.csv
- Validate Schema
- Checks :
- Feature Consistency
- Missing Values
- Data Types
- Categorical Levels
- Clean & Optimize the Dataset
- Typical preprocessing steps include :
- Removing Invalid Records
- Trimming Whitespace
- Type Conversions
- Export Processed Dataset
- The processed dataset is saved to :
data/processed/customer-churn-processed.parquet
- This step trains and evaluates the churn prediction model, generating artifacts for inference and deployment.
- Run the training pipeline :
make train- Load Processed Dataset
data/processed/customer-churn-processed.parquet
- Split data into training and testing sets.
- Build a Scikit-learn Pipeline that combines :
- Preprocessing
- Feature Engineering
- Model Training and Evaluation
- Run cross-validation to evaluate model performance.
- Apply decision threshold tuning to optimize recall.
- Save the trained model and evaluation metrics as artifacts.
- After training, the following files are created :
artifacts/models/pipe.joblib
artifacts/metrics/metrics.json
pipe.joblib
- Serialized Scikit-learn Pipeline
- Used by the
/predictendpoint for inference
metrics.json
- Training Metadata that contains :
- Evaluation Metrics
- Decision Threshold
- Model Version
- Training Timestamp
- Used by the
/infoendpoint for providing training metadata
- After generating artifacts locally, the training pipeline uploads them to Hugging Face.
- This enables centralized storage and access in deployment environments.
The system follows a local-first loading strategy with remote fallback :
- Check for the local artifacts :
artifacts/models/
artifacts/metrics/
- If present, load from local storage.
- If not present, fetch from Hugging Face.
- Enables seamless development using local artifacts.
- Avoids committing large binary files to the repository.
- Ensures production systems can operate without local dependencies.
- Provides a robust fallback mechanism for reliable model loading.
- The FastAPI server exposes endpoints for serving predictions, health checks, and model metadata.
- Run the FastAPI backend server :
make api-dev- Once the server starts, open the FastAPI documentation at :
http://localhost:8000/docs
- This interactive Swagger UI allows you to test endpoints directly.
Important
When deploying the backend on Render, make sure to configure the secrets in the Render dashboard.
Navigate to : Render Dashboard → Your Service → Environment → Environment Variables
Add the following environment variables :
ENV=your_env_name
PORT=your_api_port
ALLOWED_ORIGINS=your_allowed_originsALLOWED_ORIGINS is used to configure CORS so that the deployed React frontend can communicate with the API.
Note
The API for this project is deployed on the free tier of Render.
Services on the free tier automatically go to sleep after periods of inactivity.
If the API is asleep, open the API URL once to wake the server.
After the service starts, you can navigate to the website and generate predictions normally.
Access the live API documentation here or Click on the Image below.
- The frontend provides an interactive UI for generating on-demand churn predictions.
make frontend-dev- This command installs the Node.js dependencies and starts the React development server at :
http://localhost:5173
- The frontend communicates with the FastAPI backend to send requests and display predictions.
Important
The project includes a netlify.toml file for seamless frontend deployment on Netlify.
[build]
base = "frontend"
command = "npm run build"
publish = "dist"This configuration tells Netlify to :
- Build the application inside the
frontenddirectory. - Execute the
npm run buildcommand. - Deploy the output from the
distfolder.
Learn more about Netlify Configuration here.
Note
When deploying the frontend on Netlify, make sure to configure the secrets in the Netlify dashboard.
Navigate to : Netlify Dashboard → Site Settings → Environment Variables
VITE_API_URL=your_base_api_url
This variable defines the base API URL that the React frontend will use for generating predictions.
Without this variable, the frontend will still attempt to connect to the local API (localhost:8000).
Access the live application here or Click on the Image below.
- Instead of running services manually, you can start the entire application using Docker.
make compose-up-build- Docker Compose will build and start all required services for the application using
docker-compose.yaml.
http://localhost:3000
http://localhost:8000
- This step builds and pushes both the backend and frontend images to Docker Hub using
docker-compose.yaml. - Ensure Docker is installed and running.
- Make sure you are logged in to Docker Hub using :
make docker-login- Build both backend and frontend images as defined in
docker-compose.yaml:
make compose-build- Push both images to Docker Hub :
make compose-push- Builds the backend image using
api/Dockerfile - Builds the frontend image using
frontend/Dockerfile - Tags images using the names defined in
docker-compose.yaml:
themrityunjaypathak/churnlabs-api:v1
themrityunjaypathak/churnlabs-frontend:v1
- Pushes both images to your Docker Hub repositories.
Note
Ensure that the image: field is correctly defined for each service in docker-compose.yaml.
Without it, Docker Compose will not be able to push images to Docker Hub.
Tip
You can verify the pushed images on Docker Hub.
View the Backend Docker Image here or Click on the Image below.
View the Frontend Docker Image here or Click on the Image below.
- This step stops and removes all running containers.
make compose-down- This project adopts a config-driven approach to manage data, model, training, and artifacts.
- Instead of hardcoding values inside the codebase, these settings are stored in YAML configuration files.
- These files are loaded during preprocessing, training, inference, and artifacts logging.
config/
├── data-config.yaml
├── model-config.yaml
├── training-config.yaml
├── huggingface-config.yaml
└── artifacts-config.yaml
- Configuration values are separated from pipeline logic.
- Instead of writing :
test_size = 0.2
random_state = 42
threshold = 0.42- The values are stored in a configuration file.
training:
test_size: 0.2
random_state: 42
threshold:
value: 0.42- The code simply loads them.
config = get_training_config()
random_state = config["training"]["random_state"]- You can modify parameters without changing the code.
threshold:
value: 0.32- Changing this value updates the model behavior without touching the training pipeline.
- This project uses
uvfor dependency management instead of traditional tools likepip. uvis a modern package manager designed for speed, reproducibility, and simplicity.- It simplifies the Python workflow by combining :
- Dependency Management
- Virtual Environment Handling
- Package Installation
- Command Execution
- into a single tool.
uvis significantly faster thanpipdue to its optimized resolver and parallel package installation.- This reduces setup time, especially for large projects with many dependencies.
uv sync- In this project, uv sync reads from
uv.lockand installs all dependencies in one step.
- Dependencies are locked in the
uv.lockfile. - Ensures the same versions are installed across different systems.
uv lock
uv syncNote
This guarantees consistent behavior across development, testing, and deployment environments.
uvautomatically creates and manages a virtual environment.- No need to manually run
python -m venv .venv.
uv syncTip
By default, the virtual environment is created inside the .venv directory.
- A Makefile is a configuration file used by the
Makebuild tool. - It defines reusable commands called targets.
- Each target represents a specific task, such as :
- Installing Dependencies
- Running Training Pipeline
- Starting FastAPI Backend Server
- Launching MLflow Dashboard
- Instead of manually running multiple commands,
Makeexecutes them automatically.
- Using a Makefile improves the development workflow by simplifying command execution.
- Instead of running long commands like :
uv run python scripts/run_training.py- You can simply run :
make train- You can view all available
Makecommands by running :
make help- This will display all supported tasks defined in the Makefile.
Click Here for More Details
- Windows does not include the
Makeutility by default. - The easiest and most reliable way to use
Makeis through WSL.
- Open the Start Menu
- Search for PowerShell
- Run it as Administrator
- Run the following command :
wsl --install- This command will :
- Enable WSL on Windows
- Install the Linux Kernel
- Install Ubuntu as the default Linux Distribution
- After installation finishes, you may be prompted to restart your computer.
- After restarting :
- Open the Start Menu
- Search for Ubuntu
- Launch the App
- The first launch may take a few minutes.
- When Ubuntu runs for the first time, it will ask you to create a Linux user :
Enter new UNIX username: your_username
New password: your_password
- This sets up your Linux environment inside Windows.
- Before installing any software, update the package manager.
sudo apt update- Then upgrade installed packages :
sudo apt upgrade -y- This ensures your Linux environment is up to date.
- Now install the GNU
Makebuild tool.
sudo apt install build-essential -yMakewill now be available in your WSL environment.
- Check that
Makewas installed correctly :
make --version- You should see output similar to :
GNU Make 4.x
Built for x86_64-pc-linux-gnu
- This confirms that
Makeis installed successfully. - Remember,
Makeis available only inside the WSL terminal, not in Windows Command Prompt or PowerShell.
Click Here for More Details
- All Makefile commands are driven by
uvunder the hood. - This means
uvmust be installed inside WSL first after WSL andMakeare set up
- Open the Start Menu
- Search for Ubuntu
- Launch the App
- Run the following command inside the WSL terminal :
curl -LsSf https://astral.sh/uv/install.sh | sh- This downloads and installs
uvto~/.local/bin.
- If
uvisn't recognized after installation, reload your shell config :
source ~/.bashrc- Or open a new WSL terminal window.
- Check that
uvwas installed correctly :
uv --version- You should see output similar to :
uv 0.x.x - Once
uvis installed and verified, allmakecommands will work as expected.
Click Here for More Details
- Open VS Code
- Go to Extensions (Ctrl + Shift + X)
- Search for :
WSL by Microsoft - Click Install
- After installing the extension :
- Close VS Code
- Reopen it
- Open terminal :
Ctrl + ~ - Click the dropdown (next to +)
- You should now see something like :
Ubuntu (WSL)
- In VS Code, press
Ctrl + Shift + P - Type
WSL: Open Folder in WSL - Navigate to your project folder
- This ensures all terminal commands run inside WSL, not Windows
- This project uses MLflow to track machine learning experiments, compare models, and log artifacts.
- MLflow ensures reproducibility by tracking parameters, metrics, and artifacts in a centralized dashboard.
- To start the MLflow dashboard locally, run the following command from the project root directory :
make mlflow-ui- This command starts the MLflow tracking server using the local experiment database (
mlflow.db). - Once the server starts successfully, open the MLflow dashboard in your browser at :
http://localhost:5000- The MLflow dashboard allows you to explore experiment runs and compare model results.
- During model development, two types of experiments were performed and tracked using MLflow.
- MLflow makes it easy to compare multiple models trained during experimentation.
- For this project, seven classification models were evaluated on the same stratified 5-fold splits, including :
- Dummy Classifier
- Logistic Regression
- K-Nearest Neighbors
- Support Vector Classifier
- Decision Tree
- Random Forest
- Gradient Boosting
- Metrics tracked per model :
- Accuracy
- Precision
- Recall
- F1-Score
- ROC-AUC
- PR-AUC
- This helps identify the best-performing model based on business requirements.
- After selecting the best-performing model, another experiment is conducted to optimize the decision threshold.
- Instead of using the default threshold of 0.5, out-of-fold probabilities from cross-validation were used,
- To plot the precision-recall curve and identify the threshold that achieves ≥90% recall while maximizing precision.
- These experiments focused on :
- Analyzing recall across different thresholds.
- Selecting a threshold that meets business requirements.
- Balancing precision-recall trade-offs.
- Optimal threshold selection allowed the model to achieve ~90% recall for the churn class.
- This approach allows the trade-off between the two thresholds to be compared directly in the MLflow UI.
- MLflow also stores experiment artifacts generated during training.
- Artifacts logged in this project include confusion matrix and classification reports.
- These artifacts help analyze model behavior and support model selection.
Note
You can also reset MLflow experiments and remove stored runs for newer ones.
To reset MLflow experiments, run this command from the project root directory :
make mlflow-cleanResetting MLflow means permanently deleting all experiments, runs, metrics, and artifacts stored locally.
Click Here to view Code Snippet
# Importing load_processed_data function from loaders module
from churnlabs.data.loaders import load_processed_data
churn_data = load_processed_data()
churn_data.head()Click Here to view Code Snippet
# Splitting Data into Training and Testing Set
from churnlabs.features.split import split_data
X_train, X_test, y_train, y_test = split_data(df)Click Here to view Code Snippet
# Encoding Target Variable (Yes/No -> 1/0)
from churnlabs.models.encoder import target_encoder
y_train, y_test = target_encoder(y_train, y_test)Click Here to view Code Snippet
# Transformation for Categorical Columns
cat_cols = X_train.select_dtypes(include='category').columns
cat_trf = Pipeline(steps=[
('ohe', OneHotEncoder(sparse_output=False, drop='first', handle_unknown='ignore'))
])# Transformation for Numerical Columns
num_cols = [col for col in X_train.select_dtypes(include='number').columns if col != 'seniorcitizen']
num_trf = Pipeline(steps=[
('scaler', StandardScaler())
])# Combining Everything into ColumnTransformer
ctf = ColumnTransformer(transformers=[
('categorical', cat_trf, cat_cols),
('numerical', num_trf, num_cols)
], remainder='passthrough', n_jobs=-1)Click Here to view Code Snippet
# Importing DummyClassifier Model
dummy = DummyClassifier(strategy='most_frequent', random_state=42)# Pipeline
pipe = Pipeline(steps=[
('preprocessor', ctf),
('model', dummy)
])# Stratified K-Fold
skf = StratifiedKFold(n_splits=5, shuffle=True, random_state=42)# Cross-Validation Setup
scoring = {
'accuracy': 'accuracy',
'precision': 'precision',
'recall': 'recall',
'f1_score': 'f1',
'roc_auc': 'roc_auc',
'pr_auc': 'average_precision'
}
cv = cross_validate(estimator=pipe, X=X_train, y=y_train, cv=skf, scoring=scoring, n_jobs=-1)# Cross-Validation Result
results = {metric.replace('test_', ''): [np.mean(scores), np.std(scores)] for metric, scores in cv.items() if metric.startswith('test')}
results_df = pd.DataFrame(results, index=['mean', 'std']).T
results_dfClick Here to view Analysis
| mean | std | |
|---|---|---|
| accuracy | 0.7342 | 0.0 |
| precision | 0.0000 | 0.0 |
| recall | 0.0000 | 0.0 |
| f1_score | 0.0000 | 0.0 |
| roc_auc | 0.5000 | 0.0 |
| pr_auc | 0.2657 | 0.0 |
- The
DummyClassifiergives us a baseline to compare against real models. - It predicts the majority class (
non-churn) for every customer, ignoring all features.
# Class Distribution
churn
No 0.7342
Yes 0.2657
# Classification Metrics
accuracy 73%
precision 0
recall 0
f1_score 0
roc_auc 0.5
pr_auc 0.26
- Since the dataset contains ~73% non-churn customers, the model achieves 73% accuracy.
- However, it fails to identify any churners, resulting in zero precision, recall, and F1-Score.
- A model that never identifies a churner is useless, making recall and PR-AUC the only metrics that matter.
- ROC-AUC of 0.5 and PR-AUC of 0.26 confirm no predictive power, performing equivalent to random guessing.
- The
DummyClassifiermakes exactly the same prediction in every fold. - So every fold produces identical metrics, and that's why standard deviation is equal to 0.
- This confirms our pipeline and cross-validation setup behave as expected.
- Now any real model must surpass this benchmark to be considered a good performer.
# Real Model Expectations
Achieve PR-AUC > 0.26
Achieve ROC-AUC > 0.5
Achieve Precision > 0
Achieve Recall > 0
Achieve F1-Score > 0
- If a trained model cannot outperform this baseline, it is not useful.
Click Here to view Code Snippet
# Multi-Model Dictionary
models = {
'DC': DummyClassifier(strategy='most_frequent', random_state=42),
'LR': LogisticRegression(class_weight='balanced', max_iter=1000, random_state=42),
'KNN': KNeighborsClassifier(n_jobs=-1),
'SVC': SVC(class_weight='balanced', probability=True, random_state=42),
'DT': DecisionTreeClassifier(class_weight='balanced', random_state=42),
'RF': RandomForestClassifier(class_weight='balanced', random_state=42, n_jobs=-1),
'GB': GradientBoostingClassifier(random_state=42)
}# Plotting Metric Comparision Graph
for model in results_df.columns:
print()
print(f'Model : {model}')
print('-' * 40)
print(f'Recall : {results_df.loc["recall_mean", model]:.2f}')
print(f'PR-AUC : {results_df.loc["pr_auc_mean", model]:.2f}')
print(f'ROC-AUC : {results_df.loc["roc_auc_mean", model]:.2f}')
print(f'F1-Score : {results_df.loc["f1_score_mean", model]:.2f}')
print(f'Precision : {results_df.loc["precision_mean", model]:.2f}')
print(f'Accuracy : {results_df.loc["accuracy_mean", model]:.2f}')Click Here to view Analysis
Model : DC
----------------------------------------
Recall : 0.00
PR-AUC : 0.27
ROC-AUC : 0.50
F1-Score : 0.00
Precision : 0.00
Accuracy : 0.73
Model : LR
----------------------------------------
Recall : 0.80
PR-AUC : 0.66
ROC-AUC : 0.85
F1-Score : 0.63
Precision : 0.52
Accuracy : 0.75
Model : KNN
----------------------------------------
Recall : 0.53
PR-AUC : 0.52
ROC-AUC : 0.78
F1-Score : 0.55
Precision : 0.56
Accuracy : 0.77
Model : SVC
----------------------------------------
Recall : 0.79
PR-AUC : 0.60
ROC-AUC : 0.83
F1-Score : 0.62
Precision : 0.52
Accuracy : 0.75
Model : DT
----------------------------------------
Recall : 0.49
PR-AUC : 0.38
ROC-AUC : 0.65
F1-Score : 0.49
Precision : 0.50
Accuracy : 0.73
Model : RF
----------------------------------------
Recall : 0.47
PR-AUC : 0.63
ROC-AUC : 0.83
F1-Score : 0.55
Precision : 0.64
Accuracy : 0.79
Model : GB
----------------------------------------
Recall : 0.51
PR-AUC : 0.66
ROC-AUC : 0.85
F1-Score : 0.58
Precision : 0.66
Accuracy : 0.80
Click Here to view Code Snippet
# Creating LogisticRegression Model Object
lr = LogisticRegression(class_weight='balanced', max_iter=1000, random_state=42)# Final Pipeline with LogisticRegression
pipe = Pipeline(steps=[
('preprocessor', ctf),
('model', lr)
])# Cross-Validation Predict
y_pred_cv = cross_val_predict(estimator=pipe, X=X_train, y=y_train, cv=skf, method='predict', n_jobs=-1)Click Here to view Analysis
# Classification Report
print(classification_report(y_train, y_pred_cv, target_names=['No', 'Yes'])) precision recall f1-score support
No 0.91 0.73 0.81 4130
Yes 0.52 0.80 0.63 1495
accuracy 0.75 5625
macro avg 0.72 0.77 0.72 5625
weighted avg 0.81 0.75 0.77 5625
# Plotting Confusion Matrix
fig, ax = plt.subplots(nrows=1, ncols=2, figsize=(12,4))
ConfusionMatrixDisplay.from_predictions(y_true=y_train, y_pred=y_pred_cv, display_labels=['No', 'Yes'], cmap='crest', ax=ax[0])
ConfusionMatrixDisplay.from_predictions(y_true=y_train, y_pred=y_pred_cv, display_labels=['No', 'Yes'], cmap='crest', normalize='all', ax=ax[1])
ax[0].set_title('Confusion Matrix (Counts)')
ax[1].set_title('Confusion Matrix (Normalized)')
ax[0].grid(visible=False)
ax[1].grid(visible=False)
plt.tight_layout()
plt.show()
Click Here to view Code Snippet
# Fitting the Pipeline
pipe.fit(X_train, y_train)# Raw Column Names from ColumnTransformer (like 'numerical__totalcharges')
raw_features = pipe.named_steps['preprocessor'].get_feature_names_out()# Clean Column Names (splitting based on '__' and extracting 2nd element)
features = [feature.split('__')[1] for feature in raw_features]# Coefficients from LogisticRegression
coefficients = pipe.named_steps['model'].coef_[0]# Feature Importance DataFrame
importance_df = pd.DataFrame({'feature': features, 'importance': coefficients}).sort_values(by='importance', ascending=False)Click Here to view Analysis
# Feature Importance Plot
plt.figure(figsize=(12, 6))
colors = ['green' if val > 0 else 'red' for val in importance_df['importance']]
plt.barh(importance_df['feature'], importance_df['importance'], color=colors)
plt.xlabel('Impact on Log-Odds of Churn')
plt.title('Feature Importance Plot')
plt.axvline(0, linestyle='--')
plt.grid(axis='both', linestyle='--', alpha=1)
plt.tight_layout()
plt.show()
Values represent impact on log-odds of churn.
- 🟢 Positive coefficients → Increase churn risk
- 🔴 Negative coefficients → Decrease churn risk
Click Here to view Code Snippet
# Cross-Validation Predict
y_proba_cv = cross_val_predict(estimator=pipe, X=X_train, y=y_train, cv=skf, method='predict_proba', n_jobs=-1)[:, 1]# Compute Precision-Recall Curve
precision, recall, thresholds = precision_recall_curve(y_train, y_proba_cv)# Align Threshold with Precision/Recall
pr_results = pd.DataFrame({
"threshold": thresholds,
"precision": precision[:-1],
"recall": recall[:-1]
})
pr_results# Define a Recall Target (Business Decision)
recall_target = 0.90# Evaluate Recall across Thresholds and Choose Best Threshold
valid = pr_results[pr_results["recall"] >= recall_target]
if not valid.empty:
best_row = valid.loc[valid["precision"].idxmax()]
best_threshold = best_row["threshold"]
else:
best_threshold = 0.5
print("Warning: No threshold achieved target recall of 0.90. Falling back to 0.5.")
print(f"Best Threshold: {best_threshold:.4f}")Click Here to view Analysis
Best Threshold: 0.3632
Click Here to view Code Snippet
# Best Threshold
y_pred_best = (y_proba_cv >= best_threshold).astype(int)Click Here to view Analysis
# Classification Report
print(classification_report(y_train, y_pred_best, target_names=['No', 'Yes'])) precision recall f1-score support
No 0.95 0.61 0.74 4130
Yes 0.45 0.90 0.60 1495
accuracy 0.69 5625
macro avg 0.70 0.76 0.67 5625
weighted avg 0.81 0.69 0.70 5625
# Plotting Confusion Matrix
fig, ax = plt.subplots(nrows=1, ncols=2, figsize=(12,4))
ConfusionMatrixDisplay.from_predictions(y_train, y_pred_best, display_labels=['No', 'Yes'], cmap='crest', ax=ax[0])
ConfusionMatrixDisplay.from_predictions(y_train, y_pred_best, display_labels=['No', 'Yes'], cmap='crest', normalize='all', ax=ax[1])
ax[0].set_title('Confusion Matrix (Counts)')
ax[1].set_title('Confusion Matrix (Normalized)')
ax[0].grid(visible=False)
ax[1].grid(visible=False)
plt.tight_layout()
plt.show()
Click Here to view Code Snippet
# Fitting Pipeline on Full Training Data
pipe.fit(X_train, y_train)# Test Set Probability used for both Default and Tuned Threshold
y_test_proba = pipe.predict_proba(X_test)[:, 1]Click Here to view Analysis
# Default Threshold
y_test_pred_default = (y_test_proba >= 0.5).astype(int)# Classification Report on Default Threshold
print(classification_report(y_test, y_test_pred_default, target_names=['No', 'Yes'])) precision recall f1-score support
No 0.90 0.70 0.79 1033
Yes 0.49 0.80 0.61 374
accuracy 0.73 1407
macro avg 0.70 0.75 0.70 1407
weighted avg 0.79 0.73 0.74 1407
# Plotting Confusion Matrix on Default Threshold
fig, ax = plt.subplots(nrows=1, ncols=2, figsize=(12,4))
ConfusionMatrixDisplay.from_predictions(y_test, y_test_pred_default, display_labels=['No', 'Yes'], cmap='crest', ax=ax[0])
ConfusionMatrixDisplay.from_predictions(y_test, y_test_pred_default, display_labels=['No', 'Yes'], cmap='crest', normalize='all', ax=ax[1])
ax[0].set_title('Confusion Matrix at Default Threshold (Counts)')
ax[1].set_title('Confusion Matrix at Default Threshold (Normalized)')
ax[0].grid(visible=False)
ax[1].grid(visible=False)
plt.tight_layout()
plt.show()
Click Here to view Analysis
# Tuned Threshold
y_test_pred_tuned = (y_test_proba >= best_threshold).astype(int)# Classification Report on Tuned Threshold
print(classification_report(y_test, y_test_pred_tuned, target_names=['No', 'Yes'])) precision recall f1-score support
No 0.94 0.58 0.71 1033
Yes 0.43 0.90 0.58 374
accuracy 0.66 1407
macro avg 0.69 0.74 0.65 1407
weighted avg 0.80 0.66 0.68 1407
# Plotting Confusion Matrix on Tuned Threshold
fig, ax = plt.subplots(nrows=1, ncols=2, figsize=(12,4))
ConfusionMatrixDisplay.from_predictions(y_test, y_test_pred_tuned, display_labels=['No', 'Yes'], cmap='crest', ax=ax[0])
ConfusionMatrixDisplay.from_predictions(y_test, y_test_pred_tuned, display_labels=['No', 'Yes'], cmap='crest', normalize='all', ax=ax[1])
ax[0].set_title('Confusion Matrix at Tuned Threshold (Counts)')
ax[1].set_title('Confusion Matrix at Tuned Threshold (Normalized)')
ax[0].grid(visible=False)
ax[1].grid(visible=False)
plt.tight_layout()
plt.show()
ChurnLabs/
│
├── api/ # FastAPI backend for serving model predictions
│ ├── main.py # API endpoints (root, health, info, predict)
│ ├── schema.py # Pydantic schemas for request/response validation
│ ├── config.py # API configuration utilities
│ ├── utils.py # Helper functions used by the API
│ └── Dockerfile # Dockerfile for the backend container
│
├── artifacts/ # Saved model artifacts and evaluation metrics (ignored by Git)
│ ├── models/
│ │ └── pipe.joblib # Trained ML pipeline used for prediction
│ │
│ └── metrics/
│ └── metrics.json # Model evaluation metrics and metadata
│
├── config/ # YAML configuration files
│ ├── artifacts-config.yaml # Artifacts storage configuration
│ ├── data-config.yaml # Data paths and database configuration
│ ├── huggingface-config.yaml # Hugging Face model registry configuration
│ ├── model-config.yaml # Model configuration and thresholds
│ └── training-config.yaml # Training parameters and evaluation settings
│
├── data/ # Project datasets (ignored by Git)
│ ├── raw/
│ │ └── customer-churn-raw.csv
│ │
│ └── processed/
│ └── customer-churn-processed.parquet
│
├── frontend/
│ ├── public/ # Static assets used in the frontend
│ │ ├── favicon.svg
│ │ ├── hero-image.png
│ │ └── logo.png
│ │
│ ├── src/ # React application source code
│ │ ├── App.tsx # Main UI Component
│ │ ├── App.css # Frontend styling
│ │ ├── main.tsx # Application entry point
│ │ ├── index.css # Global styling
│ │ │
│ │ └── assets/
│ │
│ ├── Dockerfile # Dockerfile for the frontend container
│ ├── nginx.conf # Nginx configuration for serving the frontend
│ ├── package.json # Node.js dependencies
│ ├── package-lock.json # Dependency lock file
│ ├── index.html
│ ├── vite.config.ts
│ ├── eslint.config.js
│ ├── tsconfig.json
│ ├── tsconfig.app.json
│ ├── tsconfig.node.json
│ ├── .env.example # Example environment variables for frontend
│ ├── .gitignore # Frontend-specific Git ignore rules
│ ├── .dockerignore # Frontend Dockerignore rules
│ └── README.md
│
├── mlartifacts/ # MLflow artifacts storage (ignored by Git)
│ └── ... # Auto-generated MLflow run directories
│
├── notebooks/ # Jupyter notebooks for experimentation
│ ├── 01_eda.ipynb # Exploratory data analysis
│ └── 02_model.ipynb # Model experimentation and MLflow tracking
│
├── scripts/ # Executable python scripts
│ ├── run_ingestion.py # Data ingestion pipeline
│ ├── run_preprocessing.py # Data preprocessing pipeline
│ └── run_training.py # Model training pipeline
│
├── src/
│ │
│ └── churnlabs/
│ │
│ ├── core/ # Core utility module
│ │ ├── __init__.py
│ │ ├── config.py
│ │ ├── logger.py
│ │ └── settings.py
│ │
│ ├── data/ # Data loading and preprocessing module
│ │ ├── __init__.py
│ │ ├── ingestion.py
│ │ ├── loaders.py
│ │ ├── preprocessor.py
│ │ └── export.py
│ │
│ ├── features/ # Feature engineering module
│ │ ├── __init__.py
│ │ └── split.py
│ │
│ ├── models/ # Model training and evaluation module
│ │ ├── __init__.py
│ │ ├── artifact.py
│ │ ├── builder.py
│ │ ├── encoder.py
│ │ ├── evaluation.py
│ │ ├── pipeline.py
│ │ ├── transformer.py
│ │ ├── loader.py
│ │ └── uploader.py
│ │
│ └── __init__.py
│
├── .dockerignore # Backend Dockerignore rules
├── .env.example # Example environment variables for backend
├── .gitignore # Project-specific Git ignore rules
├── .python-version # Python version specification
├── LICENSE # License specifying permissions and usage rights
├── Makefile # Project commands (MLflow, FastAPI, React, Docker, etc.)
├── README.md # Project documentation
├── docker-compose.yaml # Docker orchestration for backend and frontend
├── mlflow.db # MLflow experiment tracking database (ignored by Git)
├── netlify.toml # Netlify deployment configuration
├── pyproject.toml # Python project configuration
└── uv.lock # Dependency lock file
- This project is licensed under the MIT License. You are free to use and modify the code as needed.




