Pandas с нуля: DataFrame, Series и чтение CSV
Первый практический урок по pandas: как читать CSV, понимать DataFrame и Series, фильтровать строки, выбирать столбцы и проверять пропуски.
Содержание статьи
Pandas нужен не для того, чтобы заменить SQL любой ценой. Он удобен, когда нужно быстро взять файл или результат запроса, проверить структуру, почистить данные и собрать небольшой воспроизводимый анализ рядом с графиком. Начнём с главной модели: DataFrame — таблица, Series — один её столбец.
Коротко
Хороший первый сценарий в pandas выглядит так: прочитать данные, проверить форму и типы, выбрать нужные строки и столбцы, обработать пропуски, получить сводку и сохранить результат. Каждое действие лучше делать явно, чтобы через неделю можно было восстановить, откуда взялась цифра.
DataFrameхранит строки и столбцы,Series— одну именованную колонку.head,shape,infoиdescribe— обязательная проверка перед расчётами.- Фильтр должен отвечать на вопрос аналитика, а не просто оставлять “интересные” строки.
- Пропуск, пустая строка и ноль — разные состояния.
DataFrame и Series на одной картинке
Возьмём маленькую таблицу заказов. В ней одна строка — один заказ, user_id — покупатель, channel — канал, revenue — сумма заказа. Когда пишем orders["revenue"], получаем Series. Когда выбираем несколько колонок, остаёмся в DataFrame.
| Объект | Что хранит | Пример |
|---|---|---|
| DataFrame | таблицу со строками и колонками | orders |
| Series | одну колонку с индексом | orders["revenue"] |
| Index | метки строк | orders.index |
| dtype | тип данных колонки | orders["revenue"].dtype |
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 загружает файл, но не сообщает, правильно ли ты понял его структуру. Поэтому чтение всегда заканчивай коротким профилем: количество строк и колонок, типы, пример строк, пропуски и диапазон дат. Это занимает минуту и ловит ошибки разделителя, кодировки и типов.
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() — так меньше неожиданных предупреждений.
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, во втором ноль может быть корректен. Универсальная замена всех пропусков нулём делает таблицу аккуратной только на вид.
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. Предупреждение нельзя просто отключить: сначала реши, хочешь ли изменить исходную таблицу или создать новую. Большинство непонятных результатов в учебных ноутбуках начинается именно с неявного изменения среза.
Проверяй результат присваивания на двух-трёх строках. После нормализации канала выведи исходное и новое значение, после создания даты — тип и границы. Маленькая визуальная проверка быстрее, чем искать ошибку после десяти последующих операций.
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.
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 связываются с реальной работой аналитика: сначала понять данные, потом считать.
- Сначала проверить разделитель, кодировку и заголовок.
- Записать, что означает одна строка.
- Проверить типы, пропуски, дубли и даты.
- Собрать фильтр из именованных условий.
- Сохранить контрольные числа до первой метрики.
Материалы по теме

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

Как ускорить pandas: память, типы и обработка больших файлов
Что делать, если pandas медленно работает или не помещает файл в память: категории, downcast, chunksize, Parquet и контроль размера данных.

Pandas apply и векторизация: как писать быстрее и понятнее
Когда использовать apply, почему векторные операции быстрее и как переписать медленный построчный расчёт в pandas.