Квантили и перцентили — что это и зачем аналитику
Коротко
Квантиль — значение, ниже которого лежит определённая доля данных. Перцентиль — квантиль, выраженный в процентах. Медиана — 50-й перцентиль, первый квартиль (Q1) — 25-й, третий квартиль (Q3) — 75-й. Аналитику квантили нужны для анализа распределений, определения выбросов, сегментации пользователей и SLA-метрик. На собеседованиях спрашивают про квартили, IQR и отличие от среднего.
Основные определения
Перцентиль P(k) — значение, ниже которого лежит k% данных.
- P(50) = медиана — делит данные пополам
- P(25) = первый квартиль Q1 — 25% данных ниже
- P(75) = третий квартиль Q3 — 75% данных ниже
- P(90) = 90-й перцентиль — 90% данных ниже
- P(99) = 99-й перцентиль — только 1% данных выше
Квантиль — общее понятие. Перцентиль — квантиль на 100 частей. Квартиль — на 4 части. Децильь — на 10 частей.
Данные: [10, 20, 30, 40, 50, 60, 70, 80, 90, 100]
Q1 (25-й перцентиль) = 27.5
Медиана (50-й перцентиль) = 55
Q3 (75-й перцентиль) = 82.5Визуализация
Q1 Медиана Q3
│ │ │
────┼────────┼────────┼────
25% 50% 75%
│←── IQR ──→│IQR (InterQuartile Range) = Q3 - Q1. Межквартильный размах — мера разброса данных, устойчивая к выбросам. Используется для определения выбросов: значения ниже Q1 - 1.5×IQR или выше Q3 + 1.5×IQR считаются выбросами (метод Тьюки).
Расчёт в Python
NumPy
import numpy as np
data = [15, 20, 35, 40, 50, 60, 70, 80, 85, 95]
np.percentile(data, 50) # 55.0 — медиана
np.percentile(data, 25) # 36.25 — Q1
np.percentile(data, 75) # 78.75 — Q3
np.percentile(data, 90) # 86.5 — P90
# Несколько перцентилей сразу
np.percentile(data, [25, 50, 75, 90, 99])
# Квантиль (от 0 до 1)
np.quantile(data, 0.5) # 55.0 — медиана
np.quantile(data, 0.25) # 36.25 — Q1Pandas
import pandas as pd
df = pd.DataFrame({
'user_id': range(1, 11),
'revenue': [100, 200, 500, 800, 1200, 2000, 3500, 5000, 8000, 50000]
})
# Один перцентиль
df['revenue'].quantile(0.5) # медиана
df['revenue'].quantile(0.90) # P90
# Несколько
df['revenue'].quantile([0.25, 0.5, 0.75, 0.9])
# describe() показывает квартили
df['revenue'].describe()
# count 10.0
# mean 7130.0
# std 15000+
# min 100
# 25% 575
# 50% 1600
# 75% 4625
# max 50000Перцентили по группам
# P90 выручки по сегментам
df.groupby('segment')['revenue'].quantile(0.9)
# Несколько перцентилей по группам
df.groupby('segment')['revenue'].describe(percentiles=[.25, .5, .75, .9, .95])Расчёт в SQL
PostgreSQL
-- Медиана
SELECT PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY revenue) AS median
FROM orders;
-- Несколько перцентилей
SELECT
PERCENTILE_CONT(0.25) WITHIN GROUP (ORDER BY revenue) AS q1,
PERCENTILE_CONT(0.50) WITHIN GROUP (ORDER BY revenue) AS median,
PERCENTILE_CONT(0.75) WITHIN GROUP (ORDER BY revenue) AS q3,
PERCENTILE_CONT(0.90) WITHIN GROUP (ORDER BY revenue) AS p90,
PERCENTILE_CONT(0.99) WITHIN GROUP (ORDER BY revenue) AS p99
FROM orders;
-- Перцентили по группам
SELECT
segment,
PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY revenue) AS median_revenue
FROM users
GROUP BY segment;PERCENTILE_CONT vs PERCENTILE_DISC
-- PERCENTILE_CONT — интерполирует (может вернуть значение между строками)
SELECT PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY val) FROM (VALUES (10), (20)) t(val);
-- 15.0
-- PERCENTILE_DISC — возвращает ближайшее реальное значение
SELECT PERCENTILE_DISC(0.5) WITHIN GROUP (ORDER BY val) FROM (VALUES (10), (20)) t(val);
-- 10Через оконные функции
-- NTILE: разбить на N равных групп
SELECT
user_id,
revenue,
NTILE(4) OVER (ORDER BY revenue) AS quartile
FROM users;
-- PERCENT_RANK: перцентильный ранг каждой строки
SELECT
user_id,
revenue,
ROUND(PERCENT_RANK() OVER (ORDER BY revenue) * 100, 1) AS percentile
FROM users;Применение в аналитике
SLA и время ответа
response_times = [50, 80, 100, 120, 150, 200, 250, 500, 800, 2000]
p50 = np.percentile(response_times, 50) # 175 мс
p90 = np.percentile(response_times, 90) # 1160 мс
p99 = np.percentile(response_times, 99) # 1892 мс
print(f'P50: {p50} мс, P90: {p90} мс, P99: {p99} мс')P50, P90, P99 — стандартные метрики для SLA. «P99 < 500 мс» означает: 99% запросов обрабатываются менее чем за 500 мс. Среднее время не подходит — один запрос на 10 секунд исказит картину.
Сегментация по выручке
SELECT
user_id,
total_revenue,
CASE
WHEN total_revenue >= PERCENTILE_CONT(0.9) WITHIN GROUP (ORDER BY total_revenue) OVER ()
THEN 'top_10%'
WHEN total_revenue >= PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY total_revenue) OVER ()
THEN 'top_50%'
ELSE 'bottom_50%'
END AS segment
FROM user_revenue;Или через NTILE:
SELECT
user_id,
total_revenue,
NTILE(10) OVER (ORDER BY total_revenue DESC) AS decile
FROM user_revenue;Первый дециль — топ-10% пользователей по выручке.
Определение выбросов через IQR
q1 = df['revenue'].quantile(0.25)
q3 = df['revenue'].quantile(0.75)
iqr = q3 - q1
lower = q1 - 1.5 * iqr
upper = q3 + 1.5 * iqr
outliers = df[(df['revenue'] < lower) | (df['revenue'] > upper)]
print(f'Выбросов: {len(outliers)}')Box plot (ящик с усами)
import matplotlib.pyplot as plt
df['revenue'].plot(kind='box')
plt.title('Распределение выручки')
plt.show()Box plot визуализирует квартили: коробка от Q1 до Q3, линия внутри — медиана, усы — до 1.5×IQR, точки за усами — выбросы.
Перцентиль vs среднее
| Метрика | Среднее | Медиана (P50) | P90 |
|---|---|---|---|
| Устойчивость к выбросам | Нет | Да | Да |
| Описывает | «Среднее по больнице» | Типичное значение | Верхний хвост |
| Когда использовать | Нормальное распределение | Скошенное распределение | SLA, worst-case |
Подробнее — в сравнении медианы и среднего.
Типичные ошибки
Путают перцентиль и процентиль. Это одно и то же (percentile). «90-й перцентиль» = «90-й процентиль».
Среднее вместо перцентилей для скошенных данных. Если распределение выручки: 80% пользователей платят 0–500₽, 5% — больше 50 000₽, среднее покажет 7000₽. Медиана — 200₽. Медиана точнее описывает типичного пользователя.
Путают «90-й перцентиль» и «топ-10%». P90 = значение, ниже которого 90% данных. Топ-10% — это строки выше P90. Числово P90 — граница, а не группа.
Вопросы с собеседований
-- Что такое медиана и чем она отличается от среднего? -- Медиана — 50-й перцентиль, значение, делящее данные пополам. Устойчива к выбросам. Среднее чувствительно: один экстремум сильно сдвигает среднее, но не медиану.
-- Что такое IQR и как через него определить выбросы? -- IQR = Q3 - Q1. Выбросы — значения ниже Q1 - 1.5×IQR или выше Q3 + 1.5×IQR. Метод Тьюки, используется в box plot.
-- Зачем нужен P99 для SLA? -- P99 показывает, что 99% запросов укладываются в порог. Среднее время ответа может быть 100 мс, но P99 — 5 секунд. Один медленный запрос из ста — проблема для пользователей.
-- Как посчитать медиану в SQL?
-- PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY col) — PostgreSQL. В MySQL нет встроенной функции, нужен подзапрос или оконная функция.
Потренируйтесь решать задачи — откройте тренажёр с 1500+ вопросами для подготовки к собеседованиям аналитиков.
FAQ
Квартили, децили, перцентили — в чём разница?
Всё это квантили, разница — в количестве частей. Квартили делят на 4 (Q1, Q2, Q3). Децили — на 10 (D1–D9). Перцентили — на 100 (P1–P99). Q1 = P25, Q2 = P50 = медиана, Q3 = P75.
Как выбрать между PERCENTILE_CONT и PERCENTILE_DISC?
CONT (continuous) — интерполирует между значениями, подходит для непрерывных данных (выручка, время). DISC (discrete) — возвращает ближайшее реальное значение, подходит для дискретных данных (оценки, количество).
Перцентили в ClickHouse?
quantile(0.5)(revenue) — медиана. quantiles(0.25, 0.5, 0.75)(revenue) — несколько квантилей. quantileExact — точный расчёт (медленнее).
Как тренироваться
Квантили — базовая тема статистики для аналитика. Задачи на статистику — в тренажёре Карьерник. Больше вопросов — в разделе с примерами.