Урок курса

Перцентили и межквартильный размах

Практическая аналитика данных

В предыдущем уроке медиана вычислялась как позиция в отсортированном ряду: при нечётном N брался средний элемент, при чётном — среднее двух центральных. Перцентиль обобщает эту идею на произвольную долю p, но формула выбора позиции становится явной и единой для любого p.

Перцентиль как позиция в упорядоченном ряду: формула k = floor((N-1)*p)+1

Перцентиль уровня p — это конкретное значение из отсортированного ряда N непустых наблюдений. При нижнем порядковом соглашении оно определяется одной формулой:

k = floor((N-1) * p) + 1

Здесь k — порядковый номер нужного элемента в ряду от 1 до N. Никакой интерполяции между соседними значениями нет: результат всегда существующее наблюдение.

Разберём на конкретных числах. Возьмём 8 paid-заказов с gross_revenue (руб.): 300, 1200, 800, 500, 2000, 150, 950, 600. После сортировки по возрастанию ряд выглядит так:

Позиция: 1    2    3    4    5    6     7     8
Значение: 150  300  500  600  800  950  1200  2000

Для P25 (p = 0.25):

k = floor((8-1) * 0.25) + 1
  = floor(7 * 0.25) + 1
  = floor(1.75) + 1
  = 1 + 1
  = 2

Второй элемент ряда — 300 руб. Это и есть P25.

Для P75 (p = 0.75):

k = floor((8-1) * 0.75) + 1
  = floor(7 * 0.75) + 1
  = floor(5.25) + 1
  = 5 + 1
  = 6

Шестой элемент ряда — 950 руб. Это P75.

Важно понимать, что формула не обещает «ровно 25% наблюдений ниже P25». Она просто указывает на конкретную позицию. При небольшом N и повторяющихся значениях фактическая доля наблюдений по обе стороны границы будет отличаться от точного p×100%. Результат всегда детерминирован и воспроизводим, если N и данные те же.

Восемь значений условного примера. При lower-соглашении P25 — позиция 2, P75 — позиция 6; IQR = 950 − 300 = 650 руб. Цветная полоса обозначает диапазон [P25, P75], а не гарантированную долю наблюдений.

Восемь значений условного примера. При lower-соглашении P25 — позиция 2, P75 — позиция 6; IQR = 950 − 300 = 650 руб. Цветная полоса обозначает диапазон [P25, P75], а не гарантированную долю наблюдений.

Нижнее порядковое соглашение, линейная интерполяция и медиана при чётном N

Формула k = floor((N-1)*p)+1 — это одно из нескольких соглашений о том, что считать перцентилем. Разница между ними проявляется именно тогда, когда вычисленная позиция не целая.

Нижнее порядковое (lower). Позиция k всегда округляется вниз через floor, результат — существующее наблюдение из ряда. Именно этот способ используется в курсе для P25 и P75.

Линейная интерполяция (linear) — дефолт pandas. Если позиция попадает между двумя элементами, берётся их взвешенная смесь. Например, при N=8 и p=0.25 позиция равна 1 + 7×0.25 = 2.75, и pandas по умолчанию вернёт 0.25×300 + 0.75×500 = 450.0 — число, которого нет в исходных данных. Это не ошибка linear-метода, а другое соглашение с другой семантикой.

Оба способа корректны, но давать разные числа для одних и тех же данных — их нормальное поведение. Именно поэтому важно явно фиксировать соглашение.

Связь с медианой при чётном N. Стандартная медиана при чётном N усредняет два центральных элемента — это поведение, разобранное в предыдущем уроке. Если попробовать вычислить «медиану» через нижнее порядковое соглашение при p=0.5, результат будет другим.

Пример: N=6, отсортированный ряд — a₁, a₂, a₃, a₄, a₅, a₆.

Нижний перцентиль p=0.5:
k = floor((6-1) * 0.5) + 1
  = floor(2.5) + 1
  = 2 + 1
  = 3

Лower-метод берёт только третий элемент a₃. Стандартная медиана берёт (a₃ + a₄) / 2. Если a₃ ≠ a₄, числа не совпадут.

Это не противоречие и не ошибка — это два разных определения. В данном курсе медиана вычисляется методом из предыдущего урока (среднее двух центральных при чётном N), а нижнее порядковое соглашение применяется исключительно для P25 и P75.

Вычисление P25, P75 и IQR в SQL и pandas: синтаксис и проверка совпадения

Оба инструмента реализуют одно и то же нижнее порядковое соглашение, но разными средствами. Сначала убедимся в совпадении на малом примере с известным ответом, а затем применим те же запросы к полным данным заказов.

Шаг 1. Проверка на восьми значениях

