A large share of online shoppers add an item to their cart and then leave without buying. This cart abandonment is one of the biggest sources of lost revenue in online retail, so it is genuinely useful to know, while a visit is still in progress, which carts are likely to end in a purchase and which are about to be abandoned. A store that can tell the two apart can spend its incentives on the shoppers who are actually undecided rather than on everyone.
I built this predictor from raw web traffic to work through the full lifecycle of a realistic machine learning problem, from messy event logs to a deployed service, with close attention to whether the results can actually be trusted.
The project uses the RetailRocket dataset, a public record of real activity on a live online store. It contains about 2.76 million events from roughly 1.4 million visitors. Every event is a view, a cart addition, or a purchase, stamped with a time, a visitor id, and an item id. The data is anonymised, so the only signal available is behaviour and timing. Purchases are rare, which makes the task both realistic and heavily imbalanced.
For every visit that adds at least one item to a cart, the model predicts whether that visit will end in a purchase. Visits that never add anything are left out, because predicting conversion only matters once a shopper has shown some intent.
The pipeline runs in clear stages.
- Validation. Every row is checked against a strict schema so that malformed records are caught at the entry point instead of quietly corrupting later steps.
- Sessionization. Each visitor's events are grouped into sessions, with a new session starting after thirty minutes of inactivity.
- Feature engineering. Each session is summarised into behavioural numbers such as views, cart additions, distinct items, duration, and browsing pace.
- Labelling. Sessions are marked as conversions from the logged purchases, and the model is focused on the engaged sessions.
- Modelling. Logistic Regression, Random Forest, and XGBoost are trained and compared. The class imbalance is handled with SMOTE, and the final model is tuned with Optuna using cross validation that is grouped by visitor.
- Evaluation. Performance is measured on a test set split by visitor, so that no visitor appears in both training and testing, and the decision threshold is chosen deliberately rather than left at the default.
An early version of the model scored almost perfectly, which I treated as a warning rather than a win. The cause was a quiet data leak. When the session features were computed over every event, the purchase event itself inflated the click count and the session length, so the model was effectively reading the answer from its own inputs. Recomputing the features over browsing activity only removed the leak and showed the real difficulty of the problem.
The honest model reaches a ROC AUC of about ~0.68 and a precision recall AUC of about 0.49, close to 1.8 times a random baseline, and it recovers roughly two thirds of the converting sessions. Catching the leak, correcting it, and reporting the true number is the result I am most satisfied with, because a near perfect score on this kind of task almost always hides a mistake.
Python with pandas and NumPy for data work, Pydantic for validation, scikit-learn and imbalanced-learn for modelling and resampling, XGBoost for the final classifier, Optuna for tuning, and Matplotlib for the figures. The trained model is served through a Streamlit app and packaged with Docker so that it runs the same way anywhere.
pip install -r requirements.txt
# place the RetailRocket events.csv inside the data folder
jupyter notebook notebook.ipynb
streamlit run app.py
docker build -t cart-conversion . && docker run -p 8501:8501 cart-conversionThe events.csv file is the real RetailRocket data and is not stored in this repository. It can be downloaded from Kaggle and placed in the data folder before running the notebook.