Getting data
The dataframe we will be working with has to contain a completion timestamp for each step of each process run. Depending on the source — Salesforce, an ERP system, an ETL pipeline — you’ll get there differently, but once the data is standardized you can manipulate and analyze it in Python (or, here, in your browser).
| Step 1 | Step 2 | Step 3 | Step 4 | Step 5 |
|---|---|---|---|---|
| 7.61 | NaN | NaN | NaN | NaN |
| 4.42 | 17.93 | NaN | NaN | NaN |
| 9.10 | 11.41 | 7.97 | 9.26 | 1.41 |
| 9.90 | 14.38 | NaN | NaN | NaN |
| 10.02 | 14.35 | 28.30 | 12.21 | 10.53 |
| 9.44 | 14.00 | 109.69 | 26.87 | 50.00 |
| 3.87 | 9.47 | 19.67 | 11.42 | 35.02 |
| 9.51 | 21.32 | 28.36 | 17.53 | 16.46 |
| 6.52 | 7.63 | 16.62 | 12.86 | 23.20 |
| 10.95 | 19.98 | 11.97 | 23.92 | 33.44 |
Showing first 10 of generated runs.
Dealing with missing values
We need to verify the data is complete. With real-life data it is very likely that not all process steps have been completed, leaving us with missing values. The chart below visualizes them — each row is a run, each column a step, blanks are missing values.
Missing value matrix — blanks indicate incomplete process runs
Depending on the case, you can either discard rows with empty values or replace them with a proxy. Here we simply drop them and keep completed runs only.
Dealing with outliers
Real-life data is full of surprises — and outliers. Here we use the interquartile range (IQR) method: any value below Q1 − 1.5×IQR or above Q3 + 1.5×IQR is flagged as an outlier. Pick a step to inspect:
Violin plots per step — before outlier treatment (with strip overlay)
With the goal of producing high-level insights on overall trend, we deal with these outliers by winsorizing them — clamping extreme values to the fence boundaries rather than removing them entirely. We now have a tighter distribution that limits the influence of extreme values:
Violin plots per step — after outlier treatment (winsorization)
Visualizing step completion
One way to get a visual representation of process timeliness is to overlay our data onto a Gantt chart. Each dot is a step completion timing; the triangle marks the average for each step.
Gantt overlay — actual completion dots vs. planned targets
It’s also useful to have high-level average values to share alongside the planned targets:
| Step | Target duration | Actual average duration |
|---|---|---|
| Step 1 | 8 | 8.4 |
| Step 2 | 21 | 22.8 |
| Step 3 | 38 | 44.9 |
| Step 4 | 48 | 61.1 |
| Step 5 | 70 | 89.2 |
Analyzing categories
With a good understanding of step timeliness, we can compare performance across categorical variables (e.g. teams, products). Boxplots let us visualize each category at every step.
Gantt boxplots by category — distribution per step per group
We can then compute the average timeliness for each category at each step:
| Step | Target duration | Category 1 | Category 2 | Category 3 |
|---|---|---|---|---|
| Step 1 | 8 | 7.2 | 8.2 | 9.6 |
| Step 2 | 21 | 19.5 | 22.7 | 25.9 |
| Step 3 | 38 | 35.8 | 51.7 | 47.4 |
| Step 4 | 48 | 45.4 | 65.3 | 71.8 |
| Step 5 | 70 | 71.6 | 90.7 | 103.9 |
A deviation heatmap makes this distinction immediately visible. Each cell shows how far the category’s average deviates from the target at each step — a row that’s uniformly red signals a systemic issue, while a single red cell in an otherwise neutral row points to a localized bottleneck:
Deviation heatmap — % difference from target per category per step
To go further
To summarize, we generated a process-timeliness dataset, cleaned it, and visualized actual durations against planned targets — including by category.
What next? We could explore sub-category results, or run a clustering algorithm such as K-Means or DBSCAN to identify the best and worst performers. That would surface why some groups are consistently late or performing better — and inform best practices to improve overall timeliness.