Возьмём те же 8 значений gross_revenue, что разбирались в первом разделе: 150, 300, 500, 600, 800, 950, 1200, 2000. Ожидаемый результат (из формулы, разобранной выше): P25=300, P75=950, IQR=650.

SQL-маршрут: VALUES-CTE

Чтобы не затрагивать таблицу orders, создадим данные прямо в запросе через VALUES:

WITH source(gross_revenue) AS (
    VALUES (150),(300),(500),(600),(800),(950),(1200),(2000)
),
ranked AS (
    SELECT
        gross_revenue,
        ROW_NUMBER() OVER (ORDER BY gross_revenue) AS rn,
        COUNT(*)    OVER ()                        AS n
    FROM source
)
SELECT
    MAX(CASE WHEN rn = CAST((n - 1) * 0.25 AS INTEGER) + 1
             THEN gross_revenue END) AS p25,
    MAX(CASE WHEN rn = CAST((n - 1) * 0.75 AS INTEGER) + 1
             THEN gross_revenue END) AS p75,
    MAX(CASE WHEN rn = CAST((n - 1) * 0.75 AS INTEGER) + 1
             THEN gross_revenue END)
    - MAX(CASE WHEN rn = CAST((n - 1) * 0.25 AS INTEGER) + 1
               THEN gross_revenue END)             AS iqr
FROM ranked;

Ключевой момент: CAST(... AS INTEGER) в SQLite усекает дробную часть к нулю, а не округляет. CAST(1.75 AS INTEGER) возвращает 1, а не 2. Для положительных чисел это совпадает с floor, поэтому формула корректна. Убедиться в поведении CAST можно одной строкой:

SELECT CAST(1.75 AS INTEGER);  -- 1
SELECT CAST(5.25 AS INTEGER);  -- 5

Трассировка для N=8: CAST(7 * 0.25 AS INTEGER) + 1 = 1 + 1 = 2 → rn=2 → 300; CAST(7 * 0.75 AS INTEGER) + 1 = 5 + 1 = 6 → rn=6 → 950. IQR = 950 − 300 = 650.

Результат запроса:

p25   p75   iqr
300   950   650

pandas-маршрут: pd.Series

Аналогично создаём данные явно, без чтения файла:

import pandas as pd

rev_toy = pd.Series([150, 300, 500, 600, 800, 950, 1200, 2000])

p25 = rev_toy.quantile(0.25, interpolation='lower')
p75 = rev_toy.quantile(0.75, interpolation='lower')
iqr = p75 - p25

print(p25, p75, iqr)  # 300.0  950.0  650.0

Оба маршрута дают идентичный результат: P25=300, P75=950, IQR=650. Расхождений нет, потому что оба реализуют одну формулу k = floor((N-1)*p)+1 — SQL через CAST с усечением, pandas через interpolation='lower'.

Частая ошибка: забыть про interpolation='lower' в pandas. Без него при N=8 и p=0.25 вместо 300 вернётся 450.0 — взвешенная смесь 300 и 500. Это не баг, а другое соглашение, которое не совпадает с SQL-вариантом.


Шаг 2. Те же маршруты на полной таблице orders

Теперь применяем ту же структуру запроса к реальным данным. В таблице orders читаются только paid-заказы с непустым gross_revenue:

SQL:

WITH ranked AS (
    SELECT
        gross_revenue,
        ROW_NUMBER() OVER (ORDER BY gross_revenue) AS rn,
        COUNT(*)    OVER ()                        AS n
    FROM orders
    WHERE status = 'paid'
      AND gross_revenue IS NOT NULL
)
SELECT
    MAX(CASE WHEN rn = CAST((n - 1) * 0.25 AS INTEGER) + 1
             THEN gross_revenue END) AS p25,
    MAX(CASE WHEN rn = CAST((n - 1) * 0.75 AS INTEGER) + 1
             THEN gross_revenue END) AS p75,
    MAX(CASE WHEN rn = CAST((n - 1) * 0.75 AS INTEGER) + 1
             THEN gross_revenue END)
    - MAX(CASE WHEN rn = CAST((n - 1) * 0.25 AS INTEGER) + 1
               THEN gross_revenue END)             AS iqr
FROM ranked;

pandas:

import pandas as pd

df = pd.read_csv('orders.csv')

rev = df.loc[df['status'] == 'paid', 'gross_revenue'].dropna()

p25 = rev.quantile(0.25, interpolation='lower')
p75 = rev.quantile(0.75, interpolation='lower')
iqr = p75 - p25

print(p25, p75, iqr)

Результат на полном наборе данных (N=2731 paid-заказов с непустым gross_revenue):

p25      p75      iqr
3000.0   13250.0  10250.0

