Mathew K Analytics

Lesson 2 · Supply Chain Operations Analytics

Numbers, Dates, and Quantities in Operations

This lesson explores how to work with numbers, dates, and quantities in real supply chain and operations datasets. These concepts affect everything from…

⬇ 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

Numbers, Dates, and Quantities in Operations#

  • This lesson explores how to work with numbers, dates, and quantities in real supply chain and operations datasets.
  • These concepts affect everything from inventory tracking to sales forecasting.
  • You will learn how to load, inspect, and analyze datasets containing operational numbers and timestamps.
  • We will solve real problems in procurement, sales, supply, and fulfillment.
  • You will build core skills for recognizing business patterns in the data.
import pandas as pd
import openml
import warnings
warnings.filterwarnings('ignore')

Understanding Operational Data#

  • Supply chain and operations data tracks flows of goods, orders, demand, suppliers, and fulfillment.
  • Data is usually organized as tables with columns for dates, numbers, and identifiers.
  • Common fields include order dates, delivery dates, quantities, stock levels, and purchase values.
  • Mistakes happen if you confuse date formats, misinterpret numbers, or forget that operations data can be messy and incomplete.
  • Handling missing values and checking for inconsistencies are part of every analysis.
# Load order fulfillment data from Olist
url = 'https://raw.githubusercontent.com/olist/work-at-olist-data/master/datasets/olist_orders_dataset.csv'
olist_df = pd.read_csv(url)
olist_df['order_purchase_timestamp'] = pd.to_datetime(olist_df['order_purchase_timestamp'])
olist_df['order_delivered_customer_date'] = pd.to_datetime(olist_df['order_delivered_customer_date'])
print(olist_df[['order_id', 'order_purchase_timestamp', 'order_delivered_customer_date']].head(3))
                           order_id order_purchase_timestamp  \
0  e481f51cbdc54678b7cc49136f2d6af7      2017-10-02 10:56:33   
1  53cdb2fc8bc7dce0b6741e2150273451      2018-07-24 20:41:37   
2  47770eb9100c2d0c44946d9cf07ec65d      2018-08-08 08:38:49   

  order_delivered_customer_date  
0           2017-10-10 21:25:13  
1           2018-08-07 15:27:45  
2           2018-08-17 18:06:29  
# Calculate delivery duration in days
olist_df['delivery_days'] = (olist_df['order_delivered_customer_date'] - olist_df['order_purchase_timestamp']).dt.days
print(olist_df[['order_id', 'delivery_days']].head(3))
                           order_id  delivery_days
0  e481f51cbdc54678b7cc49136f2d6af7            8.0
1  53cdb2fc8bc7dce0b6741e2150273451           13.0
2  47770eb9100c2d0c44946d9cf07ec65d            9.0
# Count missing values in the delivery date
missing_delivery = olist_df['order_delivered_customer_date'].isna().sum()
print(f'Missing delivered dates: {missing_delivery}')
Missing delivered dates: 2965
# Load OpenML inventory operations data
inv_dataset = openml.datasets.get_dataset(43900)
X_inv, y_inv, _, _ = inv_dataset.get_data(dataset_format='dataframe')
inv_df = X_inv.copy() if y_inv is None else pd.concat([X_inv, y_inv], axis=1)
print(inv_df.shape)
print(inv_df.head(3))
(32769, 10)
  ACTION RESOURCE MGR_ID ROLE_ROLLUP_1 ROLE_ROLLUP_2 ROLE_DEPTNAME ROLE_TITLE  \
0      1    39353  85475        117961        118300        123472     117905   
1      1    17183   1540        117961        118343        123125     118536   
2      1    36724  14457        118219        118220        117884     117879   

  ROLE_FAMILY_DESC ROLE_FAMILY ROLE_CODE  
0           117906      290919    117908  
1           118536      308574    118539  
2           267952       19721    117880  
# Example quantity aggregation
if 'quantity' in inv_df.columns:
    total_inventory = inv_df['quantity'].sum()
    print(f'Total inventory quantity: {total_inventory}')
else:
    print('No inventory quantity column found.')
No inventory quantity column found.
# Load supplier performance data
sup_dataset = openml.datasets.get_dataset(42125)
sup_df, _, _, _ = sup_dataset.get_data(dataset_format='dataframe')
if 'InvoiceDate' in sup_df.columns:
    sup_df['InvoiceDate'] = pd.to_datetime(sup_df['InvoiceDate'], errors='coerce')
print(sup_df.head(3))
          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  
