Python for Data course menu
Descriptive Statistics in Python: Mean, Median, Mode, Spread and Percentiles
Summarise data with descriptive statistics in pandas: mean, median and mode, when each is appropriate, range, variance and standard deviation, percentiles and the interquartile range, skewness, and describe().
On this page
Descriptive statistics summarise a column of numbers in a few values: where its centre is and how spread out it is. They are the first thing to compute for any numeric column, and the right choice of statistic can change the story a report tells.
We will use the monthly salaries (in ₹ thousands) of employees at two small companies:
import pandas as pd
startup = pd.Series([42, 45, 47, 48, 50, 52, 55, 58, 60, 250], name="startup")
agency = pd.Series([50, 52, 53, 55, 55, 56, 58, 60, 61, 63], name="agency")
print(startup.tolist())
print(agency.tolist())[42, 45, 47, 48, 50, 52, 55, 58, 60, 250]
[50, 52, 53, 55, 55, 56, 58, 60, 61, 63]Centre: mean, median and mode#
- Mean: the sum divided by the count, the everyday "average".
- Median: the middle value when sorted (the average of the two middle values for an even count).
- Mode: the most frequent value.
import pandas as pd
startup = pd.Series([42, 45, 47, 48, 50, 52, 55, 58, 60, 250])
agency = pd.Series([50, 52, 53, 55, 55, 56, 58, 60, 61, 63])
for name, s in [("startup", startup), ("agency", agency)]:
print(name, "mean", s.mean(), "median", s.median(), "mode", s.mode().tolist())startup mean 70.7 median 51.0 mode [42, 45, 47, 48, 50, 52, 55, 58, 60, 250]
agency mean 56.3 median 55.5 mode [55]Every startup salary is different, so there is no single most frequent value and mode() returns all of them; the mode is most useful for categories and repeated values (the agency's 55).
At the startup, one founder's salary of 250 drags the mean up to 70.7, higher than what 9 of the 10 employees earn. The median, 51, describes a typical employee much better. The agency has no extreme values, so its mean and median are close.
Rule of thumb: report the median for skewed data with outliers (salaries, house prices, delivery times) and the mean for roughly symmetric data. When in doubt, report both.
Spread: range, variance and standard deviation#
import pandas as pd
startup = pd.Series([42, 45, 47, 48, 50, 52, 55, 58, 60, 250])
agency = pd.Series([50, 52, 53, 55, 55, 56, 58, 60, 61, 63])
for name, s in [("startup", startup), ("agency", agency)]:
print(f"{name}: range {s.max() - s.min()}, variance {s.var():.1f}, std {s.std():.1f}")startup: range 208, variance 4001.1, std 63.3
agency: range 13, variance 17.3, std 4.2- Range: max − min. Simple, but decided entirely by the two most extreme values.
- Variance: the average squared distance from the mean.
- Standard deviation (std): the square root of the variance, in the same units as the data. A small std means values sit close to the mean.
pandas' std() and var() divide by n − 1 (the sample formula), because data is usually a sample of a bigger population. NumPy's np.std divides by n by default; pass ddof=1 to match pandas.
Percentiles and the IQR#
The p-th percentile is the value below which p% of the data falls. The 25th, 50th and 75th percentiles are the quartiles Q1, median and Q3. The interquartile range (IQR = Q3 − Q1) is the spread of the middle half, and, like the median, it is barely affected by outliers:
import pandas as pd
startup = pd.Series([42, 45, 47, 48, 50, 52, 55, 58, 60, 250])
q = startup.quantile([0.25, 0.5, 0.75, 0.9])
print(q)
print("IQR:", q[0.75] - q[0.25])0.25 47.25
0.50 51.00
0.75 57.25
0.90 79.00
dtype: float64
IQR: 10.0describe() does it all#
import pandas as pd
salaries = pd.DataFrame({
"startup": [42, 45, 47, 48, 50, 52, 55, 58, 60, 250],
"agency": [50, 52, 53, 55, 55, 56, 58, 60, 61, 63],
})
print(salaries.describe().round(1))
print(salaries.skew().round(2)) startup agency
count 10.0 10.0
mean 70.7 56.3
std 63.3 4.2
min 42.0 50.0
25% 47.2 53.5
50% 51.0 55.5
75% 57.2 59.5
max 250.0 63.0
startup 3.11
agency 0.17
dtype: float64Skewness measures asymmetry: around 0 is symmetric, positive means a long tail of large values (the startup), negative a long tail of small values. A mean far above the median is the same signal.
Summaries by group#
Descriptive statistics combine naturally with groupby:
import pandas as pd
orders = pd.DataFrame({
"city": ["Mumbai", "Mumbai", "Mumbai", "Delhi", "Delhi", "Delhi", "Delhi"],
"amount": [250, 310, 1900, 120, 140, 160, 130],
})
print(orders.groupby("city")["amount"].agg(["count", "mean", "median", "std"]).round(1)) count mean median std
city
Delhi 4 137.5 135.0 17.1
Mumbai 3 820.0 310.0 935.8Mumbai's mean is pulled up by one large order; its median and standard deviation tell you that immediately.
Common mistakes#
Practice#
Practice exercise
Delivery times (minutes) for two restaurants are A: [25, 28, 30, 31, 33, 35, 90] and B: [29, 30, 30, 31, 32, 32, 33]. Print each one's mean, median, standard deviation and IQR (rounded to 1 decimal). Which restaurant is more reliable, and which statistic shows it best?
Show hint
Loop over the two Series and use mean, median, std and quantile.
Show solution
import pandas as pd
times = {"A": pd.Series([25, 28, 30, 31, 33, 35, 90]), "B": pd.Series([29, 30, 30, 31, 32, 32, 33])}
for name, s in times.items():
iqr = s.quantile(0.75) - s.quantile(0.25)
print(name, round(s.mean(), 1), s.median(), round(s.std(), 1), round(iqr, 1))A 38.9 31.0 22.8 5.0
B 31.0 31.0 1.4 2.0Both have a median of 31 minutes, but B is far more consistent. The standard deviation shows it most clearly, because it is pulled up by A's 90-minute delivery; the IQR, which ignores the extreme delivery, differs less.
Key takeaway#
Next, the shape of the data: Distributions.
Finished this lesson?