Урок курса
Абсолютное и относительное изменение: проценты и процентные пункты
Практическая аналитика данныхВ предыдущем уроке вы получили два числа: 82 оплаченных заказа в январе 2024 и 89 — в феврале. Сами по себе эти цифры мало что говорят руководителю: разница в 7 заказов выглядит иначе, если база была 10, и совсем иначе, если база была 1000. Чтобы из двух чисел получить аналитическое утверждение, нужно ответить на два разных вопроса — на сколько единиц изменился показатель и на сколько процентов от исходного уровня.
Абсолютное и относительное изменение: формулы и единицы
Любое сравнение двух периодов даёт два принципиально разных ответа, и оба нужны.
Абсолютное изменение — это просто разность:
Δ = значение_текущего − значение_базового
Результат выражается в тех же единицах, что и сам показатель. Если считаем заказы — получаем заказы. Если считаем рубли — получаем рубли. Для оплаченных заказов «Маяка» за январь и февраль 2024:
Δ = 89 − 82 = +7 заказов
Знак показывает направление: плюс — рост, минус — падение. Абсолютное изменение определено всегда, в том числе когда базовое значение равно нулю.
Относительное изменение (темп прироста) отвечает на вопрос «насколько это много относительно того, с чего начинали»:
Δ% = (значение_текущего − значение_базового) / значение_базового × 100
Знаменатель здесь строго базовый период — более ранний. Это принципиально: если поставить в знаменатель текущий период, смысл формулы изменится, и результат станет другим числом. Для тех же данных:
Δ% = (89 − 82) / 82 × 100 ≈ +8,54%
Результат безразмерен и выражается в процентах вне зависимости от того, в чём измерялся исходный показатель — в заказах, рублях или минутах.
Важное ограничение: относительное изменение определено только при ненулевой базе. Если базовое значение равно нулю, делить не на что — формула неприменима. В таком случае можно сообщить абсолютное изменение, но процент прироста остаётся неопределённым. Для неотрицательных счётчиков (заказы, клиенты) это означает: база должна быть строго положительной.
Два этих числа вместе дают полную картину: абсолютное изменение говорит о масштабе в реальных единицах бизнеса, относительное — о темпе роста, который можно сравнить между показателями разного масштаба.
Процент vs. процентный пункт: правило и диагностика ошибки
Правило выбора единицы для абсолютного изменения зависит от природы самого показателя.
Если показатель — натуральная величина (заказы, рубли, клиенты), его абсолютное изменение выражается в тех же единицах: «+7 заказов», «−15 000 рублей». Относительное изменение — в процентах.
Если показатель сам измеряется в процентах (доля, конверсия, ставка), ситуация другая. Абсолютная разница между двумя процентными значениями выражается в процентных пунктах (п. п.), а не в процентах. Процент зарезервирован для относительного изменения этого показателя.
Разберём на конкретном примере. Доля отменённых заказов выросла с 10% до 13%.
- Абсолютное изменение: 13% − 10% = +3 п. п.
- Относительное изменение: 3 / 10 × 100 = +30%
Это два разных числа, и они отвечают на разные вопросы. Если написать «доля отмен выросла на 3%» — это технически означает 3% от 10%, то есть прирост 0,3 п. п. Именно так читает эту фразу статистически грамотный получатель. Аналитик при этом имел в виду совсем другое.
Диагностика: посмотрите на единицу измерения показателя. Если в строке стоит «%» — абсолютная разница пишется в «п. п.». Если «%» нет — разница пишется в исходных единицах.
Корректная формулировка для примера выше:
«Доля отменённых заказов выросла на 3 п. п. — с 10% до 13%, что соответствует относительному росту на 30%.»
Для числа оплаченных заказов:
«Число оплаченных заказов выросло на 7 (≈ +8,54%).»
Оба варианта: сначала абсолютное изменение с правильной единицей, затем относительное в скобках — стандартная структура аналитического вывода.

