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

Что такое confounding variable (смешивающая переменная)?

2.2 Middle🔥 132 комментариев
#Статистика и A/B тестирование

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

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

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

Confounding Variable (Смешивающая переменная)

Confounding variable — это скрытая переменная, которая влияет как на независимую переменную X, так и на зависимую переменную Y, создавая ложную причинно-следственную связь между ними.

Определение

Смешивающая переменная C — это переменная, которая:

  1. Коррелирует с X (причиной)
  2. Коррелирует с Y (следствием)
  3. Не является результатом X (иначе она — медиатор)

Результат: кажется, что X → Y, но на самом деле C → X и C → Y.

Классический пример: мороженое и утопления

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from scipy.stats import pearsonr

np.random.seed(42)

# Confounding variable: температура (лето)
temperature = np.random.uniform(10, 35, 100)  # 10-35°C

# Зависит от температуры
icecream_sales = temperature * 50 + np.random.normal(0, 100, 100)
drowning_deaths = temperature * 10 + np.random.normal(0, 20, 100)

# Вычислим корреляцию между мороженым и утоплениями
corr, p_value = pearsonr(icecream_sales, drowning_deaths)
print(f'Корреляция между мороженым и утоплениями: {corr:.3f}, p={p_value:.4f}')
# Результат: r ≈ 0.95, сильная корреляция!

# Но это НЕ означает, что мороженое вызывает утопления!
# Скрытая переменная TEMPERATURE объясняет обе корреляции

corr_icecream_temp, _ = pearsonr(icecream_sales, temperature)
corr_drowning_temp, _ = pearsonr(drowning_deaths, temperature)

print(f'\nКорреляция мороженого и температуры: {corr_icecream_temp:.3f}')
print(f'Корреляция утоплений и температуры: {corr_drowning_temp:.3f}')

# Визуализация DAG (Directed Acyclic Graph)
import matplotlib.patches as mpatches
from matplotlib.patches import FancyArrowPatch

fig, axes = plt.subplots(1, 2, figsize=(14, 5))

# График 1: Наивный взгляд
ax = axes[0]
ax.text(0.5, 0.8, 'мороженое', ha='center', fontsize=12, bbox=dict(boxstyle='round', facecolor='lightblue'))
ax.text(0.5, 0.2, 'утопления', ha='center', fontsize=12, bbox=dict(boxstyle='round', facecolor='lightcoral'))
ax.arrow(0.5, 0.75, 0, -0.45, head_width=0.05, head_length=0.05, fc='red', ec='red')
ax.text(0.55, 0.5, '❌ Ложная причинность', fontsize=11, color='red')
ax.set_xlim(0, 1)
ax.set_ylim(0, 1)
ax.axis('off')
ax.set_title('Наивная интерпретация')

# График 2: Истинная причинность
ax = axes[1]
ax.text(0.5, 0.8, 'температура (C)', ha='center', fontsize=12, bbox=dict(boxstyle='round', facecolor='yellow'))
ax.text(0.2, 0.2, 'мороженое', ha='center', fontsize=12, bbox=dict(boxstyle='round', facecolor='lightblue'))
ax.text(0.8, 0.2, 'утопления', ha='center', fontsize=12, bbox=dict(boxstyle='round', facecolor='lightcoral'))
ax.arrow(0.45, 0.75, -0.15, -0.45, head_width=0.05, head_length=0.05, fc='blue', ec='blue')
ax.arrow(0.55, 0.75, 0.15, -0.45, head_width=0.05, head_length=0.05, fc='blue', ec='blue')
ax.text(0.5, 0.5, '✓ Скрытая переменная\nобъясняет обе корреляции', fontsize=10, color='blue', ha='center')
ax.set_xlim(0, 1)
ax.set_ylim(0, 1)
ax.axis('off')
ax.set_title('Истинная причинность (DAG)')

plt.tight_layout()
plt.show()

Типы смещений при confounding

1. Positive Confounding

Смешивающая переменная усиливает наблюдаемую корреляцию.

# Пример: влияние образования на зарплату
# Истинный эффект: образование → зарплата (+)
# Confounding: интеллект → образование (+) и интеллект → зарплата (+)

# Результат: наблюдаемая корреляция образования и зарплаты больше,
# чем истинная, потому что интеллект оба усиливает

2. Negative Confounding

Смешивающая переменная ослабляет или даже маскирует корреляцию.

# Пример: курение и продолжительность жизни
# Истинный эффект: курение → смерть (-)
# Confounding: здоровье → отсутствие курения (+) и здоровье → долгожитие (+)

