Mathew K Analytics

Lesson 3 · Supply Chain Operations Analytics

Control Flow for Operational Decision Logic

In this lesson, we will learn how control flow (if/else, loops) is essential for making real-world decisions in supply chain and operations analytics. This…

⬇ 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

Control Flow for Operational Decision Logic#

  • In this lesson, we will learn how control flow (if/else, loops) is essential for making real-world decisions in supply chain and operations analytics.
  • This is important because supply chains need to respond to changing demand, delays, and exceptionsoften in real time.
  • You will explore operational decision logic using real retail and manufacturing datasets, build analytics scripts that make business decisions, and learn to avoid common pitfalls.
  • By the end, you will know how to use Python control structures to automate key operational choices.
import pandas as pd
import openml
import warnings
warnings.filterwarnings('ignore')

Understanding Operation and Supply Chain Data#

  • The datasets used in supply chain analytics may represent inventory, customer demand, supplier performance, or delivery events.
  • These datasets often contain dates, quantities, and status codes that require validation using decision logic.
  • Data is usually structured in tables: each row is an event or record; columns are variables such as item, date, and quantity.
  • Beginners often forget to handle missing dates or misunderstand what each column means, leading to wrong decisions.
  • Careful control flow (such as checking for nulls) is necessary for accurate operational insights.
