Корелация и причина
Накратко: Корелацията показва, че две неща се движат заедно, но не че едното причинява другото; трета причина или парадоксът на Симпсън могат да обърнат извода.
Проблем
„Банерът вдига поръчките“
Маркетингът сложи банер „Безплатна доставка“. Не беше тест: банерът се показваше по преценка. Дневникът има 10 000 посещения: устройство, видял ли е банера (saw_banner), поръчал ли е:
С банер поръчват 5,66% от посещенията, без банер 3,63%. Маркетингът иска да го покаже на всички: разликата е 2 пункта, а с банер поръчките са с 56% повече.
Но дали банерът я причинява?
Идеята
Кой видя банера?
Преди да вярваш на разликата, виж кой е във всяка група:
От 3 000 посещения от компютър банерът са видели 2 400. От 7 000 от телефон само 1 400. А компютрите поръчват по-често по принцип: 7,70% срещу 2,99%.
Групата „с банер“ е пълна с компютри, а „без банер“ с телефони. Така сравняваш не банер с без банер, а най-вече компютър с телефон. Устройството е объркващ фактор (confounder): то влияе и на това кой вижда банера, и на това кой поръчва.
Идеята
Раздели по устройство
Сравни банера вътре във всяко устройство. Тогава устройството е едно и също от двете страни:
На компютър с банер поръчват 7,50%, без банер 8,50%. На телефон 2,50% срещу 3,11%. Банерът е по-слаб и на двете устройства, а общо изглеждаше по-силен. Това се казва парадокс на Симпсън: посоката за всички заедно е обратна на посоката във всяка група.
По-точно, за любопитните
На всяко устройство поотделно разликата е в рамките на шума: 600 посещения от компютър без банер са малко.
Затова честният извод не е „банерът вреди“, а „няма доказателство, че банерът помага“. Обратната посока общо идва изцяло от сместа от устройства.
Пробвай
Сметни парадокса
Запиши в gap разликата в конверсията с банер − без банер за всяко устройство, в пунктове, а в overall_gap същата разлика за всички посещения заедно.
Решение
import pandas as pd
banner = pd.read_csv('banner_visits.csv')
rates = banner.pivot_table(index='device', columns='saw_banner', values='ordered', aggfunc='mean') * 100
gap = rates[1] - rates[0]
overall = banner.groupby('saw_banner')['ordered'].mean() * 100
overall_gap = overall[1] - overall[0]
print(gap.round(2))
print(round(overall_gap, 2))rates[1] - rates[0] от таблицата устройство × банер дава −1,00 пункта на компютър и −0,61 на телефон. За всички заедно разликата е +2,03 пункта. Двете числа на устройствата са отрицателни, а общото е положително. Ако делиш по банер без устройството, виждаш само +2,03.
Идеята
Защо общото се обръща
Общата конверсия на група е средна на устройствата, претеглена с това колко посещения от всяко има в групата:
С банер 63% от посещенията са от компютър, без банер само 10%. Затова групата с банер получава много от високата конверсия на компютрите: (180 + 35) поръчки от 3 800 посещения са 5,66%. Групата без банер е почти само телефони: (51 + 174) от 6 200 са 3,63%.
Общата разлика не идва от банера. Идва от това, че е показван там, където хората така или иначе поръчват повече.
Идеята
Корелация: corr
Същата грешка дебне и в числата за годината. Ето продадените бройки по седмици: напитки (минерална вода и портокалов сок) и зеленчуци (домати и краставици). Колко вървят заедно?
Корелацията е число от −1 до 1. Близо до 1: когато едното е голямо, и другото е голямо, точките лежат около права нагоре. Около 0: няма такава връзка. Близо до −1: едното расте, другото пада. a.corr(b) я смята, а df.corr() дава всички двойки колони наведнъж.
Тук е 0,46: в седмиците с повече зеленчуци се продават и повече напитки. Значи ли това, че зеленчуците продават напитки?
Идеята
Третото нещо: сезонът
Раздели седмиците на лятото (юни–август) и останалите:
През лятото средно на седмица се продават 121,6 напитки и 68,2 зеленчука, през останалото време 81,5 и 38,5. Лятото вдига и двете. Вътре в лятото обаче корелацията е −0,13, а в останалите седмици −0,15: около нула. Връзката идва от сезона, а не от зеленчуците.
Идеята
Защо A/B тестът е друго
В предния урок новата кошница беше по-добра и това беше причина, а не само връзка. Разликата с банера е една: кой решава кой какво вижда.
- В A/B теста решава случайността. И в двете групи компютрите бяха по 35%. Всичко, което не мериш, също е разпределено поравно.
- При банера решава някой. Той го показва най-вече на компютър, и групите се различават по устройството, а може би и по още неща, които не са в таблицата.
Разделянето по устройство оправя само фактора, който знаеш и си записал. Случайното разпределение оправя и тези, които не знаеш. Затова, когато можеш, пускаш тест.
Идеята
Как да го кажеш честно
Данни, които са само събрани, а не идват от тест, се казват наблюдателни. От тях можеш да кажеш „вървят заедно“, не „едното води до другото“. Сравни:
- „Банерът вдига поръчките с 2 пункта.“ Това е твърдение за причина, което данните не дават.
- „Посещенията с банер поръчват по-често, но най-вече защото са от компютър. На всяко устройство поотделно банерът не помага. За отговор ни трябва A/B тест.“
Преди да вярваш на разлика в наблюдателни данни, питай:
- Кой попада във всяка група и защо?
- Има ли трето нещо (устройство, сезон, тип клиент), което движи и двете?
- Обръща ли се разликата, ако разделиш по него?
Упражнение
Честно сравнение
Сравни банера честно: каква е конверсията с банер и без банер, ако и двете групи имаха сместа от устройства на всички посещения? Запиши в desktop_share дела на компютрите във всички посещения, а в with_banner и without_banner претеглените конверсии в проценти.
Решение
import pandas as pd
banner = pd.read_csv('banner_visits.csv')
rates = banner.pivot_table(index='device', columns='saw_banner', values='ordered', aggfunc='mean') * 100
desktop_share = (banner['device'] == 'desktop').mean()
with_banner = desktop_share * rates.loc['desktop', 1] + (1 - desktop_share) * rates.loc['mobile', 1]
without_banner = desktop_share * rates.loc['desktop', 0] + (1 - desktop_share) * rates.loc['mobile', 0]
print(round(desktop_share, 2), round(with_banner, 2), round(without_banner, 2))Компютрите са 30% от всички посещения. С банер: 0,3 × 7,5 + 0,7 × 2,5 = 4,00%. Без банер: 0,3 × 8,5 + 0,7 × 3,11 ≈ 4,72%. При еднаква смес банерът е по-слаб, а не по-силен. Средните groupby('saw_banner') дават 5,66% и 3,63%, защото всяка група има своя смес.
Подаръчната карта и тиганът се продават заедно: седмиците с много карти са и с много тигани. Запиши в overall корелацията им за всички седмици, а в outside корелацията без празничните седмици (маската holidays: от 1 декември до 4 януари).
Решение
import pandas as pd
orders = pd.read_csv('year_orders.csv', parse_dates=['ordered_at'])
items = pd.read_csv('year_items.csv')
sold = orders[orders['status'] != 'cancelled']
items = items.merge(sold[['id', 'ordered_at']], left_on='order_id', right_on='id').set_index('ordered_at')
gift = items[items['product_id'] == 25]['quantity'].resample('W').sum()
pan = items[items['product_id'] == 22]['quantity'].resample('W').sum()
weekly = pd.DataFrame({'gift': gift, 'pan': pan}).fillna(0).iloc[1:-1]
holidays = (weekly.index >= '2025-12-01') & (weekly.index <= '2026-01-04')
overall = weekly['gift'].corr(weekly['pan'])
outside = weekly[~holidays]['gift'].corr(weekly[~holidays]['pan'])
print(round(overall, 2), round(outside, 2))За всички седмици корелацията е 0,77. Без петте празнични седмици е −0,02: нищо. Картата не продава тигани и тиганът не продава карти. Коледа вдига и двете: през празничните седмици средно се продават 21,6 карти и 11,2 тигана на седмица, през останалите 1,7 и 2,7.
Тест
Провери се
Свързани уроци
Раздел „Свързани уроци“- Нужно отпреди: Първата графика · Разпределения · Проверка на хипотеза · A/B тест
- Продължава в: Линейна регресия · Защо оборотът падна?
- Задачи по темата: Статистика за решения