# Результат: истинный вредный эффект курения может быть занижен

Как контролировать confounder'ы?

1. Рандомизация (RCT)

Управляемое испытание случайно распределяет субъектов, уравнивая confounders между группами.

# Идеально: случайное распределение лечения
treatment = np.random.binomial(1, 0.5, n=1000)

# Теперь возможные confounder'ы одинаково распределены в обеих группах
treatment_group = data[data['treatment'] == 1]
control_group = data[data['treatment'] == 0]

# Средние confounders должны быть одинаковы
assert abs(treatment_group['age'].mean() - control_group['age'].mean()) < 1

2. Stratification (Стратификация)

Анализ отдельно для каждого уровня confounder'а.

# Измеряем эффект лечения, стратифицируя по возрасту

for age_group in ['young', 'middle', 'old']:
    subset = data[data['age_group'] == age_group]
    
    effect = subset[subset['treatment'] == 1]['outcome'].mean() - \
             subset[subset['treatment'] == 0]['outcome'].mean()
    
    print(f'Эффект в группе {age_group}: {effect:.3f}')

# Затем усредняем эффекты (Mantel-Haenszel test)

3. Regression Adjustment

Включение confounder'ов как контрольных переменных в регрессию.

import statsmodels.api as sm
from statsmodels.formula.api import ols

# Неправильно (без контроля):
model_biased = ols('outcome ~ treatment', data=data).fit()
print(model_biased.summary())

# Правильно (с контролем confounders):
model_adjusted = ols('outcome ~ treatment + age + education + health', data=data).fit()
print(model_adjusted.summary())

# Коэффициент treatment в adjusted модели даёт истинный эффект

4. Propensity Score Matching

Создание сравнимых групп на основе вероятности лечения.

from causalml.propensity import estimate_propensity_score
from sklearn.neighbors import NearestNeighbors

# 1. Оценить вероятность лечения (propensity score)
propensity_scores = estimate_propensity_score(
    X=data[['age', 'education', 'health']],
    y=data['treatment']
)

data['propensity_score'] = propensity_scores

# 2. Подобрать по propensity score
nn = NearestNeighbors(n_neighbors=1)
treated_idx = data[data['treatment'] == 1].index
control_idx = data[data['treatment'] == 0].index

for t_idx in treated_idx:
    ps_treated = data.loc[t_idx, 'propensity_score']
    
    # Найти ближайший control с похожим propensity score
    distances = abs(data.loc[control_idx, 'propensity_score'] - ps_treated)
    matched_control = distances.idxmin()
    
# Теперь сравниваем matched пары
effect = treated['outcome'].mean() - matched_control['outcome'].mean()

5. Instrumental Variables (Инструментальные переменные)

Использование переменной Z, которая влияет на X, но не на Y напрямую.

# Пример: влияние образования на зарплату
# Confounder: способности
# Инструмент Z: расстояние до университета (влияет на образование, но не на зарплату напрямую)

from statsmodels.sandbox.regression.gmm import IV2SLS

model_iv = IV2SLS(
    dependent=data['salary'],
    exog=data[['age', 'region']],
    endog=data[['education']],
    instruments=data[['distance_to_university']]
).fit()

print(model_iv.summary())

Как обнаружить confounding в данных?

# 1. Каузальный DAG анализ
# Нарисовать, какие переменные влияют на какие

# 2. Чувствительность анализ
# Варьировать предположения о величине confounding

from causalml.sensitivity import KieferSensitivityAnalysis

# 3. Сравнить коэффициент бивариатной и мультивариатной регрессии
coef_unadjusted = ols('Y ~ X', data=data).fit().params[1]
coef_adjusted = ols('Y ~ X + C', data=data).fit().params[1]

confounding_bias = coef_unadjusted - coef_adjusted
print(f'Смещение из-за confounding: {confounding_bias:.3f}')

if abs(confounding_bias) > 0.1:  # пороговое значение
    print('Значительное confounding обнаружено!')

На практике

Confounder'ы — основная проблема при интерпретации корреляций как причинности. В ML моделях это приводит к:

  • Неправильным предсказаниям при изменении distribution
  • Неправильной интерпретации feature importance
  • Ошибочным выводам при A/B тестировании

Всегда спрашивайте: "Есть ли скрытая переменная, объясняющая обе корреляции?" Это главный способ защиты от конфаундинга.

Что такое confounding variable (смешивающая переменная)? | PrepBro