Descriptive statistics
Before any model or test, you describe what you have. Descriptive statistics compress thousands of rows into a few numbers a manager can hold in their head - and choosing the wrong few numbers is how dashboards mislead.
Measures of centre
| Measure | What it is | Use when |
|---|---|---|
| Mean | Sum divided by count | Values are roughly symmetric |
| Median | Middle value when sorted | Data is skewed or has outliers |
| Mode | Most frequent value | Categories, or discrete counts like bill items |
import statistics as st
bills = [12, 15, 15, 18, 20, 95] # ₹ hundred, one sweets-shop counter
print(st.mean(bills), st.median(bills), st.mode(bills)) # 29.2 16.5 15
The mean of ₹2,920 describes nobody: five of the six bills are below ₹2,000. One bulk festival order dragged it up. Salaries, property prices, claim amounts and order values are all right-skewed, so report the median, and show the mean beside it only when you also show the skew.
Measures of spread
Two shops can both average ₹4 lakh a month, yet one is steady and the other swings between ₹1 lakh and ₹9 lakh. Spread tells them apart.
- Range - max minus min. One outlier ruins it.
- Variance - average squared distance from the mean, in squared units.
- Standard deviation - the square root of variance, back in rupees. Roughly the typical distance from the mean.
- IQR - Q3 minus Q1, the width of the middle 50%. Immune to outliers.
- Coefficient of variation -
std / mean, a unit-free way to compare volatility between a ₹5,000 item and a ₹5 lakh item.
Note the n-1 divisor: statistics.stdev and df.std() compute the sample standard deviation (an unbiased
estimate of the population value), while statistics.pstdev and numpy.std default to the population formula.
On 500+ rows the difference is negligible; on 8 rows it is not.
Shape, percentiles and outliers
import pandas as pd
df = pd.read_csv("invoices.csv") # column: amount_inr, city
print(df["amount_inr"].describe())
print(df["amount_inr"].skew()) # > 0 means a long right tail
q1, q3 = df["amount_inr"].quantile([0.25, 0.75])
iqr = q3 - q1
outliers = df[(df.amount_inr < q1 - 1.5 * iqr) | (df.amount_inr > q3 + 1.5 * iqr)]
print(len(outliers), "unusual invoices")
print(df.groupby("city")["amount_inr"].agg(["count", "median", "std"]))
Percentiles often beat averages in operations: an SLA of "95% of deliveries within 48 hours" (the 95th percentile) is a promise; "average delivery 31 hours" hides the 10% that take four days.
Common mistakes
- Quoting a mean for skewed money data without the median.
- Reporting a summary of a group with 4 rows next to one with 4,000 - always print
count. - Deleting outliers on sight. Check whether they are data-entry errors (a ₹5,00,000 tea bill) or real business (a genuine bulk order) before touching them.
- Comparing standard deviations across different units or scales instead of the coefficient of variation.
- Averaging percentages or ratios across groups of different sizes. Recompute from the totals instead.
Practice
Take a sales or invoice CSV. For one numeric column print count, mean, median, std, IQR and the 90th and 99th percentiles, overall and grouped by city. Flag IQR-based outliers, decide for three of them whether they are errors or real, and write two sentences a manager could read.