Сравнение подходов к множественной импутации

1. MICE (Multiple Imputation by Chained Equations)

Смысловое описание:

Техническая реализация:

imputer = IterativeImputer(
    estimator=ExtraTreesRegressor(
        n_estimators=50,
        min_samples_leaf=10,
        max_features='sqrt'
    ),
    initial_strategy='median',
    max_iter=5,
    random_state=42  # Детерминированные результаты
)

2. Гибридный подход с базовой линией и расширенными предикторами

Смысловое описание:

Техническая реализация:

# Этап 1: Создание индикаторов пропусков
missing_indicators = pd.DataFrame()
for var in independent_vars:
    missing_indicators[f'{var}_missing'] = data[var].isna().astype(int)
    # Создается бинарная переменная: 1 = пропуск, 0 = значение присутствует

# Создание расширенного списка переменных
extended_vars = independent_vars + [f'{var}_missing' for var in independent_vars]
# Если было: [q1, q2, q3, q4, q5]
# Стало: [q1, q2, q3, q4, q5, q1_missing, q2_missing, q3_missing, q4_missing, q5_missing]

# Этап 2: Импутация значений
# Создается n_imputations=5 импутаций
for i in range(n_imputations):
    current_df = working_df.copy()

    for var in independent_vars:
        missing_mask = current_df[var].isna()
        num_missing = missing_mask.sum()

        if num_missing > 0:
            if i == 0:
                # Первая импутация (i=0) - базовая линия средними значениями
                imputer = SimpleImputer(strategy='mean')
                current_df.loc[missing_mask, var] = imputer.fit_transform(
                    current_df.loc[missing_mask, [var]].fillna(var_stats[var]['mean'])
                )
            else:
                # Последующие импутации (i=1-4) - случайные значения
                # ВНИМАНИЕ: Нет random_state! Результаты недетерминированы
                random_values = np.random.normal(
                    var_stats[var]['mean'], 
                    var_stats[var]['std'], 
                    size=num_missing
                )
                # Ограничиваем значения реальным диапазоном переменной
                random_values = np.clip(
                    random_values,
                    var_stats[var]['min'],
                    var_stats[var]['max']
                )

# Объединяем импутированные данные с индикаторами
imputed_data = pd.concat([current_df, missing_indicators], axis=1)

# Этап 3: Расчет весов с расширенными предикторами
# Для каждой из 5 импутаций рассчитываем веса ОТДЕЛЬНО
hybrid_r2_values = []
hybrid_weights = {}
hybrid_percentages = {}

for idx, imputed_data in enumerate(imputed_dfs):
    # Используем EXTENDED_VARS для расчета весов (оригинальные + индикаторы)
    results = calculate_weights(imputed_data, use_extended_vars=extended_vars)

    if results:
        hybrid_r2_values.append(results['R-squared'])

        # Собираем веса для ВСЕХ переменных (оригинальные + индикаторы)
        for var in extended_vars:
            if var not in hybrid_weights:
                hybrid_weights[var] = []
                hybrid_percentages[var] = []

            weight_key = f'Weight_{var}'
            pct_key = f'Percentage_{var}'
            if weight_key in results and pct_key in results:
                hybrid_weights[var].append(results[weight_key])
                hybrid_percentages[var].append(results[pct_key])

# Этап 4: Усредняем результаты всех импутаций ("мягкая логика")
if hybrid_r2_values:
    avg_results = {
        'R-squared': np.mean(hybrid_r2_values),  # Усредненный R²
        'Dependent Variable': dependent_var
    }

    # "Мягкая логика": если переменная отсутствует в какой-то импутации - используем 0
    for var in extended_vars:
        if var in hybrid_weights and hybrid_weights[var]:
            # Переменная присутствовала в импутациях - усредняем
            avg_results[f'Weight_{var}'] = np.mean(hybrid_weights[var])
            avg_results[f'Percentage_{var}'] = np.mean(hybrid_percentages[var])
        else:
            # Переменная была константной во всех импутациях - вес = 0
            avg_results[f'Weight_{var}'] = 0
            avg_results[f'Percentage_{var}'] = 0

3. Простая импутация средними