# Check for late deliveries if possible
if 'ActualDeliveryDate' in sup_df.columns and 'ExpectedDeliveryDate' in sup_df.columns:
    sup_df['ActualDeliveryDate'] = pd.to_datetime(sup_df['ActualDeliveryDate'], errors='coerce')
    sup_df['ExpectedDeliveryDate'] = pd.to_datetime(sup_df['ExpectedDeliveryDate'], errors='coerce')
    sup_df['delivery_delay_days'] = (sup_df['ActualDeliveryDate'] - sup_df['ExpectedDeliveryDate']).dt.days
    print(sup_df[['ActualDeliveryDate', 'ExpectedDeliveryDate', 'delivery_delay_days']].head(3))
else:
    print('Delivery date columns not found in this dataset.')
Delivery date columns not found in this dataset.
# Aggregate orders by purchase date
olist_counts = olist_df.groupby(olist_df['order_purchase_timestamp'].dt.date)['order_id'].count()
print(olist_counts.head())
order_purchase_timestamp
2016-09-04    1
2016-09-05    1
2016-09-13    1
2016-09-15    1
2016-10-02    1
Name: order_id, dtype: int64
# Load OpenML sales forecasting data
sales_dataset = openml.datasets.get_dataset(4549)
X_sales, y_sales, _, _ = sales_dataset.get_data(dataset_format='dataframe')
sales_df = pd.concat([X_sales, y_sales], axis=1)
print(sales_df.head(3))
   NCD_0  NCD_1  NCD_2  NCD_3  NCD_4  NCD_5  NCD_6  AI_0  AI_1  AI_2  ...  \
0    0.0    2.0    0.0    0.0    1.0    1.0    1.0   0.0   1.0   0.0  ...   
1    2.0    1.0    0.0    0.0    0.0    0.0    4.0   2.0   1.0   0.0  ...   
2    1.0    0.0    0.0    0.0    0.0    4.0    1.0   1.0   0.0   0.0  ...   

   ADL_5  ADL_6  NAD_0  NAD_1  NAD_2  NAD_3  NAD_4  NAD_5  NAD_6  Annotation  
0    1.0    1.0    0.0    2.0    0.0    0.0    1.0    1.0    1.0         0.0  
1    0.0    1.0    2.0    1.0    0.0    0.0    0.0    0.0    4.0         0.5  
2    1.0    1.0    1.0    0.0    0.0    0.0    0.0    4.0    1.0         0.0  

[3 rows x 78 columns]
# Check for date or time columns in sales data
date_columns = [col for col in sales_df.columns if 'date' in col.lower() or 'time' in col.lower()]
print('Date/time columns:', date_columns)
if date_columns:
    sales_df[date_columns[0]] = pd.to_datetime(sales_df[date_columns[0]], errors='coerce')
    print(sales_df[date_columns[0]].head(3))
Date/time columns: []
# Plot numeric quantities if matplotlib is installed
import matplotlib.pyplot as plt
num_cols = sales_df.select_dtypes(include='number').columns
if not num_cols.empty:
    sales_df[num_cols[0]].hist(bins=30)
    plt.title(f'Distribution of {num_cols[0]}')
    plt.xlabel(num_cols[0])
    plt.ylabel('Frequency')
    plt.show()
else:
    print('No numeric columns available for visualization.')
No description has been provided for this image
# Rolling average of daily delivery durations (Olist)
olist_df_sorted = olist_df.dropna(subset=['delivery_days']).sort_values('order_purchase_timestamp')
olist_df_sorted['rolling_avg_delivery'] = olist_df_sorted['delivery_days'].rolling(window=7).mean()
print(olist_df_sorted[['order_purchase_timestamp', 'delivery_days', 'rolling_avg_delivery']].head(10))
      order_purchase_timestamp  delivery_days  rolling_avg_delivery
30710      2016-09-15 12:16:38           54.0                   NaN
93285      2016-10-03 09:44:50           23.0                   NaN
28424      2016-10-03 16:56:50           24.0                   NaN
92636      2016-10-03 21:01:41           35.0                   NaN
97979      2016-10-03 21:13:36           30.0                   NaN
88472      2016-10-03 22:06:03           27.0                   NaN
6747       2016-10-03 22:31:31           10.0             29.000000
62143      2016-10-03 22:44:10           30.0             25.571429
33504      2016-10-03 22:51:30           28.0             26.285714
78848      2016-10-04 09:06:10           18.0             25.428571
# Count of orders per month
olist_df['month'] = olist_df['order_purchase_timestamp'].dt.to_period('M')
monthly_counts = olist_df.groupby('month')['order_id'].count()
print(monthly_counts.head())
month
2016-09       4
2016-10     324
2016-12       1
2017-01     800
2017-02    1780
Freq: M, Name: order_id, dtype: int64
# Weekly order volume seasonality
olist_df['week'] = olist_df['order_purchase_timestamp'].dt.isocalendar().week
weekly_volume = olist_df.groupby('week')['order_id'].count()
print(weekly_volume.head())
week
1    1429
2    1865
3    1947
4    1945
5    2065
Name: order_id, dtype: int64
# On-time delivery KPI (percentage within X days)
on_time_threshold = 5
total_delivered = olist_df['delivery_days'].notna().sum()
on_time = (olist_df['delivery_days'] <= on_time_threshold).sum()
on_time_pct = 100 * on_time / total_delivered if total_delivered > 0 else float('nan')
print(f'On-time delivery rate (<= {on_time_threshold} days): {on_time_pct:.2f}%')
On-time delivery rate (<= 5 days): 19.94%
# Handle missing data in delivery days
missing_delivery_count = olist_df['delivery_days'].isna().sum()
if missing_delivery_count > 0:
    print(f'Warning: {missing_delivery_count} orders are missing delivery days.')
    olist_df_filled = olist_df.copy()
    olist_df_filled['delivery_days'] = olist_df['delivery_days'].fillna(-1)
