Collider bias: jak filtrowanie danych tworzy fałszywe korelacje
Często filtrowanie danych według wyniku prowadzi do mylących wniosków: zmienne niezależne zaczynają się negatywnie korelować. To właśnie collider bias — efekt, gdy warunkowanie na zmiennych typu "kolizja" zniekształca statystyki. W praktyce pojawia się przy analizie przeżywających obiektów, sukcesów czy wyfiltrowanych próbek. Symulacja w Pythonie pokazuje mechanizm na losowych danych.
Definicja kolizji
Kolizja to zmienna C, do której prowadzą ścieżki przyczynowe od A i B: A → C ← B. Warunkowanie na C (filtracja lub stratyfikacja) otwiera fałszywą zależność między A i B.
Przykłady z praktyki:
- Wiek → Używanie → Jakość (przeżyłe przedmioty wydają się lepsze).
- Inteligencja → Wejście do uczelni → Bogactwo rodziców (średnio wśród studentów bogaci wydają się mniej inteligentni).
- Talent → Sukces → Pracowitość (udane osoby z talentem wydają się leniwe).
W data science prowadzi to do błędnych modeli: gradient boosting na wyfiltrowanych danych dziedziczy ten błąd.
Symulacja na danych jednostajnych
Generujemy 1000 punktów A i B z rozkładu uniform(0,100), niezależnych. Kolizja flag = (A > 50) | (B > 50).
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import seaborn as sns
from statsmodels.nonparametric.smoothers_lowess import lowess
NUMBER_OF_VALUES = 1000
A_RANGE = (0, 100)
B_RANGE = (0, 100)
A_THRESHOLD = 50
B_THRESHOLD = 50
a = np.random.uniform(*A_RANGE, NUMBER_OF_VALUES)
b = np.random.uniform(*B_RANGE, NUMBER_OF_VALUES)
data = pd.DataFrame({'a': a, 'b': b})
data['flag'] = (data['a'] > A_THRESHOLD) | (data['b'] > B_THRESHOLD)
print(f'Korelacja między A i B: {data["a"].corr(data["b"]):.2f}')
print(f'Korelacja dla flag == True: {data[data["flag"]]["a"].corr(data[data["flag"]]["b"]):.2f}')
Wynik:
- Całość: corr ≈ 0.00
- Wyfiltrowana: corr ≈ -0.34
Wykres rozrzutu pokazuje, jak usunięcie "nieprzeżyłych" punktów (niebieskie) przesuwa trend w dół.
sns.scatterplot(data=data, x='a', y='b', hue='flag', palette={True: 'red', False: 'blue'}, alpha=0.5)
sns.regplot(data=data, x='a', y='b', scatter=False, color='black')
plt.show()
Po filtracji lmplot pokazuje ujemny nachylenie: prawy górny róg "pusty", równowaga naruszona.
Realistyczny scenariusz: rozkład normalny
Przechodzimy do np.random.normal: A ~ N(50,5), B ~ N(30,5). Kolizja probabilistyczna: normalizujemy, selection_prob = max(a_norm, b_norm)*1.5, clip(0,1), flag = rand < prob.
a = np.random.normal(loc=50, scale=5, size=NUMBER_OF_VALUES)
b = np.random.normal(loc=30, scale=5, size=NUMBER_OF_VALUES)
normal_data = pd.DataFrame({'a': a, 'b': b})
a_norm = (normal_data['a'] - normal_data['a'].min()) / (normal_data['a'].max() - normal_data['a'].min() + 1e-6)
b_norm = (normal_data['b'] - normal_data['b'].min()) / (normal_data['b'].max() - normal_data['b'].min() + 1e-6)
selection_prob = np.maximum(a_norm, b_norm) * 1.5
selection_prob = np.clip(selection_prob, 0, 1)
normal_data['flag'] = np.random.random(len(normal_data)) < selection_prob
print(f'Korelacja w całym zbiorze: {normal_data["a"].corr(normal_data["b"]):.2f}')
print(f'Korelacja gdy flag == True: {normal_data[normal_data["flag"]]["a"].corr(normal_data[normal_data["flag"]]["b"]):.2f}')
Wynik:
- Całość: corr ≈ 0.03
- Wyfiltrowana: corr ≈ -0.09
Efekt słabszy, ale istotny w A/B testach, farmach lub ML.
Jak uniknąć w analizie
- Rysuj DAG: grafy przyczynowe (directed acyclic graphs) pomagają wykryć kolizje.
- Nie filtrować po wyniku: analizuj pełną populację lub użyj IPCW (inverse probability censoring weighting).
- Sprawdzaj korelacje: porównuj dane przed i po stratyfikacji.
- Zmienne instrumentalne: do wnioskowania przyczynowego.
- Sieci bayesowskie: modelują zależności jawnie.
Kluczowe informacje
- Collider bias pojawia się przy warunkowaniu na wspólnej przyczynie — tworzy fałszywą negatywną korelację.
- Symulacja na rozkładzie jednostajnym daje corr -0.34, na normalnym -0.09; efekt skaluje się.
- Powiązany z błędem przeżywającego, paradoksem Berksa, biasem selekcji.
- Sprawdź: narysuj DAG przed filtrowaniem.
- W ML: wyfiltrowane dane → modele z biasem, niedoszacowanie/przeszacowanie cech.
— Editorial Team
Brak komentarzy.