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

Является ли подстановка средних значений вместо пропусков допустимой? Почему?

1.3 Junior🔥 181 комментариев
#Pandas и обработка данных#Машинное обучение

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

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

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

Подстановка средних значений: допустимо ли?

Краткий ответ: Это зависит от контекста. Подстановка средних (mean imputation) — это простой, но рискованный метод, который в большинстве случаев вводит смещение в анализ. Он может быть допустимым только при очень специфических условиях.

Проблемы с подстановкой средних значений

1. Уменьшение дисперсии

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt

# Оригинальные данные
np.random.seed(42)
original_data = np.random.normal(100, 15, 1000)  # среднее 100, стдев 15

# Создаём пропуски (MCAR)
mask = np.random.random(1000) < 0.2  # 20% пропусков
data_with_missing = original_data.copy()
data_with_missing[mask] = np.nan

# Заполняем средним
mean_value = np.nanmean(data_with_missing)
imputed_data = data_with_missing.copy()
imputed_data[np.isnan(imputed_data)] = mean_value

print(f"Оригинальные данные:")
print(f"  Среднее: {np.mean(original_data):.2f}")
print(f"  Стдев: {np.std(original_data):.2f}")
print(f"  Дисперсия: {np.var(original_data):.2f}")

print(f"\nДанные после заполнения средним:")
print(f"  Среднее: {np.mean(imputed_data):.2f}")
print(f"  Стдев: {np.std(imputed_data):.2f}")
print(f"  Дисперсия: {np.var(imputed_data):.2f}")

print(f"\nПотеря дисперсии: {(1 - np.var(imputed_data)/np.var(original_data)) * 100:.1f}%")

# Визуализация
fig, axes = plt.subplots(1, 3, figsize=(15, 4))

axes[0].hist(original_data, bins=50, alpha=0.7, edgecolor='black')
axes[0].set_title('Оригинальные данные')
axes[0].axvline(np.mean(original_data), color='red', linestyle='--', label='Среднее')

data_incomplete = data_with_missing[~np.isnan(data_with_missing)]
axes[1].hist(data_incomplete, bins=50, alpha=0.7, edgecolor='black')
axes[1].set_title('Данные с пропусками (видимые значения)')
axes[1].axvline(np.nanmean(data_with_missing), color='red', linestyle='--')

axes[2].hist(imputed_data, bins=50, alpha=0.7, edgecolor='black')
axes[2].set_title('После подстановки средних')
axes[2].axvline(np.mean(imputed_data), color='red', linestyle='--')

plt.tight_layout()
plt.show()

Результат: Дисперсия уменьшается на 10-30% (зависит от доли пропусков).

2. Смещение стандартных ошибок

from sklearn.linear_model import LinearRegression
from sklearn.metrics import mean_squared_error

# Данные для регрессии
np.random.seed(42)
n = 500
X = np.random.normal(0, 1, n)
y_true = 2 * X + np.random.normal(0, 0.5, n)

# Вводим пропуски в y (30%)
missing_indices = np.random.choice(n, int(0.3 * n), replace=False)
y_with_missing = y_true.copy()
y_with_missing[missing_indices] = np.nan

# Вариант 1: Удаляем пропуски
valid_mask = ~np.isnan(y_with_missing)
X_clean = X[valid_mask].reshape(-1, 1)
y_clean = y_with_missing[valid_mask]

model_clean = LinearRegression().fit(X_clean, y_clean)
print(f"После удаления пропусков:")
print(f"  Коэффициент: {model_clean.coef_[0]:.4f}")
print(f"  R-squared: {model_clean.score(X_clean, y_clean):.4f}")

# Вариант 2: Подстановка средних
mean_y = np.nanmean(y_with_missing)
y_imputed = y_with_missing.copy()
y_imputed[missing_indices] = mean_y

model_imputed = LinearRegression().fit(X.reshape(-1, 1), y_imputed)
print(f"\nПосле подстановки средних:")
print(f"  Коэффициент: {model_imputed.coef_[0]:.4f}")
print(f"  R-squared: {model_imputed.score(X.reshape(-1, 1), y_imputed):.4f}")
print(f"\nПроблема: R-squared выглядит выше, но это иллюзия")
print(f"(заполняем среднее значение y, что увеличивает объяснённую дисперсию)")

3. Исключение из анализа важной информации

# Если данные MNAR (пропуски зависят от самой переменной)
# Например: люди не хотят указывать низкий доход

np.random.seed(42)
n = 1000

# Истинный доход
income = np.random.gamma(2, 2, n) * 30000  # распределение с длинным хвостом

# Пропуски MNAR: люди с низким доходом реже отвечают
missing_prob = 1 / (1 + np.exp(income/50000 - 1.5))  # логистическая функция
missing_mask = np.random.random(n) < missing_prob

