Квантили и перцентили — что это и зачем аналитику

Коротко

Квантиль — значение, ниже которого лежит определённая доля данных. Перцентиль — квантиль, выраженный в процентах. Медиана — 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 — Q1

Pandas

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 — точный расчёт (медленнее).

Как тренироваться

Квантили — базовая тема статистики для аналитика. Задачи на статистику — в тренажёре Карьерник. Больше вопросов — в разделе с примерами.