Урок курса
Перцентили и межквартильный размах
Практическая аналитика данныхВ предыдущем уроке медиана вычислялась как позиция в отсортированном ряду: при нечётном 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], а не гарантированную долю наблюдений.](https://latorn.ru/uploads/lesson-media/quartile_positions-1791341831321-08ee7640-a8d9-4022-9ae5-d25149c8593b.png)
Восемь значений условного примера. При 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, а не
- IQR = 900 − 300 =
- Номер позиции не изменился, но значение на ней стало другим, потому что 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(n0.25 AS INTEGER)+1
C: CAST((n-1)0.75 AS INTEGER)+1
D: CAST(n0.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
Сопоставьте каждое описание с понятием или значением, которое оно определяет.
Описания:
- Номер позиции в отсортированном ряду N значений, вычисляемый как floor((N-1)*p)+1.
- Разность между третьим и первым квартилями.
- Значение ряда на позиции floor((N-1)*0.25)+1 при нижнем порядковом соглашении.
- Соглашение, при котором 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 и как крайняя сумма может повлиять на эти меры. Затем выберите верное сравнение. Письменные выводы сверяете с теорией самостоятельно; платформа проверяет только выбранный вариант.
Продолжить с сохранением прогресса
Перейдите к уроку в учебном кабинете.
