Среднее, медиана и перцентили: что когда брать
«Средняя зарплата в компании 202 тысячи. Хорошая ли это метрика?» — простой на вид вопрос с двойным дном.
Медиана — значение, которое делит упорядоченный ряд пополам: половина наблюдений меньше, половина больше. В отличие от среднего, она не реагирует на величину крайних значений.
Вопрос 1. Когда среднее врёт, а медиана — нет?
Что проверяет интервьюер. Не формулу, а чувство данных. Аналитик, который автоматически ставит AVG в каждый дашборд, однажды покажет бизнесу «средний чек 48 тысяч» при том, что 90% клиентов платят меньше 20.
import statistics
salaries = [70, 75, 80, 85, 90, 95, 100, 110, 120, 1200]
print("Среднее:", statistics.mean(salaries))
print("Медиана:", statistics.median(salaries))
print("Среднее без топ-1:", round(statistics.mean(salaries[:-1]), 1))
Вывод:
Среднее: 202.5
Медиана: 92.5
Среднее без топ-1: 91.7
Девять человек из десяти получают меньше «средней зарплаты». Одна зарплата основателя сдвинула среднее в два с лишним раза, а медиана осталась там, где живёт коллектив. Формально это свойство называется устойчивостью (робастностью): чтобы сломать медиану, нужно испортить половину данных, чтобы сломать среднее — достаточно одного значения.
| Ситуация | Что брать |
| Скошенное распределение: чеки, зарплаты, время на сайте, LTV | медиана (и перцентили) |
| Симметричные данные без тяжёлых хвостов | среднее — оно точнее и стабильнее |
| Нужно, чтобы метрика складывалась: выручка = средний чек × число чеков | среднее — медиана так не работает |
| Категории: любимый способ оплаты, топ-браузер | мода |
| SLA, скорость ответа, время загрузки | перцентили p95 / p99 |
Последняя строка важна отдельно. Среднее время ответа сервиса 200 мс звучит отлично, но если p99 равен 8 секундам, то каждый сотый пользователь ждёт восемь секунд — и именно он пишет в поддержку. Поэтому в мониторинге живут перцентили, а не среднее.
Вопрос 2. Что такое перцентили и как их читать?
p-й перцентиль — значение, ниже которого лежит p процентов наблюдений. Медиана — это p50. Квартили режут выборку на четыре части: Q1 = p25, Q2 = p50, Q3 = p75.
import statistics
times = [120, 130, 140, 150, 160, 170, 180, 200, 230, 260,
280, 300, 340, 400, 460, 520, 700, 1200, 2400, 8000]
q1, med, q3 = statistics.quantiles(times, n=4)
p90, p95 = statistics.quantiles(times, n=20)[17], statistics.quantiles(times, n=20)[18]
print("Среднее:", statistics.mean(times), "мс")
print("Медиана (p50):", med, "мс")
print("Q1 / Q3:", q1, "/", q3, "мс")
print("p90:", p90, "| p95:", p95, "мс")
Вывод:
Среднее: 817 мс
Медиана (p50): 270.0 мс
Q1 / Q3: 162.5 / 505.0 мс
p90: 2280.0 | p95: 7720.0 мс
Половина запросов укладывается в 270 мс, но каждый двадцатый ждёт около восьми секунд. Среднее в 817 мс не описывает ни одну из этих двух реальностей — оно висит в пустоте между ними.
Вопрос 3. Как понять, что распределение скошено, не рисуя график?
Простой практический признак: сравнить среднее и медиану.
- Среднее заметно больше медианы — правый хвост, «длинный хвост» больших значений. Так устроены почти все денежные метрики.
- Среднее меньше медианы — левый хвост. Встречается реже: например, оценки, упирающиеся в потолок в 5 баллов.
- Среднее ≈ медиана — распределение примерно симметрично.
И отдельно — предупреждение о бимодальности. Если у распределения два пика (например, бесплатные пользователи со средним чеком 0 и платные со средним 5000), то ни среднее, ни медиана не описывают выборку: они показывают точку, в которой не находится почти никто. Правильный ход — разделить сегменты и считать метрику отдельно в каждом.
Вопрос 4. Можно ли усреднить средние по регионам, чтобы получить среднее по стране?
Нет — если размеры регионов различаются. Это подвох, который любят добавлять в конце.
regions = [
("Москва", 1000, 4200), # (регион, число заказов, средний чек)
("Казань", 120, 1800),
("Пермь", 80, 1500),
]
naive = sum(avg for _, _, avg in regions) / len(regions)
total_orders = sum(n for _, n, _ in regions)
total_revenue = sum(n * avg for _, n, avg in regions)
weighted = total_revenue / total_orders
print("Среднее по средним:", round(naive, 1))
print("Взвешенное среднее:", round(weighted, 1))
Вывод:
Среднее по средним: 2500.0
Взвешенное среднее: 3780.0
Разница почти в полтора раза: наивный расчёт дал Перми с восемьюдесятью заказами тот же вес, что и Москве с тысячей. Правило простое: среднее считают из суммы и количества, а не из чужих средних. Если исходные данные недоступны, нужно взвешенное среднее с весами-объёмами.
Типичные ошибки кандидатов
- Усредняют средние по регионам или по дням, не взвешивая их на объём.
- Отвечают «медиана лучше среднего» без оговорок. Не лучше — устойчивее; у среднего есть свойство аддитивности, которого у медианы нет.
- Не могут объяснить, почему нельзя посчитать выручку как «медианный чек × число чеков».
- Забывают про перцентили, хотя именно они нужны для метрик скорости и SLA.
- Считают среднее по бимодальной выборке и делают вывод о «типичном пользователе».
- Путают моду с самым большим значением.
Как ответить кратко
«Среднее чувствительно к крайним значениям — одного крупного клиента хватит, чтобы сдвинуть его в разы. Медиана устойчива: чтобы её сломать, нужно испортить половину данных. Поэтому для денежных и временных метрик — чеков, зарплат, LTV, времени ответа — я беру медиану и перцентили, а среднее оставляю там, где распределение симметрично или где метрика должна складываться: выручка = средний чек × число чеков, с медианой так не получится. Быстрый признак скошенности — среднее заметно больше медианы. И если у распределения два пика, я не усредняю вообще, а разделяю сегменты».