Разница между 10% и 13% — 3 процентных пункта. Относительно прежних 10% это рост на 30%. Условный пример из теории.
Вычисление изменения в SQL: CTE и вещественное деление
Чтобы получить все четыре столбца результата в одном запросе, нужно свести данные двух периодов в одну строку. В SQLite удобнее всего сделать это через два CTE с последующим соединением.
WITH base AS (
SELECT COUNT(*) AS orders
FROM orders
WHERE status = 'paid'
AND order_date >= '2024-01-01'
AND order_date < '2024-02-01'
),
curr AS (
SELECT COUNT(*) AS orders
FROM orders
WHERE status = 'paid'
AND order_date >= '2024-02-01'
AND order_date < '2024-03-01'
)
SELECT
base.orders AS jan_orders,
curr.orders AS feb_orders,
curr.orders - base.orders AS abs_change,
ROUND((curr.orders - base.orders) * 100.0 / base.orders, 2) AS rel_change_pct
FROM base, curr;
Каждый CTE (base и curr) считает COUNT(*) по фильтру status = 'paid' и диапазону дат своего месяца. Диапазоны заданы через >= нижней границы и < первого дня следующего месяца — это стандартный способ выбрать полный календарный месяц без риска захватить лишнее.
Строка FROM base, curr — декартово произведение двух таблиц по одной строке в каждой. Никакого ON-условия не нужно: результат всегда ровно одна строка с полями обоих CTE.
В расчёте rel_change_pct критично умножение на 100.0, а не на 100. В SQLite операторы * и / имеют одинаковый приоритет и выполняются слева направо. Если написать * 100 / base.orders, вычисление идёт так: 7 * 100 = 700, затем целочисленное 700 / 82 = 8 — дробная часть теряется, и вместо 8.54 получится 8. Если же порядок другой — / base.orders * 100 — ситуация хуже: 7 / 82 = 0 (целочисленное деление), затем 0 * 100 = 0. Умножение на 100.0 (литерал с десятичной точкой) переводит выражение в вещественную арифметику до деления: 7 * 100.0 = 700.0, затем 700.0 / 82 ≈ 8.536... — и ROUND даёт правильные 8.54.
На реальных данных «Маяка» запрос вернёт одну строку:
| jan_orders | feb_orders | abs_change | rel_change_pct |
|---|---|---|---|
| 82 | 89 | 7 | 8.54 |
После получения результата его можно скачать кнопкой экспорта в браузерном раннере — именно в таком виде он и нужен для проверки.
Вычисление изменения в pandas и экспорт to_csv(index=False)
В pandas тот же расчёт строится через последовательную фильтрацию: сначала оставляем только оплаченные заказы, затем из них выбираем каждый период отдельно.
import pandas as pd
df = pd.read_csv('orders.csv')
df['order_date'] = pd.to_datetime(df['order_date'])
paid = df[df['status'] == 'paid'].copy()
jan = paid[(paid['order_date'] >= '2024-01-01') & (paid['order_date'] < '2024-02-01')]
feb = paid[(paid['order_date'] >= '2024-02-01') & (paid['order_date'] < '2024-03-01')]
base_val = len(jan) # 82
curr_val = len(feb) # 89
abs_change = curr_val - base_val
rel_change = round((curr_val - base_val) / base_val * 100, 2)
result = pd.DataFrame({
'period_base': ['2024-01'],
'period_curr': ['2024-02'],
'base_orders': [base_val],
'curr_orders': [curr_val],
'abs_change': [abs_change],
'rel_change_pct': [rel_change]
})
result.to_csv('change_result.csv', index=False)
pd.to_datetime нужен, чтобы сравнение с датами через >= и < работало корректно: если столбец остаётся строковым, лексикографическое сравнение даст правильный результат только для ISO-формата YYYY-MM-DD, но явное приведение надёжнее.
len(jan) и len(feb) считают число строк в отфильтрованном DataFrame — то есть число заказов. Это эквивалент COUNT(*) из SQL.
Параметр index=False в to_csv убирает из файла автоматически генерируемый целочисленный индекс строк DataFrame. По умолчанию pandas добавляет его первым столбцом — в итоговом CSV он выглядит как безымянный столбец 0, 1, 2, ..., который не несёт смысловой нагрузки и мешает дальнейшей обработке файла. index=False не изменяет сам DataFrame в памяти: если вы после to_csv обращаетесь к result.index, индекс там остаётся, в файл просто не попадает.
Результирующий change_result.csv будет содержать одну строку данных и шесть именованных столбцов:
period_base,period_curr,base_orders,curr_orders,abs_change,rel_change_pct 2024-01,2024-02,82,89,7,8.54
Задача 1
В условном примере доля отменённых заказов в январе составила 10%, а в феврале — 13%. Аналитик написал в выводе: «Доля отменённых заказов выросла на 3%.» Что неверно в этой формулировке?
Задача 2
Условный числовой пример (данные не связаны с реальным магазином или конкретным датасетом).
В некотором онлайн-магазине в первом месяце было 280 оплаченных заказов, во втором месяце — 350 оплаченных заказов.
Вычислите относительное изменение числа оплаченных заказов от первого месяца ко второму. Округлите до одного знака после запятой.
Введите ответ в процентах (только число, например: 14.3 или −5.2).
Задача 3
Сопоставьте каждый вид изменения с единицей измерения. Запишите ответ в формате «1-X, 2-X, 3-X, 4-X», где X — буква варианта.
Виды изменения:
- Относительное изменение выручки при росте с 500 000 до 575 000 руб.
- Абсолютное изменение конверсии при снижении с 40% до 34%.
- Абсолютное изменение числа заказов при снижении со 180 до 162.
- Относительное изменение доли мобильных заказов при росте с 25% до 31%.
Варианты единиц:
A — процентные пункты (п. п.)
B — проценты (%)
C — исходные натуральные единицы (рубли, заказы)
Один вариант может использоваться несколько раз.
Задача 4
Вы анализируете оплаченные заказы интернет-магазина за два полных месяца: ноябрь 2024 (базовый период) и декабрь 2024 (текущий период). Выбранным маршрутом (SQL в браузерном раннере или pandas в Jupyter) выполните следующие шаги:
- Отфильтруйте заказы со статусом paid за каждый месяц.
- Подсчитайте количество оплаченных заказов в ноябре и декабре.
- Вычислите абсолютное изменение (декабрь − ноябрь) и относительное изменение по формуле: (декабрь − ноябрь) / ноябрь × 100, округлите до 2 знаков.
- Соберите результат в строку с полями period_base, period_curr, base_orders, curr_orders, abs_change, rel_change_pct и экспортируйте в CSV.
Введите значение rel_change_pct (число до 2 десятичных знаков, без знака %).
Задача 5
Аналитик изучил данные условного интернет-магазина за два месяца. Оплаченных заказов: январь — 280, февраль — 322. Доля отменённых заказов: январь — 12%, февраль — 15%.
Какой из вариантов содержит корректные формулировки обоих выводов для нетехнического руководителя?
Продолжить с сохранением прогресса
Перейдите к уроку в учебном кабинете.