else:
    olist_df_filled = olist_df.copy()
    print('No missing delivery day values.')
Warning: 2965 orders are missing delivery days.
# Find deliveries before purchase dates (should never happen)
invalid_dates = olist_df[olist_df['order_delivered_customer_date'] < olist_df['order_purchase_timestamp']]
print(f'Deliveries before purchase: {invalid_dates.shape[0]}')
if not invalid_dates.empty:
    print(invalid_dates[['order_id', 'order_purchase_timestamp', 'order_delivered_customer_date']].head())
Deliveries before purchase: 0
# Demonstrate a possible incorrect join: drop mismatched IDs
test_left = olist_df[['order_id', 'order_purchase_timestamp']].iloc[:5]
test_right = olist_df[['order_id', 'order_delivered_customer_date']].iloc[2:7]
merge_result = pd.merge(test_left, test_right, on='order_id', how='outer', indicator=True)
print(merge_result)
                           order_id order_purchase_timestamp  \
0  136cce7faa42fdb2cefd53fdc79a6098                      NaT   
1  47770eb9100c2d0c44946d9cf07ec65d      2018-08-08 08:38:49   
2  53cdb2fc8bc7dce0b6741e2150273451      2018-07-24 20:41:37   
3  949d5b44dbf5de918fe9c16f97b45f8a      2017-11-18 19:28:06   
4  a4591c265e18cb1dcee52889e2d8acc3                      NaT   
5  ad21c59c0840e6cb83a9ceb5573f8159      2018-02-13 21:18:39   
6  e481f51cbdc54678b7cc49136f2d6af7      2017-10-02 10:56:33   

  order_delivered_customer_date      _merge  
0                           NaT  right_only  
1           2018-08-17 18:06:29        both  
2                           NaT   left_only  
3           2017-12-02 00:28:42        both  
4           2017-07-26 10:57:55  right_only  
5           2018-02-16 18:17:02        both  
6                           NaT   left_only  
# Example: Aggregate orders by item_id (if available)
if 'product_id' in olist_df.columns:
    order_counts = olist_df.groupby('product_id')['order_id'].count().sort_values(ascending=False)
    print(order_counts.head())
else:
    print('No product_id available in this dataset.')
No product_id available in this dataset.
# Rolling on-time KPI visualization
on_time_threshold = 7
olist_df_sorted = olist_df.sort_values('order_purchase_timestamp').copy()
olist_df_sorted['on_time'] = (olist_df_sorted['delivery_days'] <= on_time_threshold).astype(int)
olist_df_sorted['rolling_kpi'] = olist_df_sorted['on_time'].rolling(window=30, min_periods=1).mean() * 100
plt.plot(olist_df_sorted['order_purchase_timestamp'], olist_df_sorted['rolling_kpi'])
plt.title('30-Order Rolling On-Time Delivery Rate')
plt.xlabel('Order Purchase Date')
plt.ylabel('On-time Delivery %')
plt.show()
No description has been provided for this image
# Summing order counts by week for reporting
weekly_order_counts = olist_df.groupby('week')['order_id'].count()
print(weekly_order_counts.head())
week
1    1429
2    1865
3    1947
4    1945
5    2065
Name: order_id, dtype: int64
# End-to-end: Identify week with fastest deliveries
min_week = olist_df.loc[olist_df['delivery_days'] > 0].groupby('week')['delivery_days'].mean().idxmin()
fastest_mean = olist_df.loc[olist_df['week'] == min_week, 'delivery_days'].mean()
print(f'The fastest average delivery was week {min_week} with {fastest_mean:.2f} days.')
The fastest average delivery was week 34 with 7.53 days.
 

Found this useful?

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