Все материалы
Продуктовая аналитикапрактикумсредний

EDA в pandas: как провести разведывательный анализ данных

Пошаговый EDA в pandas: проверить структуру, распределения, связи и сегменты до того, как строить метрики и делать выводы.

КейсПрактика2 августа 2026 г.обновлено 15 сентября 2026 г.10 мин

Разведывательный анализ нужен до красивого дашборда и до сложной модели. Он помогает понять, что именно лежит в таблице, где данные неполные, какие значения типичны и какие сегменты ведут себя иначе. EDA не даёт автоматического ответа, но заметно снижает риск считать не то.

Коротко

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

  • Начните с зерна таблицы и периода данных.
  • Смотрите на распределение, а не только на среднее.
  • Отделяйте технические пропуски от реального отсутствия значения.
  • Проверяйте ключевые срезы до общего вывода.

Шаг 1. Профиль таблицы

Первый экран EDA должен отвечать на простые вопросы: сколько строк, какие поля, какой диапазон дат, где пропуски и сколько уникальных ключей. Если в таблице заказов 200 тысяч строк, но только 80 тысяч order_id, это уже влияет на любой последующий расчёт.

pythonБыстрый профиль DataFrame
def profile(df: pd.DataFrame) -> pd.DataFrame:
    return pd.DataFrame({
        'dtype': df.dtypes.astype(str),
        'missing': df.isna().sum(),
        'missing_share': df.isna().mean().round(3),
        'unique': df.nunique(dropna=False),
    }).sort_values('missing_share', ascending=False)

print('rows, columns:', orders.shape)
display(profile(orders))
display(orders[['created_at', 'revenue']].describe())

Пропавший день нельзя принять за падение заказов

Профиль orders показывает диапазон дат, но две крайние даты не гарантируют непрерывность выгрузки. Между ними может отсутствовать целый день. До анализа среднего чека постройте календарь отчётного периода и присоедините к нему количество заказов, сумму известных revenue и число неизвестных сумм. Предполагаем, что одна строка уже соответствует одному заказу; иначе сначала нужно восстановить это зерно. Для created_at заранее выбрана бизнес-зона, чтобы ночные операции не оказались в соседних сутках.

В учебном фрагменте ниже заказы есть 1 и 3 июля, а за 2 июля строк нет. После reindex у второго дня будет пропуск. Он сообщает только отсутствие наблюдений в файле: магазин мог не работать, а могла остановиться загрузка. Не заполняйте эту ячейку нулём, пока не сверите календарь с журналом доставки данных или контрольной выгрузкой источника. Если нулевой день подтверждён, это отдельное решение с объяснением в отчёте.

Рядом с числом заказов нужен охват денежного поля. Если за день пришли все order_id, но revenue отсутствует, количество покупок останется нормальным, а сумма может выглядеть нулевой. SUM с min_count=1 сохраняет пропуск, когда нет ни одной известной суммы. При частичной загрузке он всё ещё возвращает частичный итог; поэтому сравнивайте revenue_known с orders. Так разрыв трекинга не превращается в историю о падении платёжеспособности клиентов.

Сначала посмотрите сырой ряд, затем сравните одинаковые дни недели в полных неделях. Последний неполный день выгрузки исключите из сравнения или подпишите его отдельно. Рост от понедельника к пятнице может повторяться регулярно; изменение среднего после добавления выходных может быть календарным составом. Но нескольких точек недостаточно, чтобы объявить сезонность установленной. Для устойчивого недельного рисунка нужны повторения, а праздники и рекламные кампании подпишите рядом с датами.

Для гипотезы о пропусках соберите маленькую таблицу «день × канал»: число строк, число известных revenue и доля пропусков. Если провал появляется одновременно во всех каналах, проверьте общую загрузку; если только в одном — его источник или преобразование. Наблюдаемый рисунок помогает выбрать следующую проверку, но не доказывает, что пропуски случайны. В memo передайте конкретные даты, затронутые поля и запрос на повторную выгрузку, чтобы инженер смог воспроизвести сбой.

pythonУчебный календарь сохраняет неизвестность за 2 июля
import pandas as pd
orders = pd.DataFrame({
    'order_id': [101, 102, 103],
    'created_at': pd.to_datetime(['2026-07-01', '2026-07-01', '2026-07-03']),
    'revenue': [1200.0, 800.0, float('nan')],
})
assert orders['order_id'].is_unique
orders['day'] = orders['created_at'].dt.normalize()
daily = orders.groupby('day').agg(
    orders=('order_id', 'size'),
    revenue_known=('revenue', 'count'),
    revenue=('revenue', lambda s: s.sum(min_count=1)),
)
daily = daily.reindex(pd.date_range('2026-07-01', '2026-07-03'))
assert pd.isna(daily.loc['2026-07-02', 'orders'])
assert pd.isna(daily.loc['2026-07-03', 'revenue'])
print(daily)

