Как загружать Excel, JSON и Parquet в pandas
Практический разбор источников в pandas: прочитать Excel и JSON, выбрать лист, сохранить типы и перейти с CSV на Parquet без сюрпризов.
Содержание статьи
Аналитические данные приходят не только в CSV. Финансовый отчёт лежит в Excel с несколькими листами, API возвращает вложенный JSON, а подготовленный слой хранится в Parquet. У каждого формата есть свои ловушки: заголовки, типы, merged cells, timezone и разная структура вложенности.
Коротко
Первые минуты после загрузки нужно потратить на проверку формы: названия колонок, типы, количество строк, лист или путь внутри JSON. Сохраняй типы и схему отдельно от кода чтения, чтобы изменение файла не прошло незаметно.
- Excel — это книга с листами, а не одна таблица.
- JSON может быть списком записей, словарём или вложенным ответом API.
- Parquet хранит типы и колонки эффективнее CSV, но требует совместимого engine.
- Не смешивай файлы разных периодов без проверки схемы.
Excel: выбрать лист и пропустить служебные строки
read_excel умеет читать конкретный лист, диапазон колонок и строку заголовка. Если файл оформлен для человека, первые строки могут содержать название отчёта, а настоящие колонки начинаются ниже. После чтения проверь, что order_id действительно стал колонкой, а не попал в первую строку данных.
orders = pd.read_excel(
'input/monthly_report.xlsx',
sheet_name='Orders',
header=2,
usecols=['order_id', 'created_at', 'channel', 'revenue'],
)
print(orders.columns.tolist())
print(orders.shape)JSON: сначала понять структуру ответа
В JSON записи могут лежать внутри data, results или нескольких вложенных объектов. read_json удобен для плоского списка, а json_normalize — для ответа API, где поля находятся внутри словарей. Не разворачивай весь JSON без необходимости: оставь только поля, которые отвечают на вопрос.
import json
from pandas import json_normalize
with open('input/response.json', encoding='utf-8') as file:
payload = json.load(file)
events = json_normalize(payload['data'])
events = events.rename(columns={'user.id': 'user_id'})
events = events[['user_id', 'event_name', 'occurred_at']]Parquet: типы и выбор колонок
Parquet удобен для очищенных аналитических слоёв. В отличие от CSV, он сохраняет типы, поэтому дата и категория не превращаются в строки при каждом чтении. Если нужен только один срез, читай нужные колонки и фильтруй период как можно раньше.
orders = pd.read_parquet(
'data/clean/orders.parquet',
columns=['order_id', 'created_at', 'revenue'],
)
orders['created_at'] = pd.to_datetime(orders['created_at'], utc=True)
orders = orders.loc[orders['created_at'].dt.month.eq(8)]Единый контракт для разных источников
Если один отчёт собирается из Excel сегодня и Parquet завтра, приведи оба источника к одинаковой схеме до объединения. Одинаковые названия колонок, типы и зерно важнее формата файла. Держи проверку колонок рядом с загрузкой, чтобы ошибка появилась до merge и groupby.
EXPECTED = {'order_id', 'created_at', 'channel', 'revenue'}
missing = EXPECTED.difference(orders.columns)
if missing:
raise ValueError(f'Missing columns: {sorted(missing)}')
orders['created_at'] = pd.to_datetime(orders['created_at'], errors='coerce', utc=True)
orders['revenue'] = pd.to_numeric(orders['revenue'], errors='coerce')Формат файла не описывает смысл данных
Excel, JSON, CSV и Parquet отличаются способом хранения, но ни один формат не говорит автоматически, что означает одна строка. В Excel строка может быть итогом за день, в JSON — объектом пользователя с вложенным массивом заказов, а в Parquet — уже очищенным слоем событий. Перед чтением запиши зерно, ожидаемые ключи, период и правила обновления. Иначе технически успешная загрузка приведёт к неверной метрике.
Проверь дату выгрузки и диапазон наблюдений до объединения файлов. Один источник может быть в московском времени, другой — в UTC. Один файл может содержать полный месяц, а второй — только первые семь дней. После чтения сравни минимальную и максимальную дату, количество уникальных ключей и долю пропусков. Это дешёвая проверка, которая ловит больше проблем, чем попытка угадать правильный параметр read_*.
Хороший ingestion-слой возвращает не только DataFrame, но и метаданные: имя источника, время загрузки, число строк, хэш файла и список предупреждений. Для маленького ноутбука достаточно словаря рядом с результатом. Для регулярного pipeline эти поля стоит записывать в журнал запуска.
| Вопрос | Пример ответа |
|---|---|
| Зерно | одна строка — один заказ |
| Ключ | order_id уникален в итоговом слое |
| Период | created_at в UTC, только полный месяц |
| Обязательные поля | order_id, user_id, revenue, created_at |
| Обработка ошибок | непарсящиеся даты уходят в quarantine |
Excel: служебные строки и несколько листов
Excel часто выглядит как таблица, но фактически является презентацией отчёта. Вверху могут быть название отдела, дата формирования и объединённые ячейки, а заголовок начинается с пятой строки. На одном листе лежит итог, на другом — детализация. Поэтому сначала прочитай несколько строк без предположения о заголовке и посмотри названия листов.
Параметр usecols ограничивает вход, но не исправляет смещённый header. После чтения сразу нормализуй имена колонок: убери пробелы, приведи регистр и проверь обязательный набор. Если источник меняется вручную каждую неделю, сохрани пример плохого файла в тестовой папке. Такой regression-тест защитит от того, что менеджер добавил одну строку перед заголовком.
Формулы Excel могут быть сохранены как вычисленные значения или прочитаны иначе в зависимости от настроек и библиотеки. Для финансового отчёта сравни несколько контрольных ячеек с тем, что видит владелец файла. Не считай Excel источником истины только потому, что его удобно открыть.
book = pd.ExcelFile('data/report.xlsx')
print(book.sheet_names)
raw = pd.read_excel(
book, sheet_name='Orders', header=None, nrows=8
)
print(raw)
orders = pd.read_excel(
book, sheet_name='Orders', skiprows=4, usecols='A:F'
)
orders.columns = orders.columns.astype('string').str.strip().str.lower()JSON: сначала распрями структуру, потом считай
JSON бывает простым списком объектов, ответом API с полем data или деревом с вложенными items. pd.read_json не всегда даёт таблицу нужного зерна. Вначале посмотри верхний уровень объекта и реши, какая сущность должна стать строкой. Если один пользователь содержит массив событий, после json_normalize можно получить строку на событие, но нужно сохранить связь с user_id.
Следи за пагинацией и дубликатами. API может отдавать одну страницу по 100 записей, а последняя страница — повторять часть предыдущей при нестабильной сортировке. Собирай страницы в список сырых объектов, проверяй курсор и только потом объединяй. Успешный HTTP-ответ ещё не означает полный набор данных.
Типы в JSON тоже плавают: сегодня revenue — число, завтра API прислал "1 200", а в третьем ответе — null. Приведи число явно, сохрани количество ошибок и не превращай непонятную строку в ноль. Ноль — это наблюдение, не синоним ошибки загрузки.
payload = response.json()
rows = pd.json_normalize(
payload['data'],
record_path='events',
meta=['user_id', 'created_at'],
)
rows['event_id'] = rows['event_id'].astype('string')
assert rows['event_id'].notna().all()
assert rows['user_id'].notna().all()Parquet и изменение схемы
Parquet сохраняет типы и позволяет читать только нужные колонки, но схема всё равно может меняться между партициями. В одном файле user_id оказался integer, в другом — string; новый источник добавил device_type, а старый его не знает. Перед concat приведи ключи к общему типу и явно определи, какие колонки обязательны, а какие могут отсутствовать.
Выбор колонок при чтении снижает I/O, но не должен скрывать audit-поля. Если отчёту нужен только revenue, всё равно сохрани отдельно период, имя файла и version источника. При объединении партиций проверяй, что новые даты действительно появились, а не были отфильтрованы из-за несовместимой схемы.
Разделяй raw и clean Parquet. Raw слой должен позволять восстановить исходный ответ, clean — иметь нормализованные типы, названия и контрольные проверки. Перезаписывать единственный файл после очистки удобно в начале, но быстро лишает возможности разобраться, почему число изменилось.
Колонночный файл может быть быстрым, хорошо сжатым и при этом содержать неправильное зерно, дубли или неполный период. Проверки схемы и контрольные числа нужны после каждой загрузки.
Собери загрузку в одну повторяемую функцию
Когда источник используется больше одного раза, вынеси чтение и нормализацию из основной аналитической ячейки. Функция должна принимать путь и возвращать DataFrame с известным контрактом. Внутри можно выбрать reader по расширению, но результат обязан быть одинаковым: названия колонок, типы, часовой пояс и правила ошибок.
Такой слой делает анализ переносимым. Сегодня ты читаешь Excel от операционного менеджера, завтра — Parquet из DWH, а расчёт выручки не меняется. Если контракт нарушен, pipeline останавливается до графика. Это лучше, чем получить пустую линию и искать причину в визуализации.
Практическая проверка проста: положи рядом один нормальный и один намеренно испорченный файл. Первый должен пройти до отчёта, второй — остановиться с сообщением о конкретной колонке, типе или периоде. Это маленькая инвестиция, которая окупается при первой же смене шаблона.
- Осмотреть структуру до выбора параметров чтения.
- Нормализовать колонки и типы в одном месте.
- Проверить даты, ключи, дубли и объём источника.
- Разделить raw и clean слои.
- Считать метрики только после прохождения контракта.
Материалы по теме

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

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

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