Mathew K Analytics

Lesson 32 · Supply Chain Operations Analytics

On-Time Delivery and Fulfillment Rates in Supply Chain Analytics

This lesson explains how to measure and analyze on-time delivery and fulfillment rates using real-world datasets. On-time delivery and order fulfillment…

⬇ Download notebookOpen in Colab ↗

What you'll learn

Data

No separate download needed — the notebook creates or downloads everything it uses.

📓 Full notebook

Download .ipynb

On-Time Delivery and Fulfillment Rates in Supply Chain Analytics#

  • This lesson explains how to measure and analyze on-time delivery and fulfillment rates using real-world datasets.
  • On-time delivery and order fulfillment rates are critical operational KPIs for both retailers and logistics providers.
  • You will learn to load real ecommerce order data, compute on-time rates, explore fulfillment bottlenecks, avoid common pitfalls, and build real analytics workflows.
  • By the end, you will be able to extract and interpret key metrics from supply chain operations data for decision-making.
import pandas as pd
import numpy as np
import warnings
warnings.filterwarnings('ignore')

Understanding operational data behind on-time delivery and fulfillment rates#

  • Most supply chain analytics use data about orders, deliveries, and shipments.
  • Fields often include order IDs, timestamps, delivery estimates, actual delivery dates, and fulfillment status.
  • Data is usually structured as flat tables with one row per order or transaction.
  • Beginners often forget to convert time columns to datetime or fail to handle missing/incomplete events.
  • Always verify column types, especially for dates and status fields before analysis.
# Beginner Example 1: Load a real ecommerce order dataset
url = 'https://raw.githubusercontent.com/olist/work-at-olist-data/master/datasets/olist_orders_dataset.csv'
orders = pd.read_csv(url)
print(orders.shape)
print(orders.head(3))
(99441, 8)
                           order_id                       customer_id  \
0  e481f51cbdc54678b7cc49136f2d6af7  9ef432eb6251297304e76186b10a928d   
1  53cdb2fc8bc7dce0b6741e2150273451  b0830fb4747a6c6d20dea0b8c802d7ef   
2  47770eb9100c2d0c44946d9cf07ec65d  41ce2a54c0b03bf3443c3d931a367089   

  order_status order_purchase_timestamp    order_approved_at  \
0    delivered      2017-10-02 10:56:33  2017-10-02 11:07:15   
1    delivered      2018-07-24 20:41:37  2018-07-26 03:24:27   
2    delivered      2018-08-08 08:38:49  2018-08-08 08:55:23   

  order_delivered_carrier_date order_delivered_customer_date  \
0          2017-10-04 19:55:00           2017-10-10 21:25:13   
1          2018-07-26 14:31:00           2018-08-07 15:27:45   
2          2018-08-08 13:50:00           2018-08-17 18:06:29   

  order_estimated_delivery_date  
0           2017-10-18 00:00:00  
1           2018-08-13 00:00:00  
2           2018-09-04 00:00:00  
# Beginner Example 2: Convert date columns to datetime type
orders['order_purchase_timestamp'] = pd.to_datetime(orders['order_purchase_timestamp'])
orders['order_delivered_customer_date'] = pd.to_datetime(orders['order_delivered_customer_date'])
orders['order_estimated_delivery_date'] = pd.to_datetime(orders['order_estimated_delivery_date'])
# Beginner Example 3: Count total delivered orders
delivered_mask = orders['order_status'] == 'delivered'
print('Delivered orders:', delivered_mask.sum())
Delivered orders: 96478
# Intermediate Example 1: Calculate delivery time lag (days) for each order
orders['delivery_lag'] = (orders['order_delivered_customer_date'] - orders['order_purchase_timestamp']).dt.days
# Intermediate Example 2: Compare actual vs estimated delivery
orders['on_time'] = orders['order_delivered_customer_date'] <= orders['order_estimated_delivery_date']
# Intermediate Example 3: Calculate on-time delivery rate
total_delivered = delivered_mask.sum()
on_time_delivered = orders.loc[delivered_mask, 'on_time'].sum()
on_time_rate = on_time_delivered / total_delivered
print(f'On-time delivery rate: {on_time_rate:.2%}')
On-time delivery rate: 91.88%
# Intermediate Example 4: Monthly on-time delivery trend
monthly = orders[delivered_mask].set_index('order_purchase_timestamp').resample('M')['on_time'].mean()
print(monthly.tail())
order_purchase_timestamp
2018-04-30    0.946896
2018-05-31    0.917617
2018-06-30    0.985899
2018-07-31    0.954700
2018-08-31    0.896079
Freq: ME, Name: on_time, dtype: float64
# Intermediate Example 5: Identify late deliveries
late_orders = orders[delivered_mask & (orders['on_time'] == False)]
print(late_orders[['order_id', 'delivery_lag', 'order_estimated_delivery_date']].head())
                            order_id  delivery_lag  \
