- BrainTools - https://www.braintools.ru -
Бабушкин телевизор работает уже сорок лет. Дед ездит на жигулях, видевших мороженое за пять копеек. Отцовский перфоратор пережил десяток ремонтов – и хоть бы что. А твой новый ноутбук умер на третий год, смартфон не держит заряд к концу дня, и Cloudflare лёг третий раз за месяц.
Раньше делали на совесть. Это же очевидно.
Нет.
Почему же нет? Ответ кроется буквально у нас под носом. Если понаблюдать за своим бытом, то сразу можно заметить, что есть у людей привычка выбрасывать нерабочие и ненужные вещи, вследствие которой нас окружают только те, что хоть как-то функционируют. Таким образом мы совершенно внезапно, в своём обиходе, встречаемся с одной из фундаментальных ошибок статистики – Collider bias.
Для выяснения деталей, давайте на время из быта перейдём в область формализма.
Нет ничего проще, чем увидев древнюю, чудом уцелевшую детальку кивнуть головой и многозначительно сказать: “Ну, вещь”.
Куда сложнее ответить на вопрос: “Является ли её сохранность следствием качества из былых времён?”.
Или же: “Каковы причины качества найденной детальки?”
Заметили слова “следствие”, “причина”? Именно о них мы и продолжим разговор.
Причинно-следственный анализ – отвечает на вопрос почему что-то происходит и что можно сделать чтобы изменить результат.
Казалось бы – всё просто. Берём x. Берём y. Строим график зависимости y от x. Если график идёт вправо и вверх – значит, чем больше “x” возьмёшь тем больше “y” получишь. Задача решена.
Была бы, если бы мы изучали линейные функции. В области аналитики данных реального мира встретить линейные зависимости – счастье и редкость. В большинстве случаев мы даже не можем утверждать, что связь между переменными существует. Конфаундеры, обратные зависимости и прочие чудеса статистики не дают нам возможности делать простых выводов. Все осветить в рамках одной статьи трудно, однако нам сейчас важно одно: связи между переменными бывают ложными. И один из самых коварных способов получить такую ложную связь — коллайдер.
Коллайдер – переменная, являющаяся следствием других переменных. То есть та, в которой два и более причинных пути сходятся. Зачастую коллайдерами становятся разного рода результаты: последствия действий, выводы, искомые значения.
Формально: если есть A и B, то коллайдер это такая переменная C, которая зависит и от A и от B
A ⇨ C ⇦ B
Возвращаясь же к “деталькам”:
Возраст ⇨ Употребление в быту ⇦ Качество
Коллайдеры легко найти вокруг себя. Вот более заезженный пример:
Ум ⇨ Поступление в ВШЭ ⇦ Богатые родители
Или же:
Талант ⇨ Успех ⇦ Трудолюбие
Опасность коллайдера в том, что его интуитивно хочется учесть в анализе — и именно это открывает ложную связь там, где её нет.
Одним из вариантов “учёта в анализе” можно назвать условие по переменной. Чем же оно опасно в случае с коллайдером, и почему на нём нельзя уславливаться?
Прежде чем мы рассмотрим, как так получается я позволю себе сделать ряд утверждений без доказательств. Предположим у нас всё же есть данные из примеров выше. Мы построили график и сделали выводы:
Среди абитуриентов, поступивших в ВШЭ богатые тупее умных.
Среди успешных людей талантливые – ленивые, трудолюбивые – бесталанны.
При этом взяв статистику по популяции мы таких выводов больше сделать не сможем (или они будут сильно менее выраженными).
Обратите внимание [1], что фраза “Среди” в данном случае и является ранее упомянутой условностью. Однако всё ещё не понятно а что происходит с данными. Почему ранее несвязные переменные становятся отрицательно скоррелированными?
Откуда же появляется ложная связь? Давайте разбираться.
Одним из наиболее честных вариантов демонстрации статистических ошибок я вижу в их симуляции на случайных числах. Внутри случайных чисел нет закономерностей, корреляции, по закону больших чисел, стремятся к нулю и появление закономерностей даст нам чёткий сигнал о нарушении их свойств.
Проведём простой эксперимент на Python. Для симуляции используем 1000 точек, значения A, B от 0 до 100 и пороги для определения коллайдера = 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
Мы возьмём 2 множества случайных чисел, A и B. Очевидно что связи между ними быть не может.
На данный момент одна запись нашего датасетта выглядит как значение A и B. Это могли бы быть выраженности различных симптомов пациента, зарплата и KPI сотрудника, успех и трудолюбие. Но в нашем случае просто A и B.
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})
А также создадим третью переменную значение которой будет зависеть от наших двух раннее сгенерированных переменных. Сие есть прямое определение коллайдера. Создавать переменную flag мы будем с помощью условия “ИЛИ”. Или A больше некоторого порога, или B больше некоторого порога. Если запись в нашем датасетте удовлетворяющее хотя бы одному из условий, то Flag – “да” если ни одному, то – “нет”.
data['flag'] = (data['a'] > A_THRESHOLD) | (data['b'] > B_THRESHOLD) # Условие "или"!
Посчитаем корреляцию среди всех данных. Это настоящие свойства данных A, B.
print(f"Корреляция между A и B: {data['a'].corr(data['b']):.2f}")
Корреляция между A и B: 0.01
Закономерно, она приблизительно равна нулю. Данные случайны и независимы.
А теперь условимся по коллайдеру, и посмотрим корреляцию для тех значений чей flag – “да”.
print(f"Корреляция между только теми A и B где истинный флаг: {data[data['flag']]['a'].corr(data[data['flag']]['b']):.2f}")
Корреляция между только теми A и B где истинный флаг: -0.34
Корреляция около – 0,3. Это значительная корреляция, говорящая об умеренной связности между переменными, и она может привести к неверным выводам, несоответствующим действительности.
Давайте визуализируем данные и посмотрим с чем связанно появление ложной корреляции.
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', label='General Trend')
plt.legend()
plt.show()

