Skip to content

Repository files navigation

Time-to-invoice prediction with survival analysis

Python port of the core modelling from my Master's thesis: predicting how long a customer order takes to become an invoice. The original data was real ERP data from a manufacturing company, which obviously can't go in a public repo, so everything here runs on a synthetic dataset built to mirror the same variables and roughly the same hazard ratios the original Cox model found. Business line and item-type values are generic labels (LINE A, ITEM 1, and so on) rather than the real category names, so the company stays unidentifiable.

Why survival analysis and not a normal regression

Some of the orders in the dataset haven't been invoiced yet by the time the data was pulled. A regular regression on "days until invoiced" doesn't know what to do with that: drop those rows and you bias the sample toward fast orders, treat them as already resolved and you're making up an outcome that hasn't happened. Survival analysis is built for exactly this situation through censoring — an order still open just tells you it survived at least this long, nothing more, nothing less. That's why the models here are Kaplan-Meier curves and hazard-based models (Cox, Random Survival Forest, Gradient Boosting) instead of a regression.

Results

Three models, same 5-fold cross-validation:

Model Cross-validated C-index (5 folds)
Cox proportional hazards 0.741
Random Survival Forest 0.726
Gradient Boosting (tree-based, Cox loss) 0.742

They land close to each other, within about a point and a half. In the original thesis the Random Survival Forest scored noticeably worse than the other two, but that was mostly an artifact of the R package available at the time not handling categorical variables directly — it forced an ordinal encoding that penalized the forest. One-hot encoding in Python gets rid of most of that gap. Worth flagging though: this doesn't prove the real invoicing process is additive, since the synthetic data is itself generated from an additive log-hazard, so there's no interaction structure for the tree models to find in the first place. More on that in the notebook 2 conclusion.


Kaplan-Meier curve, all orders.

Kaplan-Meier curves split by (anonymized) business line.

Cox model, top 10 hazard ratios by significance.

Predicted survival at different values of the seasonal month_sin term.

Cross-validated C-index, all three models.

What it does

  • Builds a synthetic order dataset with the same structure as the original ERP data (business line, requested lead time, delivery status, etc.)
  • Calibrates the synthetic time-to-invoice distribution against the real Kaplan-Meier curve from the thesis at day 30
  • Fits and cross-validates Cox, Random Survival Forest, and Gradient Boosting, all with the same 5-fold split
  • Revisits the RSF encoding issue from the thesis with proper one-hot encoding instead of the ordinal encoding R forced

Notebooks

  1. 01_synthetic_data.ipynb — builds and calibrates the synthetic dataset, saves synthetic_orders.csv
  2. 02_survival_models.ipynb — Kaplan-Meier, Cox, Random Survival Forest, Gradient Boosting, and the comparison

Limitations

The dataset is synthetic — generated from an exponential time-to-event distribution calibrated to match the real Kaplan-Meier curve at a single point (day 30). It reproduces the general shape and the modelling pipeline, not the real data itself; the actual observed times have an early peak and a long tail that a simple distribution doesn't capture.

The additivity argument in the results section is also weaker than it might look at first glance: since the synthetic log-hazard is a linear sum of effects by construction, three models converging on a similar C-index mostly confirms the encoding fix works, not that the real process is additive.

Tech stack

Python, lifelines for Kaplan-Meier and Cox, scikit-survival for the Random Survival Forest and Gradient Boosting, plus pandas, numpy, scipy and matplotlib.

Running it

pip install -r requirements.txt
jupyter lab

Run 01_synthetic_data.ipynb first, then 02_survival_models.ipynb.

About

Python survival-analysis port of my Master's thesis — Cox, Random Survival Forest, and Gradient Boosting on time-to-invoice data

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages