Skip to content
Laviyx Learn

Try

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().

Intermediate8 min readUpdated
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:

Python
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())
Output
[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.
Python
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())
Output
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#

Python
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}")
Output
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:

Python
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])
Output
0.25    47.25
0.50    51.00
0.75    57.25
0.90    79.00
dtype: float64
IQR: 10.0

describe() does it all#

Python
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))
Output
       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: float64

Skewness 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:

Python
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))
Output
        count   mean  median    std
city
Delhi       4  137.5   135.0   17.1
Mumbai      3  820.0   310.0  935.8

Mumbai'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
Python
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))
Output
A 38.9 31.0 22.8 5.0
B 31.0 31.0 1.4 2.0

Both 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?