Statistics Basics

Кореляція та взаємозв'язки

Коваріація, коефіцієнт кореляції Пірсона, матриці кореляції, візуалізація та критичне розуміння зв'язків між змінними

Кореляція та взаємозв'язки

🎯 Мета підтеми

  • Зрозуміти різницю між коваріацією та кореляцією
  • Опанувати коефіцієнт кореляції Пірсона
  • Навчитися інтерпретувати кореляційні матриці
  • Візуалізувати зв'язки через scatter plots та heatmaps
  • Усвідомити критичний принцип: кореляція ≠ причинність

🔑 Ключові терміни

  • Коваріація (covariance) — міра спільної мінливості двох змінних
  • Кореляція (correlation) — нормалізована коваріація, від −1 до +1
  • Коефіцієнт Пірсона (Pearson's r) — найпоширеніша міра лінійного зв'язку
  • Матриця кореляції (correlation matrix) — таблиця всіх парних кореляцій
  • Кореляція ≠ причинність — золоте правило інтерпретації

Історія про зріст і вагу

Уявіть: ви збираєте дані про студентів вашого університету. Фіксуєте зріст (см) та вагу (кг) кожного з 100 студентів.

Інтуїтивно зрозуміло: вищі люди зазвичай важчі. Але як це описати математично? Як виміряти силу цього зв'язку?

height_weight_intro.ipynb
Python 3 (ipykernel)
Code
In [1]:
import numpy as np
import matplotlib.pyplot as plt

rng = np.random.default_rng(seed=42)

# Генеруємо реалістичні дані зі зв'язком
height = rng.normal(170, 10, 100)  # Зріст: N(170, 10²)
weight = 0.8 * height + rng.normal(0, 5, 100) - 70  # Вага залежить від зросту + шум

# Візуалізація
plt.figure(figsize=(10, 6))
plt.scatter(height, weight, alpha=0.6, s=50, edgecolors='black', linewidths=0.5)
plt.xlabel('Зріст (см)', fontsize=12)
plt.ylabel('Вага (кг)', fontsize=12)
plt.title('Зв\'язок між зростом і вагою', fontweight='bold', fontsize=14)
plt.grid(alpha=0.3)
plt.show()

print("Статистика:")
print(f"  Зріст: μ = {height.mean():.1f} см, σ = {height.std():.1f} см")
print(f"  Вага:  μ = {weight.mean():.1f} кг, σ = {weight.std():.1f} кг")
print("\nВізуально помітно: точки утворюють хмару, що йде 'знизу-вліво до верху-вправо'")
print("Це сигнал ПОЗИТИВНОГО зв'язку: ↑зріст → ↑вага")
Out[1]:

Статистика:
  Зріст: μ = 169.5 см, σ = 7.7 см
  Вага:  μ = 65.5 кг, σ = 8.2 кг

Візуально помітно: точки утворюють хмару, що йде 'знизу-вліво до верху-вправо'
Це сигнал ПОЗИТИВНОГО зв'язку: ↑зріст → ↑вага

Питання:

  1. Чи зріст завжди передбачає вагу? Ні — спостерігається природний розкид.
  2. Чи зв'язок є лінійним? Наближено так: через хмару точок можна провести узагальнюючу пряму лінію.
  3. Наскільки сильний цей зв'язок? Потрібна об'єктивна числова міра!

Для цього в статистиці використовують поняття коваріації та кореляції.

Аналогія: Кореляція — це як «сила магнітного поля» між двома змінними. Позитивна кореляція означає, що коли одна змінна зростає, інша притягується і зростає слідом (як північний і південний полюси). Негативна кореляція — коли зростання однієї виштовхує іншу донизу. Нульова кореляція — повна байдужість змінних одна до одної (магнітне поле відсутнє).


Коваріація (Covariance)

Коваріація (від латинського co- — «разом», «спільно» та variance — «мінливість», «дисперсія») — це фундаментальна міра спільної мінливості, яка показує, як дві випадкові величини змінюються разом. Вона визначає загальний напрямок сумісної лінійної тенденції двох числових ознак: чи зростають вони одночасно, чи змінюються в протилежних напрямках, чи взагалі не демонструють лінійної залежності.

Інтуїтивна аналогія:
Уявіть двомісний байдарочний човен або дитячу гойдалку-балансир:

  • Позитивна коваріація: Обидва веслярі веслують в один бік — човен стрімко рухається вперед. Коли зусилля першого весляра перевищує його середній рівень, зусилля другого також перевищує середнє. Вони діють синхронно.
  • Негативна коваріація: Дитяча гойдалка-балансир: коли одна дитина злітає вгору вище точки рівноваги, друга обов'язково опускається вниз. Їхні рухи узгоджені, але мають суворо протилежні напрямки.
  • Нульова коваріація: Один весляр гребе, а другий пасажир поруч читає книгу або перевіряє пошту. Рівень зусиль першого ніяк не пов'язаний з тим, яку саме сторінку відкриває другий. Їхні відхилення від середнього повністю автономні.

Формула коваріації

Для генеральної сукупності (коли відомі абсолютно всі Завантаження... об'єктів або повна історія процесу):

Завантаження...

Для реальної вибірки (з корекцією Бесселя Завантаження..., яка компенсує втрату одного ступеня вільності через використання вибіркових середніх Завантаження... та Завантаження...):

Завантаження...

Анатомія формули: як вона працює під капотом?

Формула коваріації здається складною лише на перший погляд. Насправді вона виконує всього три послідовні математичні дії:

  1. Крок 1: Центрування (відхилення від «центру тяжіння»)
    Для кожної точки обчислюються зміщення відносно вибіркових середніх:
    Завантаження...

    Геометрично ми просто переносимо початок координат Завантаження... у центр мас нашої хмари точок — координату Завантаження.... Тепер важливі не абсолютні кілограми чи метри, а напрямок відхилення від типового середнього значення.
  2. Крок 2: Множення відхилень та правило знаків
    Для кожного спостереження ми перемножуємо отримані зміщення: Завантаження.... У цьому множенні криється вся магія знаку коваріації:
    • Якщо обидві змінні більші за середнє (Завантаження... і Завантаження...), маємо Завантаження....
    • Якщо обидві змінні менші за середнє (Завантаження... і Завантаження...), маємо Завантаження.... Зверніть увагу: якщо обидві величини одночасно впали нижче за свої середні, це також підтверджує узгодженість їхньої поведінки, тому внесок залишається строго додатним!
    • Якщо ж одна змінна виросла понад норму, а інша впала нижче середнього (Завантаження... або Завантаження...), добуток стає від'ємним Завантаження....
  3. Крок 3: Сумування та усереднення «голосів»
    Ми додаємо всі отримані добутки. Кожна точка є «виборцем»: вона голосує або за додатний зв'язок (квадранти I та III), або за від'ємний (квадранти II та IV). Якщо узгоджених точок більше і вони лежать далі від центру, сумарна сума додатна (Завантаження...). Якщо точки взаємно компенсують одна одну — сума прямує до нуля (Завантаження...).
Loading diagram...
@startuml
skinparam style plain
skinparam backgroundColor #FFFFFF
skinparam defaultTextAlignment center

rectangle "Розподіл простору відхилень навколо центру (x̄, ȳ)" {
  rectangle "Квадрант II (Лівий верхній)\nΔx < 0,  Δy > 0\nДобуток: (-) × (+) = **ВІД'ЄМНИЙ (-)**\n[X нижче середнього, Y вище середнього]" as Q2 #FFF5F5
  rectangle "Квадрант I (Правий верхній)\nΔx > 0,  Δy > 0\nДобуток: (+) × (+) = **ДОДАТНИЙ (+)**\n[Обидва X та Y вище середнього]" as Q1 #F0FFF4

  rectangle "Квадрант III (Лівий нижній)\nΔx < 0,  Δy < 0\nДобуток: (-) × (-) = **ДОДАТНИЙ (+)**\n[Обидва X та Y нижче середнього]" as Q3 #F0FFF4
  rectangle "Квадрант IV (Правий нижній)\nΔx > 0,  Δy < 0\nДобуток: (+) × (-) = **ВІД'ЄМНИЙ (-)**\n[X вище середнього, Y нижче середнього]" as Q4 #FFF5F5
}

Q2 -right-> Q1 : "Вісь X (відхилення Δx > 0)"
Q3 -right-> Q4 : "Вісь X (відхилення Δx > 0)"
Q3 -up-> Q2 : "Вісь Y (відхилення Δy > 0)"
Q4 -up-> Q1 : "Вісь Y (відхилення Δy > 0)"

note bottom of Q4
  Центральна точка перетину: середні координати (x̄, ȳ).
  Точки в зелених секторах (I, III) збільшують коваріацію.
  Точки в рожевих секторах (II, IV) зменшують коваріацію.
end note
@enduml

Практичний сценарій: покроковий розрахунок коваріації вручну

Щоб наочно побачити, як суха математична формула працює на практиці, розглянемо простий та життєвий сценарій: дослідження зв'язку між кількістю годин самостійної підготовки до іспиту (Завантаження..., години) та фінальною оцінкою за 100-бальною шкалою (Завантаження..., бали) для 5 студентів групи:

  • Студент А: Завантаження... год, Завантаження... балів
  • Студент Б: Завантаження... год, Завантаження... балів
  • Студент В: Завантаження... год, Завантаження... балів
  • Студент Г: Завантаження... год, Завантаження... балів
  • Студент Д: Завантаження... год, Завантаження... балів

Крок 1. Обчислення вибіркових середніх (знаходження центру тяжіння)

Спочатку знаходимо середньоарифметичні значення Завантаження... та Завантаження...:

Завантаження...
Завантаження...

Отже, наш «центр мас» вибірки розташований у точці Завантаження....


Крок 2. Центрування відхилень та перемноження «голосів»

Складемо розрахункову таблицю, щоб побачити внесок кожного студента у спільну тенденцію:

СтудентЗавантаження... (год)Завантаження... (бали)Центроване Завантаження...Центроване Завантаження...Добуток Завантаження...КвадрантВнесок у спільну тенденцію
Студент А250Завантаження...Завантаження...Завантаження...IIIЗавантаження... — обидва показники нижче середнього (синхронний спад)
Студент Б460Завантаження...Завантаження...Завантаження...IIIЗавантаження... — узгоджений помірний спад
Студент В670Завантаження...Завантаження...Завантаження...ЦентрТочка точно в центрі вибірки (нейтральний внесок)
Студент Г875Завантаження...Завантаження...Завантаження...IЗавантаження... — обидва показники вище середнього
Студент Д1095Завантаження...Завантаження...Завантаження...IЗавантаження... — максимальний позитивний ривок
Сума (Завантаження...)3035000+210—Сумарний узгоджений імпульс

Чому сума відхилень дорівнює нулю?
Зверніть увагу: Завантаження... та Завантаження.... Це фундаментальна перевірочна властивість середнього арифметичного: сума відхилень від власного центру тяжіння завжди суворо дорівнює нулю.


Крок 3. Обчислення вибіркової коваріації

Застосовуємо вибіркову формулу з поправкою Бесселя (Завантаження...):

Завантаження...

Що розповідає нам це число? (Три інженерні висновки)

  1. Знак плюс (Завантаження...):
    Зв'язок однозначно прямо пропорційний (позитивний). Студенти, які витрачають більше годин на підготовку, закономірно демонструють вищі результати на тестуванні.
  2. Пастка одиниць вимірювання («людино-годино-бали»):
    Числове значення коваріації вимірюється в комбінованих одиницях: година Завантаження... бал. Через це значення Завантаження... неможливо порівняти з іншими дослідженнями: це сильний зв'язок чи слабкий? Якщо ми переведемо час з годин у хвилини (Завантаження...), коваріація миттєво зросте в 60 разів:
    Завантаження...
    Хоча реальна поведінка студентів і ступінь їхньої старанності не змінилися ані на краплю!
  3. Місток до кореляції:
    Саме для подолання проблеми масштабу та розмірностей Карл Пірсон запропонував нормалізувати коваріацію — розділити її на добуток стандартних відхилень обох ознак. Це перетворює «незручні» Завантаження... на елегантне безрозмірне число від Завантаження... до Завантаження....
# Перевірка результату в Python / NumPy:
import numpy as np

hours = np.array([2, 4, 6, 8, 10])
scores = np.array([50, 60, 70, 75, 95])

# np.cov повертає симетричну коваріаційну матрицю 2x2:
# [[Var(X),     Cov(X, Y)],
#  [Cov(Y, X),  Var(Y)]]
cov_matrix = np.cov(hours, scores)
print(f"Вибіркова коваріація: {cov_matrix[0, 1]:.2f}")  # 52.50

Дисперсія як окремий випадок коваріації

Що станеться, якщо ми підставимо у вибіркову формулу коваріації змінну Завантаження... замість Завантаження...? Тобто розрахуємо коваріацію ознаки із самою собою:

Завантаження...

Це глибокий концептуальний зв'язок: дисперсія — це просто коваріація змінної із самою собою. Дисперсія показує, як одновимірна величина розсіюється навколо власного середнього, тоді як коваріація узагальнює цей принцип на спільне сумісне розсіювання двох різних вимірів.

Інтерпретація знаку коваріації:

Значення Завантаження...Інтерпретація зв'язкуГеометрична поведінка хмари точок
Завантаження...Прямий (позитивний): коли Завантаження... зростає, Завантаження... також має тенденцію зростатиТочки витягуються вздовж висхідної прямої (квадранти I та III)
Завантаження...Обернений (негативний): коли Завантаження... зростає, Завантаження... прямує внизТочки витягуються вздовж низхідної прямої (квадранти II та IV)
Завантаження...Відсутній лінійний зв'язок: зміна Завантаження... не дає інформації про напрямок зміни Завантаження...Точки рівномірно заповнюють площину без вираженого нахилу

Де застосовується коваріація на практиці?

Коваріація — це не просто теоретична проміжна формула. Вона є активним обчислювальним рушієм у багатьох сферах IT, науки про дані та інженерії:

1. Мінімізація ризику портфеля (Теорія Марковіца)

У фінансовому менеджменті інвестор намагається отримати максимальний дохід за мінімального ризику. Ризик портфеля з двох активів Завантаження... та Завантаження... із частками Завантаження... і Завантаження... визначається його дисперсією:

Завантаження...

Якщо підібрати активи з від'ємною коваріацією (Завантаження...) — наприклад, акції золотодобувної компанії (дорожчають під час кризи) та акції авіаперевізника (дешевшають через кризу або зростання цін на пальне), — доданок Завантаження... стає від'ємним. У результаті загальний ризик портфеля стає меншим, ніж ризик будь-якого з цих активів окремо. Цей ефект є математичним фундаментом диверсифікації.

2. Злиття сенсорів та автопілоти (Фільтр Калмана)

Безпілотні автомобілі та дрони одночасно зчитують координати з GPS, гіроскопа (IMU) та одометра коліс. Кожен датчик має випадкові похибки: GPS втрачає супутники між хмарочосами, а колеса буксують на мокрому асфальті.

Фільтр Калмана використовує матрицю коваріації похибок оцінки Завантаження.... Її позадіагональні елементи показують спільну невизначеність: «наскільки похибка у визначенні поточної швидкості впливає на похибку визначення положення у просторі?». Завдяки коваріації, щойно система отримує свіже вимірювання швидкості від коліс, вона миттєво коригує оцінку координат GPS, усуваючи накопичений дрейф траєкторії.

3. Стиснення даних та ML (Метод головних компонент — PCA)

У машинному навчанні датасети часто містять сотні або тисячі ознак. Якщо дві ознаки мають сильну коваріацію (наприклад, площа квартири та кількість кімнат, або яскравість двох сусідніх пікселів медичного томографа), вони несуть майже однакову інформацію (проблема мультиколінеарності).

Алгоритм PCA бере повну коваріаційну матрицю ознак і знаходить її власні вектори. Це дає змогу повернути координатні осі вздовж напрямків найбільшої спільної мінливості та замінити 100 початкових взаємозалежних ознак на 5 нових некорельованих компонент, зберігши понад 95% корисної інформації датасету.

4. Моніторинг систем та SRE (DevOps Telemetry)

Інженери надійності сайтів (SRE) відстежують поведінку мікросервісів за допомогою коваріації між вхідним навантаженням (RPS — запити за секунду) та затримкою обробки (Latency p95/p99).

Доки сервер має достатньо ресурсів, зростання RPS не призводить до деградації сервісу, і коваріація між ними тримається близько нуля. Проте, якщо база даних вичерпує пул з'єднань або процесор потрапляє в throttling, коваріація між навантаженням та затримкою різко підскакує вгору. Системи моніторингу (наприклад, Prometheus або Datadog) використовують зростання коваріації як завчасний індикатор виникнення «пляшкового горла» (bottleneck), що дає змогу автоматично масштабувати сервіс до того, як клієнти почнуть отримувати помилки таймауту HTTP 504.

Коваріація критично залежить від одиниць вимірювання змінних:Якщо зріст Завантаження... вимірюється в метрах, а вага Завантаження... — у кілограмах, коваріація Завантаження... має розмірність «метр · кілограм». Якщо ж перевести зріст у сантиметри, а вагу в грами, числове значення коваріації підскочить рівно в Завантаження... разів!Через це неможливо сказати, чи Завантаження... — це сильний зв'язок, чи практично нульовий, без врахування дисперсій самих змінних.Рішення: нормувати коваріацію, поділивши її на стандартні відхилення обох змінних. Результатом такої нормалізації є коефіцієнт лінійної кореляції Пірсона.

Обчислення коваріації в NumPy

covariance_calculation.ipynb
Python 3 (ipykernel)
Code
In [1]:
import numpy as np

# Дані: температура (°C) та продажі морозива (шт)
temperature = np.array([15, 18, 22, 25, 28, 30, 32, 35])
ice_cream_sales = np.array([50, 65, 80, 95, 110, 125, 135, 150])

# Спосіб 1: Вручну за математичною формулою
temp_mean = temperature.mean()
sales_mean = ice_cream_sales.mean()

cov_manual = np.sum((temperature - temp_mean) * (ice_cream_sales - sales_mean)) / (len(temperature) - 1)

# Спосіб 2: Через матрицю коваріацій NumPy
cov_matrix = np.cov(temperature, ice_cream_sales)  # Матриця коваріацій 2×2
cov_numpy = cov_matrix[0, 1]  # Позадіагональний елемент [0,1] або [1,0]

print("Результати:")
print(f"  Коваріація (вручну):  {cov_manual:.2f}")
print(f"  Коваріація (NumPy):   {cov_numpy:.2f}")
print(f"\nІнтерпретація:")
print(f"  Cov > 0 → позитивний зв'язок")
print(f"  Коли температура зростає, продажі морозива теж зростають")
print(f"\nПроблема масштабу:")
print(f"  Чи {cov_numpy:.2f} — це багато? Без контексту дисперсій відповісти неможливо!")

# Візуалізація: розділення на квадранти відхилень навколо центру мас
import matplotlib.pyplot as plt

plt.figure(figsize=(9, 6))
plt.axvline(temp_mean, color='#dc2626', linestyle='--', label=f'$\\bar{{x}} = {temp_mean:.1f}^\\circ\\mathrm{{C}}$')
plt.axhline(sales_mean, color='#2563eb', linestyle='--', label=f'$\\bar{{y}} = {sales_mean:.1f}$ шт')
plt.scatter(temperature, ice_cream_sales, s=80, color='#0284c7', edgecolors='#0369a1', zorder=5, label='Дні спостережень')
plt.scatter([temp_mean], [sales_mean], s=130, color='#eab308', edgecolors='#ca8a04', zorder=6, label='Центр мас')
plt.title(f'Обчислення коваріації: Температура vs Продажі морозива (Cov = +{cov_numpy:.2f})', fontweight='bold')
plt.xlabel('Температура повітря (X, °C)')
plt.ylabel('Продажі морозива (Y, шт)')
plt.grid(True, linestyle=':', alpha=0.5)
plt.legend()
plt.tight_layout()
plt.show()
Out[1]:

Результати:
  Коваріація (вручну):  242.68
  Коваріація (NumPy):   242.68

Інтерпретація:
  Cov > 0 → позитивний зв'язок
  Коли температура зростає, продажі морозива теж зростають

Проблема масштабу:
  Чи 242.68 — це багато? Без контексту дисперсій відповісти неможливо!
  1. Завантаження... — коваріація випадкової величини зі самою собою дорівнює її дисперсії.
  2. Завантаження... — операція коваріації строго симетрична.
  3. Завантаження... — білінійність: адитивні константи зникають, а мультиплікативні коефіцієнти виносяться наперед.
  4. Якщо змінні Завантаження... та Завантаження... незалежні, то Завантаження.... Увага: зворотне твердження не завжди істинне (нульова коваріація гарантує лише відсутність лінійного зв'язку, але не виключає нелінійну залежність).

Кореляція Пірсона (Pearson Correlation Coefficient)

Коефіцієнт лінійної кореляції Пірсона (Завантаження... або Завантаження...) — це нормалізована коваріація, значення якої завжди суворо обмежені інтервалом Завантаження....

Формула кореляції

Завантаження...

В розгорнутому алгебраїчному вигляді для вибіркових спостережень:

Завантаження...

Анатомія математичної формули кореляції Пірсона

Щоб глибинно зрозуміти, чому коефіцієнт Пірсона працює бездоганно і гарантовано потрапляє в діапазон Завантаження..., розберемо його математичну структуру на складові блоки:

  1. Центрування даних (Завантаження... та Завантаження...):
    • Кожне окреме спостереження перетворюється на вектор відхилення від свого середнього. Це робить показник інваріантним до зсуву координат: якщо до кожного зросту або ваги додати константу Завантаження..., кореляція абсолютно не зміниться.
  2. Чисельник — узгодженість напрямку (спільна варіація):
    • Якщо об'єкт має і Завантаження..., і Завантаження... вище за середнє: Завантаження... (додатний внесок).
    • Якщо об'єкт має обидва параметри нижче за середнє: Завантаження... (підсилює позитивний зв'язок!).
    • Якщо одне вище, а інше нижче: Завантаження... (штовхає кореляцію у від'ємну сторону).
    • Сума всіх цих добутків у чисельнику є ненормалізованою коваріацією Завантаження....
  3. Знаменник — масштабний нормалізатор (евклідові норми):
    • Добуток квадратних коренів — це добуток довжин (евклідових Завантаження...-норм) центрованих векторів Завантаження... та Завантаження....
    • З погляду лінійної алгебри формула Завантаження... є косинусом кута Завантаження... між двома центрованими векторами в багатовимірному просторі Завантаження...:
      Завантаження...
    • За нерівністю Коші-Буняковського (Завантаження...), косинус завжди належить діапазону Завантаження.... Якщо вектори паралельні й однаково спрямовані (Завантаження...), Завантаження.... Якщо вектори ортогональні (Завантаження...), лінійний зв'язок відсутній (Завантаження...). Якщо вони спрямовані в протилежні боки (Завантаження...), Завантаження....

Інтерпретація значень r

Значення rІнтерпретаціяВізуальна картина
r = +1Ідеальний позитивний лінійний зв'язокТочки лежать на прямій ↗
0.7 < r < 1Сильний позитивний зв'язокЧітка висхідна хмара
0.3 < r < 0.7Помірний позитивний зв'язокРозмита висхідна хмара
0 < r < 0.3Слабкий позитивний зв'язокМайже хаотична хмара
r ≈ 0Немає лінійного зв'язкуХаотична хмара (коло, U-форма)
-0.3 < r < 0Слабкий негативний зв'язокМайже хаотична хмара
-0.7 < r < -0.3Помірний негативний зв'язокРозмита спадна хмара
-1 < r < -0.7Сильний негативний зв'язокЧітка спадна хмара
r = −1Ідеальний негативний лінійний зв'язокТочки лежать на прямій ↘
Емпіричні пороги шкали Чеддока:
  • Завантаження... — слабка кореляція
  • Завантаження... — помірна кореляція
  • Завантаження... — сильна (висока) кореляція
Контекст має вирішальне значення: в експериментальній фізиці чи мікроелектроніці значення Завантаження... може вважатися ненадійним, тоді як в економіці, медицині чи аналізі поведінки користувачів Завантаження... є визначним інсайтом!

Практичні приклади кореляції: від IT-інфраструктури до повсякденності

Щоб абстрактні числові значення Завантаження... набули відчутного інженерного сенсу, проаналізуймо взаємозв'язки змінних у трьох ключових практичних сферах:

Обчислення кореляції в NumPy та SciPy

correlation_calculation.ipynb
Python 3 (ipykernel)
Code
In [1]:
import numpy as np
from scipy import stats

# Дані: температура та продажі морозива
temperature = np.array([15, 18, 22, 25, 28, 30, 32, 35])
ice_cream_sales = np.array([50, 65, 80, 95, 110, 125, 135, 150])

# Спосіб 1: Вручну (за формулою через коваріацію та стандарти)
cov = np.cov(temperature, ice_cream_sales)[0, 1]
std_temp = temperature.std(ddof=1)
std_sales = ice_cream_sales.std(ddof=1)
r_manual = cov / (std_temp * std_sales)

# Спосіб 2: np.corrcoef() — повертає матрицю кореляцій 2×2
corr_matrix = np.corrcoef(temperature, ice_cream_sales)
r_numpy = corr_matrix[0, 1]

# Спосіб 3: scipy.stats.pearsonr() — повертає r та p-value для перевірки гіпотези
r_scipy, p_value = stats.pearsonr(temperature, ice_cream_sales)

print("Результати обчислення кореляції:")
print(f"  Вручну:        r = {r_manual:.4f}")
print(f"  np.corrcoef(): r = {r_numpy:.4f}")
print(f"  scipy:         r = {r_scipy:.4f}, p-value = {p_value:.6f}")
print(f"\nІнтерпретація:")
print(f"  r = {r_scipy:.3f} → майже ідеальний позитивний лінійний зв'язок")
print(f"  p-value < 0.05 → зв'язок статистично значущий (нульова гіпотеза про r=0 відкидається)")

# Візуалізація: порівняння сирих одиниць та стандартизованого Z-простору
import matplotlib.pyplot as plt

slope, intercept, r_val, p_val, _ = stats.linregress(temperature, ice_cream_sales)
z_temp = (temperature - temperature.mean()) / std_temp
z_sales = (ice_cream_sales - ice_cream_sales.mean()) / std_sales

fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(14, 5.5))
ax1.scatter(temperature, ice_cream_sales, color='#0284c7', s=60, label='Дані')
x_grid = np.linspace(14, 36, 100)
ax1.plot(x_grid, slope * x_grid + intercept, color='#ea580c', lw=2, label=f'Тренд ($r = {r_val:.3f}$)')
ax1.set_title('Вихідні одиниці (Температура ↔ Продажі)', fontweight='bold')
ax1.set_xlabel('Температура (°C)')
ax1.set_ylabel('Продажі (шт)')
ax1.grid(True, linestyle=':', alpha=0.5)
ax1.legend()

ax2.scatter(z_temp, z_sales, color='#16a34a', s=60, label='Z-точки')
z_grid = np.linspace(-1.8, 1.8, 100)
ax2.plot(z_grid, r_val * z_grid, color='#dc2626', lw=2, label=f'Нахил = r = {r_val:.3f}')
ax2.axvline(0, color='#94a3b8', linestyle='--')
ax2.axhline(0, color='#94a3b8', linestyle='--')
ax2.set_title('Стандартизований Z-простір: кут нахилу = r', fontweight='bold')
ax2.set_xlabel('Z-оцінка температури')
ax2.set_ylabel('Z-оцінка продажів')
ax2.grid(True, linestyle=':', alpha=0.5)
ax2.legend()
plt.tight_layout()
plt.show()
Out[1]:

Результати обчислення кореляції:
  Вручну:        r = 0.9973
  np.corrcoef(): r = 0.9973
  scipy:         r = 0.9973, p-value = 0.000000

Інтерпретація:
  r = 0.997 → майже ідеальний позитивний лінійний зв'язок
  p-value < 0.05 → зв'язок статистично значущий (нульова гіпотеза про r=0 відкидається)

Візуалізація різних кореляцій

correlation_examples.ipynb
Python 3 (ipykernel)
Code
In [1]:
import numpy as np
import matplotlib.pyplot as plt

rng = np.random.default_rng(seed=42)
n = 100

def generate_correlated_data(r, n=100):
    """Генерує дві нормальні змінні з заданим коефіцієнтом кореляції r"""
    x = rng.standard_normal(n)
    y = r * x + np.sqrt(1 - r**2) * rng.standard_normal(n)
    return x, y

correlations = [1.0, 0.8, 0.4, 0.0, -0.4, -0.8, -1.0]
fig, axes = plt.subplots(2, 4, figsize=(16, 8))
axes = axes.ravel()

for i, r_target in enumerate(correlations):
    x, y = generate_correlated_data(r_target, n)
    r_actual = np.corrcoef(x, y)[0, 1]

    axes[i].scatter(x, y, alpha=0.6, s=30, edgecolors='black', linewidths=0.5)
    axes[i].set_title(f'r = {r_actual:.2f}', fontweight='bold', fontsize=12)
    axes[i].set_xlabel('X')
    axes[i].set_ylabel('Y')
    axes[i].grid(alpha=0.3)
    axes[i].axhline(0, color='gray', linewidth=0.5, linestyle='--')
    axes[i].axvline(0, color='gray', linewidth=0.5, linestyle='--')

# 8-й графік: нелінійний функціональний зв'язок (коло), де r ≈ 0
theta = np.linspace(0, 2*np.pi, n)
x_circle = np.cos(theta) + rng.standard_normal(n) * 0.1
y_circle = np.sin(theta) + rng.standard_normal(n) * 0.1
r_circle = np.corrcoef(x_circle, y_circle)[0, 1]

axes[7].scatter(x_circle, y_circle, alpha=0.6, s=30, edgecolors='black', linewidths=0.5, color='red')
axes[7].set_title(f'Коло: r = {r_circle:.2f}', fontweight='bold', fontsize=12)
axes[7].set_xlabel('X')
axes[7].set_ylabel('Y')
axes[7].grid(alpha=0.3)
axes[7].text(0, 0, 'Нелінійний зв\'язок!\nr ≈ 0', ha='center', fontsize=10,
             bbox=dict(boxstyle='round', facecolor='yellow', alpha=0.7))

plt.tight_layout()
plt.show()

print("Висновки:")
print("  r = +1: точки лежать точно на висхідній прямій ↗")
print("  r = -1: точки лежать точно на спадній прямій ↘")
print("  r ≈ 0: хаотична хмара АБО нелінійний зв'язок (коло, парабола)")
print("\n⚠ ВАЖЛИВО: r = 0 НЕ означає 'немає зв'язку', це означає лише 'немає ЛІНІЙНОГО зв'язку'!")
Out[1]:

Висновки:
  r = +1: точки лежать точно на висхідній прямій ↗
  r = -1: точки лежать точно на спадній прямій ↘
  r ≈ 0: хаотична хмара АБО нелінійний зв'язок (коло, парабола)

⚠ ВАЖЛИВО: r = 0 НЕ означає 'немає зв'язку', це означає лише 'немає ЛІНІЙНОГО зв'язку'!

Квартет Анскомба (Anscombe's Quartet): чому не можна вірити лише числу r

У 1973 році британський статистик Френсіс Анскомб сформував чотири набори числових даних, щоб проілюструвати фундаментальне правило: ніколи не робіть висновків про залежність змінних лише за коефіцієнтом кореляції без побудови графіка розсіювання (scatter plot).

Усі чотири набори даних мають майже ідентичні зведені статистичні характеристики:

  • Середнє значення Завантаження..., вибіркова дисперсія Завантаження... (Завантаження...)
  • Середнє значення Завантаження..., вибіркова дисперсія Завантаження... (Завантаження...)
  • Коефіцієнт кореляції Пірсона Завантаження...
  • Лінія регресії Завантаження... (Завантаження...)

Набір I (Лінійний зв'язок)

Класична хмара точок із помірним лінійним трендом та нормальним розподілом помилок. Коефіцієнт Завантаження... бездоганно відображає реальний стан справ.

Набір II (Нелінійна парабола)

Між змінними існує строга детермінована квадратична залежність. Лінійна кореляція Завантаження... повністю сліпа до кривини зв'язку і вводить аналітика в оману.

Набір III (Вплив одного викиду)

Майже ідеальна пряма лінія (Завантаження...), яку «змазав» усього один-єдиний вертикальний викид, знизивши кореляцію до Завантаження....

Набір IV (Важільна точка — Leverage)

Усі спостереження, крім одного, мають Завантаження... (нульова варіація Завантаження...). Одна екстремальна точка Завантаження... самотужки створила ілюзію потужного лінійного зв'язку Завантаження...!

Перед застосуванням моделі лінійної регресії чи формуванням висновків про взаємозв'язок ознак завжди будуйте діаграми розсіювання (scatter plot). Одне сухе число Завантаження... не здатне розрізнити плавну параболу, дію аномального шуму чи штучний нахил через один екстремальний викид.

Матриця кореляції (Correlation Matrix)

У реальних датасетах ми зазвичай маємо десятки або сотні ознак. Як швидко з'ясувати, які змінні тісно пов'язані між собою, а які дублюють інформацію?

Матриця кореляції — це симетрична таблиця розмірності Завантаження..., де елемент на перетині Завантаження...-го рядка та Завантаження...-го стовпця дорівнює коефіцієнту кореляції між змінними Завантаження... та Завантаження....

Структура матриці

Loading diagram...
@startuml
skinparam style plain
skinparam backgroundColor #FFFFFF
skinparam defaultTextAlignment center

rectangle "Матриця кореляції" {
  rectangle "       X1      X2      X3      X4" as header
  rectangle "X1 | **1.00**  0.85  -0.42   0.12" as row1
  rectangle "X2 | 0.85  **1.00**  -0.38   0.05" as row2
  rectangle "X3 |-0.42  -0.38  **1.00**  -0.65" as row3
  rectangle "X4 | 0.12   0.05  -0.65  **1.00**" as row4
}

note right of header
  Головна діагональ = 1.00
  (кореляція ознаки із самою собою)
end note

note bottom of row4
  Симетрична матриця:
  Corr(Xi, Xj) = Corr(Xj, Xi)
end note
@enduml

Фундаментальні властивості:

  1. Головна діагональ завжди дорівнює 1.00 (Завантаження...).
  2. Симетричність відносно діагоналі: Завантаження....
  3. Діапазон усіх значень: Завантаження....

Обчислення та візуалізація матриці (Heatmap)

correlation_matrix.ipynb
Python 3 (ipykernel)
Code
In [1]:
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns

rng = np.random.default_rng(seed=42)

# Створюємо синтетичний датасет (100 зразків, 5 змінних)
n = 100
age = rng.integers(20, 60, n)
income = age * 1200 + rng.normal(0, 5000, n)  # Тісно корелює з віком
education_years = rng.normal(14, 3, n)
savings = 0.15 * income + 1000 * education_years + rng.normal(0, 3000, n)
debt = -0.1 * income + rng.normal(5000, 2000, n)  # Від'ємна кореляція з доходом

# Формуємо DataFrame
data = pd.DataFrame({
    'Вік': age,
    'Дохід': income,
    'Освіта (роки)': education_years,
    'Заощадження': savings,
    'Борг': debt
})

# Обчислюємо кореляційну матрицю
corr_matrix = data.corr()

print("Матриця кореляції:\n")
print(corr_matrix.round(2))

# Візуалізуємо через теплову карту (Heatmap)
plt.figure(figsize=(9, 7.5))
sns.heatmap(corr_matrix, annot=True, fmt='.2f', cmap='coolwarm', center=0, vmin=-1, vmax=1,
            square=True, linewidths=1, cbar_kws={"shrink": 0.8, "label": "Коефіцієнт кореляції r"})
plt.title('Heatmap: Матриця кореляції фінансових показників', fontweight='bold', fontsize=13)
plt.tight_layout()
plt.show()

print("\n📊 Інтерпретація кольорів:")
print("  🔴 Червоний (r → +1): сильний позитивний зв'язок")
print("  🔵 Синій (r → -1): сильний негативний зв'язок")
print("  ⚪ Білий (r ≈ 0): лінійний зв'язок відсутній")
Out[1]:

Матриця кореляції:

                Вік  Дохід  Освіта (роки)  Заощадження  Борг
Вік            1.00   0.95          -0.12         0.19 -0.59
Дохід          0.95   1.00          -0.07         0.27 -0.64
Освіта (роки) -0.12  -0.07           1.00         0.64  0.01
Заощадження    0.19   0.27           0.64         1.00 -0.27
Борг          -0.59  -0.64           0.01        -0.27  1.00

📊 Інтерпретація кольорів:
  🔴 Червоний (r → +1): сильний позитивний зв'язок
  🔵 Синій (r → -1): сильний негативний зв'язок
  ⚪ Білий (r ≈ 0): лінійний зв'язок відсутній
  • Вік ↔ Дохід (Завантаження...): надзвичайно потужний позитивний зв'язок — з віком та накопиченням професійного стажу дохід стабільно зростає.
  • Освіта ↔ Заощадження (Завантаження...): помітний позитивний зв'язок — додаткові роки навчання сприяють фінансовій грамотності та інвестиціям.
  • Дохід ↔ Борг (Завантаження...): помірний від'ємний зв'язок — люди з вищим рівнем заробітку мають менше проблем із непогашеною заборгованістю.
  • Освіта ↔ Вік (Завантаження...) та Освіта ↔ Борг (Завантаження...): взаємозв'язок практично відсутній.
Матриця кореляцій дозволяє миттєво відфільтрувати інформативний сигнал від статистичного шуму у великих табличних даних!

Кореляція ≠ Причинність (Correlation ≠ Causation)

Це найважливіший методологічний розділ в аналізі даних та емпіричній науці.

Якщо між змінними Завантаження... та Завантаження... виявлено високу кореляцію (Завантаження...), це в жодному разі не доводить, що:
  • Завантаження... є причиною виникнення Завантаження...;
  • Завантаження... спричиняє Завантаження...;
  • між ними взагалі існує причинно-наслідковий механізм.
Кореляція констатує виключно спільну узгоджену мінливість (асоціацію), але не причинність!

Класичні приклади помилкової інтерпретації

1. Продажі морозива та кількість утоплень

Емпіричний факт: Продажі морозива та кількість нещасних випадків на воді демонструють вражаючу позитивну кореляцію (Завантаження...).

Хибний висновок (псевдопричинність): «Вживання морозива викликає судоми у воді або потоплення! Необхідно негайно заборонити продаж морозива на пляжах!».

Реальність (Confounding Variable — прихована третя змінна): Обидва явища одночасно зумовлені третім фактором — літньою спекою:

  • Спекотна погода Завантаження... різко зростає попит на прохолодні ласощі (морозиво Завантаження...);
  • Спекотна погода Завантаження... мільйони людей масово вирушають купатися у відкритих водоймах Завантаження... зростає кількість інцидентів на воді (Завантаження...).
Loading diagram...
@startuml
skinparam style plain
skinparam backgroundColor #FFFFFF
skinparam defaultTextAlignment center

rectangle "Температура повітря\n(Літня спека)" as temp #LightBlue

rectangle "Продажі\nморозива ↑" as ice #LightCoral
rectangle "Кількість\nутоплень ↑" as drown #LightCoral

temp -down-> ice : "причинний зв'язок"
temp -down-> drown : "причинний зв'язок"

ice ..> drown : "<b>кореляція r ≈ 0.9\n(немає причинності!)</b>"

note bottom of drown
  Морозиво НЕ викликає трагедії на воді.
  Обидва процеси керовані прихованою
  змінною температури (Confounder).
end note
@enduml

2. Кількість пожежників та матеріальні збитки від пожежі

Факт: Чим більше пожежних машин та рятувальників прибуває на ліквідацію вогню, тим більші фінансові збитки реєструє страхова компанія (Завантаження...).

Хибний висновок: «Пожежники самі провокують руйнування будівель!».

Реальність (Reverse Causality — зворотна причинність): Масштаб самої пожежі визначає як рівень збитків, так і категорію тривоги, за якою диспетчер відправляє додаткові бригади на виклик.

3. Споживання органічних продуктів та аутизм

Факт: За останні десятиліття зростання продажів органічних фермерських продуктів і кількість зареєстрованих випадків аутизму в розвинених країнах зростали майже паралельно (Завантаження...).

Реальність (Спільний тренд часу): Обидва графіки монотонно зростали через глобальні історичні тренди (популяризація здорового харчування з одного боку, та вдосконалення діагностичних критеріїв DSM і скринінгу дітей з іншого).

Три сценарії походження кореляції

correlation_causation_scenarios.ipynb
Python 3 (ipykernel)
Code
In [1]:
import matplotlib.pyplot as plt
from matplotlib.patches import FancyArrowPatch

fig, axes = plt.subplots(1, 3, figsize=(15, 4.5))

scenarios = [
    ("Сценарій 1:\nX → Y", "X викликає Y\n(Пряма причинність)", [(0.25, 0.5, 0.75, 0.5)]),
    ("Сценарій 2:\nY → X", "Y викликає X\n(Зворотна причинність)", [(0.75, 0.5, 0.25, 0.5)]),
    ("Сценарій 3:\nZ → X, Z → Y", "Спільна причина Z\n(Хибна кореляція)", [(0.5, 0.75, 0.25, 0.35), (0.5, 0.75, 0.75, 0.35)])
]

for ax, (title, subtitle, arrows) in zip(axes, scenarios):
    ax.set_xlim(0, 1)
    ax.set_ylim(0, 1)
    ax.axis('off')
    ax.set_title(title, fontweight='bold', fontsize=14, pad=10)
    ax.text(0.5, 0.12, subtitle, ha='center', fontsize=11, fontweight='semibold',
            bbox=dict(boxstyle='round,pad=0.5', facecolor='#fef3c7', edgecolor='#f59e0b', alpha=0.9))

    for x1, y1, x2, y2 in arrows:
        arrow = FancyArrowPatch((x1, y1), (x2, y2),
                                arrowstyle='-|>', mutation_scale=22,
                                linewidth=3, color='#dc2626')
        ax.add_patch(arrow)

    if len(arrows) == 1:
        ax.plot(0.25, 0.5, 'o', markersize=42, color='#2563eb')
        ax.plot(0.75, 0.5, 'o', markersize=42, color='#ea580c')
        ax.text(0.25, 0.5, 'X', ha='center', va='center', fontsize=18, fontweight='bold', color='white')
        ax.text(0.75, 0.5, 'Y', ha='center', va='center', fontsize=18, fontweight='bold', color='white')
    else:
        ax.plot(0.5, 0.75, 'o', markersize=42, color='#16a34a')
        ax.plot(0.25, 0.35, 'o', markersize=42, color='#2563eb')
        ax.plot(0.75, 0.35, 'o', markersize=42, color='#ea580c')
        ax.text(0.5, 0.75, 'Z', ha='center', va='center', fontsize=18, fontweight='bold', color='white')
        ax.text(0.25, 0.35, 'X', ha='center', va='center', fontsize=18, fontweight='bold', color='white')
        ax.text(0.75, 0.35, 'Y', ha='center', va='center', fontsize=18, fontweight='bold', color='white')

plt.tight_layout()
plt.show()

print("При виявленні кореляції між X та Y необхідно ретельно перевірити ВСІ три сценарії!")
print("Без проведення контрольованих рандомізованих експериментів строго довести причинність неможливо!")
Out[1]:

При виявленні кореляції між X та Y необхідно ретельно перевірити ВСІ три сценарії!
Без проведення контрольованих рандомізованих експериментів строго довести причинність неможливо!

Як у науці доводять причинність?

Кореляція — це лише первинна евристика для формулювання наукової гіпотези. Для доведення причинно-наслідкового зв'язку потрібні:

  1. Рандомізовані контрольовані дослідження (RCT — Randomized Controlled Trials):
    • Випадковим чином розбити вибірку на тестову (де штучно змінюється Завантаження...) та контрольну (де Завантаження... зафіксовано на базовому рівні).
    • Якщо статистично значущі зміни Завантаження... виникають виключно в тестовій групі, причинність доведено.
  2. Темпоральний пріоритет (послідовність у часі):
    • Причина повинна строго передувати наслідку (Завантаження...). Якщо відгук змінюється раніше за тригер, гіпотеза відкидається.
  3. Контроль заважаючих факторів (Confounders):
    • Усунення або статистична фіксація впливу всіх прихованих факторів Завантаження... (наприклад, через A/B-тестування або метод інструментальних змінних).
  4. Фізичний або біологічний механізм:
    • Наявність чіткої наукової теорії, що пояснює, яким саме чином вплив передається від Завантаження... до Завантаження....
Мовний етикет дата-аналітика:✅ Коректно стверджувати:
  • «Ознака Завантаження... статистично асоційована зі зростанням Завантаження...»
  • «Між параметрами виявлено помірний коваріаційний зв'язок»
  • «Змінна Завантаження... володіє прогностичною силою щодо Завантаження...»
❌ Неприпустимо без експерименту:
  • «Ознака Завантаження... призводить до падіння Завантаження...»
  • «Підвищення параметра Завантаження... викликає зростання Завантаження...»

Spurious Correlations (хибні кореляції)

Відомий проєкт Тайлера Вігена (Tyler Vigen) наочно доводить: якщо зібрати тисячі незалежних часових рядів, закон великих чисел гарантує, що деякі з них випадково покажуть Завантаження...:

  • Споживання сиру моцарела на душу населення ↔ Кількість захищених докторських дисертацій з цивільної інженерії (Завантаження...)
  • Кількість людей, що втонули в басейнах США ↔ Кількість фільмів за участю Ніколаса Кейджа (Завантаження...)
spurious_correlation_example.ipynb
Python 3 (ipykernel)
Code
In [1]:
import numpy as np
import matplotlib.pyplot as plt

rng = np.random.default_rng(seed=42)

# Дві повністю незалежні змінні з глобальним трендом часу
years = np.arange(2000, 2020)
cheese_consumption = 10 + 0.5 * np.arange(20) + rng.normal(0, 1, 20)
engineering_phd = 500 + 25 * np.arange(20) + rng.normal(0, 30, 20)

# Обчислення кореляції
r = np.corrcoef(cheese_consumption, engineering_phd)[0, 1]

# Візуалізація двох шкал
fig, ax1 = plt.subplots(figsize=(11, 5.5))

ax1.plot(years, cheese_consumption, 'o-', color='#f59e0b', linewidth=2.2, markersize=7, label='Споживання сиру (кг/особу)')
ax1.set_xlabel('Рік', fontsize=12)
ax1.set_ylabel('Споживання сиру (кг/особу)', color='#d97706', fontsize=12)
ax1.tick_params(axis='y', labelcolor='#d97706')

ax2 = ax1.twinx()
ax2.plot(years, engineering_phd, 's--', color='#2563eb', linewidth=2.2, markersize=7, label='Докторські з інженерії')
ax2.set_ylabel('Кількість захищених дисертацій', color='#1d4ed8', fontsize=12)
ax2.tick_params(axis='y', labelcolor='#1d4ed8')

plt.title(f'Spurious Correlation: r = {r:.3f}\n(Спільний часовий тренд без жодного причинно-наслідкового зв\'язку)',
          fontweight='bold', fontsize=13)
fig.legend(loc='upper left', bbox_to_anchor=(0.12, 0.88))
plt.grid(True, linestyle=':', alpha=0.5)
plt.tight_layout()
plt.show()

print(f"Кореляція: r = {r:.3f}")
print("Пояснення: обидва процеси зростають в часі з незалежних причин.")
print("Це класичний випадок хибної кореляції трендів (Spurious Correlation)!")
Out[1]:

Кореляція: r = 0.959
Пояснення: обидва процеси зростають в часі з незалежних причин.
Це класичний випадок хибної кореляції трендів (Spurious Correlation)!
У моніторингу розподілених бекенд-систем (Datadog, Prometheus, Grafana) хибні кореляції — це щоденний кошмар чергових інженерів.Уявіть ситуацію:
  • Щоранку о 09:00 спостерігається стрімкий стрибок навантаження CPU мікросервісу auth-service (з 15% до 85%).
  • Одночасно на балансувальнику навантаження (Nginx/Cloudflare) різко зростає кількість помилок HTTP 404 Not Found.
  • Матриця кореляцій показує майже ідеальний зв'язок: Завантаження....
Хибний висновок: «Сервіс авторизації перевантажений, через що балансувальник губить маршрути і повертає 404!». Реальність (Confounding Variable — прихований фактор): Третім фактором є початок робочого дня співробітників компанії. О 09:00 тисячі працівників одночасно відкривають ноутбуки:
  1. Вони проходять процедуру SSO-авторизації (навантажуючи auth-service).
  2. Їхні браузери автоматично відкривають закешовані вкладки із застарілими endpoint-ами, які бекенд-команда видалила під час нічного деплою (викликаючи сплеск 404).
Між auth-service та HTTP 404 немає жодного причинного зв'язку. Якщо інженер кинеться оптимізувати сервіс авторизації, щоб зменшити помилки 404, він даремно змарнує робочий час на боротьбу з міражем.

Мультиколінеарність (Multicollinearity)

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

Чому це критична проблема для моделей?

  1. Нестійкість коефіцієнтів регресії: незначна зміна у вибірці або невеликий шум спричиняють стрибки ваг моделі від Завантаження... до Завантаження....
  2. Втрата інтерпретованості: стає неможливо визначити, яка саме ознака насправді впливає на цільову змінну, оскільки ознаки-дублікати «крадуть» вагу одна в одної.
  3. Завищена дисперсія помилок: стандартні похибки коефіцієнтів стрімко зростають, роблячи t-статистику неінформативною.

Аналогія: Уявіть, що ви прогнозуєте вартість квартири, використовуючи одночасно дві ознаки: «Площа в м²» та «Площа у фут²». Оскільки Завантаження..., коефіцієнт кореляції між ними Завантаження.... Алгоритм лінійної регресії не може вирішити, як розподілити вагу між цими ознаками-клонами!

Для software-інженерів мультиколінеарність має прямий еквівалент у реляційних базах даних та лінійній алгебрі:
  1. Порушення 3-ї нормальної форми (3NF): Якщо в таблиці users зберігати стовпчики birth_date, age_years та age_months, ви вносите функціональну транзитивну залежність. У СУБД це призводить до аномалій оновлення (UPDATE), а в ML — змушує оптимізатор штучно ділити градієнт між дублікатами.
  2. Сингулярність матриці Грама (Завантаження...): Аналітичний розв'язок методу найменших квадратів (OLS) задається нормальним рівнянням:
    Завантаження...

    Якщо два стовпці матриці Завантаження... лінійно залежні, визначник Завантаження.... Матриця стає майже виродженою (число обумовленості condition number прямує до нескінченності). Похибки округлення чисел з плаваючою комою (floating-point errors) лавиноподібно зростають, руйнуючи обчислення.

Виявлення мультиколінеарності

multicollinearity_detection.ipynb
Python 3 (ipykernel)
Code
In [1]:
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns

rng = np.random.default_rng(seed=42)
n = 100

# Генеруємо штучні ознаки з колінеарністю
area_m2 = rng.uniform(30, 150, n)
area_ft2 = area_m2 * 10.764               # Ідеальна колінеарність (r = 1.0)
rooms = rng.poisson(3, n)
age = rng.integers(0, 50, n)
age_squared = age ** 2                    # Нелінійна монотонна залежність (r ≈ 0.97)

# Цільова змінна: ціна
price = 1000 * area_m2 - 500 * age + rng.normal(0, 5000, n)

data = pd.DataFrame({
    'Площа (м²)': area_m2,
    'Площа (фут²)': area_ft2,
    'Кімнати': rooms,
    'Вік': age,
    'Вік²': age_squared,
    'Ціна': price
})

corr_matrix = data.corr()

# Візуалізація
plt.figure(figsize=(9, 7.5))
sns.heatmap(corr_matrix, annot=True, fmt='.2f', cmap='RdYlGn', center=0, vmin=-1, vmax=1,
            square=True, linewidths=1.5, linecolor='white', cbar_kws={'shrink': 0.8, 'label': 'Коефіцієнт r'})
plt.title('Матриця кореляцій: Виявлення мультиколінеарності ознак', fontweight='bold', fontsize=13)
plt.tight_layout()
plt.show()

# Автоматичний пошук колінеарних пар
print("⚠ ВИЯВЛЕНА МУЛЬТИКОЛІНЕАРНІСТЬ (|r| > 0.90 серед предикторів):\n")
features = [col for col in corr_matrix.columns if col != 'Ціна']
for i in range(len(features)):
    for j in range(i + 1, len(features)):
        r_val = corr_matrix.loc[features[i], features[j]]
        if abs(r_val) > 0.90:
            print(f"  {features[i]} ↔ {features[j]}: r = {r_val:.3f}")
            print(f"    → Рекомендація: вилучити одну з ознак або застосувати регуляризацію!\n")
Out[1]:

⚠ ВИЯВЛЕНА МУЛЬТИКОЛІНЕАРНІСТЬ (|r| > 0.90 серед предикторів):

  Площа (м²) ↔ Площа (фут²): r = 1.000
    → Рекомендація: вилучити одну з ознак або застосувати регуляризацію!

  Вік ↔ Вік²: r = 0.968
    → Рекомендація: вилучити одну з ознак або застосувати регуляризацію!

Метрика VIF (Variance Inflation Factor)

Для суворого кількісного оцінювання мультиколінеарності в економетриці та ML застосовують коефіцієнт інфляції дисперсії (VIF):

Завантаження...

де Завантаження... — коефіцієнт детермінації моделі, в якій ознака Завантаження... регресується на всі інші предиктори.

  • Завантаження... — ознака абсолютно некорельована з іншими.
  • Завантаження... — помірна, безпечна колінеарність.
  • Завантаження... (або Завантаження...) — критична мультиколінеарність; оцінки параметрів моделі стають ненадійними.

Стратегії подолання мультиколінеарності


Інші міри статистичного зв'язку

Коефіцієнт Пірсона чудово вимірює лінійний зв'язок нормальних неперервних змінних. Проте в реальних інженерних задачах дані часто бувають порядковими, містять грубі викиди або мають нелінійний монотонний характер.

Кореляція Спірмена (Spearman's Rank Correlation, ρ)

Ранговий коефіцієнт Спірмена (Завантаження... або Завантаження...) — це непараметрична міра, яка оцінює, наскільки монотонно зростає або спадає одна змінна при зростанні іншої, не вимагаючи строгої лінійності.

Алгоритм обчислення:

  1. Замінити сирі значення Завантаження... та Завантаження... на їхні ранги Завантаження... та Завантаження... (номери за порядком зростання від 1 до Завантаження...).
  2. Якщо зв'язаних (однакових) рангів немає, формула набуває елегантного вигляду:
    Завантаження...
    де Завантаження... — різниця між рангами Завантаження...-го об'єкта.

Переваги Спірмена:

  • Стійкість до викидів (Robustness): аномальне значення 1,000,000 отримує максимальний ранг Завантаження..., не зміщуючи результат лавиноподібно.
  • Діагностика монотонних нелінійностей: для експоненти чи логарифма (Завантаження...) коефіцієнт Спірмена дорівнює точній одиниці (Завантаження...), тоді як Пірсон видає занижені значення.
  • Підтримка порядкових (ординальних) шкал: оцінки задоволеності (1–5 зірок), рівні пріоритету задач у Jira (P1–P4).
spearman_correlation.ipynb
Python 3 (ipykernel)
Code
In [1]:
import numpy as np
from scipy import stats
import matplotlib.pyplot as plt

rng = np.random.default_rng(seed=42)

# Нелінійна, але суворо монотонна функціональна залежність
x = np.linspace(0, 5, 50)
y = np.exp(x) + rng.normal(0, 5, 50)

# Обчислення коефіцієнтів
pearson_r, _ = stats.pearsonr(x, y)
spearman_r, _ = stats.spearmanr(x, y)

# Візуалізація
plt.figure(figsize=(10, 6))
plt.scatter(x, y, alpha=0.8, s=60, edgecolors='#1e293b', linewidths=0.7, color='#0284c7', label='Спостереження')
plt.plot(x, np.exp(x), color='#ea580c', linestyle='-', linewidth=2, label='Базова експонента y = exp(x)')

plt.xlabel('X', fontsize=12)
plt.ylabel('Y', fontsize=12)
plt.title('Нелінійний монотонний зв\'язок: порівняння коефіцієнтів Пірсона та Спірмена', fontweight='bold', fontsize=13)
plt.grid(True, linestyle=':', alpha=0.5)
plt.text(0.3, max(y) * 0.75, f'Pearson r = {pearson_r:.3f}\nSpearman ρ = {spearman_r:.3f}',
         fontsize=12, fontweight='semibold', bbox=dict(boxstyle='round,pad=0.6', facecolor='#fef08a', edgecolor='#eab308', alpha=0.9))
plt.legend(loc='upper left')
plt.tight_layout()
plt.show()

print("Порівняльні висновки:")
print(f"  Pearson r = {pearson_r:.3f} — недооцінює силу зв'язку через вигин експоненти")
print(f"  Spearman ρ = {spearman_r:.3f} — бездоганно фіксує збереження порядку рангів")
Out[1]:

Порівняльні висновки:
  Pearson r = 0.856 — недооцінює силу зв'язку через вигин експоненти
  Spearman ρ = 0.955 — бездоганно фіксує збереження порядку рангів

Рангова кореляція Кендалла (Kendall's Tau, τ)

Коефіцієнт кореляції Кендалла (Завантаження...) базується на аналізі узгодженості (конкордантності) між усіма можливими парами спостережень.

Для будь-якої пари спостережень Завантаження... та Завантаження...:

  • Узгоджена (concordant) пара: коли знак різниць однаковий, тобто Завантаження... (обидві змінні зростають або обидві спадають).
  • Неузгоджена (discordant) пара: коли знаки протилежні, тобто Завантаження....

Математична формула:

Завантаження...

де Завантаження... — кількість узгоджених пар, а Завантаження... — кількість неузгоджених пар.

Коли Кендалл кращий за Спірмена?

  • На малих вибірках (Завантаження...) Кендалл дає меншу похибку оцінки генерального параметра.
  • Володіє прямою ймовірнісною інтерпретацією: Завантаження... — це різниця між імовірністю того, що пара точок узгоджена, та ймовірністю того, що вона неузгоджена.
  • Надійніший при наявності великої кількості однакових (зв'язаних) значень.

Як обрати міру взаємозв'язку? (Дерево рішень)

Loading diagram...
@startuml
skinparam style plain
skinparam backgroundColor #FFFFFF
skinparam defaultTextAlignment center

start
:Який тип даних та шкала вимірювання?;

if (Шкала?) then (Кількісна / Неперервна)
  if (Розподіл нормальний і зв'язок лінійний?) then (Так, без викидів)
    #LightGreen:<b>Кореляція Пірсона (r)</b>\nМаксимальна статистична потужність;
  else (Ні: монотонний, викиди, асиметрія)
    if (Обсяг вибірки n?) then (n > 30)
      #LightBlue:<b>Кореляція Спірмена (ρ)</b>\nШвидке ранжування великих даних;
    else (n ≤ 30 або багато дублікатів)
      #LightCyan:<b>Кореляція Кендалла (τ)</b>\nМенша дисперсія на малих вибірках;
    endif
  endif
else (Порядкова / Ординальна)
  #LightCyan:<b>Спірмен (ρ) або Кендалл (τ)</b>\nРейтинги, лікертівські шкали;
endif

stop
@enduml

Коефіцієнт детермінації (R²)

Коефіцієнт детермінації (Завантаження...) показує, яка частка дисперсії залежної змінної Завантаження... пояснюється моделлю регресії від Завантаження...:

Завантаження...

Для простої однофакторної лінійної регресії виконується елегантна тотожність:

Завантаження...

Практична інтерпретація:

  • Завантаження...: модель пояснює 64% варіації цільової змінної (а 36% зумовлені неврахованими факторами або шумом).
  • Завантаження...: пояснюється лише 9% варіації (прогнозна сила слабка).
  • У множинній регресії (де предикторів багато) Завантаження..., тому використовують скоригований Завантаження....

Практичні завдання

Рівень 1: Базові вправи


Рівень 2: Алгоритмічні задачі


Рівень 3: Проєктні завдання


Часті запитання студентів (FAQ)


Резюме: ключові takeaways

Коваріація vs Кореляція

Коваріація показує напрямок спільної зміни, але є масштабозалежною. Кореляція — нормалізований безрозмірний показник від Завантаження... до Завантаження....

Кореляція Пірсона (r)

Вимірює виключно лінійний зв'язок між нормальними величинами. Еквівалентна косинусу кута між центрованими векторами ознак у багатовимірному просторі.

Матриця кореляції

Симетрична таблиця парних зв'язків. Головна діагональ завжди дорівнює одиниці. Heatmap візуалізація — найшвидший спосіб знайти приховані патерни.

Кореляція ≠ Причинність

Фундаментальне правило аналізу: кореляція доводить спільну мінливість, але не механізм впливу. Завжди перевіряйте наявність третіх прихованих факторів (Confounders).

Мультиколінеарність

Сильний зв'язок між предикторами спричиняє нестабільність матриць у лінійних моделях. Контролюється через показник VIF та видалення дублікатів.

Рангові міри (Спірмен, Кендалл)

Працюють із монотонними нелінійними залежностями та ординальними даними, будучи повністю захищеними від впливу екстремальних викидів.

Що далі?

Ви опанували фундаментальні інструменти дослідження взаємозв'язків між змінними. Тепер ви вмієте:

  • Професійно інтерпретувати коваріацію та коефіцієнт Пірсона;
  • Будувати та читати теплові карти кореляційних матриць;
  • Уникати пасток хибної причинності в телеметрії та бізнес-аналітиці;
  • Підбирати правильну міру зв'язку (Пірсон, Спірмен чи Кендалл) під тип даних.

Наступний крок — виявлення та обробка аномалій і викидів (Outliers), які здатні спотворювати статистичні тести, зміщувати кореляції та руйнувати навчання ML-моделей.

💡 Порада для закріплення навичок: Завантажте реальний датасет (наприклад, змагання Kaggle з прогнозу цін або кредитного скорингу). Побудуйте кореляційну матрицю ознак. Для кожної сильної кореляції поставте критичне запитання: «Чи це реальний причинний зв'язок, чи прояв неврахованого спільного фактора?». Це критичне мислення вирізняє провідного дата-саєнтиста!
Copyright © 2026