Как тестировать аналитические расчёты на Python и pandas
Практический гайд по тестам для аналитика: проверить метрики на маленьком датасете, поймать регрессию и защитить расчёт от тихих изменений.

Тесты нужны не только для backend-кода. Аналитический расчёт тоже может сломаться после переименования колонки, изменения фильтра или нового правила дедупликации. Самая опасная ошибка — не падение программы, а правдоподобная цифра, которая стала другой.
Коротко
Тест аналитического кода проверяет не стиль Python, а смысл результата: правильный знаменатель, уникальность пользователя, границы периода и ожидаемое поведение на маленьком наборе данных. Начать можно с нескольких DataFrame и обычных `assert`, а затем вынести проверки в pytest.
- Пиши маленький тестовый датасет, где ответ можно посчитать вручную.
- Проверяй не только тип и форму, но и конкретные значения метрики.
- Отдельно тестируй крайние случаи: пустой период, дубликаты, пропуски и нулевой знаменатель.
- Тесты должны быть независимыми от реального CSV и текущей даты.
Сначала вынеси расчёт в функцию
Код в ячейке сложно проверить, если он одновременно читает файл, фильтрует даты и рисует график. Вынеси бизнес-правило в функцию с DataFrame на входе. Тогда тест сможет передать маленький набор строк и сравнить результат с ожидаемым.
import pandas as pd
def calculate_dau(events: pd.DataFrame) -> pd.DataFrame:
active = events.loc[events['event_name'].eq('core_action')].copy()
active['event_date'] = pd.to_datetime(active['occurred_at']).dt.date
return (
active.groupby('event_date', as_index=False)
.agg(dau=('user_id', 'nunique'))
.sort_values('event_date')
)Контрольный датасет должен быть маленьким и хитрым
Четыре строки, где каждый пользователь совершает действие один раз, проверяют только счастливый путь. Добавь повторное событие одного пользователя, событие другого типа, следующий день и пользователя с пропуском. Такой набор лучше ловит ошибку `count` вместо `nunique` и неправильный фильтр.
events = pd.DataFrame({
'user_id': [1, 1, 2, 3, 3],
'occurred_at': [
'2026-08-01 10:00', '2026-08-01 10:05',
'2026-08-01 11:00', '2026-08-01 12:00',
'2026-08-02 09:00',
],
'event_name': ['core_action', 'core_action', 'login', 'core_action', 'core_action'],
})
actual = calculate_dau(events)
expected = pd.DataFrame({
'event_date': [pd.to_datetime('2026-08-01').date(), pd.to_datetime('2026-08-02').date()],
'dau': [2, 1],
})
pd.testing.assert_frame_equal(actual.reset_index(drop=True), expected)Что проверять кроме одного числа
Для метрики важны структура и инварианты. DAU не может быть больше числа уникальных пользователей в исходном срезе. Выручка после фильтра paid не должна стать отрицательной. Доля групп должна складываться в 100% с оговоркой на округление. Такие проверки переживают изменение конкретных значений.
| Объект | Проверка |
|---|---|
| DAU | не больше числа уникальных пользователей в периоде |
| Retention | от 0 до 1 и не выше размера стартовой когорты |
| Конверсия | от 0 до 1, знаменатель не равен нулю |
| Выручка | неотрицательна после явной обработки возвратов |
| Сегменты | ключи не пропали после merge, unknown виден отдельно |
pytest делает тесты частью запуска
Когда функций становится больше, тесты удобно хранить отдельно и запускать одной командой. `pytest` покажет, какой сценарий сломался. Для аналитика особенно полезны читаемые имена: по названию должно быть ясно, какую бизнес-ошибку защищает тест.
def test_dau_counts_unique_users_per_day():
events = make_events_for_two_days()
result = calculate_dau(events)
assert result.loc[result['event_date'].eq(date(2026, 8, 1)), 'dau'].item() == 2
assert result['dau'].sum() == 3Он защищает зафиксированное правило. Если само определение DAU изменилось, сначала обнови решение и документацию, а потом тест. Не меняй expected только ради зелёного CI.
Материалы по теме
Как ускорить pandas: память, типы и обработка больших файлов
Что делать, если pandas медленно работает или не помещает файл в память: категории, downcast, chunksize, Parquet и контроль размера данных.
Читать материал
Pandas apply и векторизация: как писать быстрее и понятнее
Когда использовать apply, почему векторные операции быстрее и как переписать медленный построчный расчёт в pandas.
Читать материал
Как загружать Excel, JSON и Parquet в pandas
Практический разбор источников в pandas: прочитать Excel и JSON, выбрать лист, сохранить типы и перейти с CSV на Parquet без сюрпризов.
Читать материал