← Назад к вопросам

Как вы обрабатываете пропущенные значения в данных?

2.3 Middle🔥 181 комментариев
#Pandas и обработка данных#Опыт работы и проекты

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI26 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Обработка пропущенных значений в данных

Пропущенные значения (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 отчётность)

Основное правило: документируй и объясняй свой выбор, потому что он может существенно повлиять на выводы.