Смысловое описание:

Техническая реализация:

imputer = SimpleImputer(strategy='mean')
X_imputed = imputer.fit_transform(X)

Сравнение подходов

Пример восстановления данных:

Исходные данные:
X1 = [5, ?, 7, 4, ?, 6]   # среднее = 5.5, стд = 1.2
X2 = [3, 8, ?, 5, 4, ?]   # среднее = 5.0, стд = 1.8
(есть корреляция между X1 и X2 = 0.7)

MICE:
X1 = [5, 6.2, 7, 4, 5.8, 6]   # учитывает корреляцию с X2 (↑ из-за X2[1]=8)
X2 = [3, 8, 6.5, 5, 4, 7.1]   # учитывает корреляцию с X1 (↑ где X1 высокий)

Гибридный подход с расширенными предикторами (5 импутаций):
Импутация 0 (базовая):
X1 = [5, 5.5, 7, 4, 5.5, 6]   # средние значения
X2 = [3, 8, 5.0, 5, 4, 5.0]   # средние значения
X1_missing = [0, 1, 0, 0, 1, 0]   # индикаторы пропусков X1
X2_missing = [0, 0, 1, 0, 0, 1]   # индикаторы пропусков X2

Импутация 1 (случайная):
X1 = [5, 5.8, 7, 4, 4.9, 6]   # случайные из N(5.5, 1.2), ограничены [4, 7]
X2 = [3, 8, 5.2, 5, 4, 4.8]   # случайные из N(5.0, 1.8), ограничены [3, 8]
X1_missing = [0, 1, 0, 0, 1, 0]   # индикаторы идентичны во всех импутациях
X2_missing = [0, 0, 1, 0, 0, 1]   # индикаторы идентичны во всех импутациях

Импутация 2 (случайная):
X1 = [5, 6.1, 7, 4, 5.2, 6]   # другие случайные значения
X2 = [3, 8, 4.7, 5, 4, 5.3]   # другие случайные значения
(индикаторы те же)

Импутация 3 и 4: аналогично, но с другими случайными значениями

→ Модель анализирует 4 переменные (X1, X2, X1_missing, X2_missing) вместо 2
→ Для каждой импутации рассчитываются веса отдельно
→ Финальные веса = среднее по 5 импутациям

Простая импутация:
X1 = [5, 5.5, 7, 4, 5.5, 6]   # всегда одно и то же среднее
X2 = [3, 8, 5.0, 5, 4, 5.0]   # всегда одно и то же среднее

Плюсы и минусы

MICE:

✅ Плюсы: - Сохраняет взаимосвязи между переменными - Более точное восстановление структуры данных - Детерминированные результаты (random_state=42) - повторные запуски дают идентичные результаты - Стабильные и воспроизводимые результаты - Одна импутация для всего датасета (быстрее чем гибридный подход)

❌ Минусы: - Сложнее в реализации - Может переобучаться на шуме - Требует больше вычислительных ресурсов чем простая импутация - Не учитывает сам факт пропуска как предиктор

Гибридный подход с расширенными предикторами:

✅ Плюсы: - Проще в реализации чем MICE (нет сложных зависимостей) - Более устойчив к выбросам (clip ограничивает значения) - Явно учитывает неопределенность через случайность (5 разных импутаций) - Сохраняет базовые статистики переменных (mean, std, min, max) - Добавляет информацию о паттернах пропусков через индикаторы - Может выявить, если сам факт пропуска значения является предиктором - Стабильные результаты благодаря усреднению по 5 импутациям - Позволяет анализировать вклад как значений переменных, так и их отсутствия - "Мягкая логика" корректно обрабатывает константные переменные

❌ Минусы: - НЕдетерминированные результаты (нет random_state) - каждый запуск даст немного разные результаты - Не учитывает взаимосвязи между переменными при импутации (независимая обработка) - Может генерировать нереалистичные комбинации значений переменных - Требует больше вычислений: 5 импутаций × расчет весов для каждой - Удваивает количество предикторов в модели (может привести к переобучению) - Результаты менее интерпретируемы из-за большего количества переменных - Веса распределяются между большим количеством переменных (оригинальные + индикаторы) - Более долгий расчет по сравнению с MICE и простой импутацией

