EthicsComputerCourses
Join training +91 90532 10052

Mean, Median & Mode

Statistics · Lesson 7 of 28 Beginner 7 min read Updated

"What is the average?" is the most common question in statistics, and it has more than one answer. The mean, the median and the mode each describe the centre of the data in a different way, and they can give very different numbers for the same list. Choosing the right one is often more important than calculating it.

In this lesson you will compute all three by hand and with Python, see how one extreme value changes them, and learn three useful variations: the weighted mean (used for your CGPA), the trimmed mean (used in judged sports) and the mean and median of grouped data.

The three measures

These are the formulas in plain text. Σ (sigma) means "add up".

Text
mean   = (x1 + x2 + ... + xn) / n  =  Σx / n
median = the middle value after sorting
         n odd:  the value in position (n + 1) / 2
         n even: the average of the two middle values
mode   = the value that appears most often

Work through a batter's scores from nine innings. First sort them: 0, 7, 12, 21, 33, 45, 45, 78, 104.

  • Mean: the total is 345, and 345 / 9 = 38.33 runs.
  • Median: n = 9 is odd, so the median is the value in position (9 + 1) / 2 = 5, which is 33.
  • Mode: 45 appears twice and every other score once, so the mode is 45.

The same steps in plain Python, without any statistics library:

PythonRun it yourself
# Scores of a batter in 9 innings (invented)
scores = [12, 45, 0, 78, 33, 104, 7, 45, 21]

mean = sum(scores) / len(scores)

ordered = sorted(scores)
n = len(ordered)
mid = n // 2
if n % 2 == 1:
    median = ordered[mid]
else:
    median = (ordered[mid - 1] + ordered[mid]) / 2

counts = {}
for s in scores:
    counts[s] = counts.get(s, 0) + 1
top = max(counts.values())
modes = [value for value, c in counts.items() if c == top]

print("Sorted:", ordered)
print("Mean:", round(mean, 2), " Median:", median, " Mode:", modes)
Output
Sorted: [0, 7, 12, 21, 33, 45, 45, 78, 104]
Mean: 38.33  Median: 33  Mode: [45]

