Чтобы сделать эти идеи конкретными, давайте работать с небольшим датасетом электронной коммерции.

Представьте, что мы анализируем заказы в интернет-магазине. Каждая строка представляет заказ и включает информацию о доходе и скидках.

import pandas as pd
orders = pd.DataFrame({
"order_id": [1001, 1002, 1003, 1004],
"customer_id": [1, 2, 2, 3],
"revenue": ["120", "250", "80", "300"], # выглядит как числа
"discount": [None, 10, None, 20]
})
orders

На первый взгляд всё выглядит нормально. У нас есть значения выручки, несколько скидок и несколько пропущенных записей.

Теперь ответим на простой вопрос:

Какова общая выручка?

orders["revenue"].sum()

Вы можете ожидать что-то вроде:

750

Вместо этого Pandas возвращает:

'12025080300'

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

Причина тонкая, но невероятно важна:

Столбец revenue выглядит как числа, но Pandas на самом деле хранит его как текст.

Мы можем подтвердить это, проверив типы данных датафрейма.

orders.dtypes

Эта маленькая деталь вводит один из наиболее распространённых источников ошибок в рабочих процессах Pandas: типы данных.

Давайте исправим это дальше.

1. Типы данных: скрытый источник многих ошибок Pandas

Проблема, которую мы только что видели, сводится к чему-то простому: типы данных.

Хотя столбец revenue выглядит как числовой, Pandas интерпретировал его как object (по сути текст).

Мы можем подтвердить это:

orders.dtypes

Результат:

order_id int64 
customer_id int64 
revenue object 
discount float64 
dtype: object

Поскольку revenue хранится как текст, операции ведут себя иначе. Когда мы попросили Pandas просуммировать столбец ранее, он объединил строки вместо сложения чисел.

Такой вид проблемы удивительно часто появляется при работе с реальными датасетами. Данные, экспортированные из электронных таблиц, CSV-файлов или API часто хранят числа как текст.

Самый безопасный подход — это явно определять типы данных вместо того, чтобы полагаться на предположения Pandas.

Мы можем исправить столбец, используя astype():

orders["revenue"] = orders["revenue"].astype(int)

Теперь, если мы снова проверим типы:

orders.dtypes

Получим:

order_id int64 
customer_id int64 
revenue int64 
discount float64 
dtype: object

И расчёт наконец-то ведёт себя ожидаемым образом:

orders["revenue"].sum()

Результат:

750

Простая оборонительная привычка

Теперь, когда я загружаю новый датасет, одно из первых действий, которое я выполняю:

orders.info()

Это дает быстрый обзор:

  • типов данных столбцов
  • отсутствующих значений
  • использования памяти

Этот простой шаг часто выявляет тонкие проблемы перед тем, как они превратятся в запутанные ошибки позже.

Но типы данных — это только часть истории.

Другое поведение Pandas вызывает ещё большее замешательство — особенно при объединении датасетов или выполнении вычислений.

Это что-то называемое выравниванием индексов.

Выравнивание индексов: Pandas сопоставляет метки, а не строки

Одно из самых мощных — и запутанных — поведений в Pandas это выравнивание индексов.

Когда Pandas выполняет операции между объектами (такими как Series или DataFrames), он не сопоставляет строки по позиции.

Вместо этого он сопоставляет их по меткам индексов.

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

Давайте посмотрим на простой пример.

revenue = pd.Series([120, 250, 80], index=[0, 1, 2])
discount = pd.Series([10, 20, 5], index=[1, 2, 3])
revenue + discount

Результат выглядит так:

0 NaN
1 260
2 100
3 NaN
dtype: float64

На первый взгляд это может показаться странным.

Почему Pandas произвёл четыре строки вместо трёх?

Причина в том, что Pandas выравнял значения на основе меток индексов.

Pandas выравнивает значения, используя метки их индексов. Внутренне расчёт выглядит так:

  • В индексе 0 revenue существует, но discount нет → результат становится NaN
  • В индексе 1 оба значения существуют → 250 + 10 = 260
  • В индексе 2 оба значения существуют → 80 + 20 = 100
  • В индексе 3 discount существует, но revenue нет → результат становится NaN

Что произвёло:

0 NaN
1 260
2 100
3 NaN
dtype: float64

Строки без соответствующих индексов дают отсутствующие значения, по сути.

Это поведение на самом деле одна из сильных сторон Pandas, потому что оно позволяет датасетам с различными структурами комбинироваться разумно.

Но это также может вводить тонкие ошибки.

Как это проявляется в реальном анализе

Давайте вернёмся к нашему датасету orders.

Предположим, мы фильтруем заказы со скидками:

discounted_orders = orders[orders["discount"].notna()]

Теперь представьте, что мы пытаемся рассчитать чистую выручку, вычтя скидку.

orders["revenue"] - discounted_orders["discount"]

Вы можете ожидать простое вычитание.

Вместо этого Pandas выравнивает строки, используя исходные индексы.

Результат будет содержать отсутствующие значения, потому что отфильтрованный датафрейм больше не имеет ту же структуру индексов.

Это легко может привести к:

  • неожиданным значениям NaN
  • неправильным расчётам метрик
  • запутанным результатам на выходе

И снова — Pandas не вызовет ошибку.

Оборонительный подход

Если вы хотите, чтобы операции ведут себя построчно, хорошей практикой является сброс индекса после фильтрации.

discounted_orders = orders[orders["discount"].notna()].reset_index(drop=True)

Теперь строки выравниваются по позиции снова.

Другой вариант — явно выравнивать объекты перед выполнением операций:

orders.align(discounted_orders)

Или в ситуациях, когда выравнивание не требуется, вы можете работать с необработанными массивами:

orders["revenue"].values

В конечном итоге всё сводится к этому.

В Pandas операции выравниваются по меткам индексов, а не по порядку строк.

Понимание этого поведения помогает объяснить многие загадочные значения NaN, которые появляются во время анализа.

Но есть ещё одно поведение Pandas, которое запутало почти каждого аналитика данных в какой-то момент.

Вы, вероятно, видели это раньше:

SettingWithCopyWarning

Давайте разберём, что на самом деле происходит там.

Проблема Copy vs View (и знаменитое предупреждение)

Если вы используете Pandas уже какое-то время, вы, вероятно, видели это предупреждение ранее:

SettingWithCopyWarning

Когда я впервые столкнулся с этим, я в основном его игнорировал. Код всё ещё запускался, и результат выглядел нормально, поэтому это не казалось большой проблемой.

Но это предупреждение указывает на что-то важное о том, как работает Pandas: иногда вы изменяете исходный датафрейм, а иногда вы изменяете копию.