Оба маршрута снова совпадают. Заметьте, что числа принципиально отличаются от значений игрушечного примера: реальное распределение шире и смещено вправо относительно набора из 8 точек. Именно поэтому маршруты нельзя проверять, запуская полный SQL против таблицы orders и ожидая вывода 300/950/650.

IQR как устойчивая к выбросам мера разброса и её ограничения

IQR = P75 − P25 описывает разброс в центральной части ряда — той, которая не подвержена сильному влиянию крайних значений. По сравнению с размахом max − min, IQR менее чувствителен к экстремальным наблюдениям, но это не означает полной нечувствительности.

Одно из достаточных условий сохранения IQR при замене крайнего значения: N остаётся прежним и значения на квартильных позициях в отсортированном ряду не меняются. Это именно достаточное условие, а не единственный сценарий: IQR может сохраниться и тогда, когда обе границы сдвигаются на одинаковую величину — P75_new − P25_new = P75_old − P25_old.

Разберём три примера на ряду из 8 значений (150, 300, 500, 600, 800, 950, 1200, 2000). Позиции P25 = rn 2 и P75 = rn 6.

Пример 1: замена максимума, которая сохраняет IQR (квартильные значения не меняются). Заменим 2000 на 50 000 руб. После сортировки: 150, 300, 500, 600, 800, 950, 1200, 50 000 — второй элемент (300) и шестой (950) остались прежними. IQR = 950 − 300 = 650. Здесь 50 000 остаётся правее P75, квартильные позиции не затронуты.

Пример 2: замена максимума, которая меняет IQR. Заменим 2000 на 900 руб. После сортировки: 150, 300, 500, 600, 800, 900, 950, 1200. Шестой элемент теперь 900, а не

  1. IQR = 900 − 300 =
  2. Номер позиции не изменился, но значение на ней стало другим, потому что 900 оказалось ниже прежнего P75 = 950.

Пример 3: замена максимума, которая сохраняет IQR при изменении обеих квартильных границ. Заменим 2000 на 150 руб. Новый ряд после сортировки: 150, 150, 300, 500, 600, 800, 950, 1200. Второй элемент теперь 150 (P25 изменился с 300 на 150), шестой элемент — 800 (P75 изменился с 950 на 800). Однако IQR = 800 − 150 = 650 — столько же, сколько в исходном ряду. Обе границы сдвинулись на одинаковую величину (−150), и их разность сохранилась.

Этот контрпример показывает: неизменность значений на квартильных позициях — лишь один из путей к сохранению IQR, но не единственный. Поэтому правильная формулировка: IQR менее чувствителен к крайним величинам, чем размах, но не гарантированно инвариантен к любым изменениям данных за пределами квартильных позиций.

Если добавить новое наблюдение, N меняется, и позиции квартилей пересчитываются. Добавление значения 1 000 000 к исходному ряду даёт N = 9:

P25: k = floor(8 * 0.25) + 1 = floor(2) + 1 = 3 → 500
P75: k = floor(8 * 0.75) + 1 = floor(6) + 1 = 7 → 1200
IQR = 1200 − 500 = 700

IQR вырос с 650 до 700 — добавление крайнего значения сдвинуло квартильные позиции.


Посмотрим на реальные данные «Маяка». Для января 2024 года (82 paid-заказа):

P25 = 2 400 руб.
P75 = 16 100 руб.
IQR = 13 700 руб.
min = 150 руб.
max = 49 000 руб.

Размах max − min = 48 850 руб. — число определяется двумя крайними заказами и резко изменится, если появится один заказ на 200 000 руб. IQR в 13 700 руб. более стабилен: типичные заказы укладываются в диапазон примерно от 2,4 до 16,1 тыс. рублей, и этот ориентир меньше зависит от единичных аномалий.


Ограничение: фактические доли наблюдений не гарантированы.

Нижнее порядковое соглашение не обещает, что ровно 25% наблюдений окажутся строго ниже P25, а ровно 50% — внутри включённого интервала [P25, P75]. Доля зависит от N и от повторяющихся значений.

На малом примере (N=8, P25=300, P75=950): строго ниже 300 находится одно значение (150) — это 1/8 = 12,5%, а не 25%. Внутри включённого интервала [300, 950] пять из восьми заказов — 62,5%, а не 50%.

Для января 2024 (N=82) нижняя квартильная граница 2400 встречается в двух заказах. Несмотря на это, фактическая доля наблюдений внутри включённого интервала [2400, 16100] составляет 41 из 82 = 50% — это результат конкретного распределения, а не гарантия метода.

При массовых повторах ситуация может быть радикальной даже при большом N. Если 1000 заказов имеют одинаковую сумму, например 100 руб., то P25 = P75 = 100, строго ниже каждой границы — 0 наблюдений, а внутри включённого интервала [100, 100] — все 100%. Большой N сам по себе не решает проблему: он уменьшает дискретность рангов, но не устраняет эффект массовых совпадений на квартильных позициях.

