21. Stats You Need for Data Work
21.3 Spread: Min, Max, Quartiles and IQR
Why. Two cities can share the same median and still feel different. Pune might swing ₹40–₹2,000. Solapur might sit ₹90–₹180. Spread tells the manager about risk and staffing.
What.
- Min / max — ends of the sorted list (watch typos: 999 mins, ₹0).
- Quartiles — Q1 (25%), Q2 (median, 50%), Q3 (75%).
- IQR — Q3 − Q1, the width of the middle 50%.
How.
Read spread in pandas
df["amount"].min(),.max().df["amount"].quantile([0.25, 0.5, 0.75]).- Or
df["amount"].describe()— includes 25%, 50%, 75%. - IQR = Q3 − Q1.
- Optional fence (exploratory only): values below Q1 − 1.5×IQR or above Q3 + 1.5×IQR are candidates to check, not automatic deletes.
Worked example A — Delivery minutes, six cities (fictional).
import pandas as pd
mins = pd.DataFrame({
"city": (
["Pune"] * 8
+ ["Solapur"] * 6
+ ["Nashik"] * 6
+ ["Sambhaji Nagar"] * 5
+ ["Kolhapur"] * 5
+ ["Nagpur"] * 6
),
"delivery_mins": [
8, 9, 10, 11, 12, 14, 18, 45, # Pune: one late
10, 11, 11, 12, 13, 14, # Solapur calm
9, 10, 12, 15, 16, 40, # Nashik one late
11, 12, 13, 14, 55, # Sambhaji Nagar late
10, 11, 12, 12, 13, # Kolhapur tight
9, 10, 11, 12, 20, 22, # Nagpur wider
],
})
g = mins.groupby("city")["delivery_mins"]
summary = g.agg(
n="count",
min="min",
q1=lambda s: s.quantile(0.25),
median="median",
q3=lambda s: s.quantile(0.75),
max="max",
)
summary["iqr"] = summary["q3"] - summary["q1"]
print(summary.round(1))
What you should see. Kolhapur IQR is tiny (tight SLA). Pune and Sambhaji Nagar show a high max — investigate those rows (rain? wrong geo pin?) before blaming riders.
Worked example B — ₹ IQR for Blinkit Pune evening slot.
pune_amt = pd.Series([55, 60, 72, 80, 90, 95, 110, 120, 135, 150, 180, 900])
q1, q2, q3 = pune_amt.quantile([0.25, 0.5, 0.75])
iqr = q3 - q1
low, high = q1 - 1.5 * iqr, q3 + 1.5 * iqr
print("Q1", q1, "median", q2, "Q3", q3, "IQR", iqr)
print("fence", low, high)
print("flag rows", pune_amt[(pune_amt < low) | (pune_amt > high)].tolist())
The ₹900 gift box is flagged. Amir does not delete it blindly — he checks order_id BLK-261019-088 and keeps it with a note “festival SKU”.
Worked example C — Min/max traps.
bad = pd.Series([9, 10, 11, 12, 999]) # 999 = sensor default in raw extract
print("max with typo", bad.max())
print("max after fix", bad[bad < 120].max())
Always eye-max. A single 999 delivery-mins row can break a dashboard SLA tile.
Ravindra Bagale's Tip
IQR = madhlya 50% chi width. Manager la “average late” nahi; “middle half kiti spread” sanga. Max alag line la theva.