Простая импутация:

✅ Плюсы: - Очень быстрая и простая - Сохраняет среднее значение переменной - Легко интерпретируемая - Стабильные результаты

❌ Минусы: - Искажает дисперсию (занижает) - Не учитывает взаимосвязи переменных - Может искажать корреляции - Не отражает неопределенность в данных

Когда что использовать:

Пример интерпретации результатов гибридного подхода:

Допустим, у вас 5 переменных (q1-q5) и получены такие результаты:

Percentage_q1: 4%          Percentage_q1_missing: 2%
Percentage_q2: 14%         Percentage_q2_missing: 6%
Percentage_q3: 32%         Percentage_q3_missing: 1%
Percentage_q4: 22%         Percentage_q4_missing: 5%
Percentage_q5: 28%         Percentage_q5_missing: 8%

Интерпретация: - Переменная q3 имеет наибольший вес (32%), и низкий вес индикатора пропуска (1%) → Значение q3 важно, а факт пропуска не влияет - Переменная q5 имеет высокий вес (28%), и высокий вес индикатора (8%) → И значение q5, И факт его отсутствия важны для предсказания - Переменная q1 имеет низкий вес (4%), но индикатор тоже низкий (2%) → Переменная q1 мало влияет на результат

Детали усреднения в гибридном подходе:

Процесс расчета финальных весов:

  1. Для каждой из 5 импутаций рассчитываются веса отдельно
  2. Каждая импутация может дать разные веса из-за разных значений
  3. Для каждой переменной собираются все веса из разных импутаций
  4. Финальный вес = среднее арифметическое по всем импутациям

"Мягкая логика" для константных переменных:

Если переменная имеет нулевую дисперсию (все значения одинаковые) в какой-то импутации: - Эта переменная автоматически исключается из расчета для этой импутации - В списке весов для этой переменной будет меньше значений - Если переменная константна во ВСЕХ импутациях → финальный вес = 0

Пример расчета:

Допустим, есть переменная q1 и её индикатор q1_missing.

Импутация 0: Weight_q1 = 0.025, Weight_q1_missing = 0.005
Импутация 1: Weight_q1 = 0.030, Weight_q1_missing = 0.004
Импутация 2: Weight_q1 = 0.022, Weight_q1_missing = 0.006
Импутация 3: Weight_q1 = 0.028, Weight_q1_missing = исключена (константа)
Импутация 4: Weight_q1 = 0.026, Weight_q1_missing = 0.005

Финальные веса:
Weight_q1 = (0.025 + 0.030 + 0.022 + 0.028 + 0.026) / 5 = 0.0262
Weight_q1_missing = (0.005 + 0.004 + 0.006 + 0.005) / 4 = 0.005
                    ^только 4 значения, т.к. в импутации 3 была константой

То же самое с R²:
R²_final = (R²_imp0 + R²_imp1 + R²_imp2 + R²_imp3 + R²_imp4) / 5

Сравнение детерминированности:

Метод Детерминированность Причина
MICE ✅ Да random_state=42 фиксирует все случайности
Гибридный ❌ Нет np.random.normal без random_state
Простая ✅ Да Нет случайных элементов (только среднее)

Практический совет: Если вам нужны абсолютно воспроизводимые результаты для гибридного подхода, можно добавить np.random.seed(42) в начало функции hybrid_imputation.


Наглядные примеры: когда методы дают близкие или различающиеся результаты

Сценарий 1: Методы дают БЛИЗКИЕ результаты

Исходные данные (удовлетворенность сервисом, шкала 1-10):

Переменная X1 (качество):     [8, 7, 9, 8, 99, 7, 8, 9, 8, 7]  ← 10% пропусков
Переменная X2 (скорость):     [7, 8, 8, 7, 8, 7, 99, 8, 7, 8]  ← 10% пропусков  
Зависимая Y (общая оценка):   [8, 7, 9, 8, 8, 7, 8, 9, 8, 7]

