Почему пропуск — это не всегда просто отсутствие числа
Когда в таблице появляется `NaN`, первая реакция обычно техническая: надо как-то заполнить или удалить. Но пропуск — это еще и сообщение о процессе сбора данных. Иногда данных нет случайно. Иногда значение отсутствует, потому что пользователь не дошел до этапа формы. Иногда поле не применимо к части объектов. Иногда система сломалась. И от этого происхождения зависит, что с пропуском делать дальше.
Поэтому работа с missing values — это не только preprocessing, но и диагностика самого процесса появления данных.
Почему нельзя всегда просто заменить средним
$$ x_{filled} = \begin{cases} x, & x \neq \varnothing \\ \bar{x}, & x = \varnothing \end{cases} $$Заполнение средним — один из самых простых способов иммутации. Он удобен, но может скрывать структуру пропусков и искусственно сглаживать распределение признака.
x_{filled}— значение признака после заполненияx— исходное наблюдение, если оно известно\varnothing— отсутствующее значение\bar{x}— среднее по наблюдаемым значениям признака
В некоторых задачах такое заполнение нормально работает как baseline. Но если пропуски несут содержательную информацию, среднее может стереть важный сигнал. Например, сам факт отсутствия дохода в анкете может быть информативнее, чем приблизительно подставленная цифра.
Как мыслить про пропуски по-взрослому
Сначала полезно понять масштаб проблемы: где пропусков много, а где мало. Потом — проверить, не связаны ли они с target или сегментами данных. После этого уже выбирать стратегию: удалить строки, удалить признак, заполнить медианой, модой, модельной иммутацией или добавить бинарный индикатор пропуска. Очень часто сильным решением оказывается комбинация: аккуратное заполнение плюс отдельный флаг, показывающий, что значение было отсутствующим.
То есть цель не «убрать NaN любой ценой», а сохранить максимальное количество честной информации о данных.
Как это делается в Python
import pandas as pd # создаем пример таблицы с пропусками в числовом признаке
from sklearn.impute import SimpleImputer # используем базовую иммутацию для числового столбца
frame = pd.DataFrame({ # задаем признак с missing values
'income': [45000, None, 72000, None, 61000], # часть значений неизвестна
})
frame['income_missing'] = frame['income'].isna().astype(int) # сохраняем отдельный индикатор пропуска как возможный сигнал
imputer = SimpleImputer(strategy='median') # выбираем медиану как более устойчивое заполнение
frame['income_filled'] = imputer.fit_transform(frame[['income']]) # подставляем медиану вместо отсутствующих значений
print(frame) # сравниваем исходный столбец, индикатор пропуска и заполненную версиюХороший preprocessing пропусков начинается именно здесь: мы не просто маскируем дыру, а стараемся сохранить смысл того, что в данных чего-то не было.