Как работать с пропущенными значениями в датасете
Что проверяют
Умеете ли вы сначала понять, почему значение пропало, и только потом выбирать способ обработки. Заодно проверяют, не допускаете ли вы утечку при заполнении. Следом часто спрашивают, как выбрать между удалением и заполнением для конкретного признака.
Как отвечать
Шаг первый, диагностика. df.isna().mean() даёт долю пропусков по колонкам, библиотека missingno показывает, пропадают ли колонки вместе. Проверьте, связан ли сам факт пропуска с целевой переменной: если да, пропуск информативен.
Шаг второй, механизм:
- MCAR: пропуск случаен, например сбой датчика. Удаление небольшого числа строк почти не смещает оценки;
- MAR: пропуск зависит от других признаков, например молодые клиенты чаще не указывают доход. Заполнение с учётом этих признаков работает;
- MNAR: пропуск зависит от самого значения, доход скрывают те, у кого он очень высокий или низкий. Здесь нужен признак-индикатор пропуска, одно заполнение смещение не уберёт.
Шаг третий, способ:
- по смыслу: иногда пропуск означает ноль, например нет покупок за период. Это лучше любой статистики;
- статистика: медиана устойчивее среднего к выбросам, для категорий мода или отдельная категория «неизвестно»;
- модели:
KNNImputerилиIterativeImputerв духе MICE; - временные ряды:
ffillвнутри группы по объекту или интерполяция.bfillтянет значение из будущего; - ничего не делать: LightGBM, XGBoost, CatBoost и
HistGradientBoostingClassifierобрабатывают пропуски в числовых признаках сами.
Выбор подтверждайте кросс-валидацией, сравнивая стратегии на одной модели, а заполнение держите внутри Pipeline, чтобы оно обучалось только на обучающих фолдах.
Где спотыкаются
Не замечают скрытых пропусков: -999, 0, пустая строка или «н/д». isna() их не видит, и медиана считается по мусору.
Вызывают dropna() без оглядки и теряют половину строк, причём неслучайную половину.
Заполняют средним по всему датасету до разбиения или используют bfill во временных данных. Оценка на валидации выходит лучше, чем будет на проде.