Характеристики данных: - ✅ Мало пропусков (10%) - ✅ Нормальное распределение (mean ≈ 7.8, std ≈ 0.7) - ✅ Слабая корреляция между X1 и X2 (r ≈ 0.2) - ✅ Пропуски случайны (MAR - Missing At Random)

Результаты импутации:

Метод X1[4] импут. X2[6] импут. Различие от Simple
Simple 7.9 7.6 0.82 -
MICE 7.8 7.7 0.83 +1.2%
Hybrid 7.9±0.1 7.6±0.1 0.82 +0.0%

Вывод: Все три метода дают очень близкие результаты — различия не превышают 2-3%. При таких условиях можно использовать любой метод, выбирая по критерию скорости или простоты интерпретации.


Сценарий 2: Методы дают РАЗНЫЕ результаты ⚠️

(Case A: Сильная корреляция между переменными)

Исходные данные (лояльность к бренду, NPS-подобная шкала):

X1 (намерение купить):    [9, 9, 8, 99, 99, 99, 2, 2, 1, 1]  ← 30% пропусков
X2 (рекомендация):        [9, 8, 9, 8,  8,  7,  2, 1, 2, 1]
Y (общая лояльность):     [9, 9, 8, 8,  7,  7,  2, 2, 1, 1]

Характеристики данных: - ❌ Много пропусков (30%) - ❌ Бимодальное распределение (промоутеры 8-9 vs детракторы 1-2) - ❌ Очень сильная корреляция X1↔X2 (r ≈ 0.95) - ❌ Пропуски в "средней зоне" — пассивные респонденты чаще отвечают "затрудняюсь" (99)

Результаты импутации:

Метод X1[3,4,5] (импутированные) Относит. вес X1 Различие R² от Simple
Simple 5.6, 5.6, 5.6 0.65 52% -
MICE 8.2, 7.8, 7.4 0.81 48% +25%
Hybrid 6.1±1.2, 5.8±1.3, 5.9±1.1 0.71 51% +9%

Анализ различий:

  1. Simple (средними значениями):
  2. Импутирует глобальным средним (5.6), игнорируя корреляцию с X2
  3. Когда X2=8 (высокая рекомендация), логично ожидать X1≈8-9, но Simple ставит 5.6
  4. Это "размывает" корреляции и занижает R²

  5. MICE (учитывает зависимости):

  6. Видит, что при X2=8 обычно X1≈8-9, поэтому импутирует высокие значения
  7. Учитывает паттерн: "если человек рекомендует (X2 высокий), то и купит (X1 высокий)"
  8. Дает наиболее точную импутацию при сильных корреляциях
  9. Максимальный R² благодаря сохранению структуры данных

  10. Hybrid (среднее + индикаторы):

  11. Импутирует средними/случайными значениями (не учитывает корреляцию)
  12. НО добавляет индикатор X1_missing, который сам предсказывает низкую лояльность
  13. Частично компенсирует потери через индикаторы пропусков

Вывод: При сильных корреляциях различия в R² достигают 25%. MICE дает наиболее точные результаты.


Сценарий 3: Методы дают ОЧЕНЬ РАЗНЫЕ результаты

(Case B: Неслучайные пропуски - MNAR)

Исходные данные (удовлетворенность ценой):

X1 (цена справедлива):    [8, 7, 9, 99, 99, 99, 99, 99, 3, 2]  ← 50% пропусков!
X2 (готовы платить):      [8, 7, 8,  4,  3,  4,  3,  4, 3, 2]
Y (общая удовлетвор.):    [8, 7, 9,  5,  4,  5,  4,  5, 3, 2]

Характеристики данных: - ❌ Очень много пропусков (50%) - ❌ MNAR (Missing Not At Random): пропуски неслучайны — люди отказываются отвечать, когда цена несправедлива - ❌ Асимметричное распределение - ❌ Сам факт ответа "99" несет информацию: "не хочу говорить, потому что цена завышена"

Результаты импутации:

Метод Средний импут. X1 Интерпретация
Simple 5.8 0.72 ❌ Завышает справедливость
MICE 4.2 0.78 ⚠️ Ближе к реальности, но игнорирует паттерн "отказ = недовольство"
Hybrid + индикатор 5.5±1.1 0.83 X1_missing сам значим!