20  203096f03d82e0dffbc41ebc2e2bcfb7          21.0   
25  fbf9ac61453ac646ce8ad9783d7d0af6          28.0   
35  8563039e855156e48fccee4d611a3196          30.0   
41  6ea2f835b4556291ffdc53fa0b3b95e8          33.0   
57  66e4624ae69e7dc89bd50222b59f581f          24.0   

   order_estimated_delivery_date  
20                    2017-09-28  
25                    2018-03-12  
35                    2018-03-20  
41                    2017-12-21  
57                    2018-04-02  
# Advanced Example 1: Calculating fulfillment rate (all orders, not just delivered)
fulfillment_rate = (orders['order_status'] == 'delivered').mean()
print(f'Overall fulfillment rate: {fulfillment_rate:.2%}')
Overall fulfillment rate: 97.02%
# Advanced Example 2: Average lateness when orders are late
average_lateness = late_orders['delivery_lag'] - (late_orders['order_estimated_delivery_date'] - late_orders['order_purchase_timestamp']).dt.days
print(f'Average days late: {average_lateness.mean():.2f}')
Average days late: 9.45
# Advanced Example 3: Save late order analysis to CSV for team sharing
late_orders[['order_id', 'delivery_lag', 'order_estimated_delivery_date']].to_csv('late_orders_summary.csv', index=False)
print('Exported late orders summary to late_orders_summary.csv')
Exported late orders summary to late_orders_summary.csv
# Error Handling: Detect and count missing delivery dates
missing_delivery = orders['order_delivered_customer_date'].isna()
print(f'Orders missing delivery date: {missing_delivery.sum()}')
Orders missing delivery date: 2965
# Error Handling: Catch impossible delivery lags (negative or extreme values)
impossible_lags = orders['delivery_lag'] < 0
print('Negative delivery lags:', impossible_lags.sum())
Negative delivery lags: 0
# Error Handling: Check for mismatches in date ranges
future_dates = orders['order_delivered_customer_date'] > orders['order_estimated_delivery_date'] + pd.Timedelta(days=180)
print('Deliveries more than 6 months late:', future_dates.sum())
Deliveries more than 6 months late: 2

Best Practices for On-Time and Fulfillment KPI Analysis#

  • Always standardize all timestamps before KPI calculations.
  • Check for and document any missing or inconsistent data.
  • Aggregate KPIs at several time intervals (weekly, monthly, by business quarter).
  • Segment your analysis by product, region, or supply partner when possible.
  • Validate new KPIs against sample transactions and known business logic.
# Operational Pattern: Weekly fulfillment and on-time rates
orders['week'] = orders['order_purchase_timestamp'].dt.isocalendar().week
weekly_kpis = orders[delivered_mask].groupby('week').agg({'on_time': 'mean', 'order_id': 'count'})
weekly_kpis.rename(columns={'on_time':'on_time_rate','order_id':'delivered_orders'}, inplace=True)
print(weekly_kpis.tail())
      on_time_rate  delivered_orders
week                                
48        0.832926              2047
49        0.880441              1631
50        0.937089              1367
51        0.931794               953
52        0.963827               857
# Operational Pattern: KPI calculation function
def calc_kpis(df):
    dmask = df['order_status']=='delivered'
    on_time = df.loc[dmask, 'on_time'].mean()
    fulfillment = dmask.mean()
    return on_time, fulfillment
sample_on_time, sample_fulfillment = calc_kpis(orders)
print(f'On-time: {sample_on_time:.2%} | Fulfillment: {sample_fulfillment:.2%}')
On-time: 91.88% | Fulfillment: 97.02%
# End-to-End Problem: From raw orders to operational insight
# 1. Collect delivered orders
df_delivered = orders[orders['order_status']=='delivered'].copy()
# 2. Calculate delivery lag and on-time status
df_delivered['delivery_lag'] = (df_delivered['order_delivered_customer_date'] - df_delivered['order_purchase_timestamp']).dt.days
df_delivered['on_time'] = df_delivered['order_delivered_customer_date'] <= df_delivered['order_estimated_delivery_date']
# 3. Compute monthly fulfillment and on-time rates
df_delivered['month'] = df_delivered['order_purchase_timestamp'].dt.to_period('M')
monthly_metrics = df_delivered.groupby('month').agg(on_time_rate=('on_time','mean'), avg_lag=('delivery_lag','mean'), delivered_orders=('order_id','count'))
print(monthly_metrics.tail())
         on_time_rate    avg_lag  delivered_orders
month                                             
2018-04      0.946896  11.048544              6798
2018-05      0.917617  10.959105              6749
2018-06      0.985899   8.774442              6099
2018-07      0.954700   8.503736              6159
2018-08      0.896079   7.286412              6351

Recap - What you learned about On-Time Delivery and Fulfillment Rates#

  • You explored real-world order delivery data and transformed it into business-ready KPIs.
  • You handled real data quality problems and learned patterns for robust operational analytics.
  • You practiced best practices such as date cleaning, aggregation, and reporting.
  • Next, try joining order and product data for richer supply chain insights.
  • For more supply chain analytics workflows, explore relevant channels on YouTube.

Found this useful?

All lessons, notebooks and datasets here are free. If they helped you, a coffee keeps new lessons coming.