Шаг 2. Смотреть на распределения

Средний чек может быть 1 500 ₽, хотя половина заказов меньше 600 ₽, а несколько крупных клиентов тянут среднее вверх. Поэтому для денег и времени полезно смотреть медиану, квантили и хвост. Выброс — не всегда ошибка: иногда это отдельный тип пользователя или важный бизнес-сценарий.

Какие числа добавить к среднему
ПоказательЧто показываетОсторожность
medianтипичное центральное значениене показывает размер хвоста
quantile(0.9)уровень, ниже которого 90% наблюденийзависит от размера выборки
min / maxграницы наблюдаемых значенийчасто чувствительны к ошибкам
stdразброс вокруг среднегоплохо объясняет сильно асимметричные данные
pythonКвантили выручки вместо одного среднего
revenue_summary = orders['revenue'].describe(percentiles=[.5, .75, .9, .99])
print(revenue_summary)

p99 = orders['revenue'].quantile(.99)
large_orders = orders.loc[orders['revenue'] > p99]
print('share of orders above p99:', len(large_orders) / len(orders))

Шаг 3. Сегменты важнее общей средней

Общий показатель может быть устойчивым, пока внутри него меняются сегменты. Средний заказ по всем каналам скрывает разницу между новым и возвращающимся покупателем. EDA помогает найти срезы, где меняется форма данных, а затем уже решать, нужен ли отдельный KPI или дополнительная гипотеза.

pythonСравнить распределение по каналам
segment_summary = (
    orders.groupby('channel')
    .agg(
        orders=('order_id', 'nunique'),
        buyers=('user_id', 'nunique'),
        median_revenue=('revenue', 'median'),
        p90_revenue=('revenue', lambda s: s.quantile(.9)),
    )
    .sort_values('median_revenue', ascending=False)
)
display(segment_summary)

EDA не доказывает причину

Если пользователи из referral-канала покупают чаще, это ещё не означает, что канал сам по себе создаёт лучший опыт. В него могли попадать более лояльные клиенты или другой тип продукта. Разведывательный анализ формирует вопрос: «почему сегмент отличается?». Для причинности нужны эксперимент, квазиэксперимент или дополнительный дизайн исследования.

  • Не удаляй выбросы только ради более красивого среднего.
  • Не называйте корреляцию влиянием.
  • Запишите, какие фильтры применил к исследованию.
  • Отделяйте находки, которые увидел заранее, от найденных перебором срезов.

Сформулируйте карту вопросов до просмотра графиков

EDA легко превращается в бесконечное листание диаграмм. Чтобы этого не произошло, разделите вопросы на четыре группы: полнота данных, распределение, сегменты и время. Сначала проверьте, все ли пользователи и периоды попали в набор. Затем посмотрите форму ключевых числовых полей. Только после этого переходите к сравнениям.

Для каждой находки записывайте статус: факт, гипотеза или вопрос для проверки. Например, «Android revenue ниже» — факт описательного среза. «На Android проблема в оплате» — гипотеза. «Есть ли ошибка после версии 4.2?» — следующий вопрос. Такая маркировка не даёт EDA незаметно превратиться в причинный вывод.

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

Четыре слоя разведывательного анализа
СлойВопросПример проверки
полнотаесть ли все строки и периодыдиапазон дат и объём по дням
формакак распределено значениеквантили, выбросы, пропуски
сегментыгде группы отличаютсяplatform, channel, plan
времякогда появилась разницатренд и точка изменения

Пропуски и выбросы нужно описать, а не спрятать

Доля пропусков — только начало. Важно понять, какие строки пропущены: один канал, один день, новый тип устройства или случайный набор. Сравнивайте missingness по сегментам и времени. Если revenue отсутствует только после релиза, это уже сигнал о контракте события, а не повод заполнить все значения средним.

Выбросы могут быть ошибкой, крупным клиентом или редким, но важным сценарием. До удаления посмотрите исходную строку и влияние на метрику. Для среднего полезно показать медиану и квантили рядом, а для бизнеса — отдельно посчитать результат с и без спорного сегмента. Это честнее, чем незаметно обрезать верхний процент.

