Lesson 31 · Supply Chain Operations Analytics
Lead Time Variability Analysis in Supply Chains
We will learn how to analyze the variation in delivery lead times using real order data. Lead time variability affects inventory costs, stockouts, and…
- CourseSupply Chain Operations Analytics
- Lesson31 of 27
- Video31 min
- FormatJupyter notebook · 25 code cells
What you'll learn
- Understanding Lead Time Data in Supply Chains
- Key Lead Time Fields
- Beginner Example 1: Basic Lead Time Calculation
- Beginner Example 2: Flagging Outlier Lead Times
- Beginner Example 3: Lead Time by Status
- Intermediate Example 1: Lead Time Variability by State
- Intermediate Example 2: Month-over-Month Lead Time Trends
- Intermediate Example 3: Lead Time Variability by Seller
Data
No separate download needed — the notebook creates or downloads everything it uses.
📓 Full notebook
Download .ipynbLead Time Variability Analysis in Supply Chains#
- We will learn how to analyze the variation in delivery lead times using real order data.
- Lead time variability affects inventory costs, stockouts, and customer satisfaction.
- Understanding this helps operations managers improve planning and reduce risks.
- You will build hands-on skills to measure and visualize lead time variability in real supply chain data.
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import warnings
warnings.filterwarnings('ignore')
Understanding Lead Time Data in Supply Chains#
- In supply chains, lead time is the interval from order placement to order delivery.
- Lead time variability is the fluctuation in how long deliveries actually take.
- Real-world data typically includes timestamps for order placement, shipment, and final delivery.
- Columns may have missing or poorly formatted dates that can create major analysis mistakes.
- A common error is to compute negative or zero lead times due to missing data or time zone issues.
url = 'https://raw.githubusercontent.com/olist/work-at-olist-data/master/datasets/olist_orders_dataset.csv'
df = pd.read_csv(url)
print(df.shape)
print(df.head(3))
df['order_purchase_timestamp'] = pd.to_datetime(df['order_purchase_timestamp'])
df['order_delivered_customer_date'] = pd.to_datetime(df['order_delivered_customer_date'])
print(df[['order_purchase_timestamp', 'order_delivered_customer_date']].head())
Key Lead Time Fields#
- The main date columns are:
- order_purchase_timestamp: when the customer placed the order.
- order_delivered_customer_date: when the customer received the order.
- Some orders may have missing delivery dates (for example, canceled or pending orders).
- Always check for missing or inconsistent fields before analysis.
df['lead_time_days'] = (df['order_delivered_customer_date'] - df['order_purchase_timestamp']).dt.days
print(df[['order_purchase_timestamp', 'order_delivered_customer_date', 'lead_time_days']].head())
lead_times = df['lead_time_days'].dropna()
print('Number of orders with valid lead times:', lead_times.shape[0])
print('Typical lead time (days):', lead_times.median())
plt.figure(figsize=(8,4))
plt.hist(lead_times, bins=30, color='skyblue', edgecolor='black')
plt.title('Distribution of Delivery Lead Times (days)')
plt.xlabel('Lead Time (days)')
plt.ylabel('Number of Orders')
plt.show()
Beginner Example 1: Basic Lead Time Calculation#
- Compute the average lead time for all non-canceled orders.
- This metric gives operations managers an overview of typical delivery speed.
- Average values can be influenced by outliers, so always check distribution.
valid = df[df['lead_time_days'].notnull() & (df['lead_time_days'] >= 0)]
average_lead_time = valid['lead_time_days'].mean()
print('Average delivery lead time (days):', round(average_lead_time, 2))
Beginner Example 2: Flagging Outlier Lead Times#
- Outliers can distort your metrics and operational decisions.
- Learn how to flag unusually short or long deliveries.
q_low = valid['lead_time_days'].quantile(0.01)
q_high = valid['lead_time_days'].quantile(0.99)
outliers = valid[(valid['lead_time_days'] < q_low) | (valid['lead_time_days'] > q_high)]
print('Orders with lead times outside the 1st and 99th percentiles:', outliers.shape[0])
Beginner Example 3: Lead Time by Status#
- Orders can have different delivery statuses (delivered, shipped, canceled, etc).
- Group your analysis by order status to separate operational realities.
status_group = valid.groupby('order_status')['lead_time_days'].mean()
print(status_group)
status_counts = valid['order_status'].value_counts()
plt.figure(figsize=(8,3))
plt.bar(status_counts.index, status_counts.values, color='orange')
plt.title('Count of Completed Orders by Status')
plt.xlabel('Order Status')
plt.ylabel('Number of Orders')
plt.show()
Intermediate Example 1: Lead Time Variability by State#
- Sometimes, delivery times vary greatly by region or state.
- Group lead time analysis by state to find regional issues.
customers_url = 'https://raw.githubusercontent.com/olist/work-at-olist-data/master/datasets/olist_customers_dataset.csv'
customers = pd.read_csv(customers_url)
df_state = pd.merge(valid, customers[['customer_id','customer_state']], on='customer_id', how='left')
state_stats = df_state.groupby('customer_state')['lead_time_days'].agg(['mean','std','count']).sort_values(by='mean')
print(state_stats.head())
plt.figure(figsize=(12,5))
plt.bar(state_stats.index, state_stats['mean'], yerr=state_stats['std'], color='seagreen')
plt.title('Mean Lead Time by State with Variability')
plt.xlabel('State')
plt.ylabel('Mean Lead Time (days)')
plt.xticks(rotation=90)
plt.show()
Intermediate Example 2: Month-over-Month Lead Time Trends#
- Delivery performance may change seasonally or monthly.
- Plot and measure lead time variability across different months.
valid['order_month'] = valid['order_purchase_timestamp'].dt.to_period('M')
month_stats = valid.groupby('order_month')['lead_time_days'].agg(['mean','std','count'])
print(month_stats.tail(12))
plt.figure(figsize=(10,4))
plt.errorbar(month_stats.index.astype(str), month_stats['mean'], yerr=month_stats['std'], fmt='-o', color='navy')
plt.title('Monthly Mean Lead Time with Variability')
plt.xlabel('Month')
plt.ylabel('Mean Lead Time (days)')
plt.xticks(rotation=45)
plt.show()
Intermediate Example 3: Lead Time Variability by Seller#
- In some supply chain networks, each seller or supplier may contribute their own delays.
- Identify which sellers have the highest lead time variability.
sellers_url = 'https://raw.githubusercontent.com/olist/work-at-olist-data/master/datasets/olist_order_items_dataset.csv'
items = pd.read_csv(sellers_url)
seller_lead = pd.merge(valid, items[['order_id','seller_id']], on='order_id', how='left')
seller_stats = seller_lead.groupby('seller_id')['lead_time_days'].agg(['mean','std','count'])
top_sellers = seller_stats.sort_values('count', ascending=False).head(5)
print(top_sellers)
Advanced Example 1: Identifying Orders with Missing or Negative Lead Time#
- Real datasets often include errors: missing delivery dates or negative intervals.
- These orders can cause misleading analysis or failed reports.
missing_lt = df['lead_time_days'].isnull().sum()
negative_lt = (df['lead_time_days'] < 0).sum()
print(f'Orders missing lead time: {missing_lt}')
print(f'Orders with negative lead time: {negative_lt}')
problem_orders = df[(df['lead_time_days'].isnull()) | (df['lead_time_days'] < 0)]
print(problem_orders[['order_id', 'order_status', 'lead_time_days']].head())
Advanced Example 2: Quantifying Lead Time Variability using Coefficient of Variation#
- The coefficient of variation (CV) measures variability relative to mean lead time.
- High CV means unpredictable delivery and higher safety stock needs.
mean_lt = lead_times.mean()
std_lt = lead_times.std()
cv_lt = std_lt / mean_lt if mean_lt != 0 else np.nan
print(f'Coefficient of Variation (lead time): {cv_lt:.2f}')
Advanced Example 3: Simulating Safety Stock Needs#
- Lead time variability drives up inventory and working capital requirements.
- Managers need to estimate extra stock to buffer against delays.
- Use lead time CV to simulate safety stock for a typical SKU.
avg_daily_sales = 50
z = 1.65 # for about 95 percent service level
safety_stock = z * avg_daily_sales * std_lt ** 0.5
print(f'Estimated safety stock units (given observed lead time variability): {int(safety_stock)}')
Error Handling: Dealing with Missing Dates#
- Before calculating any lead time metric, always check for missing purchase or delivery dates.
- Fill, drop, or flag missing rows to prevent misleading KPI calculations.
- Decide on a standard method for handling missing or duplicate timestamps.
null_rows = df[df['order_purchase_timestamp'].isnull() | df['order_delivered_customer_date'].isnull()]
print(f'Orders with missing critical dates: {null_rows.shape[0]}')
Error Handling: Avoiding Incorrect Joins#
- When merging data frames (for example, with seller or customer info), always check row counts before and after.
- Incorrect joins may duplicate, drop, or disorder records, distorting average lead time results.
# Example: Bad join scenario
pre_join = valid.shape[0]
joined = pd.merge(valid, customers, on='customer_id', how='left')
post_join = joined.shape[0]
print(f'Rows before join: {pre_join}, after join: {post_join}')
KPI Calculation Patterns: Lead Time Service Level#
- Many companies track the fraction of orders delivered within an SLA or promised time.
- Calculate and visualize service level KPIs directly from lead time data.
- Group KPIs by month, region, or seller to influence operational strategy.
sla_days = 7
service_level = (valid['lead_time_days'] <= sla_days).mean()
print(f'Percent of orders delivered within {sla_days} days: {service_level:.2%}')
monthly_sla = valid.groupby('order_month').apply(lambda g: (g['lead_time_days'] <= sla_days).mean())
monthly_sla.plot(kind='bar', figsize=(10,4), color='teal')
plt.title('Monthly SLA Service Level')
plt.xlabel('Month')
plt.ylabel('Percent Orders < 7 days')
plt.show()
Tiny End-to-End Example: Diagnosing a Sudden Lead Time Spike#
- Suppose management sees a sudden spike in delivery lead time in one month.
- Use all the analysis skills above to identify cause and generate an action plan.
- Steps: spot the spike, find affected orders, segment by seller, and list potential operational reasons.
worst_month = month_stats['mean'].idxmax()
print('Month with highest mean delivery lead time:', worst_month)
bad_month_orders = valid[valid['order_month'] == worst_month]
late_sellers = pd.merge(bad_month_orders, items[['order_id','seller_id']], on='order_id', how='left')
seller_risk = late_sellers.groupby('seller_id')['lead_time_days'].mean().sort_values(ascending=False).head(3)
print('Sellers with highest mean lead time during worst month:')
print(seller_risk)
Congratulations, You Now Have Lead Time Variability Analytics Skills!#
- Try these patterns on your own firm's supply chain data.
- Want more deep dives? Subscribe to our YouTube channel for operational analytics walkthroughs.
Found this useful?
All lessons, notebooks and datasets here are free. If they helped you, a coffee keeps new lessons coming.



