Mathew K Analytics

Lesson 29 · Data analytics zero to hero

Business Operations Analytics Deep Dive | Data Analytics #29

Video twenty-nine of the 30-part series, the second capstone-style project: a genuine logistics and customer-satisfaction analysis. The business question:…

What you'll learn

Datasets used in this lesson

Save these next to the notebook. In Google Colab, upload them with the 📁 icon on the left first.

📓 Full notebook

Download .ipynb

Data Analytics Zero to Hero, Video 29: Real-World Project, Business Operations Deep Dive#

  • Video twenty-nine of the 30-part series, the second capstone-style project: a genuine logistics and customer-satisfaction analysis.
  • The business question: is late delivery actually hurting real customer satisfaction, and where should operations focus first?
  • Back to the real, multi-table Olist Brazilian E-Commerce dataset, this time including real delivery dates and real customer reviews.
  • Let's jump straight in.

Before You Start#

  • Open a new Jupyter Notebook in VS Code and select your Python interpreter as the kernel.
  • Place orders_sample.csv, customers_sample.csv, order_items_sample.csv, payments_sample.csv, and reviews_sample.csv in the same folder as this notebook.

Part 1: Load and Merge#

import pandas as pd

orders = pd.read_csv('orders_sample.csv', parse_dates=['order_purchase_timestamp', 'order_delivered_customer_date', 'order_estimated_delivery_date'])
customers = pd.read_csv('customers_sample.csv')
payments = pd.read_csv('payments_sample.csv')
reviews = pd.read_csv('reviews_sample.csv')
print(orders.shape, customers.shape, payments.shape, reviews.shape)
(1200, 8) (1200, 5) (1245, 5) (1195, 7)
delivered = orders[orders['order_status'] == 'delivered'].copy()
df = (delivered
      .merge(customers, on='customer_id', how='left')
      .merge(reviews[['order_id', 'review_score']], on='order_id', how='left')
      .merge(payments.groupby('order_id')['payment_value'].sum().reset_index(), on='order_id', how='left'))
print(df.shape)
print(df[['order_id', 'customer_state', 'review_score', 'payment_value']].head(3))
(1175, 14)
                           order_id customer_state  review_score  \
0  e481f51cbdc54678b7cc49136f2d6af7             SP           4.0   
1  53cdb2fc8bc7dce0b6741e2150273451             BA           4.0   
2  47770eb9100c2d0c44946d9cf07ec65d             GO           5.0   

   payment_value  
0          38.71  
1         141.46  
2         179.12  

Part 2: Delivery Performance#

df['delay_days'] = (df['order_delivered_customer_date'] - df['order_estimated_delivery_date']).dt.days
late_share = (df['delay_days'] > 0).mean()
print(f'{late_share:.1%} of real delivered orders arrived after the estimate')
print(df['delay_days'].describe())
6.4% of real delivered orders arrived after the estimate
count    1175.000000
mean      -12.129362
std         9.652494
min       -52.000000
25%       -17.000000
50%       -13.000000
75%        -7.000000
max        49.000000
Name: delay_days, dtype: float64

Part 3: Does Delay Hurt Satisfaction?#

delay_bins = pd.cut(df['delay_days'], bins=[-100, -1, 0, 7, 100], labels=['Early', 'On Time', '1-7 Days Late', '8+ Days Late'])
review_by_delay = df.groupby(delay_bins, observed=True)['review_score'].agg(['mean', 'count']).round(2)
print(review_by_delay)
               mean  count
delay_days                
Early          4.29   1079
On Time        4.50     14
1-7 Days Late  2.54     37
8+ Days Late   1.76     34
from scipy import stats
clean = df.dropna(subset=['delay_days', 'review_score'])
corr, p_val = stats.pearsonr(clean['delay_days'], clean['review_score'])
print(f'Delay-Satisfaction correlation: {corr:.3f} (p={p_val:.6f})')
Delay-Satisfaction correlation: -0.245 (p=0.000000)

Part 4: Payment Behavior#

payment_types = pd.read_csv('payments_sample.csv')
print(payment_types['payment_type'].value_counts())
print(payment_types.groupby('payment_type')['payment_installments'].mean().round(1))
payment_type
credit_card    940
boleto         229
voucher         56
debit_card      19
not_defined      1
Name: count, dtype: int64
payment_type
boleto         1.0
credit_card    3.4
debit_card     1.0
not_defined    1.0
voucher        1.0
Name: payment_installments, dtype: float64

Part 5: Regional Delivery Performance#

state_delay = df.groupby('customer_state')['delay_days'].agg(['mean', 'count'])
state_delay = state_delay[state_delay['count'] >= 10].sort_values('mean', ascending=False)
print(state_delay.round(1).head(6))
                mean  count
customer_state             
SP             -11.1    500
GO             -11.2     33
RJ             -11.6    131
SC             -11.9     34
BA             -12.2     51
MT             -12.5     11

A Quick Sanity Check on 'Worst'#

print((state_delay['mean'] < 0).all())
print(f"Overall mean delay: {df['delay_days'].mean():.1f} days")
True
Overall mean delay: -12.1 days

Part 6: Executive Summary#

worst_state = state_delay.index[0]
worst_state_delay = state_delay['mean'].iloc[0]
on_time_score = review_by_delay.loc['On Time', 'mean']
late_score = review_by_delay.loc['8+ Days Late', 'mean']

summary = f'''
BUSINESS OPERATIONS SUMMARY
{late_share:.1%} of real delivered orders arrived after the estimated date.
Average review score falls from {on_time_score:.2f} on-time to {late_score:.2f} when delivery runs 8+ days late.
{worst_state} has the smallest average delivery buffer among states with sufficient real order volume, at {worst_state_delay:.1f} days before the estimate, the state genuinely closest to tipping into late.
Recommendation: monitor {worst_state} carrier performance closely as volume grows, since it has the least real margin for error before deliveries start arriving late.
'''
print(summary)
BUSINESS OPERATIONS SUMMARY
6.4% of real delivered orders arrived after the estimated date.
Average review score falls from 4.50 on-time to 1.76 when delivery runs 8+ days late.
SP has the smallest average delivery buffer among states with sufficient real order volume, at -11.1 days before the estimate, the state genuinely closest to tipping into late.
Recommendation: monitor SP carrier performance closely as volume grows, since it has the least real margin for error before deliveries start arriving late.

Wrap-Up: What You Learned#

  • Merging four real tables into one analysis-ready view, filtered to a specific real business scope.
  • Measuring real delivery performance directly from date arithmetic.
  • Testing whether delay genuinely relates to satisfaction, with both binned averages and a formal correlation.
  • Reading real payment behavior as an operational signal, not just a sales metric.
  • Finding where a real problem concentrates geographically, not just its overall average.
  • A real, generated executive summary tying every finding to one concrete recommendation.
  • Video thirty is the final capstone: one complete, end-to-end real project bringing together the entire series. Subscribe so it lands automatically see you there.

Found this useful?

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