imputer = IterativeImputer(
estimator=ExtraTreesRegressor(
n_estimators=50,
min_samples_leaf=10,
max_features='sqrt'
),
initial_strategy='median',
max_iter=5,
random_state=42 # Детерминированные результаты
)
# Этап 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
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] # всегда одно и то же среднее
✅ Плюсы: - Сохраняет взаимосвязи между переменными - Более точное восстановление структуры данных - Детерминированные результаты (random_state=42) - повторные запуски дают идентичные результаты - Стабильные и воспроизводимые результаты - Одна импутация для всего датасета (быстрее чем гибридный подход)
❌ Минусы: - Сложнее в реализации - Может переобучаться на шуме - Требует больше вычислительных ресурсов чем простая импутация - Не учитывает сам факт пропуска как предиктор
✅ Плюсы: - Проще в реализации чем MICE (нет сложных зависимостей) - Более устойчив к выбросам (clip ограничивает значения) - Явно учитывает неопределенность через случайность (5 разных импутаций) - Сохраняет базовые статистики переменных (mean, std, min, max) - Добавляет информацию о паттернах пропусков через индикаторы - Может выявить, если сам факт пропуска значения является предиктором - Стабильные результаты благодаря усреднению по 5 импутациям - Позволяет анализировать вклад как значений переменных, так и их отсутствия - "Мягкая логика" корректно обрабатывает константные переменные
❌ Минусы: - НЕдетерминированные результаты (нет random_state) - каждый запуск даст немного разные результаты - Не учитывает взаимосвязи между переменными при импутации (независимая обработка) - Может генерировать нереалистичные комбинации значений переменных - Требует больше вычислений: 5 импутаций × расчет весов для каждой - Удваивает количество предикторов в модели (может привести к переобучению) - Результаты менее интерпретируемы из-за большего количества переменных - Веса распределяются между большим количеством переменных (оригинальные + индикаторы) - Более долгий расчет по сравнению с MICE и простой импутацией
✅ Плюсы: - Очень быстрая и простая - Сохраняет среднее значение переменной - Легко интерпретируемая - Стабильные результаты
❌ Минусы: - Искажает дисперсию (занижает) - Не учитывает взаимосвязи переменных - Может искажать корреляции - Не отражает неопределенность в данных
Когда важна стабильность результатов при повторных запусках
Гибридный подход с расширенными предикторами:
ВНИМАНИЕ: Результаты будут немного отличаться при каждом запуске из-за отсутствия random_state
Простая импутация:
Допустим, у вас 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 мало влияет на результат
Если переменная имеет нулевую дисперсию (все значения одинаковые) в какой-то импутации: - Эта переменная автоматически исключается из расчета для этой импутации - В списке весов для этой переменной будет меньше значений - Если переменная константна во ВСЕХ импутациях → финальный вес = 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-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] импут. | R² | Различие от 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%. При таких условиях можно использовать любой метод, выбирая по критерию скорости или простоты интерпретации.
Исходные данные (лояльность к бренду, 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] (импутированные) | R² | Относит. вес 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% |
Анализ различий:
Это "размывает" корреляции и занижает R²
MICE (учитывает зависимости):
Максимальный R² благодаря сохранению структуры данных
Hybrid (среднее + индикаторы):
X1_missing, который сам предсказывает низкую лояльностьВывод: При сильных корреляциях различия в R² достигают 25%. MICE дает наиболее точные результаты.
Исходные данные (удовлетворенность ценой):
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 | R² | Интерпретация |
|---|---|---|---|
| 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 метод выявляет, что сам факт отказа отвечать несет информацию. Различия в интерпретации — кардинальные!
Исходные данные (оценка политика, поляризованное мнение):
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] импут. | Влияние на дисперсию | R² |
|---|---|---|---|
| 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 | Наиболее статистически корректный |
Проверьте долю пропусков:
python
missing_rate = df[variable].isna().sum() / len(df)
if missing_rate < 0.15:
# Любой метод подойдет
elif missing_rate < 0.30:
# MICE или Hybrid
else:
# Hybrid обязательно
Проверьте корреляции:
python
corr_matrix = df[independent_vars].corr()
if corr_matrix.abs().max() > 0.5:
# Используйте MICE
Проверьте тип распределения:
python
from scipy.stats import normaltest
_, p_value = normaltest(df[variable].dropna())
if p_value < 0.05:
# Распределение не нормальное → лучше Hybrid
Проверьте, случайны ли пропуски: ```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 с индикаторами пропусков.