Python lists start at index 0, so the fifth value is ordered[4], which is ordered[n // 2] when n is 9. The mode code collects every value that ties for the top count, because a dataset can have more than one mode.

The same thing with libraries

Python's statistics module, NumPy, SciPy and pandas all compute these measures. They agree on the values but return slightly different types.

PythonRun it yourself
import statistics
import numpy as np
import pandas as pd
from scipy import stats

scores = [12, 45, 0, 78, 33, 104, 7, 45, 21]

print("statistics:", statistics.mean(scores), statistics.median(scores), statistics.mode(scores))
print("NumPy:     ", np.mean(scores), np.median(scores))
res = stats.mode(scores)
print("SciPy:     ", res.mode, "appears", res.count, "times")
s = pd.Series(scores)
print("pandas:    ", s.mean(), s.median(), s.mode().tolist())

print("Median of an even count:", statistics.median([12, 45, 0, 78, 33, 104, 7, 45, 21, 60]))
print("Two modes:", statistics.multimode([2, 3, 3, 5, 5, 8]))
Output
statistics: 38.333333333333336 33 45
NumPy:      38.333333333333336 33.0
SciPy:      45 appears 2 times
pandas:     38.333333333333336 33.0 [45]
Median of an even count: 39.0
Two modes: [3, 5]

NumPy has no mode function, so use scipy.stats.mode() (which returns the smallest mode if there is a tie) or pandas mode() (which returns all of them). With ten scores, the median is the average of the 5th and 6th sorted values, 33 and 45, which gives 39.0. statistics.multimode() returns every mode, which matters for data such as shoe sizes where two values can be equally common.

Outliers pull the mean, not the median

The mean uses every value, so one extreme value can drag it a long way. The median only cares about the middle position, so it barely moves.

PythonRun it yourself
import numpy as np

# Monthly salaries (₹) at a small Jaipur office (invented)
staff = np.array([18_000, 21_000, 22_000, 24_000, 25_000, 27_000, 30_000])
print(f"Staff only:     mean ₹{staff.mean():,.0f}  median ₹{np.median(staff):,.0f}")

with_owner = np.append(staff, 3_50_000)
print(f"Owner included: mean ₹{with_owner.mean():,.0f}  median ₹{np.median(with_owner):,.0f}")
Output
Staff only:     mean ₹23,857  median ₹24,000
Owner included: mean ₹64,625  median ₹24,500

Adding the owner's ₹3,50,000 nearly triples the mean, to a salary that nobody in the office earns. The median moves by only ₹500. Python allows underscores inside numbers, so you can write 3_50_000 in the Indian lakh style. This is why reports on incomes and house prices usually give the median: those data have a long tail of very large values.

Which average should you use?

SituationBest measureWhy
Roughly symmetric numbers (marks, heights)MeanUses all the data; mean and median are close anyway
Skewed numbers or outliers (salaries, rainfall, prices)MedianNot pulled by extreme values
Categories (city, favourite subject)ModeThe only one that works for names
Ordinal data (ratings, grades)MedianOrder is meaningful, exact gaps are not

When in doubt, report both the mean and the median. A big gap between them is itself useful information: it signals skewed data or outliers.

Weighted and trimmed means

A weighted mean gives some values more importance than others. Your semester grade point average is one: a 4-credit subject counts four times as much as a 1-credit subject.

Text
weighted mean = Σ(w x x) / Σw      (w = weight of each value)

A trimmed mean removes a fixed share of the smallest and largest values and averages the rest. Some judged sports use this idea, dropping the highest and lowest judges' scores to reduce the effect of one unfair judge.

PythonRun it yourself
import numpy as np
from scipy import stats

# Meera's semester: grade points and credits per subject (invented)
grade_points = np.array([9, 8, 10, 7, 9])
credits = np.array([4, 4, 2, 3, 1])

by_hand = (grade_points * credits).sum() / credits.sum()
print("Weighted mean by hand:", round(by_hand, 2))
print("np.average(weights=): ", round(np.average(grade_points, weights=credits), 2))
print("Plain mean (wrong):   ", grade_points.mean())

judges = np.array([8.5, 9.0, 8.8, 9.1, 4.0, 8.9, 9.9])
print("Judges, mean:        ", round(judges.mean(), 2))
print("Judges, trimmed mean:", round(stats.trim_mean(judges, 0.15), 2))
Output
Weighted mean by hand: 8.43
np.average(weights=):  8.43
Plain mean (wrong):    8.6
Judges, mean:         8.31
Judges, trimmed mean: 8.86

By hand: 9x4 + 8x4 + 10x2 + 7x3 + 9x1 = 118 grade-point credits, divided by 14 credits, gives 8.43. The plain mean of 8.6 is wrong because it treats the 1-credit subject like the 4-credit ones. stats.trim_mean(judges, 0.15) cuts 15% of the values from each end; with 7 scores, 15% is 1.05, which is rounded down to one score from each end. That removes the unfair 4.0 and the 9.9, and the remaining five average 8.86.

Advanced: mean and median of grouped data

Sometimes you only have a frequency table with class intervals, not the raw values, as in many school textbook problems. You then estimate the centre:

Text
grouped mean   = Σ(f x m) / n        m = midpoint of each class, f = its frequency
grouped median = L + ((n/2 - cf) / f) x h
   L  = lower limit of the median class (the class where the running total passes n/2)
   cf = cumulative frequency before the median class
   f  = frequency of the median class,  h = class width
PythonRun it yourself
# Marks of 50 students in a class test, grouped into classes (invented)
classes = [(0, 10), (10, 20), (20, 30), (30, 40), (40, 50)]
freq = [4, 9, 16, 13, 8]

n = sum(freq)
mids = [(lo + hi) / 2 for lo, hi in classes]
mean = sum(m * f for m, f in zip(mids, freq)) / n
print("n =", n, " grouped mean =", mean)

cum = 0
for (lo, hi), f in zip(classes, freq):
    if cum + f >= n / 2:              # the median class
        h = hi - lo
        median = lo + (n / 2 - cum) / f * h
        print(f"Median class {lo}-{hi}: {lo} + ({n / 2} - {cum}) / {f} x {h} = {median}")
        break
    cum += f
Output
n = 50  grouped mean = 27.4
Median class 20-30: 20 + (25.0 - 13) / 16 x 10 = 27.5

The midpoints are 5, 15, 25, 35 and 45, so the mean is (4x5 + 9x15 + 16x25 + 13x35 + 8x45) / 50 = 1370 / 50 = 27.4. For the median, n/2 = 25 and the running total is 4, 13, then 29, so the 25th student is in the 20-30 class. The formula assumes the 16 students in that class are spread evenly across it. Both answers are estimates, because the exact marks inside each class are unknown.

Common mistakes

  • Forgetting to sort before taking the median. The middle of an unsorted list means nothing.
  • Using the mean for skewed data. One huge value can make the "average" describe nobody.
  • Averaging averages. The mean of two class averages is only the overall average if both classes have the same number of students. Otherwise use a weighted mean.
  • Expecting NumPy to have a mode. Use scipy.stats.mode(), pandas mode() or statistics.multimode().

Key points

  • Mean = Σx / n; median = middle of the sorted values; mode = most frequent value.
  • The mean is pulled by outliers and long tails; the median resists them.
  • Use the mode for categories and the median for ordinal or skewed data.
  • A weighted mean, Σ(w x x) / Σw, is needed when values count unequally, such as credits in a CGPA.
  • stats.trim_mean() drops a share of values from each end before averaging.

Exercise

In cricket, a batting average is total runs divided by the number of times the batter was out, not by the number of innings. Kabir's last 8 innings were 45, 12*, 67, 0, 23*, 88, 31, 9, where * means not out. Compute his batting average this way, the plain mean runs per innings, and the median runs per innings.

Show solution
Python
import numpy as np

runs = np.array([45, 12, 67, 0, 23, 88, 31, 9])
not_out = np.array([False, True, False, False, True, False, False, False])

dismissals = (~not_out).sum()
print("Times out:", dismissals)
print("Batting average:", round(runs.sum() / dismissals, 2))
print("Mean runs per innings:", runs.mean())
print("Median runs per innings:", np.median(runs))
Output
Times out: 6
Batting average: 45.83
Mean runs per innings: 34.375
Median runs per innings: 27.0

The total is 275 runs. Dividing by 6 dismissals gives 45.83, while dividing by 8 innings gives 34.375. ~not_out flips True and False, so summing it counts the innings in which he was out.

Quick Quiz

1. What is the median of 4, 9, 1, 7, 3?

2. A dataset of house prices has a mean of ₹85 lakh and a median of ₹60 lakh. What is the most likely reason?

3. Which measure of centre can you use for a column of favourite sports?

4. Subjects with 4, 3 and 1 credits have grade points 8, 6 and 10. What is the weighted mean?

Learn Statistics with a mentor

Free lessons get you started. Our Data Science & Machine Learning training adds real projects and code reviews from working developers.

  • Project-based learning
  • Mentor code reviews
  • Free counselling call