# Load a real retail demand dataset
url = 'https://archive.ics.uci.edu/ml/machine-learning-databases/00502/online_retail_II.xlsx'
df = pd.read_excel(url, sheet_name='Year 2010-2011')
df['InvoiceDate'] = pd.to_datetime(df['InvoiceDate'])
print(df.shape)
print(df.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  
# Load an order fulfillment and delivery dataset
url = 'https://raw.githubusercontent.com/olist/work-at-olist-data/master/datasets/olist_orders_dataset.csv'
orders_df = pd.read_csv(url)
orders_df['order_purchase_timestamp'] = pd.to_datetime(orders_df['order_purchase_timestamp'])
orders_df['order_delivered_customer_date'] = pd.to_datetime(orders_df['order_delivered_customer_date'])
print(orders_df.shape)
print(orders_df.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: Use if-else to flag late deliveries
orders_df['is_late'] = None
for idx, row in orders_df.iterrows():
    if pd.isnull(row['order_delivered_customer_date']):
        orders_df.at[idx, 'is_late'] = 'Unknown'
    elif row['order_delivered_customer_date'] > row['order_purchase_timestamp'] + pd.Timedelta(days=7):
        orders_df.at[idx, 'is_late'] = 'Yes'
    else:
        orders_df.at[idx, 'is_late'] = 'No'
print(orders_df[['order_purchase_timestamp', 'order_delivered_customer_date', 'is_late']].head(6))
  order_purchase_timestamp order_delivered_customer_date is_late
0      2017-10-02 10:56:33           2017-10-10 21:25:13     Yes
1      2018-07-24 20:41:37           2018-08-07 15:27:45     Yes
2      2018-08-08 08:38:49           2018-08-17 18:06:29     Yes
3      2017-11-18 19:28:06           2017-12-02 00:28:42     Yes
4      2018-02-13 21:18:39           2018-02-16 18:17:02      No
5      2017-07-09 21:57:05           2017-07-26 10:57:55     Yes
# Beginner: Count unknown, late, and on-time cases
counts = orders_df['is_late'].value_counts(dropna=False)
print(counts)
is_late
Yes        70430
No         26046
Unknown     2965
Name: count, dtype: int64
# Beginner: Use list comprehension for simple demand flagging
df['high_demand'] = ['Yes' if qty > 50 else 'No' for qty in df['Quantity']]
print(df[['Quantity', 'high_demand']].head(6))
   Quantity high_demand
0         6          No
1         6          No
2         8          No
3         6          No
4         6          No
5         2          No
# Beginner: Filter only high demand transactions using boolean logic
high_demand_df = df[df['high_demand'] == 'Yes']
print(high_demand_df[['StockCode', 'Quantity', 'high_demand']].head(5))
    StockCode  Quantity high_demand
46      22086        80         Yes
83      21733        64         Yes
96      21212       120         Yes
102    85071B        96         Yes
176    85099C       100         Yes
# Intermediate: Classify orders as delivered, not delivered, or unknown
def classify_status(row):
    if pd.isnull(row['order_delivered_customer_date']):
        return 'Unknown'
    elif row['order_delivered_customer_date'] <= pd.Timestamp.now():
        return 'Delivered'
    else:
        return 'Not delivered'
orders_df['delivery_status'] = orders_df.apply(classify_status, axis=1)
print(orders_df[['order_delivered_customer_date', 'delivery_status']].head(6))
  order_delivered_customer_date delivery_status
0           2017-10-10 21:25:13       Delivered
1           2018-08-07 15:27:45       Delivered
2           2018-08-17 18:06:29       Delivered
3           2017-12-02 00:28:42       Delivered
4           2018-02-16 18:17:02       Delivered
5           2017-07-26 10:57:55       Delivered
# Intermediate: Flag orders with abnormal delivery time using nested ifs
def abnormal_delivery(row):
    if pd.isnull(row['order_delivered_customer_date']):
        return 'Unknown'
    else:
        days = (row['order_delivered_customer_date'] - row['order_purchase_timestamp']).days
        if days < 0:
            return 'Error'
        elif days > 30:
            return 'Extremely late'
        elif days > 7:
            return 'Late'
        else:
            return 'On time'
orders_df['delivery_flag'] = orders_df.apply(abnormal_delivery, axis=1)
print(orders_df[['order_purchase_timestamp', 'order_delivered_customer_date', 'delivery_flag']].head(8))
  order_purchase_timestamp order_delivered_customer_date delivery_flag
0      2017-10-02 10:56:33           2017-10-10 21:25:13          Late
1      2018-07-24 20:41:37           2018-08-07 15:27:45          Late
2      2018-08-08 08:38:49           2018-08-17 18:06:29          Late
3      2017-11-18 19:28:06           2017-12-02 00:28:42          Late
4      2018-02-13 21:18:39           2018-02-16 18:17:02       On time
5      2017-07-09 21:57:05           2017-07-26 10:57:55          Late
6      2017-04-11 12:22:08                           NaT       Unknown
7      2017-05-16 13:10:30           2017-05-26 12:55:51          Late
# Intermediate: Using for-loops to summarize per-week demand in real retail data
df['week'] = df['InvoiceDate'].dt.strftime('%Y-%U')
week_demand = {}
for week in df['week'].unique():
    week_qty = df[df['week']==week]['Quantity'].sum()
    if week_qty > 5000:
        week_demand[week] = 'High Demand Week'
    else:
        week_demand[week] = 'Normal'
print(list(week_demand.items())[:5])
[('2010-48', 'High Demand Week'), ('2010-49', 'High Demand Week'), ('2010-50', 'High Demand Week'), ('2010-51', 'High Demand Week'), ('2011-01', 'High Demand Week')]
# Intermediate: Find the first date of stockout using control flow
stockout_item = df['StockCode'].value_counts().idxmax()
item_df = df[df['StockCode']==stockout_item]
item_df = item_df.sort_values('InvoiceDate')
cumulative_qty = 0
stockout_date = None
for idx, row in item_df.iterrows():
    cumulative_qty += row['Quantity']
    if cumulative_qty <= 0 and stockout_date is None:
        stockout_date = row['InvoiceDate']
        break
print('Stockout item:', stockout_item)
print('Stockout date:', stockout_date)
Stockout item: 85123A
Stockout date: None
# Advanced: Fetch and analyze a supplier performance dataset
dataset = openml.datasets.get_dataset(42125)
supply_df, _, _, _ = dataset.get_data(dataset_format='dataframe')
print(supply_df.shape)
print(supply_df.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  
# Advanced: Use decision logic to classify supplier risk
def supplier_risk(row):
    if row['OnTimeDeliveryRate'] < 0.85 or row['DefectRate'] > 0.05:
        return 'High Risk'
    elif row['OnTimeDeliveryRate'] < 0.95 or row['DefectRate'] > 0.02:
        return 'Medium Risk'
    else:
        return 'Low Risk'
if 'OnTimeDeliveryRate' in supply_df.columns and 'DefectRate' in supply_df.columns:
    supply_df['SupplierRisk'] = supply_df.apply(supplier_risk, axis=1)
    print(supply_df[['OnTimeDeliveryRate', 'DefectRate', 'SupplierRisk']].head(6))
else:
    print('Risk columns missing: cannot compute supplier risk.')
Risk columns missing: cannot compute supplier risk.
# Advanced: Create flexible business rules using lambda and np.select
import numpy as np
conditions = [
    (orders_df['delivery_flag'] == 'On time') & (orders_df['is_late'] == 'No'),
    (orders_df['delivery_flag'] == 'Late'),
    (orders_df['delivery_flag'] == 'Extremely late')
]
choices = ['Reward', 'Investigate', 'Escalate']
orders_df['action_decision'] = np.select(conditions, choices, default='Check')
print(orders_df[['delivery_flag', 'is_late', 'action_decision']].head(10))
  delivery_flag  is_late action_decision
0          Late      Yes     Investigate
1          Late      Yes     Investigate
2          Late      Yes     Investigate
3          Late      Yes     Investigate
4       On time       No          Reward
5          Late      Yes     Investigate
6       Unknown  Unknown           Check
7          Late      Yes     Investigate
8          Late      Yes     Investigate
9          Late      Yes     Investigate
# Advanced: Automatically generate an alert report when high-risk suppliers are found
high_risk_suppliers = None
if 'SupplierRisk' in supply_df.columns:
    high_risk_suppliers = supply_df[supply_df['SupplierRisk']=='High Risk']
    high_risk_suppliers[['SupplierName','OnTimeDeliveryRate','DefectRate']].to_csv('high_risk_suppliers.csv', index=False)
    print('Alert: Saved high-risk supplier report!')
else:
    print('SupplierRisk not available, report not generated.')
SupplierRisk not available, report not generated.
ffghfh
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[16], line 1
----> 1 ffghfh

NameError: name 'ffghfh' is not defined
 

Found this useful?

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