Зависимости, которых не существует: как фильтрация искажает данные и что такое коллайдеры. python.. python. selection bias.. python. selection bias. каузальный анализ.. python. selection bias. каузальный анализ. коллайдер.. python. selection bias. каузальный анализ. коллайдер. корреляция.. python. selection bias. каузальный анализ. коллайдер. корреляция. ошибка выжившего.. python. selection bias. каузальный анализ. коллайдер. корреляция. ошибка выжившего. причинно-следственный анализ.. python. selection bias. каузальный анализ. коллайдер. корреляция. ошибка выжившего. причинно-следственный анализ. симуляция.. python. selection bias. каузальный анализ. коллайдер. корреляция. ошибка выжившего. причинно-следственный анализ. симуляция. статистика.. python. selection bias. каузальный анализ. коллайдер. корреляция. ошибка выжившего. причинно-следственный анализ. симуляция. статистика. статистические ошибки.

Бабушкин телевизор работает уже сорок лет. Дед ездит на жигулях, видевших мороженое за пять копеек. Отцовский перфоратор пережил десяток ремонтов – и хоть бы что. А твой новый ноутбук умер на третий год, смартфон не держит заряд к концу дня, и Cloudflare лёг третий раз за месяц.
Раньше делали на совесть. Это же очевидно.

Нет.

Почему же нет? Ответ кроется буквально у нас под носом. Если понаблюдать за своим бытом, то сразу можно заметить, что есть у людей привычка выбрасывать нерабочие и ненужные вещи, вследствие которой нас окружают только те, что хоть как-то функционируют. Таким образом мы совершенно внезапно, в своём обиходе, встречаемся с одной из фундаментальных ошибок статистики – Collider bias.

Для выяснения деталей, давайте на время из быта перейдём в область формализма.

Причинно – следственный анализ


Нет ничего проще, чем увидев древнюю, чудом уцелевшую детальку кивнуть головой и многозначительно сказать: “Ну, вещь”.

Куда сложнее ответить на вопрос: “Является ли её сохранность следствием качества из былых времён?”.

Или же: “Каковы причины качества найденной детальки?”

Заметили слова “следствие”, “причина”? Именно о них мы и продолжим разговор.

Причинно-следственный анализ – отвечает на вопрос почему что-то происходит и что можно сделать чтобы изменить результат.

Казалось бы – всё просто. Берём x. Берём y. Строим график зависимости y от x. Если график идёт вправо и вверх – значит, чем больше “x” возьмёшь тем больше “y” получишь. Задача решена.

Была бы, если бы мы изучали линейные функции. В области аналитики данных реального мира встретить линейные зависимости – счастье и редкость. В большинстве случаев мы даже не можем утверждать, что связь между переменными существует. Конфаундеры, обратные зависимости и прочие чудеса статистики не дают нам возможности делать простых выводов. Все осветить в рамках одной статьи трудно, однако нам сейчас важно одно: связи между переменными бывают ложными. И один из самых коварных способов получить такую ложную связь — коллайдер.

Коллайдер – переменная, являющаяся следствием других переменных. То есть та, в которой два и более причинных пути сходятся. Зачастую коллайдерами становятся разного рода результаты: последствия действий, выводы, искомые значения.

Формально: если есть A и B, то коллайдер это такая переменная C, которая зависит и от A и от B

A ⇨ C ⇦ B

Возвращаясь же к “деталькам”:

Возраст ⇨ Употребление в быту ⇦ Качество

Коллайдеры легко найти вокруг себя. Вот более заезженный пример:

Ум ⇨ Поступление в ВШЭ ⇦ Богатые родители

Или же:

Талант ⇨ Успех ⇦ Трудолюбие

Опасность коллайдера в том, что его интуитивно хочется учесть в анализе — и именно это открывает ложную связь там, где её нет.

Одним из вариантов “учёта в анализе” можно назвать условие по переменной. Чем же оно опасно в случае с коллайдером, и почему на нём нельзя уславливаться?

Прежде чем мы рассмотрим, как так получается я позволю себе сделать ряд утверждений без доказательств. Предположим у нас всё же есть данные из примеров выше. Мы построили график и сделали выводы:
Среди абитуриентов, поступивших в ВШЭ богатые тупее умных.
Среди успешных людей талантливые – ленивые, трудолюбивые – бесталанны.

При этом взяв статистику по популяции мы таких выводов больше сделать не сможем (или они будут сильно менее выраженными).

Обратите внимание, что фраза “Среди” в данном случае и является ранее упомянутой условностью. Однако всё ещё не понятно а что происходит с данными. Почему ранее несвязные переменные становятся отрицательно скоррелированными?
Откуда же появляется ложная связь? Давайте разбираться.

