Как вы обрабатываете пропущенные значения в данных?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Обработка пропущенных значений в данных
Пропущенные значения (Missing Values, NA, NaN) — распространённая проблема в аналитике данных. Неправильная их обработка может привести к смещённым выводам и некорректным моделям. Есть несколько стратегий в зависимости от контекста.
Почему пропущенные значения возникают?
Технические причины:
- Ошибки при сборе данных (сбой датчика, потеря пакета)
- Несоответствие источников данных
- Проблемы в ETL процессе
Смысловые причины:
- Информация не применима (например, зарплата безработного)
- Информация неизвестна (например, возраст не указан в форме)
- Информация отсутствует по объективным причинам
Анализ пропусков
Первый шаг — понять, сколько пропусков и где:
import pandas as pd
import numpy as np
# Загрузка данных
df = pd.read_csv('data.csv')
# Количество пропусков по колонкам
print(df.isnull().sum())
# name 0
# age 15
# salary 120
# email 3
# Процент пропусков
print((df.isnull().sum() / len(df) * 100).round(2))
# salary 12.0% <- Слишком много!
# Визуализация пропусков
import seaborn as sns
sns.heatmap(df.isnull(), yticklabels=False, cbar=True)
# Строки с пропусками
print(df[df.isnull().any(axis=1)]) # Все строки с пропусками
print(df.dropna()) # Только полные строки
Стратегии обработки
1. Удаление (Deletion)
Полное удаление строк или колонок с пропусками.
# Удалить строки с пропусками
df_clean = df.dropna() # Удаляет все строки с ANY пропусками
# Удалить строки, где пропуск в критичной колонке
df_clean = df.dropna(subset=['name', 'salary'])
# Удалить колонку, если > 50% пропусков
df_clean = df.dropna(thresh=len(df)*0.5, axis=1)
Плюсы:
- Просто реализовать
- Не вводит новые значения
Минусы:
- Теряем данные (особенно если пропусков много)
- Может смещать распределение (если пропуски не случайны)
- Может привести к потере информации (MCAR vs MNAR)
Когда использовать:
- Пропусков < 5% от всех данных
- Пропуски случайны (MCAR — Missing Completely At Random)
2. Заполнение одним значением (Imputation)
Средним значением:
# Заполнить средним
df['age'].fillna(df['age'].mean(), inplace=True)
# Заполнить медианой (более устойчива к выбросам)
df['salary'].fillna(df['salary'].median(), inplace=True)
# Заполнить нулём
df['bonus'].fillna(0, inplace=True)
Предыдущим значением (для временных рядов):
# Forward fill: используй предыдущее значение
df['value'].fillna(method='ffill', inplace=True)
# Backward fill: используй следующее значение
df['value'].fillna(method='bfill', inplace=True)
# Интерполяция для временных рядов
df['value'].interpolate(method='linear', inplace=True)
Самым частым значением (для категорий):
# Mode — самое частое значение
most_common = df['category'].mode()[0]
df['category'].fillna(most_common, inplace=True)
Плюсы:
- Сохраняет все строки
- Быстро
Минусы:
- Уменьшает дисперсию
- Может исказить распределение
- Вводит смещение, если пропуски неслучайны
Когда использовать:
- Пропусков мало (< 5%)
- Пропуски случайны
- Нужна простая модель
3. Продвинутые методы (Advanced Imputation)
K-Nearest Neighbors (KNN):
from sklearn.impute import KNNImputer
# Заполнить на основе соседних значений
imputer = KNNImputer(n_neighbors=5)
df_imputed = imputer.fit_transform(df[['age', 'salary', 'experience']])
df[['age', 'salary', 'experience']] = df_imputed
Multiple Imputation by Chained Equations (MICE):
from sklearn.experimental import enable_iterative_imputer
from sklearn.impute import IterativeImputer
# MICE метод (более точный, но медленнее)
imputer = IterativeImputer(random_state=42, max_iter=10)
df_imputed = imputer.fit_transform(df)
Predict с помощью машинного обучения:
from sklearn.linear_model import LinearRegression
# Предсказать salary через другие переменные
train = df[df['salary'].notna()]
X_train = train[['age', 'experience']]
y_train = train['salary']
model = LinearRegression()
model.fit(X_train, y_train)
# Заполнить пропуски
missing = df[df['salary'].isna()]
X_missing = missing[['age', 'experience']]
df.loc[df['salary'].isna(), 'salary'] = model.predict(X_missing)
Плюсы:
- Более точные значения
- Сохраняют связи между переменными
Минусы:
- Сложнее
- Медленнее
- Требуют дополнительную подготовку
Когда использовать:
- Данные важны для модели
- Зависимость между переменными
- Пропусков не очень много
4. Создание флага (Indicator Variable)
Оставь пропуск, но добавь бинарный флаг:
# Создать флаг для пропусков
df['age_missing'] = df['age'].isnull().astype(int)
# Заполнить пропуски нулём
df['age'].fillna(0, inplace=True)
# Теперь модель может различить: 0 (реальный ноль) vs 0 (пропуск)
Это полезно когда пропуск сам по себе информативен.
Тип пропусков (важно!)
Вид пропуска влияет на выбор метода:
MCAR — Missing Completely At Random
- Пропуск не связан ни с какими другими переменными
- Пример: случайный сбой датчика
- Решение: можно удалять или заполнять
MAR — Missing At Random
- Пропуск зависит от других известных переменных
- Пример: молодые люди реже указывают возраст, но возраст не связан с другими признаками
- Решение: MICE или KNN (учитывают связи)
MNAR — Missing Not At Random
- Пропуск зависит от самого значения переменной
- Пример: люди с низким доходом чаще не указывают зарплату
- Решение: сложнее (может потребоваться доменный анализ или специальные модели)
Практический пример
import pandas as pd
import numpy as np
from sklearn.impute import KNNImputer
# Загружу датасет с пропусками
df = pd.DataFrame({
'age': [25, 30, np.nan, 35, 28, np.nan, 32],
'salary': [50000, 60000, 55000, np.nan, 52000, 58000, 62000],
'experience': [2, 5, 3, 8, 4, np.nan, 6]
})
print("Исходные данные:")
print(df)
print(f"\nПропусков: {df.isnull().sum().sum()}")
# Анализ пропусков
print(f"\nПроцент пропусков по колонкам:")
print((df.isnull().sum() / len(df) * 100).round(2))
# Вариант 1: Удалить строки (теряем 43% данных!)
df_deleted = df.dropna()
print(f"\nПосле удаления строк: {len(df_deleted)} строк из {len(df)}")
# Вариант 2: Заполнить средним
df_mean = df.copy()
df_mean['age'].fillna(df_mean['age'].mean(), inplace=True)
df_mean['salary'].fillna(df_mean['salary'].mean(), inplace=True)
df_mean['experience'].fillna(df_mean['experience'].mean(), inplace=True)
print(f"\nПосле заполнения средним:")
print(df_mean)
# Вариант 3: KNN imputation (лучше)
imputer = KNNImputer(n_neighbors=2)
df_knn = pd.DataFrame(
imputer.fit_transform(df),
columns=df.columns
)
print(f"\nПосле KNN imputation:")
print(df_knn)
Чеклист при обработке пропусков
- Проанализировать количество и распределение пропусков
- Определить тип пропуска (MCAR, MAR, MNAR)
- Проверить, есть ли закономерность в пропусках (correlation)
- Выбрать метод в зависимости от контекста и типа данных
- Задокументировать принятые решения
- Валидировать, как обработка влияет на анализ
- Сравнить результаты разных методов при возможности
Итог
Нет универсального способа обработать пропуски. Выбор зависит от:
- Количества пропусков (< 5% vs > 50%)
- Типа пропуска (MCAR vs MAR vs MNAR)
- Типа переменной (числовая vs категоричная)
- Контекста задачи (анализ vs предсказание vs отчётность)
Основное правило: документируй и объясняй свой выбор, потому что он может существенно повлиять на выводы.