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

Pandas с нуля: DataFrame, Series и чтение CSV

Первый практический урок по pandas: как читать CSV, понимать DataFrame и Series, фильтровать строки, выбирать столбцы и проверять пропуски.

КПКейсПрактика29 июля 2026 г.18 мин

Pandas нужен не для того, чтобы заменить SQL любой ценой. Он удобен, когда нужно быстро взять файл или результат запроса, проверить структуру, почистить данные и собрать небольшой воспроизводимый анализ рядом с графиком. Начнём с главной модели: DataFrame — таблица, Series — один её столбец.

Коротко

Хороший первый сценарий в pandas выглядит так: прочитать данные, проверить форму и типы, выбрать нужные строки и столбцы, обработать пропуски, получить сводку и сохранить результат. Каждое действие лучше делать явно, чтобы через неделю можно было восстановить, откуда взялась цифра.

  • DataFrame хранит строки и столбцы, Series — одну именованную колонку.
  • head, shape, info и describe — обязательная проверка перед расчётами.
  • Фильтр должен отвечать на вопрос аналитика, а не просто оставлять “интересные” строки.
  • Пропуск, пустая строка и ноль — разные состояния.

DataFrame и Series на одной картинке

Возьмём маленькую таблицу заказов. В ней одна строка — один заказ, user_id — покупатель, channel — канал, revenue — сумма заказа. Когда пишем orders["revenue"], получаем Series. Когда выбираем несколько колонок, остаёмся в DataFrame.

Базовые объекты pandas
ОбъектЧто хранитПример
DataFrameтаблицу со строками и колонкамиorders
Seriesодну колонку с индексомorders["revenue"]
Indexметки строкorders.index
dtypeтип данных колонкиorders["revenue"].dtype
pythonСоздать небольшой DataFrame
import pandas as pd

orders = pd.DataFrame({
    'order_id': [101, 102, 103, 104],
    'user_id': [1, 2, 1, 3],
    'channel': ['organic', 'paid', 'organic', 'email'],
    'revenue': [1200, 800, 450, 1500],
})

revenue = orders['revenue']       # Series
order_slice = orders[['channel', 'revenue']]  # DataFrame

Прочитать CSV и сразу проверить данные

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

pythonЗагрузка и первичная проверка файла
orders = pd.read_csv(
    'data/orders.csv',
    parse_dates=['created_at'],
)

print(orders.shape)
print(orders.dtypes)
display(orders.head())
display(orders.isna().mean().sort_values(ascending=False).head())
Не доверяй расширению файла

CSV может быть разделён запятой, точкой с запятой или табуляцией. Если таблица выглядит как один широкий столбец, сначала проверь sep, encoding и кавычки, а не начинай чистить строки вручную.

Выбрать строки и колонки

Для понятного анализа разделяй выборку на два шага: какие строки относятся к вопросу и какие поля нужны для ответа. loc работает с условиями и именами колонок, query иногда делает длинный фильтр читабельнее. Не меняй исходный DataFrame без явного присваивания или copy() — так меньше неожиданных предупреждений.

pythonФильтр оплаченных заказов за период
paid_orders = orders.loc[
    (orders['status'] == 'paid')
    & (orders['created_at'] >= '2026-07-01')
    & (orders['created_at'] < '2026-08-01'),
    ['order_id', 'user_id', 'channel', 'revenue'],
].copy()

paid_orders = paid_orders.sort_values('revenue', ascending=False)
  • Для нескольких условий используй круглые скобки вокруг каждого сравнения.
  • В pandas нужен & для “и” и | для “или”, а не обычные and и or.
  • Правая граница периода < next_start не включает следующий месяц.

Пропуски: сначала смысл, потом fillna

Пропуск в channel может означать неизвестный источник, а пропуск в discount — отсутствие скидки. В первом случае нужна явная категория unknown, во втором ноль может быть корректен. Универсальная замена всех пропусков нулём делает таблицу аккуратной только на вид.

pythonЯвно обработать разные типы пропусков
orders['channel'] = orders['channel'].fillna('unknown')
orders['discount'] = orders['discount'].fillna(0)

missing_revenue = orders['revenue'].isna().sum()
if missing_revenue > 0:
    raise ValueError('Revenue is missing: check source data')

