Значения попали не в те строки: выравнивание Series по индексу
После сортировки заказов вы рассчитали скидку и записали её новым столбцом. В таблице появились пропуски или скидки оказались у других заказов. Длина результата совпадает с длиной таблицы, поэтому ошибка выглядит особенно странно. Причина часто в том, что pandas связывает Series со строками по меткам индекса.
Порядок строк и их имена — разные вещи
У DataFrame есть видимый порядок строк и отдельный индекс. Сортировка меняет порядок, но обычно сохраняет старые метки. Сброс индекса, фильтрация и результаты группировки могут создать другую систему меток. Когда справа от присваивания стоит Series, pandas ищет совпадающие метки, а не просто берёт первое значение для первой строки.
Это удобно, когда результат расчёта уже привязан к тем же объектам. Например, Series со скидкой по номеру заказа можно присвоить таблице, индексированной тем же номером, даже после сортировки. Но если индекс справа — обычные 0 и 1, а слева — номера 20 и 10, совпадений вообще нет. Получатся пропуски, хотя оба объекта содержат две строки.
Сохранить связь через ключ
Представим, что внешний расчёт вернул скидки для заказов 10 и 20. В рабочем отчёте сначала стоит более дорогой заказ 20. Превращать результат в массив только ради исчезновения NaN опасно: тогда позиционная запись назначит скидку первого заказа второму. Правильный шаг — назвать ключ, которому принадлежит каждый результат.
В примере индекс результата состоит из order_id. Метод map ищет скидку для значения order_id в каждой строке. Полученная Series наследует индекс самой таблицы, поэтому последующее присваивание сохраняет порядок. Проверки привязаны к номеру заказа: проверять только сумму скидок было бы недостаточно.
import pandas as pd
orders = pd.DataFrame({"order_id": [20, 10], "amount": [500, 100]}, index=[7, 3])
discounts = pd.Series([5, 30], index=[10, 20])
orders["discount"] = orders["order_id"].map(discounts)
orders["payable"] = orders["amount"] - orders["discount"]
print(orders.to_string(index=False))
assert orders.set_index("order_id")["discount"].to_dict() == {20: 30, 10: 5}
assert orders["payable"].sum() == 565У заказа 20 скидка 30, у заказа 10 — 5. К оплате остаются 470 и 95. Результат не зависит от того, какой заказ стоит первым. Пустой результат map теперь означает конкретную проблему: для этого ключа нет скидки. Перед публикацией отчёта выделите такие строки и решите, допускается ли отсутствие скидки по правилам источника.
Когда подходит запись по позиции
Если функция действительно получает текущие строки в текущем порядке и возвращает по одному значению на каждую, можно использовать массив. Перед присваиванием проверяют размер и сохраняют неизменность порядка между вычислением и записью. Если за это время строки отфильтровали или переставили, одной проверки длины уже недостаточно. Для результатов из другого файла, сервиса или группировки предпочтительнее явный ключ.
Сложнее ситуация с повторяющимся ключом. Один заказ может занимать несколько товарных строк, а скидка относиться ко всему заказу. map повторит её на каждой строке. Это корректно как атрибут заказа, но суммировать такой столбец по товарным строкам нельзя. Сначала определите единицу результата: скидка заказа, скидка позиции или доля общей скидки. Выравнивание решает вопрос соответствия, а смысл суммы задаёт модель данных.
Оставьте в контрольном примере хотя бы две строки в обратном порядке и не подряд идущий индекс. Такой вход обнаруживает зависимость от позиции лучше, чем аккуратная таблица с индексом 0, 1, 2. После любого изменения подготовки данных проверьте значения конкретных ключей, а затем общий итог.
Закрепить на практике
Чтобы уверенно выбирать строки перед расчётом, пройдите урок про loc и iloc в бесплатном тренажёре NumPy и pandas. Там вы отработаете выбор по меткам и позициям; пример статьи добавляет к этой базе явную связь результата с ключом заказа. Откройте бесплатный урок и задачи.
Продолжить на практике
- NumPy и pandas: практический тренажёр ↗
Бесплатный тренажёр NumPy и pandas для тех, кто знает основы Python. Научитесь очищать таблицы, объединять данные и собирать отчёты: 31 урок и 124 задачи с кодом и автоматической проверкой.
