Ravindra BagaleCourses & study guides मराठी Track your progress

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

  1. df["amount"].min(), .max().
  2. df["amount"].quantile([0.25, 0.5, 0.75]).
  3. Or df["amount"].describe() — includes 25%, 50%, 75%.
  4. IQR = Q3 − Q1.
  5. 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.