Ключевое отличие Hybrid метода:

В гибридном подходе добавляется переменная-индикатор:

X1_missing = [0, 0, 0, 1, 1, 1, 1, 1, 0, 0]

В модели получается уравнение вида:

Y = 0.3·X1 + 0.4·X2 + (-0.25)·X1_missing
                        ↑ негативный эффект!

Относительные веса (% объясненной дисперсии):

Метод X1 X2 X1_missing Интерпретация
Simple 60% 40% - Переоценка роли X1
MICE 55% 45% - Более сбалансированно
Hybrid 35% 40% 25% Сам факт пропуска — предиктор!

Вывод: При неслучайных пропусках (MNAR) только Hybrid метод выявляет, что сам факт отказа отвечать несет информацию. Различия в интерпретации — кардинальные!


Сценарий 4: Бимодальное распределение с пропусками в разных зонах

Исходные данные (оценка политика, поляризованное мнение):

X1 (компетентность):  [10, 9, 10, 9, 99, 99, 1, 2, 1, 2]
X2 (честность):       [9, 10, 9, 8, 99, 99, 2, 1, 2, 1]
Y (общая оценка):     [10, 9, 10, 9, 5, 4, 1, 2, 1, 2]

Характеристики: - Бимодальное распределение: сторонники (9-10) vs противники (1-2) - Пропуски в "нейтральной зоне" (индекс 4, 5 → Y=5, Y=4) - 20% пропусков

Результаты импутации:

Метод X1[4,5] импут. Влияние на дисперсию
Simple 5.5, 5.5 ❌ Искусственно создает "средних" 0.88
MICE 6.2, 5.1 ⚠️ Пытается угадать, но данных мало 0.89
Hybrid 6.1±2.1, 4.8±2.3 ✅ Сохраняет неопределенность 0.88

Анализ:

Вывод: При бимодальных распределениях Hybrid метод лучше сохраняет дисперсию через случайные импутации.


Итоговая таблица рекомендаций

Условия данных Различия методов Рекомендуемый метод Причина
Пропусков <15%, MAR, нормальное распределение <5% Любой метод Все дают близкие результаты, можно выбирать по скорости
Пропусков 15-30%, есть корреляции >0.5 10-25% MICE Лучше всего сохраняет взаимосвязи
Пропусков >30%, подозрение на MNAR >30% Hybrid с индикаторами Только он выявляет значимость самого факта пропуска
Бимодальное распределение 15-30% Hybrid Случайные импутации сохраняют дисперсию
"Затрудняюсь ответить" — содержательный ответ Критично Обязательно Hybrid Код 99 сам по себе информативен
Нужна воспроизводимость - MICE или Simple У них есть random_state / детерминизм
Нужна скорость - Simple Самый быстрый
Сложный анализ, важна точность - MICE Наиболее статистически корректный

Практические рекомендации

Как определить, какой метод использовать:

  1. Проверьте долю пропусков: python missing_rate = df[variable].isna().sum() / len(df) if missing_rate < 0.15: # Любой метод подойдет elif missing_rate < 0.30: # MICE или Hybrid else: # Hybrid обязательно

  2. Проверьте корреляции: python corr_matrix = df[independent_vars].corr() if corr_matrix.abs().max() > 0.5: # Используйте MICE

  3. Проверьте тип распределения: python from scipy.stats import normaltest _, p_value = normaltest(df[variable].dropna()) if p_value < 0.05: # Распределение не нормальное → лучше Hybrid

  4. Проверьте, случайны ли пропуски: ```python # Сравните среднее Y для пропущенных vs непропущенных X has_missing = df[X_var].isna() mean_y_missing = df[has_missing][Y_var].mean() mean_y_present = df[~has_missing][Y_var].mean()

if abs(mean_y_missing - mean_y_present) > 1.0: # Пропуски неслучайны (MNAR) → используйте Hybrid ```

Главное преимущество каждого метода:

Золотое правило: Если есть подозрение, что "затрудняюсь ответить" или отказ от ответа сам по себе что-то значит (недовольство, смущение, незнание) — обязательно используйте Hybrid с индикаторами пропусков.