Mathew K Analytics

Lesson 37 · Supply Chain Operations Analytics

Transforming Operations Notebooks into Scalable Production Pipelines in Supply Chain Analytics

How real-world supply chain analytics moves from ad hoc analysis in notebooks to robust, repeatable production pipelines Why productionizing analytics…

⬇ 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

From Operations Notebook to Production Pipeline#

  • How real-world supply chain analytics moves from ad hoc analysis in notebooks to robust, repeatable production pipelines
  • Why productionizing analytics matters for efficiency and trust in operational decision making
  • In this lesson, you will build examples using real supply chain data, handle common issues, and learn best practices for operationalization
  • You will practice everything from exploratory analysis to robust reporting using public datasets
import pandas as pd
import numpy as np
import warnings
warnings.filterwarnings('ignore')

Understanding Operational Data in Supply Chains#

  • Operational data captures things like sales demand, supplier performance, inventory, and logistics events
  • These data tables often include timestamps, IDs, locations, and metrics about quantities and times
  • Beginners often mistake operational event timestamps (like order or delivery time) or do not check for missing data
  • Mistakes in joining tables from different sources are a frequent source of errors
# Example 1: Load retail demand (UCI Online Retail)
url = 'https://archive.ics.uci.edu/ml/machine-learning-databases/00502/online_retail_II.xlsx'
df_retail = pd.read_excel(url, sheet_name='Year 2010-2011')
df_retail['InvoiceDate'] = pd.to_datetime(df_retail['InvoiceDate'])
print(df_retail.shape)
print(df_retail.head(3))
(541910, 8)
  Invoice StockCode                         Description  Quantity  \
0  536365    85123A  WHITE HANGING HEART T-LIGHT HOLDER         6   
1  536365     71053                 WHITE METAL LANTERN         6   
2  536365    84406B      CREAM CUPID HEARTS COAT HANGER         8   

          InvoiceDate  Price  Customer ID         Country  
0 2010-12-01 08:26:00   2.55      17850.0  United Kingdom  
1 2010-12-01 08:26:00   3.39      17850.0  United Kingdom  
2 2010-12-01 08:26:00   2.75      17850.0  United Kingdom  
# Example 2: Load supplier performance data (OpenML Supply Chain)
import openml
dataset = openml.datasets.get_dataset(42125)
df_supplier, _, _, _ = dataset.get_data(dataset_format='dataframe')
print(df_supplier.shape)
print(df_supplier.head(3))
(9228, 13)
          full_name gender  current_annual_salary  2016_gross_pay_received  \
0    Aarhus, Pam J.      F               69222.18                 71225.98   
1   Aaron, David J.      M               97392.47                103088.48   
2  Aaron, Marsha M.      F              104717.28                107000.24   

   2016_overtime_pay department                          department_name  \
0             416.10        POL                     Department of Police   
1            3326.19        POL                     Department of Police   
2            1353.32        HHS  Department of Health and Human Services   

                                            division assignment_category  \
0  MSB Information Mgmt and Tech Division Records...    Fulltime-Regular   
1         ISB Major Crimes Division Fugitive Section    Fulltime-Regular   
2      Adult Protective and Case Management Services    Fulltime-Regular   

       employee_position_title underfilled_job_title date_first_hired  \
0  Office Services Coordinator                  None       09/22/1986   
1        Master Police Officer                  None       09/12/1988   
2             Social Worker IV                  None       11/19/1989   

   year_first_hired  
