# Домашнє завдання · Графіки matplotlib

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

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

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

```python
import numpy as np

дні = np.arange(90)
шум = (дні * 1103515245 + 12345) % 2 ** 31 % 1201 - 600     # від -600 до +600 кроків
вихідний = np.isin(дні % 7, [5, 6])                          # субота й неділя
кроки = 6000 + 40 * дні + вихідний * 2500 + шум
```

---

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

Побудуй **один** графік, у якого є все, чого лекція вимагала від читабельної картинки.

1. Лінія `кроки` в часі плюс друга лінія — `кроки_торік = кроки - 1500`.
2. Заголовок, підпис осі `x`, підпис осі `y` **з одиницями**, легенда з двома записами,
   напівпрозора сітка.
3. Горизонтальна лінія на рівні 10 000 кроків (`ax.axhline`) з власним підписом у легенді.
4. Збережи результат у файл `kroky.png` з `dpi=200` і `bbox_inches="tight"` — і зроби це
   **до** `plt.show()`.
5. Надрукуй, скільки днів із девʼяноста перевищили 10 000 кроків.

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

- проходить `assert list(кроки[:5]) == [5735, 6190, 5876, 6331, 6017]` — формула саме та;
- проходить `assert (кроки > 10000).sum() == 13`, і це число надруковане в консолі;
- проходить `assert ax.get_ylabel() != "" and ax.get_xlabel() != ""`;
- проходить `assert len(ax.get_legend().get_texts()) == 3` — дві лінії плюс поріг;
- файл `kroky.png` існує, і його ширина в пікселях більша за 1500
  (перевір через `PIL.Image.open("kroky.png").size`).

---

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

Одні дані, три різні питання — і три різні типи графіка в одній сітці.

1. Сітка `plt.subplots(2, 2, constrained_layout=True)`.
2. **Ліворуч угорі** — лінія по днях: чи є зростання за три місяці.
3. **Праворуч угорі** — гістограма всіх девʼяноста значень: як вони розподілені.
4. **Ліворуч унизу** — стовпчики: середня кількість кроків **по днях тижня**
   (`дні % 7`, сім стовпчиків).
5. **Праворуч унизу** — точки: кроки проти номера тижня (`дні // 7`), щоб побачити розкид
   усередині кожного тижня.
6. Кожна область має власний заголовок і підписи обох осей.
7. Під сіткою надрукуй три речення: що видно на кожному типі й чого на ньому **не** видно.

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

- проходить `assert axes.shape == (2, 2)` — і в коді видно, що це масив `numpy`;
- проходить `assert round(кроки[~вихідний].mean()) == 7788` і
  `assert round(кроки[вихідний].mean()) == 10299`, а обидва числа підписані на
  стовпчиковому графіку;
- у стовпчиках по днях тижня два останні стовпчики помітно вищі, і `assert` це підтверджує:
  `assert середні_по_днях[5] > середні_по_днях[:5].max()`;
- у трьох реченнях висновку названо принаймні одну річ, яку кожен тип **приховує**.

---

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

Зроби власний «квартет»: чотири набори з однаковими числами й різними формами.

1. Придумай і згенеруй **чотири** набори по 40 точок, у яких збігаються до другого знака:
   середнє `x`, середнє `y`, стандартне відхилення `y` і коефіцієнт кореляції.
   Форми мають бути різні: пряма з шумом, дуга, дві окремі хмари, пряма з одним викидом.
   Підганяти можна масштабом і зсувом: якщо взяти будь-яку форму й перерахувати її як
   `y_нове = (y - y.mean()) / y.std() * потрібне_відхилення + потрібне_середнє`,
   середнє й відхилення стануть точно такими, як треба, а форма не зміниться.
2. Надрукуй таблицю показників усіх чотирьох наборів і доведи `assert`-ом, що вони
   однакові до другого знака.
3. Намалюй усі чотири в сітці `1×4` зі **спільними** межами осей і однією й тією самою
   прямою регресії.
4. Окремо зроби пару «оманливо / чесно» на своїх даних кроків: візьми середнє за перший
   і за останній місяць, покажи їх стовпчиками двічі — з віссю від нуля й з обрізаною
   віссю — і **порахуй числом**, у скільки разів перебільшене враження.
5. Опиши одним абзацом третій спосіб збрехати графіком, якого не було в лекції, і
   покажи його кодом.

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

- проходить `assert (показники.round(2).nunique() == 1).all()` для таблиці з чотирьох
  рядків — усі показники однакові;
- кореляція в усіх чотирьох наборів не менша за 0.6 за модулем (інакше «однаковість»
  досягнута тим, що звʼязку немає ніде);
- на картинці всі чотири форми справді різні, і в коментарі до кожної написано, яке
  рішення вона диктує;
- для пари «оманливо / чесно» надруковано два числа: справжня різниця у відсотках і
  візуальне відношення довжин стовпчиків, а також коефіцієнт перебільшення;
- третій спосіб збрехати описано словами **й** відтворено кодом на тих самих даних.

---

## Підказки

- **Рівень 1.** `ax.axhline(10000, label="поріг 10 000")` додає лінію й одразу її підпис.
  Легенда збирає підписи звідусіль, де був `label=`, — тому три записи виходять самі.
- **Рівень 1, файл.** Якщо `kroky.png` вийшов порожнім, найімовірніша причина —
  `savefig` після `show()`. Порядок має бути зворотним.
- **Рівень 2.** Середнє по днях тижня зручно рахувати без циклу:
  `np.array([кроки[дні % 7 == д].mean() for д in range(7)])` — сім чисел, кожне з
  одного зрізу за умовою.
- **Рівень 3, підгонка.** Спершу зроби чотири різні **форми**, а вже потім кожну окремо
  перерахуй так, щоб середнє й відхилення стали потрібними. Кореляцію підганяти важче:
  найпростіше змішувати «чистий звʼязок» і шум у пропорції, яку підбираєш кількома
  спробами в циклі.
- **Рівень 3, викид.** Одна точка, віднесена далеко вбік, змінює кореляцію сильніше за
  решту тридцяти девʼяти разом узятих. Це і є причина, чому четвертий набір Анскомба
  виглядає так, як виглядає.
- **Рівень 3, пункт 5.** Подумай про подвійну вісь `y` (`ax.twinx()`), про пропущені дні
  на осі часу й про порядок категорій, підібраний під потрібний висновок.