income_observed = income.copy()
income_observed[missing_mask] = np.nan

print(f"Истинный средний доход: ${np.mean(income):.0f}")
print(f"Наблюдаемый средний доход (видимые данные): ${np.nanmean(income_observed):.0f}")
print(f"Средний доход после подстановки средних: ${np.nanmean(income_observed):.0f}")
print(f"\nОшибка смещения: ${(np.nanmean(income_observed) - np.mean(income)):.0f}")
print(f"\nДаже подстановка среднего видимых данных неправильна!")
print(f"Истинное среднее пропущенных значений (которые мы не видим):")
print(f"  ${np.mean(income[missing_mask]):.0f}")
print(f"Это намного ниже, чем видимые значения!")

Когда подстановка средних МОЖЕТ быть допустимой

1. Очень маленькая доля пропусков (< 1-2%)

print("При < 1-2% пропусков:")
print("- Влияние на дисперсию минимально")
print("- Смещение коэффициентов регрессии маленькое")
print("- Если данные MCAR, смещение приблизительно нулевое")
print("\nНО: всё ещё лучше использовать более продвинутые методы")

2. Описательная статистика для быстрого анализа

print("Для разведочного анализа (EDA):")
print("- Можно подставить средние для быстрого просмотра")
print("- Помни про ограничения метода")
print("- Для финального анализа используй лучшие методы")

3. Данные MCAR с очень маленькой корреляцией пропусков с другими переменными

print("Редкий случай:")
print("- Доказанная MCAR механизм")
print("- Пропуски абсолютно случайны")
print("- Не коррелируют с целевой переменной")
print("\nВ таком случае подстановка среднего даёт несмещённые оценки")
print("(но всё ещё имеет проблемы с дисперсией и стандартными ошибками)")

Почему это плохо

Проблема 1: Неправильные стандартные ошибки

# Модель недооценивает неопределённость
from scipy.stats import t

print("После подстановки средних:")
print("- Дисперсия ошибок уменьшается")
print("- Стандартные ошибки коэффициентов становятся меньше")
print("- p-values становятся меньше (false confidence)")
print("- Доверительные интервалы слишком узкие")
print("\nРезультат: статистически значимые результаты,")
print("которые на самом деле шум из-за подстановки!")

Проблема 2: Нарушение распределения

from scipy.stats import normaltest

# Если подставляешь среднее в 30% данных
# Распределение становится мультимодальным

data_imputed_many = imputed_data.copy()
print(f"\nОригинальное распределение:")
print(f"  Нормальное? Тест: статистика = {normaltest(original_data)[0]:.2f}")

print(f"\nДанные с импутацией средних (30% пропусков):")
print(f"  Нормальное? Тест: статистика = {normaltest(imputed_data)[0]:.2f}")
print(f"  Видно два пика: нормальное распределение + пик на среднем значении")

Лучшие альтернативы

1. Удаление (Deletion)

df_clean = df.dropna()
print("Плюсы: просто, честно")
print("Минусы: теряем данные")
print("Когда: пропусков < 5%, данные MCAR")

2. KNN Imputation

from sklearn.impute import KNNImputer
imputer = KNNImputer(n_neighbors=5)
df_imputed = pd.DataFrame(
    imputer.fit_transform(df),
    columns=df.columns
)
print("Плюсы: учитывает соседей, лучше сохраняет дисперсию")
print("Минусы: медленнее, требует расчёта расстояний")

3. Итеративное заполнение

from sklearn.experimental import enable_iterative_imputer
from sklearn.impute import IterativeImputer
imputer = IterativeImputer()
df_imputed = pd.DataFrame(
    imputer.fit_transform(df),
    columns=df.columns
)
print("Плюсы: учитывает зависимости между переменными")
print("Минусы: может быть slow, есть гиперпараметры")

4. Множественная импутация

print("Создаём несколько версий данных с разными импутациями")
print("Обучаем модель на каждой версии")
print("Усредняем результаты")
print("\nПлюсы: учитывает неопределённость импутации")
print("Минусы: вычислительные затраты")

Практический вывод

print("НИКОГДА не подставляй средние, если:")
print("✗ Пропусков > 5%")
print("✗ Нужны правильные стандартные ошибки")
print("✗ Используешь статистические тесты")
print("✗ Подозреваешь MNAR механизм")
print("✗ Переменная коррелирует с целевой")
print()
print("Только подставляй средние, если:")
print("✓ Пропусков < 1% И")
print("✓ Разведочный анализ (не финальный результат) И")
print("✓ Даёшь явное предупреждение о методе")

Вывод: Подстановка средних — это быстрый способ справиться с пропусками, но для серьёзного анализа используй KNN, итеративную импутацию или множественную импутацию. Честные результаты важнее скорости кодирования.