0              1986  
1              1988  
2              1989  
# Example 3: Load e-commerce orders (Olist Order Fulfillment Dataset)
url = 'https://raw.githubusercontent.com/olist/work-at-olist-data/master/datasets/olist_orders_dataset.csv'
df_orders = pd.read_csv(url)
df_orders['order_purchase_timestamp'] = pd.to_datetime(df_orders['order_purchase_timestamp'])
df_orders['order_delivered_customer_date'] = pd.to_datetime(df_orders['order_delivered_customer_date'])
print(df_orders.shape)
print(df_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  
# Example 4: Count unique customers who made purchases
unique_customers = df_retail['Customer ID'].nunique()
print(f'Total unique customers: {unique_customers}')
Total unique customers: 4372
# Example 5: Calculate basic supplier salary summary
supplier_salary_mean = df_supplier['current_annual_salary'].mean()
supplier_salary_max = df_supplier['current_annual_salary'].max()
print(f'Average supplier salary: {supplier_salary_mean:.2f}')
print(f'Highest supplier salary: {supplier_salary_max:.2f}')
Average supplier salary: 73390.18
Highest supplier salary: 303091.00
# Example 6: Compute order delivery time for all fulfilled orders
mask_delivered = df_orders['order_delivered_customer_date'].notnull()
df_delivered = df_orders[mask_delivered].copy()
df_delivered['delivery_days'] = (df_delivered['order_delivered_customer_date'] - df_delivered['order_purchase_timestamp']).dt.days
print(df_delivered[['order_id', 'order_status', 'delivery_days']].head(5))
                           order_id order_status  delivery_days
0  e481f51cbdc54678b7cc49136f2d6af7    delivered              8
1  53cdb2fc8bc7dce0b6741e2150273451    delivered             13
2  47770eb9100c2d0c44946d9cf07ec65d    delivered              9
3  949d5b44dbf5de918fe9c16f97b45f8a    delivered             13
4  ad21c59c0840e6cb83a9ceb5573f8159    delivered              2
# Example 7: Analyze order status distribution
order_status_counts = df_orders['order_status'].value_counts()
print(order_status_counts)
order_status
delivered      96478
shipped         1107
canceled         625
unavailable      609
invoiced         314
processing       301
created            5
approved           2
Name: count, dtype: int64
# Example 8: Time series - group retail sales by month
monthly_sales = df_retail.set_index('InvoiceDate').resample('M')['Quantity'].sum()
print(monthly_sales.head(6))
InvoiceDate
2010-12-31    342228
2011-01-31    308966
2011-02-28    277989
2011-03-31    351872
2011-04-30    289098
2011-05-31    380391
Freq: ME, Name: Quantity, dtype: int64
# Example 9: Filter top supplier divisions by staff count
top_divisions = df_supplier['division'].value_counts().head(5)
print(top_divisions)
division
School Health Services           300
Transit Silver Spring Ride On    299
Transit Gaithersburg Ride On     278
Highway Services                 226
Child Welfare Services           186
Name: count, dtype: int64
# Example 10: Detect potential duplicate retail invoices
dupes = df_retail['Invoice'].duplicated().sum()
print(f'Duplicate invoice count: {dupes}')
Duplicate invoice count: 516010
# Example 11: Calculate late deliveries in Olist orders
late_orders = df_delivered[df_delivered['order_delivered_customer_date'] > df_delivered['order_estimated_delivery_date']]
late_pct = len(late_orders) / len(df_delivered) * 100
print(f'Late deliveries: {len(late_orders)} ({late_pct:.2f}%)')
Late deliveries: 7827 (8.11%)
# Example 12: Remove canceled orders for efficiency analysis
active_orders = df_delivered[df_delivered['order_status'] != 'canceled'].copy()
print(f'Active orders (not canceled): {active_orders.shape[0]}')
Active orders (not canceled): 96470
# Example 13: Export delivery performance KPI to CSV for reporting
delivery_kpi = df_delivered.groupby('order_status').agg(avg_days=('delivery_days', 'mean'), count=('order_id', 'size')).reset_index()
delivery_kpi.to_csv('delivery_performance.csv', index=False)
print(delivery_kpi.head())
  order_status   avg_days  count
0     canceled  19.833333      6
1    delivered  12.093604  96470
# Example 14: Robust missing data handling for operations pipeline
missing_invoices = df_retail['Invoice'].isnull().sum()
missing_dates = df_retail['InvoiceDate'].isnull().sum()
print(f'Missing invoice IDs: {missing_invoices}')
print(f'Missing invoice dates: {missing_dates}')
Missing invoice IDs: 0
Missing invoice dates: 0
# Example 15: Saving supplier division summary for downstream automation
div_summary = df_supplier.groupby('division').agg(staff_count=('full_name', 'nunique')).reset_index()
div_summary.to_csv('supplier_division_summary.csv', index=False)
print(div_summary.head())
                 division  staff_count
0  24 Hours Crisis Center           38
1  ADA - HIPPA Compliance            1
2          ADA Compliance            6
3         Absentee Voting            3
4  Abused Persons Program           13
# Example 16: Parameterize time window for demand analysis (for automation)
def sales_in_window(df, start, end):
    mask = (df['InvoiceDate'] >= start) & (df['InvoiceDate'] < end)
    return df.loc[mask, 'Quantity'].sum()
window_total = sales_in_window(df_retail, pd.Timestamp('2010-12-01'), pd.Timestamp('2010-12-31'))
print(f'Sales units Dec 2010: {window_total}')
Sales units Dec 2010: 342228
# Example 17: Handling missing delivery dates in orders
missing_delivery = df_orders['order_delivered_customer_date'].isnull().sum()
print(f'Orders with missing delivery date: {missing_delivery}')
Orders with missing delivery date: 2965
# Example 18: Debugging illogical delivery times
negative_delivery = (df_delivered['delivery_days'] < 0).sum()
print(f'Orders with negative delivery days: {negative_delivery}')
Orders with negative delivery days: 0
# Example 19: Catching incorrect data merges (join on wrong key)
df_tmp = df_orders.merge(df_retail, left_on='order_id', right_on='Invoice', how='inner')
print('Joined shape:', df_tmp.shape)
Joined shape: (0, 16)
# Example 20: Handling misinterpreted lead time (numeric vs. string)
test_leadtime = pd.Series(['5', '10', '3', '8'])
numeric_leadtime = pd.to_numeric(test_leadtime, errors='coerce')
print(numeric_leadtime)
0     5
1    10
2     3
3     8
dtype: int64

Best Practices: Operational Analytics Patterns#

  • Aggregating by time (daily, weekly, monthly) to understand trends in supply chain and operations
  • Using groupby and pivot to summarize key operational metrics like cost, speed, and quality
  • Saving summaries to file for automation and integration
  • Always handle missing/ambiguous values and check joins
  • Validate business metrics with simple counts and prints
  • Test pipeline logic on a sample before full production
# End-to-end: Find monthly late delivery percentage for Olist
monthly_late = df_delivered.copy()
monthly_late['late'] = monthly_late['order_delivered_customer_date'] > monthly_late['order_estimated_delivery_date']
monthly_group = monthly_late.set_index('order_purchase_timestamp').resample('M')['late'].mean().reset_index()
monthly_group['late_pct'] = monthly_group['late'] * 100
print(monthly_group[['order_purchase_timestamp', 'late_pct']].head(6))
  order_purchase_timestamp    late_pct
0               2016-09-30  100.000000
1               2016-10-31    1.111111
2               2016-11-30         NaN
3               2016-12-31    0.000000
4               2017-01-31    3.066667
5               2017-02-28    3.206292

Lesson review and practice#

  • You have learned to handle real supply chain data from import, through quality checks, to analytics summaries and pipeline automation
  • Try modifying one of the export steps to include a new calculated field or add a filter
  • What steps would be required to schedule or automate this analysis in your company?
  • Explore a YouTube guide on productionizing supply chain analytics for more advanced tips

Found this useful?

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