Нормализация масштаба тоже влияет на вывод. Два канала могут иметь одинаковое среднее, но совершенно разную дисперсию. Boxplot или таблица p50/p90/p99 помогает понять, одинаков ли пользовательский опыт или его портит небольшой хвост.

pythonПрофиль пропусков и хвостов распределения
profile = pd.DataFrame({
    'missing_share': orders.isna().mean(),
    'unique_values': orders.nunique(dropna=False),
})

revenue_stats = orders['revenue'].quantile([.5, .9, .99])
print(profile.sort_values('missing_share', ascending=False))
print(revenue_stats)

Сегменты: сравните не только средние

Разница между сегментами может возникнуть из-за состава. Сравнивайте размер группы, долю пропусков, период наблюдения и распределение ключевого показателя. Если одна группа в два раза меньше, один крупный пользователь способен изменить среднее. Для первой проверки используйте медиану, квантили и доверительный интервал, а не только одно число.

Не строй сразу двадцать разрезов и не выбирайте самый красивый. Сначала выберите сегмент по продуктовой гипотезе, затем один независимый разрез для проверки устойчивости. Если эффект появляется только в одном из двадцати случайных срезов, это повод для осторожности, а не для заголовка отчёта.

Кросс-сегментация помогает найти составной эффект, но быстро уменьшает базу. Подписывайте размер каждой ячейки и объединяйте редкие категории, если это соответствует смыслу. Пустая или очень маленькая ячейка не даёт права на сильный вывод.

EDA формирует очередь проверок

После каждого сигнала запишите, какие данные или эксперимент смогут его подтвердить. Не превращайте визуальную корреляцию в готовое решение.

Связь скидки с выручкой может целиком состоять из разных корзин

После сравнения каналов возникает следующий вопрос: связана ли скидка с размером заказа. Допустим, к orders добавили discount — скидку в рублях. Начните с определения revenue: сумма до скидки или после неё. Если discount составляет фиксированную долю стоимости корзины, положительная связь с суммой встроена в формулу. Она не показывает, что скидка убедила человека купить больше. Для такой гипотезы нужен другой источник вариации, а не корреляция двух производных одной корзины.

Подготовьте пары значений на одном зерне. Если discount записана на уровне заказа, а revenue повторяется в строках товаров, большой заказ получит больший вес просто потому, что в нём больше позиций. Сверните строки до заказа и проверьте число полных пар после исключения пропусков. При расчёте матрицы корреляций разные пары колонок могут использовать разные подвыборки; одинаковый размер исходного DataFrame не делает их сопоставимыми.

Посмотрите scatterplot отдельно для новых и возвращающихся покупателей, а затем по каналу, который уже исследовал выше. Представьте две группы корзин: небольшие заказы без скидки и крупные заказы с персональным предложением. Между группами связь сильная, внутри каждой может почти отсутствовать. Сравните диапазоны: если без скидки встречаются только маленькие корзины, данные не дают сопоставимых заказов для проверки эффекта скидки на большие. Регрессионная линия сама это ограничение не устранит.

Pearson чувствителен к линейной связи и крайним значениям; Spearman работает с рангами и помогает проверить монотонность. Расхождение двух коэффициентов — повод осмотреть хвост распределения, а не выбрать тот, который подтверждает историю. Если крупные заказы настоящие, оставьте их в основном описании и покажите чувствительность вывода к их исключению отдельно. Для повторных заказов одного пользователя учитывайте, что строки зависимы: обычная оценка неопределённости по независимым заказам будет слишком оптимистичной.

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

Практика: закончить EDA коротким memo

Финал EDA — не папка из двадцати графиков, а короткое memo. В нём должны быть размер и период источника, три устойчивых наблюдения, две гипотезы, ограничения и следующий шаг. Для каждого наблюдения приложи ссылку на таблицу или код, из которого оно получено.

Отдельно перечислите, что вы не проверяли: причинность, сезонность, качество трекинга или влияние маркетингового состава. Это не ослабляет исследование. Наоборот, команда понимает границы и не использует описательный срез как доказательство эффекта.

Если EDA повторяется еженедельно, превратите стабильные проверки в функции или SQL-контроль. Исследовательская часть может оставаться интерактивной, а профиль источника и sanity-check должны запускаться одинаково.

  • Начать с полноты и схемы источника.
  • Посмотреть пропуски, квантили и выбросы.
  • Сравнить сегменты с размером базы.
  • Разделить факт, гипотезу и следующий вопрос.
  • Закончить memo с ограничениями и действием.
Продолжить чтение
Вся библиотека