Какой метод заполняет пропуски в данных?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Методы заполнения пропусков в данных (Imputation)
Пропуски в данных (Missing Values) — это распространённая проблема в реальных датасетах. Их нужно обработать перед анализом, иначе модели могут работать неправильно или вообще отказать. Разберу все основные методы заполнения пропусков и когда их использовать.
Почему пропуски проблема?
import pandas as pd
import numpy as np
# Датасет с пропусками
df = pd.DataFrame({
'age': [25, 30, None, 35, 40],
'salary': [50000, 60000, 75000, None, 80000],
'experience': [1, 5, 3, 8, 10]
})
print(df)
# age salary experience
# 0 25 50000 1
# 1 30 60000 5
# 2 NaN 75000 3
# 3 35 NaN 8
# 4 40 80000 10
# Проблемы:
print(df.mean()) # NaN values игнорируются
print(df.corr()) # Может быть неправильным
# Модели часто не работают с NaN
from sklearn.linear_model import LinearRegression
try:
model = LinearRegression()
model.fit(df[['age', 'experience']], df['salary']) # Ошибка!
except ValueError as e:
print(f"Ошибка: {e}")
1. Удаление строк с пропусками (Deletion)
Идея: Просто удалить строки, в которых есть пропуски.
# Удалить строки с пропусками в любом столбце
df_clean = df.dropna()
print(df_clean)
# age salary experience
# 0 25 50000 1
# 1 30 60000 5
# 4 40 80000 10
# Удалить только если пропуск в конкретном столбце
df_clean = df.dropna(subset=['age'])
# Удалить если пропуск во всех столбцах
df_clean = df.dropna(how='all')
# Удалить если пропуск в большинстве столбцов
df_clean = df.dropna(thresh=2) # Оставить если минимум 2 не-NaN
Преимущества:
- ✅ Просто и быстро
- ✅ Нет предположений о данных
Недостатки:
- ❌ Теряем данные
- ❌ Если пропусков много, потеряем большую часть датасета
- ❌ Может быть смещение (если пропуски неслучайны)
Когда использовать:
- Пропусков < 5%
- Пропуски случайны
2. Заполнение константой (Forward/Backward Fill)
Идея: Заполнить пропуски одним значением (например, 0 или средним).
# Заполнить нулями
df_filled = df.fillna(0)
print(df_filled)
# age salary experience
# 0 25 50000 1
# 1 30 60000 5
# 2 0 75000 3
# 3 35 0 8
# 4 40 80000 10
# Заполнить определённым значением
df_filled = df.fillna({'age': -1, 'salary': 0})
# Заполнить последним известным значением (Forward Fill)
# Полезно для временных рядов
df_ts = pd.DataFrame({
'date': pd.date_range('2024-01-01', periods=5),
'value': [10, None, None, 20, 25]
})
df_filled = df_ts.fillna(method='ffill') # Повторить последнее значение
print(df_filled)
# date value
# 0 2024-01-01 10.0
# 1 2024-01-02 10.0 <- Повторили 10
# 2 2024-01-03 10.0 <- Повторили 10
# 3 2024-01-04 20.0
# 4 2024-01-05 25.0
# Заполнить следующим известным значением (Backward Fill)
df_filled = df_ts.fillna(method='bfill')
print(df_filled)
# date value
# 0 2024-01-01 10.0
# 1 2024-01-02 20.0 <- Возьму следующее 20
# 2 2024-01-03 20.0 <- Возьму следующее 20
# 3 2024-01-04 20.0
# 4 2024-01-05 25.0
Преимущества:
- ✅ Просто для временных рядов
- ✅ Сохраняет форму данных
Недостатки:
- ❌ Может исказить анализ
- ❌ Искусственно снижает вариацию
3. Заполнение средним / медианой / модой (Mean/Median/Mode Imputation)
Идея: Заполнить пропуск на основе статистики других значений.
# Среднее (Mean)
mean_value = df['age'].mean() # Игнорирует NaN
df_filled = df.copy()
df_filled['age'].fillna(mean_value, inplace=True)
print(f"Заполнили среднее возрасте: {mean_value}")
# Медиана (для несимметричных данных)
median_value = df['salary'].median()
df_filled['salary'].fillna(median_value, inplace=True)
# Мода (для категориальных данных)
df_cat = pd.DataFrame({
'city': ['Moscow', 'SPB', None, 'Moscow', 'SPB']
})
mode_value = df_cat['city'].mode()[0]
df_cat['city'].fillna(mode_value, inplace=True)
print(df_cat)
# city
# 0 Moscow
# 1 SPB
# 2 Moscow <- Заполнили модой
# 3 Moscow
# 4 SPB
# Через pandas fillna с методом
df_filled = df[['age', 'salary']].fillna(df.mean())
Преимущества:
- ✅ Сохраняет основные статистические свойства
- ✅ Просто в реализации
- ✅ Работает для категориальных данных (мода)
Недостатки:
- ❌ Снижает дисперсию
- ❌ Может быть неправильным, если пропуски неслучайны
- ❌ Не учитывает взаимосвязи между переменными
Когда использовать:
- Пропусков мало (< 10%)
- Пропуски случайны
- Нет сильных взаимосвязей с другими переменными
4. Группировочное заполнение (Group-based Imputation)
Идея: Заполнить пропуск средним/медианой в группе, не по всему датасету.
df = pd.DataFrame({
'department': ['Sales', 'IT', 'Sales', 'IT', 'HR'],
'salary': [50000, 80000, None, 85000, 45000]
})
# Заполнить пропуск в зарплате средней зарплатой в отделе
df['salary'] = df.groupby('department')['salary'].transform(
lambda x: x.fillna(x.mean())
)
print(df)
# department salary
# 0 Sales 50000
# 1 IT 80000
# 2 Sales 50000 <- Средняя зарплата Sales (50000)
# 3 IT 85000
# 4 HR 45000
Когда использовать:
- Есть естественная группировка
- Пропуски могут различаться по группам
5. KNN Imputation
Идея: Заполнить пропуск значением из k-ближайших соседей.
from sklearn.impute import KNNImputer
import numpy as np
df = pd.DataFrame({
'age': [25, 30, np.nan, 35, 40],
'salary': [50000, 60000, 75000, np.nan, 80000],
'experience': [1, 5, 3, 8, 10]
})
# KNN Imputer
imputer = KNNImputer(n_neighbors=2)
df_imputed = pd.DataFrame(
imputer.fit_transform(df),
columns=df.columns
)
print(df_imputed)
# age salary experience
# 0 25.00 50000.00 1
# 1 30.00 60000.00 5
# 2 32.50 75000.00 3 <- Среднее 30 и 35 (ближайшие)
# 3 35.00 70000.00 8 <- Среднее 60000 и 80000
# 4 40.00 80000.00 10
Преимущества:
- ✅ Учитывает структуру данных
- ✅ Лучше, чем просто среднее
- ✅ Работает с многомерными данными
Недостатки:
- ❌ Вычислительно дорого
- ❌ Зависит от выбора k
- ❌ Медленнее для больших датасетов
6. Multiple Imputation (MICE)
Идея: Создать несколько датасетов с разными импутированными значениями и провести анализ на каждом.
from sklearn.experimental import enable_iterative_imputer
from sklearn.impute import IterativeImputer
df = pd.DataFrame({
'age': [25, 30, np.nan, 35, 40],
'salary': [50000, 60000, 75000, np.nan, 80000],
'experience': [1, 5, 3, 8, 10]
})
# Iterative Imputer (MICE-like)
imputer = IterativeImputer(max_iter=10, random_state=42)
df_imputed = pd.DataFrame(
imputer.fit_transform(df),
columns=df.columns
)
print(df_imputed)
# Или использовать statsmodels для полноценной MICE
from statsmodels.imputation.mice import MICEData
mice_data = MICEData(df)
mices = mice_data.mice()
Преимущества:
- ✅ Статистически правильный метод
- ✅ Учитывает неопределённость
- ✅ Лучше для больших доля пропусков
Недостатки:
- ❌ Вычислительно сложно
- ❌ Требует больше кода
- ❌ Медленно
7. Заполнение с предсказанием (Predictive Imputation)
Идея: Предсказать пропускающееся значение на основе других переменных.
from sklearn.linear_model import LinearRegression
df = pd.DataFrame({
'age': [25, 30, np.nan, 35, 40],
'salary': [50000, 60000, 75000, np.nan, 80000],
'experience': [1, 5, 3, 8, 10]
})
# Заполнить пропуск в age на основе salary и experience
# Шаг 1: Обучить модель на полных строках
mask = df['age'].notna()
X_train = df.loc[mask, ['salary', 'experience']]
y_train = df.loc[mask, 'age']
model = LinearRegression()
model.fit(X_train, y_train)
# Шаг 2: Предсказать пропускающиеся значения
mask_missing = df['age'].isna()
X_missing = df.loc[mask_missing, ['salary', 'experience']]
y_pred = model.predict(X_missing)
df.loc[mask_missing, 'age'] = y_pred
print(df)
# age salary experience
# 0 25 50000 1
# 1 30 60000 5
# 2 32.4 75000 3 <- Предсказано моделью
# 3 35 80000 8
# 4 40 80000 10
Когда использовать:
- Сильные корреляции между переменными
- Хочешь максимально реалистичное значение
8. Создание индикатора пропуска (Missing Indicator)
Идея: Вместо заполнения пропуска, добавить бинарную переменную-флаг.
df = pd.DataFrame({
'age': [25, 30, np.nan, 35, 40],
'salary': [50000, 60000, 75000, np.nan, 80000]
})
# Создать флаги
df['age_is_missing'] = df['age'].isna().astype(int)
df['salary_is_missing'] = df['salary'].isna().astype(int)
# Заполнить пропуски
df['age'].fillna(df['age'].mean(), inplace=True)
df['salary'].fillna(df['salary'].mean(), inplace=True)
print(df)
# age salary age_is_missing salary_is_missing
# 0 25 50000 0 0
# 1 30 60000 0 0
# 2 32.5 75000 1 0 <- Было NaN в age
# 3 35 70000 0 1 <- Было NaN в salary
# 4 40 80000 0 0
Преимущества:
- ✅ Сохраняет информацию о пропусках
- ✅ Может помочь модели
- ✅ Показывает качество данных
Сравнение методов
| Метод | Скорость | Качество | Сложность | Когда использовать |
|---|---|---|---|---|
| Удаление | ⚡⚡⚡ | 📊 | ⭐ | Пропусков < 5% |
| Forward Fill | ⚡⚡⚡ | 📊 | ⭐ | Временные ряды |
| Mean/Median | ⚡⚡⚡ | 📊📊 | ⭐ | Пропусков < 10% |
| Группировочное | ⚡⚡ | 📊📊 | ⭐⭐ | Есть группы |
| KNN | ⚡ | 📊📊📊 | ⭐⭐ | Средние пропуски |
| MICE | 🐢 | 📊📊📊📊 | ⭐⭐⭐ | Много пропусков |
| Предсказание | ⚡ | 📊📊📊 | ⭐⭐⭐ | Сильные корреляции |
Практический рабочий процесс
import pandas as pd
import numpy as np
from sklearn.impute import SimpleImputer, KNNImputer
def handle_missing_values(df):
"""Обработка пропусков"""
# 1. Анализ пропусков
missing_pct = (df.isnull().sum() / len(df)) * 100
print("Процент пропусков:")
print(missing_pct[missing_pct > 0])
# 2. Удалить столбцы с > 50% пропусков
df = df.loc[:, missing_pct < 50]
# 3. Удалить строки с > 3 пропусками
df = df.loc[df.isnull().sum(axis=1) <= 3]
# 4. Числовые колонки: KNN imputation
numeric_cols = df.select_dtypes(include=[np.number]).columns
imputer = KNNImputer(n_neighbors=5)
df[numeric_cols] = imputer.fit_transform(df[numeric_cols])
# 5. Категориальные колонки: мода
categorical_cols = df.select_dtypes(include=['object']).columns
for col in categorical_cols:
df[col].fillna(df[col].mode()[0], inplace=True)
return df
# Использование
df_clean = handle_missing_values(df)
Ключевые выводы
✅ Не удаляй данные без необходимости
✅ Понимай ПРИЧИНУ пропусков (случайные vs неслучайные)
✅ Для временных рядов используй Forward Fill
✅ Для случайных пропусков используй среднее/медиану
✅ Для сложных случаев используй KNN или MICE
✅ Всегда создавай флаги пропускающихся значений для моделей
✅ Проверь результат визуально после импутации