Одним из наиболее честных вариантов демонстрации статистических ошибок я вижу в их симуляции на случайных числах. Внутри случайных чисел нет закономерностей, корреляции, по закону больших чисел, стремятся к нулю и появление закономерностей даст нам чёткий сигнал о нарушении их свойств.

Симуляция эффекта


Проведём простой эксперимент на 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()
Зависимости, которых не существует: как фильтрация искажает данные и что такое коллайдеры - 1

Красным показаны данные удовлетворяющее хотя бы одному условию. Синим – ни одному. Чёрная линия – линия тренда на всех данных. Она показывает тенденции развития величины. Так как величина случайна линия тренда горизонтальна. Данные по закону больших чисел компенсируют сами себя.

Давайте же применим условие и “вырежем” данные не удовлетворяющее условию.

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

Линия тренда наклонилась, вслед за искусственно созданной нами отрицательной зависимостью. Это видно по оставшимся данным, вырезав точки, не удовлетворяющие обоим условиям, мы нарушили “равновесие”, и правая половина графика “перевесила” левую. Отсюда и отрицательная корреляция и ложная связность.

Это и есть проявление смещения, вследствие фиксации коллайдера. Так, если бы мы без задней мысли анализировали эти данные, то получили что то вроде такого графика:

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()
Зависимости, которых не существует: как фильтрация искажает данные и что такое коллайдеры - 3

Но ни направление графика, ни его сглаженные изгибы не отражают природы данных.


Возможно, вы справедливо заметите, что данные не отражают реальных ситуаций. Мало кто анализирует абсолютно случайные, равномерные величины, ведь в этом попусту нет практической пользы (они сущностно представляют и себя белый шум). Поэтому заменим равномерное распределение нормальным, гауссовским. Оно встречается в природе повсеместно.

Вторая проблема данных – жёсткие условия отсечения. Сделаем их вероятностными, как в жизни. Не все же кто предрасположен к раку обязательно от него умрут. Есть ещё грузовик, кирпич на крыше и т.д.

Что ж, давайте посмотрим, как 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')
Зависимости, которых не существует: как фильтрация искажает данные и что такое коллайдеры - 4

Видим типичную картину нормального распределения, тренд ожидаемо нулевой (параллелен горизонтали).

Разобьём данные на 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}")
Зависимости, которых не существует: как фильтрация искажает данные и что такое коллайдеры - 5
Корреляция во всём датасете: 0.03
Корреляция, когда флаг истинный: -0.09

За счёт добавления вероятностного выбора коллайдера разница между истинной и ложной корреляцией упала, в сравнении с наивным примером выше, и теперь находится на низких значениях. Но даже такие отклонения в области, например, фармакологии, опасны.

Также, стоит учитывать, что в реальности ошибка может давать ещё большее влияние на данные.

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

Наш график после удаления “неистинных” (на прошлом графике – синих) точек. Линия тренда очевидно наклонилась – очевидный признак смещения.

Выводы


Итак, мы увидели конкретный механизм: стоит обусловиться по коллайдеру — и независимые переменные начинают «тянуть» друг друга. Этот эффект не очевиден, потому что фильтровать данные, кажется, разумным действием. Именно поэтому коллайдер опаснее многих других ошибок — он прячется за здравый смысл. Практический вывод один: прежде чем делить выборку, нарисуй причинную диаграмму и спроси — это коллайдер?


Вернёмся же к телевизорам из начала статьи. Искушённый читатель заметил, что приведённый пример является классическим проявлением Ошибки выжившего. Однако в данных она реализуется именно как коллайдер, с условностью на выживании. Кстати, через тот же механизм реализуются парадокс Берксона и Selection Bias.

Таким образом, на пальцах, мы подтвердили постулат причинно-следственного анализа:

Из корреляции не следует каузация.

Даже красивый график может быть лишь ошибкой анализа.

Притом эффект присутствует везде – от госпитализации в медицине до отбора в образовании. Поэтому понимание подобных механизмов, по моему мнению, может помочь в постановке гипотез, вынесении теорий и формировании стратегий. Возможно, именно принятие во внимание условности по коллайдеру когда-то убережёт вас от ошибочных решений или суждений.

Мой Kaggle Nootebook с кодом симуляции, если захотите поэкспериментировать сами.

Спасибо за внимание. Это моя первая статья, поэтому фидбэк был бы крайне полезен.

Так же может быть интересно:

  • DAG, причинные диаграммы

  • Конфаундеры, медиаторы

  • Автокорреляции(как ещё один распространённый пример ошибок)

Рекомендую статьи для углубления:

Автор: Toshi_im

Источник

Rambler's Top100