Красным показаны данные удовлетворяющее хотя бы одному условию. Синим – ни одному. Чёрная линия – линия тренда на всех данных. Она показывает тенденции развития величины. Так как величина случайна линия тренда горизонтальна. Данные по закону больших чисел компенсируют сами себя.
Давайте же применим условие и “вырежем” данные не удовлетворяющее условию.
sns.lmplot(data=data[data['flag']], x='a', y='b',
scatter_kws={'alpha': 0.5}, line_kws={'color': 'Black'})

Линия тренда наклонилась, вслед за искусственно созданной нами отрицательной зависимостью. Это видно по оставшимся данным, вырезав точки, не удовлетворяющие обоим условиям, мы нарушили “равновесие”, и правая половина графика “перевесила” левую. Отсюда и отрицательная корреляция и ложная связность.
Это и есть проявление смещения, вследствие фиксации коллайдера. Так, если бы мы без задней мысли анализировали эти данные, то получили что то вроде такого графика:
filtered = data[data['flag']]
smoothed = lowess(filtered['b'], filtered['a'], frac=0.8)
plt.figure()
plt.plot(smoothed[:, 0], smoothed[:, 1], color='black', linewidth=2)
plt.xlabel('a')
plt.ylabel('b')
plt.tight_layout()
plt.show()

Но ни направление графика, ни его сглаженные изгибы не отражают природы данных.
Возможно, вы справедливо заметите, что данные не отражают реальных ситуаций. Мало кто анализирует абсолютно случайные, равномерные величины, ведь в этом попусту нет практической пользы (они сущностно представляют и себя белый шум). Поэтому заменим равномерное распределение нормальным, гауссовским. Оно встречается в природе повсеместно.
Вторая проблема данных – жёсткие условия отсечения. Сделаем их вероятностными, как в жизни. Не все же кто предрасположен к раку обязательно от него умрут. Есть ещё грузовик, кирпич на крыше и т.д.
Что ж, давайте посмотрим, как collider bias проявит себя на данных, значительно приближенных к реальности.
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})
sns.scatterplot(data=normal_data, x='a', y='b', alpha=0.5)
sns.regplot(data=normal_data, x='a', y='b', scatter=False, color='black', label='General Trend')