Мини-профиль перед первой метрикой

До группировки проверь, что дата действительно дата, сумма — число, а идентификатор заказа уникален там, где это ожидается. Если одна строка — позиция, нельзя считать количество строк количеством заказов. Python не знает смысл бизнес-полей без твоей проверки.

  • orders.shape — размер таблицы.
  • orders.nunique() — количество уникальных значений по колонкам.
  • orders.duplicated("order_id").sum() — контроль повторных заказов.
  • orders["created_at"].min() и .max() — фактическое окно дат.
  • orders.describe(include="all") — быстрый обзор распределений.

Индексы, копии и предупреждения pandas

Индекс pandas — это не обязательно идентификатор строки из базы. Он помогает выровнять Series и DataFrame по меткам, но после фильтра может сохранять старые номера. Для экспорта или сравнения иногда нужен reset_index(drop=True), а для присоединения по бизнес-ключу — явный merge. Не полагайся на индекс как на order_id, если ты не создавал такой контракт.

После фильтра используй .copy(), когда собираешь независимый слой. Это делает намерение явным и уменьшает риск SettingWithCopyWarning. Предупреждение нельзя просто отключить: сначала реши, хочешь ли изменить исходную таблицу или создать новую. Большинство непонятных результатов в учебных ноутбуках начинается именно с неявного изменения среза.

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

pythonЯвно создать независимый срез
paid_orders = orders.loc[orders['status'].eq('paid')].copy()
paid_orders['revenue'] = pd.to_numeric(
    paid_orders['revenue'], errors='coerce'
)

print(paid_orders.index[:3].tolist())
paid_orders = paid_orders.reset_index(drop=True)

Фильтр должен быть переводом вопроса в код

Перед loc сформулируй выборку словами: «оплаченные заказы в полном июле, кроме тестовых аккаунтов». Затем преврати каждую часть в отдельное условие. Такой код легче проверить и изменить, чем одна строка с несколькими вложенными скобками. Имена is_paid, is_in_period, is_real_user показывают бизнес-логику прямо в ноутбуке.

Границы периода задавай полуинтервалом: больше или равно началу и меньше следующего начала. Это работает для timestamp и не зависит от того, сколько секунд в последнем дне месяца. Если написать <= 31 июля 23:59:59, события с миллисекундами можно потерять.

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

Диагностика фильтра
ПроверкаЧто она показывает
len(orders)размер исходной базы
is_paid.mean()доля оплаченных строк
is_in_period.mean()доля строк в окне дат
is_real_user.mean()доля после исключения тестовых аккаунтов
len(filtered)итоговый размер выборки

Чтение CSV: кодировка и разделитель — часть контракта

Если CSV открылся одной широкой колонкой, проблема обычно в sep, а не в pandas. В русскоязычных выгрузках встречается точка с запятой, десятичная запятая и разные кодировки. Перед расчётами проверь несколько сырых строк и явно укажи параметры чтения. Не исправляй разделитель заменой символов внутри строк: так можно повредить текстовые поля.

Денежное поле иногда приходит как "1 234,50". Сначала зафиксируй локаль и очисти пробелы, затем приведи значение к числу. Если в одном файле смешаны форматы, не скрывай ошибку через errors=coerce: посчитай число непарсящихся строк и покажи их владельцу источника.

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

pythonПрочитать CSV с явным контрактом
orders = pd.read_csv(
    'data/orders.csv',
    sep=';',
    encoding='utf-8-sig',
    usecols=['order_id', 'status', 'created_at', 'revenue'],
)
orders['revenue'] = (
    orders['revenue'].astype('string')
    .str.replace(' ', '', regex=False)
    .str.replace(',', '.', regex=False)
    .pipe(pd.to_numeric, errors='coerce')
)

Практика: от первой таблицы к проверенному срезу

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

Затем измени один параметр намеренно: подставь другой месяц или убери фильтр статуса. Хороший ноутбук должен показать предсказуемое изменение, а не требовать ручной перестановки ячеек. Если результат меняется неожиданно, вернись к индексу, типам и границам дат.

После этого переходи к groupby и merge. У тебя уже будет таблица с понятным контрактом, а не случайный набор строк. Так основы pandas связываются с реальной работой аналитика: сначала понять данные, потом считать.

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