A machine learning project that ranks dormant B2B clients by their probability of returning, turning a 2-million-row transaction log into a campaign-ready priority list for marketing.
VarGroup's marketing team needed a way to decide which inactive customers to re-engage first. With tens of thousands of dormant clients and a finite campaign budget, random outreach wastes money on clients who will never come back. The goal of this project was to build a reactivation propensity score that ranks every silent client by how likely they are to make a positive-NET purchase if contacted — so the sales team can focus on the top of the list.
| Metric | Value |
|---|---|
| Inactive clients scored | 13,244 |
| Reactivations observed in target window | 875 (6.61%) |
| Best model | Extra Trees Classifier |
| AUC-ROC | 0.70 |
| PR-AUC (Average Precision) | 0.22 |
| F1-optimal threshold | 0.55 |
| Lift over random targeting | 3.4× |
For every 100 clients contacted using the model, the campaign reaches 3.4× more reactivatable customers than a random outreach of the same size.
- Observation cutoff: 2019-12-31
- Feature window: all transactions strictly before the cutoff
- Target window: January–June 2020 (6 months after cutoff)
- Target = 1 if the client has at least one positive-NET transaction in the target window; returns and zero-value lines do not count
- Population filter: only clients with no purchases in the 24 months before the cutoff are included — i.e. the model learns the transition from inactive to active, not the next purchase of an already-engaged customer
Transactions are aggregated from 2M rows to one row per client (an Analytical Base Table), capturing:
- RFM core — recency, frequency, monetary sum/mean/max/std
- Inter-purchase gaps — mean, max, and standard deviation of days between purchases
- Product mix — unique items, unique families, entropy of purchase categories
- Spend windows — last-12-month and last-36-month spend
- Tool vs. non-tool split — spend share and transaction share on core tool products
- Spend momentum — monthly spend trend over the client's history
- Ratios — average ticket, spend per active month, tool share of transactions
- Firmographics — region, trade sector, employee count, economic potential, potential gap
Reactivators are ~6.6% of the population. Rather than using SMOTE (which can inject noise into tabular data), random undersampling of the majority class was used. Ratios 1:5, 1:10 and 1:20 were tested; 1:10 gave the best Precision–Recall trade-off.
Three models trained on the balanced set and evaluated on the original imbalanced test set (so metrics reflect real deployment):
- Extra Trees Classifier ← selected
- XGBoost
- Logistic Regression
Selection criterion: PR-AUC, the most informative metric under heavy class imbalance.
A threshold sweep from 0.10 to 0.90 was used to map the Precision/Recall trade-off. The F1-maximising threshold of 0.55 is the default operating point, but it is deliberately exposed as a business lever: lower it to cast a wider net, raise it to prioritise conversion rate over reach.
- Both native (impurity-based) and permutation importance were computed — permutation is model-agnostic and more robust against high-cardinality bias
- A dedicated leakage audit retrained the model without registry snapshot fields (columns ending in
_first) and compared AUC delta, to verify that results are not driven by information that leaked from the target window into the static client registry
The final model is applied to the full inactive population to produce reactivation_scores.csv — every client ranked by predicted reactivation probability. The subset above threshold is written to campaign_targets.csv, ready for the CRM.
- High-spend clients have the highest reactivation propensity. The "whales" that have gone quiet are disproportionately worth chasing.
- Tool-focused buyers (60–80% tool spend share) are the best campaign targets. Pure generalists and pure specialists are harder to bring back.
- Recency alone does not predict reactivation. Behavioural patterns — product diversity, spend momentum, inter-purchase variability — matter far more than "how long since they last bought".
- The top-ranked features by permutation importance are TRADE SECTOR, REGION, and MAX_GAP_DAYS — a mix of firmographics and behavioural variability.
VarGroup-Reactivation-Model/
├── README.md
├── requirements.txt
├── .gitignore
├── notebooks/
│ └── project_final.ipynb # Full end-to-end analysis
├── outputs/
│ ├── reactivation_scores.csv # All 13,244 inactive clients, ranked
│ └── campaign_targets.csv # Subset above the F1-optimal threshold
└── data/
└── README.md # Data is confidential and not shipped
The raw transactional data (TOOL_CLIENT.csv, TOOL_SALES.csv) is not included in this repository — it contains confidential client information. The notebook assumes both files are placed in data/ before running.
The shipped outputs/ files contain anonymised client IDs and model scores only, no transactional detail.
# 1. Clone
git clone https://github.com/<your-username>/VarGroup-Reactivation-Model.git
cd VarGroup-Reactivation-Model
# 2. Create a virtual environment (recommended)
python -m venv .venv
source .venv/bin/activate # macOS / Linux
# .venv\Scripts\activate # Windows
# 3. Install dependencies
pip install -r requirements.txt
# 4. Place the raw data in data/ (not shipped — see Data section)
# data/TOOL_CLIENT.csv
# data/TOOL_SALES.csv
# 5. Open the notebook
jupyter lab notebooks/project_final.ipynb- Python 3.10+
- pandas, numpy — data wrangling
- scikit-learn — Extra Trees, Logistic Regression, pipelines, metrics, permutation importance
- xgboost — gradient-boosted trees
- category_encoders — target encoding for high-cardinality categoricals
- matplotlib, seaborn — EDA and result visualisation
- Calibration — Platt scaling or isotonic regression to make scores directly interpretable as probabilities
- Uplift modeling — move from "who will reactivate?" to "who will reactivate because we contacted them?", which is the real marketing question
- Time-aware cross-validation — rolling-origin CV across multiple observation dates for a more robust generalisation estimate
- Survival analysis framing — model time-to-reactivation rather than a binary 6-month window
- Cost-sensitive thresholding — replace F1 with a business-loss function that weighs campaign cost against lifetime value
This project is released under the MIT License — see LICENSE for details.
Built as an end-to-end case study in customer analytics: from raw transactions to a deployed marketing priority list.