Видим типичную картину нормального распределения, тренд ожидаемо нулевой (параллелен горизонтали).
Разобьём данные на 2 группы используя вероятностный метод.
# Нормализуем значения, приводим к диапазону (0, 1)
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
sns.scatterplot(data=normal_data, x='a', y='b', hue='flag',
palette={True: 'red', False: 'blue'}, alpha=0.5)
plt.title('Вероятностная выборка коллайдера')
plt.show()
print(f"Корреляция во всём датасете: {normal_data['a'].corr(normal_data['b']):.2f}")
print(f"Корреляция, когда флаг истинный: {normal_data[normal_data['flag']]['a'].corr(normal_data[normal_data['flag']]['b']):.2f}")

Корреляция во всём датасете: 0.03
Корреляция, когда флаг истинный: -0.09
За счёт добавления вероятностного выбора коллайдера разница между истинной и ложной корреляцией упала, в сравнении с наивным примером выше, и теперь находится на низких значениях. Но даже такие отклонения в области, например, фармакологии, опасны.
Также, стоит учитывать, что в реальности ошибка [2] может давать ещё большее влияние на данные.
sns.lmplot(data=normal_data[normal_data['flag']], x='a', y='b',
scatter_kws={'alpha': 0.5}, line_kws={'color': 'Black'})

Наш график после удаления “неистинных” (на прошлом графике – синих) точек. Линия тренда очевидно наклонилась – очевидный признак смещения.
Итак, мы увидели конкретный механизм: стоит обусловиться по коллайдеру — и независимые переменные начинают «тянуть» друг друга. Этот эффект не очевиден, потому что фильтровать данные, кажется, разумным действием. Именно поэтому коллайдер опаснее многих других ошибок — он прячется за здравый смысл. Практический вывод один: прежде чем делить выборку, нарисуй причинную диаграмму и спроси — это коллайдер?
Вернёмся же к телевизорам из начала статьи. Искушённый читатель заметил, что приведённый пример является классическим проявлением Ошибки выжившего. Однако в данных она реализуется именно как коллайдер, с условностью на выживании. Кстати, через тот же механизм реализуются парадокс [3] Берксона и Selection Bias.
Таким образом, на пальцах, мы подтвердили постулат причинно-следственного анализа:
Из корреляции не следует каузация.
Даже красивый график может быть лишь ошибкой анализа.
Притом эффект присутствует везде – от госпитализации в медицине до отбора в образовании. Поэтому понимание подобных механизмов, по моему мнению, может помочь в постановке гипотез, вынесении теорий и формировании стратегий. Возможно, именно принятие во внимание условности по коллайдеру когда-то убережёт вас от ошибочных решений или суждений.
Мой Kaggle Nootebook с кодом симуляции [4], если захотите поэкспериментировать сами.
Спасибо за внимание. Это моя первая статья, поэтому фидбэк был бы крайне полезен.
Так же может быть интересно:
DAG, причинные диаграммы
Конфаундеры, медиаторы
Автокорреляции(как ещё один распространённый пример ошибок)
Рекомендую статьи для углубления:
Автор: Toshi_im
Источник [6]
Сайт-источник BrainTools: https://www.braintools.ru
Путь до страницы источника: https://www.braintools.ru/article/27675
URLs in this post:
[1] внимание: http://www.braintools.ru/article/7595
[2] ошибка: http://www.braintools.ru/article/4192
[3] парадокс: http://www.braintools.ru/article/8221
[4] Мой Kaggle Nootebook с кодом симуляции: https://www.kaggle.com/code/gpugobrrr/collider-bias
[5] Причинно-следственный анализ в машинном обучении: https://habr.com/ru/companies/ods/articles/544208/
[6] Источник: https://habr.com/ru/articles/1014586/?utm_campaign=1014586&utm_source=habrahabr&utm_medium=rss
Нажмите здесь для печати.