Interactive Streamlit dashboard for customer behaviour analytics using the UCI Online Retail II Dataset.
The app includes:
- sales and product performance views
- RFM customer segmentation
- churn propensity modelling
- probabilistic CLV prediction
- revenue time-series forecasting
- Overview: KPI cards, monthly trends and top countries/products by revenue
- RFM Segmentation: K-Means baseline with elbow/silhouette previews and R/F/M-derived segment labels, plus a Compare tab benchmarking Gaussian mixture, Agglomerative and HDBSCAN side by side
- Churn Prediction: random forest classification (5-fold stratified CV) with threshold tuning, confusion matrix and precision-recall / ROC curves
- CLV Prediction: BG/NBD + Gamma-Gamma modelling via
pymc-marketing - Revenue Forecasting: SARIMA, Theta, ETS (Holt-Winters) and a seasonal-naive model (
statsmodels) with holdout backtest metrics and configurable prediction intervals. Every model is scored against the seasonal-naive baseline ("same period last year"), so the backtest shows whether it actually beats the obvious benchmark. Short backtest windows (< 2 seasonal cycles) reconstruct seasonality from training data only, so the holdout forecast stays seasonal without look-ahead
Requires Python 3.13+.
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txtThe app reads precomputed Parquet files rather than the raw CSV (see Data pipeline). Generate them once from the source CSV:
python scripts/build_dataset.pyThis writes data/online_retail_clean.parquet, data/online_retail_cancels.parquet
and data/dataset_meta.json. Re-run it whenever the raw CSV or the cleaning
logic in utils/data.py changes.
streamlit run app.py.
├── app.py # entry point; defines st.navigation
├── app_pages/
│ ├── 1_Overview.py
│ ├── 2_RFM_Segmentation.py
│ ├── 3_Churn_Prediction.py
│ ├── 4_CLV_Prediction.py
│ └── 5_Revenue_Forecasting.py
├── utils/ # shared package
│ ├── data.py # loading, cleaning and Parquet artifacts
│ ├── transforms.py # RFM, cohort, churn and CLV feature builds
│ ├── clustering.py # segmentation algorithms and metrics
│ ├── theme.py # Plotly template and colour palette
│ └── ui.py # page chrome, headers, sidebar filters
├── scripts/
│ └── build_dataset.py # precompute cleaned Parquet artifacts from the CSV
├── .streamlit/
│ ├── config.toml # theme and server config
│ └── pages.toml
├── data/
│ ├── online_retail_II.csv # raw source (build input only)
│ ├── online_retail_clean.parquet # cleaned orders (read at runtime)
│ ├── online_retail_cancels.parquet # cancel/return invoices
│ └── dataset_meta.json # raw row count for data-quality stats
├── static/ # fonts, logos, favicon
├── requirements.txt
└── LICENSE
streamlitfor the app UIpandas,numpy,pyarrowfor data wrangling and Parquet I/Oplotlyfor interactive visualisationsscikit-learn,scipyfor clustering and transformationsstatsmodelsfor SARIMA and Theta-method forecastingpymc-marketing(with itspytensorbackend) for CLV modelling
See requirements.txt for pinned version constraints.
The raw CSV is ~90 MB / 1.07M rows. Parsing and cleaning it on every cold start
peaks at ~1 GB RSS, which exceeds the Streamlit Community Cloud memory limit and
causes OOM restarts. To avoid this, the cleaning runs once, offline, via
scripts/build_dataset.py:
utils/data.pyholds the cleaning logic (_build_clean_orders,_build_cancels).- The build script writes compact Parquet artifacts (~7 MB total).
- At runtime,
load_data()/load_cancels()read those Parquet files (with a CSV fallback if they are missing), so the app never re-parses the CSV.
This drops the cold-start peak memory from ~1 GB to a few hundred MB and the
resident dataset from ~125 MB to ~80 MB (the heavily repeated Description
column is stored as a category).
- At runtime the app reads
data/online_retail_clean.parquet(inutils/data.py); reads are cached with@st.cache_data. The rawonline_retail_II.csvis only needed by the build script. - Navigation is defined programmatically via
st.navigationinapp.py.
Released under the MIT License.