
Пропуск и ноль в pandas: как не подменить смысл данных при очистке


В таблицах заказов ноль и пропуск часто стоят рядом, но отвечают на разные вопросы. Ноль означает, что значение известно и оно равно нулю. Пропуск означает, что значение неизвестно, не пришло из источника или еще не рассчитано. Если при очистке данных превратить оба случая в одно и то же число, отчет станет аккуратнее на вид, но менее точным по смыслу.
На практике самая частая ошибка выглядит безобидно: аналитик пишет fillna(0), чтобы убрать пустые ячейки перед расчетом. Код работает, таблица становится полной, ошибки типов исчезают. Проблема в том, что вместе с пропусками исчезает и информация о неопределенности. Для сумм заказов это особенно заметно: общий итог может не измениться, а среднее, количество нулевых заказов и выводы менеджера меняются.
Чем неизвестное значение отличается от нуля
Когда в колонке amount стоит 0, это уже факт. Например, заказ оформлен на бесплатный образец, стоимость полностью покрыта бонусами, или сумма действительно обнулилась после скидки. Мы можем спорить о причинах, но не о том, известно ли значение: оно известно.
Пропуск в той же колонке говорит о другом. Сумма могла не загрузиться из внешней системы, платеж еще не сопоставлен, строка пришла до завершения расчета, или поле вовсе не было заполнено. В этот момент аналитик знает меньше, чем в случае с нулем. Поэтому замена пропуска на ноль — это не техническая правка, а смысловое решение: вы утверждаете, что неизвестная сумма равна нулю.
В pandas это различие тоже отражено в поведении операций. Пропуски распознаются как специальные значения отсутствия данных, а агрегаты вроде среднего по умолчанию умеют их пропускать. Это удобно ровно до тех пор, пока вы не заменили пропуск конкретным числом и не превратили неизвестность в наблюдение.
Учебный DataFrame с тремя случаями
Учебный пример. Возьмем мини-таблицу из трех заказов. В первом заказе сумма известна и равна нулю. Во втором сумма неизвестна. В третьем сумма известна и равна 300. Задача — посчитать среднюю сумму заказа и не перепутать неизвестное с бесплатным.
Исходные данные можно записать так: заказ A — 0, заказ B — пропуск, заказ C — 300. Уже на этом шаге видно, что в таблице есть три разных состояния: известный ноль, неизвестное значение и известная положительная сумма. Для отчета это полезнее, чем просто три числа.
import pandas as pd
df = pd.DataFrame(
{
"order_id": [101, 102, 103],
"amount": pd.Series([0, pd.NA, 300], dtype="Float64"),
"comment": [
"известно: заказ бесплатный",
"сумма не загрузилась",
"известно: обычная оплата",
],
}
)
known_mean = df["amount"].mean()
filled_mean = df["amount"].fillna(0).mean()
zero_count_before = df["amount"].eq(0).fillna(False).sum()
zero_count_after = df["amount"].fillna(0).eq(0).sum()
Здесь выбран тип Float64, чтобы колонка хранила и числа, и пропуск как отдельное значение. Важно не это само по себе, а дисциплина мышления: сначала описать состояние данных, потом решать, что именно допустимо заменять.
Что меняет fillna(0): ручной расчет и результат в отчете
Посчитаем все вручную. Если не подменять пропуск нулем, известны только две суммы: 0 и 300. Их сумма равна 300. Количество известных значений — 2. Среднее по известным суммам равно 300 / 2 = 150. При этом у нас остается отдельный факт: один заказ имеет неизвестную сумму.
Теперь применим fillna(0). Таблица превращается в 0, 0, 300. Сумма остается 300, потому что вместо неизвестного значения мы подставили ноль, который итог не увеличивает. Но среднее становится другим: 300 / 3 = 100. Мы получили не просто новое число, а новый смысл: будто все три заказа имеют подтвержденные суммы, и два из них бесплатны.
| Показатель | Без fillna(0) | После fillna(0) |
|---|---|---|
| Сумма заказов | 300 | 300 |
| Количество известных сумм | 2 | 3 |
| Средняя сумма | 150 | 100 |
| Заказов с нулевой суммой | 1 | 2 |
| Заказов с неизвестной суммой | 1 | 0 |
Разница в среднем — 50 единиц, то есть среднее стало ниже на треть относительно исходного значения 150. Если по такому отчету делать вывод, что средний чек просел, причина окажется не в поведении клиентов, а в том, что пропуск silently превратили в ноль. Именно поэтому fillna(0) часто искажает не итоговую сумму, а интерпретацию качества данных и поведения заказов.
Правило заполнения должно отвечать на вопрос о смысле данных
Хорошее правило заполнения начинается не с метода pandas, а с вопроса: что именно означает пропуск в этом поле? Если пропуск означает «сумма еще неизвестна», его нельзя автоматически делать нулем. Если в источнике есть документированное правило, что пустая сумма равна отсутствию оплаты и это подтверждено структурой процесса, тогда заполнение нулем может быть осмысленным. Но это нужно знать заранее, а не предполагать ради удобства.
Для отчетов по заказам безопаснее сначала разделить две задачи. Первая — считать метрики по известным суммам: среднее, медиану, распределение. Вторая — отдельно показывать качество данных: сколько сумм неизвестно и какую долю строк они составляют. Тогда руководитель видит не только результат, но и степень определенности этого результата.
На уровне таблицы это часто означает, что вместо одного действия fillna(0) лучше сделать два поля: само значение amount оставить как есть, а рядом завести признак состояния, например amount_status со значениями known_zero, unknown, known_positive. Такой подход чуть длиннее в коде, зато потом проще объяснить, почему в отчете среднее посчитано по двум известным заказам, а один заказ вынесен в отдельный счетчик пропусков.
Практический результат этого правила простой: вы не прячете неопределенность внутрь числа. Отчет получается менее «гладким», зато честным. А уже после этого можно отдельно обсуждать, нужно ли дообогащать источник, дожидаться загрузки платежей или создавать витрину с явно оговоренным правилом замены.
Как проверить себя перед очисткой и перед публикацией отчета
Перед тем как заполнять пропуски, полезно пройти короткую проверку. Во-первых, что означает пустая ячейка в бизнес-процессе: неизвестно, не применимо или действительно ноль? Во-вторых, какая метрика изменится после подстановки: сумма, среднее, количество нулей, доля бесплатных заказов? В-третьих, сможете ли вы словами защитить это правило перед человеком, который не видел код?
Есть и чисто техническая проверка. Пропуски в pandas лучше искать через isna(), а не сравнивать с pd.NA оператором равенства. Для этой задачи важнее не синтаксис, а предсказуемость: код должен явно отличать неизвестные значения от числовых нулей, а не надеяться на случайное поведение сравнений.
- Не заполняйте пропуски нулем, пока не сформулировали смысл такого нуля.
- Считайте отдельно метрики по известным значениям и количество пропусков.
- Проверяйте вручную хотя бы один маленький пример, как в таблице выше.
- Смотрите не только на сумму, но и на среднее, количество наблюдений и число нулевых строк.
Если хотите потренировать подобные решения в учебной среде, в сохраненном описании каталога курса «Сбор и обработка данных на Python: от парсинга до анализа» есть тема про манипуляции с DataFrame и работу с пропусками в Pandas. Официальная страница курса недоступна, поэтому ориентироваться можно только на это сохраненное описание программы.





















