# Домашнє завдання · PCA — метод головних компонент

> Лекція: [lecture.html](lecture.html) · Практика: [practice.ipynb](practice.ipynb) · Тест: [quiz.html](quiz.html)

Тема була про те, що PCA — це геометрія, а не заклинання. Тому й завдання перевіряють
не вміння викликати `PCA()`, а розуміння: що саме метод максимізує, чого він не бачить
і де він бреше, якщо його неправильно поставити.

---

## 🟢 Рівень 1 — База

Візьми зошит із практики й доведи до кінця те, що там залишилось незакритим:
**графік накопиченої поясненої дисперсії**.

1. По горизонталі — кількість компонент від 1 до 7, по вертикалі — накопичений відсоток.
2. Постав горизонтальну пунктирну лінію на рівні **90 %**.
3. Виділи (кольором або підписом) ту точку, у якій крива вперше перетинає поріг.
4. Поруч намалюй стовпчики внеску кожної окремої компоненти — так, як це зроблено
   в другому інтерактиві лекції.

Далі — те саме на іншому наборі: візьми `sklearn.datasets.load_wine` (13 числових ознак,
178 рядків, завантажується локально), стандартизуй його й побудуй такий самий графік.

**Зроблено, якщо:** є два графіки з підписаними осями й порогом 90 %, і є одне речення
на кожен набір: скільки компонент потрібно для 90 % і скільки відсотків тримають перші
дві. Числа для нашої дошки мають збігтися з лекцією (5 компонент, 55.8 % на двох).

---

## 🟡 Рівень 2 — Плюс

Перевір на числах, що PCA справді знаходить **надлишкові** ознаки, а не «найкорисніші».

Зроби три версії таблиці ознак і для кожної порахуй PCA на стандартизованих даних:

| версія | що в ній |
|---|---|
| A | сім ознак як у практиці |
| B | без `оцінка_каталогу` — тієї, що корелює з `типова_ціна` на 0.989 |
| C | сім ознак плюс **восьма**: `ціна_в_тисячах = ціна / 1000` |

Для кожної версії випиши: усі власні числа, накопичену частку на двох компонентах
і кількість компонент, потрібну для 90 %.

Питання, на які треба відповісти словами:

- чому у версії **C** останнє власне число дорівнює нулю (а не просто маленькому числу,
  як сьоме власне число у версії **A**, хоча пара 0.989 там теж була);
- версія **B** втратила майже-дублікат — і все одно для 90 % їй потрібна **та сама**
  кількість компонент, що й версії A. Чому викидання надлишкової колонки не скоротило
  список? Підказка: подивись, як змінилася накопичена частка на двох компонентах.

**Зроблено, якщо:** є таблиця з трьох рядків із трьома числами в кожному, і є дві
відповіді по два-три речення. У відповіді про версію C має бути сказано, що дві ознаки
повʼязані **точно**, а не приблизно, і тому один вимір зникає повністю. У відповіді про
версію B має прозвучати, що надлишкова колонка займала не цілий вимір, а лише десяту
частку відсотка — і викинути її означає прибрати саме стільки.

---

## 🔴 Рівень 3 — Виклик

Напиши **власну функцію PCA** й доведи, що вона працює.

```python
def мій_pca(X, скільки_компонент):
    """Повертає (координати, компоненти, пояснена_дисперсія)."""
    ...
```

Вимоги до реалізації:

1. центрування робиш сам, `StandardScaler` для нього не використовуєш;
2. власні числа й вектори береш через `np.linalg.eigh` і сортуєш за спаданням;
3. функція повертає координати обʼєктів у нових осях, самі компоненти й вектор
   поясненої дисперсії;
4. знак кожної компоненти фіксуєш за домовленістю: **найбільша за модулем вага
   в компоненті має бути додатною** — інакше порівняння з бібліотекою впаде на знаку.

Далі — три перевірки, кожна окремим `assert`:

- пояснена дисперсія збігається з `PCA().fit(X).explained_variance_`;
- компоненти збігаються з `pca.components_` після приведення знака;
- **сума квадратів відстаней від точок до їхніх відновлених копій** для k компонент
  дорівнює сумі відкинутих власних чисел, помноженій на (n − 1). Це і є те саме
  твердження з розділу 4 лекції, тільки для семи вимірів замість двох.

Останню перевірку сформулюй сам і поясни в коментарі, звідки береться множник (n − 1).

**Зроблено, якщо:**

1. функція запускається на нашій матриці `X` і на `load_wine`, і всі три `assert`
   проходять на обох;
2. у коді немає жодного виклику `sklearn.decomposition.PCA`, окрім як усередині перевірок;
3. є коментар, який пояснює множник (n − 1) у третій перевірці.

---

## Підказки

- **Власні вектори `eigh` стоять у стовпцях, а не в рядках.** `sklearn` тримає компоненти
  в рядках. Якщо забути транспонувати, порівняння з бібліотекою впаде — і причина буде
  зовсім не там, де ти її шукатимеш.
- **Знак не помилка.** Якщо твоя перша компонента дорівнює бібліотечній з протилежним
  знаком — це той самий напрямок. Порівнюй `np.abs(...)` або фіксуй знак за домовленістю
  з пункту 4.
- **Для третього рівня не потрібна нова математика.** Втрачена частина — це та сама
  d² зі схеми 1 лекції, тільки просумована по всіх точках і всіх викинутих напрямках.
  Спочатку перевір формулу на шести точках із розділу 5, де ти вже знаєш відповідь
  (140 і 20), і лише потім на семи ознаках.
- **Версія C другого рівня зламається красиво.** Останнє власне число не просто мале —
  воно порядку `1e-16`, тобто нуль із точністю до похибки обчислень. Це нормально:
  дві точно повʼязані колонки живуть в одному вимірі, а не у двох.