Практический вывод: при наличии существенной массы повторяющихся значений на квартильных границах рекомендуется отдельно измерять фактическую долю наблюдений ниже каждой границы и внутри включённого интервала — независимо от размера выборки.

IQR остаётся полезным ориентиром: он показывает, где сосредоточена центральная часть распределения gross_revenue, и позволяет сравнивать разброс между месяцами без доминирующего влияния единичных аномалий.

Задача 1

Дан отсортированный ряд из 8 значений gross_revenue (руб.): 150, 300, 500, 600, 800, 950, 1200, 2000. Используя нижнее порядковое соглашение (k = floor((N-1)*p)+1), вычислите P25. Введите ответ целым числом.

Без регистрации · результат не сохраняется

Задача 2

Ряд из 8 значений: 150, 300, 500, 600, 800, 950, 1200, 2000. Используя нижнее порядковое соглашение, вычислите IQR = P75 − P25. Введите ответ целым числом.

Без регистрации · результат не сохраняется

Задача 3

Для набора из 6 значений: 200, 400, 600, 800, 1000, 1200 (уже отсортированы) аналитик вычислил «медиану по нижнему порядковому соглашению» как перцентиль p=0.5 по формуле k = floor((N-1)*0.5)+1 и получил значение 600. Стандартная медиана для этих же данных равна 700. Почему результаты различаются?

Выберите один вариант
Без регистрации · результат не сохраняется

Задача 4

В SQL-запросе для вычисления P25 и P75 нужно заполнить два пропуска — выражения для позиций квартилей. Доступны четыре варианта выражений:

A: CAST((n-1)0.25 AS INTEGER)+1
B: CAST(n
0.25 AS INTEGER)+1
C: CAST((n-1)0.75 AS INTEGER)+1
D: CAST(n
0.75 AS INTEGER)+1

где n — общее число строк в выборке, rn — порядковый номер строки.

Запрос:
WITH ranked AS (
SELECT gross_revenue,
ROW_NUMBER() OVER (ORDER BY gross_revenue) AS rn,
COUNT(*) OVER () AS n
FROM orders
WHERE status = 'paid' AND gross_revenue IS NOT NULL
)
SELECT
MAX(CASE WHEN rn = (1) THEN gross_revenue END) AS p25,
MAX(CASE WHEN rn = (2) THEN gross_revenue END) AS p75
FROM ranked;

Укажите метки для пропусков (1) и (2) в формате «X / Y», например «B / D».

Без регистрации · результат не сохраняется

Задача 5

Вычислите P25, P75 и IQR суммы заказов (gross_revenue) для paid-заказов магазина за июнь 2024 года (с 2024-06-01 включительно по 2024-07-01 исключительно) с известным gross_revenue. Используйте нижнее порядковое соглашение (interpolation='lower' в pandas или CAST((N-1)*p AS INTEGER)+1 в SQL).

Введите три числа в рублях без разделителей тысяч в формате: P25;P75;IQR

Пример формата (числа условные): 1000;5000;4000

Без регистрации · результат не сохраняется

Задача 6

Сопоставьте каждое описание с понятием или значением, которое оно определяет.

Описания:

  1. Номер позиции в отсортированном ряду N значений, вычисляемый как floor((N-1)*p)+1.
  2. Разность между третьим и первым квартилями.
  3. Значение ряда на позиции floor((N-1)*0.25)+1 при нижнем порядковом соглашении.
  4. Соглашение, при котором pandas берёт взвешенную смесь двух соседних значений, и результат может не совпадать ни с одним наблюдением.

Понятия:
A. P25
B. IQR
C. k — целевая позиция перцентиля
D. Линейная интерполяция (interpolation='linear')

Запишите ответ в формате «1-X, 2-X, 3-X, 4-X», где X — буква понятия. Например: «1-B, 2-A, 3-D, 4-C» (это не правильный ответ, только пример формата).

Без регистрации · результат не сохраняется

Задача 7

В отдельном условном примере для paid-заказов P25 = 420 руб., P75 = 1850 руб., IQR = 1430 руб. Самостоятельно напишите для руководителя два вывода по 2–3 предложения: (1) что эти значения говорят о центральном разбросе; (2) чем IQR отличается от max−min и как крайняя сумма может повлиять на эти меры. Затем выберите верное сравнение. Письменные выводы сверяете с теорией самостоятельно; платформа проверяет только выбранный вариант.

Выберите один вариант
Без регистрации · результат не сохраняется

Продолжить с сохранением прогресса

Перейдите к уроку в учебном кабинете.

Перейти к интерактивному уроку