Python з нуля · Блок 8 · Тема 35

Побачити дані

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

За дві попередні теми ми навчилися дані тримати. У темі 33 зʼявився масив, який рахує суму мільйона чисел одним рядком. У темі 34 — таблиця, яка знає імена своїх колонок і вміє групувати. Обидві охоче видають середнє, медіану, кореляцію. І саме тут ховається пастка, з якої почнеться ця тема: число — це вирок без пояснення. Воно каже «середній виторг 4289 гривень» і жодним чином не каже, що половина цієї суми припадає на два дні тижня.

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

01 / НавіщоЧому таблиця чисел мовчить

Почнемо з досліду, після якого сперечатись уже нема про що. Візьмемо чотири маленькі набори даних — у кожному по одинадцять пар чисел. Уяви, що це чотири міста, у кожному ми одинадцять місяців поспіль записували, скільки витратили на рекламу (x) і скільки отримали виторгу (y).

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

набір 1 : x̄ = 9.00 ȳ = 7.50 s(y) = 2.03 r = 0.82 y = 0.50·x + 3.00 набір 2 : x̄ = 9.00 ȳ = 7.50 s(y) = 2.03 r = 0.82 y = 0.50·x + 3.00 набір 3 : x̄ = 9.00 ȳ = 7.50 s(y) = 2.03 r = 0.82 y = 0.50·x + 3.00 набір 4 : x̄ = 9.00 ȳ = 7.50 s(y) = 2.03 r = 0.82 y = 0.50·x + 3.00

Чотири однакові рядки. Якби ми ухвалювали рішення за таблицею, ми б сказали: міста поводяться однаково, скрізь помірний позитивний звʼязок, вкладай усюди однаково. А тепер подивись, що це насправді за дані.

Схема 1 · Однакові числа, різні форми

Чотири набори по 11 точок. Усі п'ять показників збігаються до другого знака після коми. Масштаб осей однаковий у всіх чотирьох, щоб порівняння було чесним.

Кожен набір потребує іншого рішення. Перший — звичайний шум навколо прямої, тут лінійна модель доречна. Другий — чиста дуга: звʼязок є, але він не прямий, і пряма описує його погано на обох кінцях. Третій — ідеальна пряма з одним зіпсованим виміром; правильна дія — знайти й перевірити цю точку, а не міняти модель. Четвертий узагалі не має звʼязку: десять вимірів зроблено за однакового x, і всю кореляцію в поодинці створює єдина точка праворуч. Цей набір відомий як квартет Анскомба, і він тут не заради краси: це найкоротший можливий доказ, що зведення даних до кількох чисел втрачає саме те, заради чого дані збирали.

Статистика — це стиснення. Одинадцять пар чисел стиснулись у пʼять показників, і, як у будь-якому стисненні з втратами, щось зникло. Графік теж не показує всього, але він втрачає інше: він не дасть тобі точного значення кореляції, зате миттєво покаже дугу, викид або порожнечу.

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

02 / БудоваFigure і Axes

matplotlib — це бібліотека, яку ставлять окремо, як ми вчилися в темі 17:

python3 -m pip install matplotlib

Найперший її графік виглядає так — три рядки, і на екрані вікно з кривою:

import matplotlib.pyplot as plt fig, ax = plt.subplots() # аркуш і система координат на ньому ax.plot([1, 2, 3, 4], [10, 14, 12, 19]) plt.show() # показати вікно

Розберемо перший рядок, бо в ньому вся будова бібліотеки. Функція plt.subplots() повертає дві речі, і плутати їх не варто.

Figure — це аркуш паперу. Він має розмір у дюймах, колір тла, роздільність і вміє зберігатися у файл. Сам він нічого не малює. Змінну для нього прийнято називати fig.

Axes — це накреслена на аркуші система координат. Саме вона має дві осі, підписи, сітку, легенду й усе намальоване всередині. Малює вона: ax.plot, ax.bar, ax.set_title. На одному аркуші може бути одна така область, а може бути дванадцять.

Найпідступніша назва в бібліотеці. Axes — це не «осі» в множині. Осі — це Axis (їх дві: ax.xaxis і ax.yaxis), а Axes — уся область побудови разом з ними. Різниця в одну літеру, і саме через неї новачки шукають метод не там, де він живе.

Схема 2 · Анатомія одного графіка

Десять частин, кожна названа й показана на тому самому прикладі: виторг кавʼярні за два тижні. Праворуч — метод, яким ця частина створюється.

Запамʼятай межу між ① і ②. Усе, що стосується аркуша цілком — розмір, збереження у файл, спільний заголовок, — робить fig. Усе, що стосується конкретної картинки — дані, підписи, легенда, межі, — робить ax. Дев'ять із десяти методів, які ти писатимеш, починаються з ax., і саме тому змінну ax треба мати під рукою явно.

Чому fig, ax = plt.subplots(), а не просто plt.plot()

Той самий графік можна намалювати коротше:

plt.plot([1, 2, 3, 4], [10, 14, 12, 19]) plt.xlabel("день") plt.show()

Так пишуть у більшості коротких прикладів, і для одного графіка в зошиті це цілком нормально. Але тут працює механіка, яку варто розуміти з самого початку: plt.plot малює не «десь», а в поточній області. Якщо поточної немає — вона мовчки створюється. Якщо є — малюється туди, хай навіть це залишок від клітинки, яку ти запускав десять хвилин тому.

Наслідки видно на трьох типових ситуаціях:

Тому далі в лекції — тільки явний варіант. Це не питання смаку: явна змінна перетворює «намалюй мені щось» на «намалюй ось сюди», а різниця між цими двома формулюваннями — та сама, що між глобальною змінною й аргументом функції з теми 15.

03 / ТипиЧотири типи й що кожен показує

Тепер до наших даних. Кавʼярня працювала шістдесят днів; для кожного дня ми знаємо виторг у гривнях. Це звичайний масив numpy завдовжки 60 — той самий, що з теми 33.

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

ax.plot(дні, виторг) # лінія: як величина змінювалась у часі ax.scatter(дні, виторг) # точки: як дві величини повʼязані між собою ax.bar(дні, виторг) # стовпчики: яка категорія більша за яку ax.hist(виторг, bins=12) # гістограма: як значення розподілені

Різниця не косметична. Лінія сполучає сусідні точки відрізком і цим стверджує: між ними є неперервний перехід. Для днів це правда, для назв напоїв — ні, тому лінія по категоріях завжди неправильна. Точки нічого не стверджують про порядок і тому годяться там, де порядку немає: зріст і вага, реклама і виторг. Стовпчики порівнюють висоти, і око робить це точно — але тільки поки стовпчиків небагато. Гістограма взагалі не має осі «що це за спостереження»: вона ріже діапазон значень на смуги (bins) і рахує, скільки спостережень потрапило в кожну.

Інтерактив 1 · Ті самі 60 днів чотирма способами

Дані не змінюються — змінюється лише один виклик. Дивись, що зʼявляється й що зникає при кожному перемиканні.

відповідає на питання
що при цьому зникає
Порівняй перший і четвертий варіанти. Лінія показує тижневий ритм — вісім однакових зубців — і повільне зростання; про те, що виторг збирається у дві окремі купки, вона не каже нічого. Гістограма показує рівно ці дві купки — 44 буденні дні й 16 вихідних, з порожнечею між ними, — і повністю знищує час: за нею неможливо сказати, коли саме був який день. Це не «краще» і не «гірше»: це відповіді на два різні питання.

04 / ПідписиПідписи, заголовок, легенда

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

fig, ax = plt.subplots(figsize=(8, 4)) ax.plot(дні, виторг, label="2025") ax.plot(дні, виторг_торік, label="2024") ax.set_xlabel("день роботи") # що по горизонталі ax.set_ylabel("виторг, грн") # що по вертикалі І В ЧОМУ ax.set_title("Кавʼярня «Дві ложки»: щоденний виторг") ax.legend() # підписи ліній — з label= ax.grid(True, alpha=0.3) # ледь помітна сітка

Три речі тут варті окремої уваги.

Одиниці виміру — частина підпису. «Виторг» — це не підпис, «виторг, грн» — підпис. Через півроку саме ти й будеш тією людиною, яка дивиться на свій графік і не памʼятає, гривні там чи тисячі гривень.

Легенда бере назви з label=. Виклик ax.legend() без аргументів обходить усе намальоване й збирає підписи звідти. Якщо жодного label= не було, matplotlib надрукує попередження й намалює порожню рамку. Легенда потрібна тоді й тільки тоді, коли ліній більше однієї: підписувати єдину лінію — це витрачати місце на те, що вже написано в заголовку.

Сітка має бути тлом, а не візерунком. alpha=0.3 робить її напівпрозорою. Сітка допомагає зчитати значення, тож вона мусить бути видимою, але не мусить конкурувати з даними: якщо ти помічаєш сітку раніше за лінію, вона зайва.

Швидка перевірка на читабельність. Покажи графік людині, яка не знає задачі, і не кажи ані слова. Якщо за десять секунд вона не може сказати, що по осях і що тут головне, — бракує не даних, а підписів. Ця перевірка коштує хвилину й економить години суперечок про те, «що ж воно все-таки показує».

05 / СіткаКілька графіків у сітці

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

fig, axes = plt.subplots(2, 2, figsize=(11, 7), constrained_layout=True) axes[0, 0].plot(дні, виторг) axes[0, 0].set_title("виторг у часі") axes[0, 1].hist(виторг, bins=12) axes[0, 1].set_title("розподіл виторгу") axes[1, 0].bar(напої, порції) axes[1, 0].set_title("продано порцій") axes[1, 1].scatter(відвідувачі, виторг) axes[1, 1].set_title("відвідувачі й виторг")

Коли рядків або стовпців більше одного, plt.subplots віддає не одну область, а масив numpy — той самий двовимірний масив із теми 33. Тому й індексується він так само: axes[рядок, стовпець]. Для однієї смуги (plt.subplots(1, 3)) масив одновимірний: axes[0], axes[1], axes[2]. Це часте джерело помилки IndexError: too many indices — коду написали axes[0, 1], а масив плаский.

Два аргументи роблять сітку придатною для читання:

06 / ФайлЗберегти у файл

Графік у вікні живе, поки програма не закрилась. У звіт, у листа чи в репозиторій його треба покласти файлом — це вміє сам аркуш:

fig.savefig("виторг.png", dpi=150, bbox_inches="tight")

dpi — крапок на дюйм. Розмір аркуша задано в дюймах (figsize=(8, 4)), тож фактичний розмір картинки в пікселях — це добуток: 8 × 150 = 1200 пікселів завширшки. Значення за замовчуванням, 100, дає 800 пікселів — на екрані нормально, а в друкованому звіті шрифт підписів розсиплеться на квадратики. Робочі орієнтири: 100 — чернетка й нотатник, 150 — презентація й екран високої роздільності, 300 — друк.

bbox_inches="tight" — обрізати білі поля. Без нього matplotlib збереже аркуш цілком, разом із порожнечею навколо, і довгий підпис осі може навіть не поміститись і виявитись обрізаним.

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

Формат вибирається за розширенням імені: .png — точковий, годиться всюди; .svg — векторний, не втрачає якості при збільшенні, зручний для вебу; .pdf — векторний, для документів і друку. Векторні формати не мають dpi взагалі: у них зберігаються не пікселі, а вказівки, як намалювати.

07 / ВибірЯкий тип під які дані

Тип графіка не обирають за красою — його обирають за питанням, на яке треба відповісти. Ось коротка відповідність, яка покриває майже всі щоденні випадки.

Схема 3 · Дані → графік → що видно і що ні

Кожен тип щось показує ціною того, що приховує інше. Останню колонку варто читати уважніше за другу.

які в тебе данівикликщо покажещо приховає
Одна величина в часі: виторг по днях, температура по годинах ax.plot напрям і форму зміни, ритм, різкі стрибки розподіл: скільки взагалі було високих і низьких днів
Розподіл однієї величини: усі чеки за місяць ax.hist де скупчення, чи є два горби, чи довгий хвіст порядок у часі та кожне окреме спостереження
Дві величини разом: реклама і виторг, зріст і вага ax.scatter форму звʼязку, силу розкиду, викиди скільки спостережень злилось в одну точку
Порівняння категорій: продано лате, чаю, какао ax.bar хто більший і приблизно на скільки розкид усередині категорії й кількість спостережень
Розкид усередині кількох категорій: чеки по днях тижня ax.boxplot медіану, розмах і викиди в кожній групі одразу форму розподілу: два горби виглядатимуть як один
Кілька рядів у часі: цього року й торік ax.plot ×2 де ряди сходяться й розходяться усе, якщо ліній більше пʼяти — вони зіллються
Кругової діаграми в таблиці немає, і це навмисно. Око погано порівнює кути: різницю між 24 % і 27 % на «пирозі» не бачить майже ніхто, а на стовпчиках її видно одразу. Кругова діаграма виправдана хіба що для двох-трьох часток, які помітно різні, — тобто в тих випадках, коли достатньо було й речення тексту.

08 / ЧесністьЯк збрехати ненавмисно

Найпоширеніший спосіб зробити оманливий графік — не злий намір, а налаштування, яке здавалося розумним. Найпоширеніше з них — обрізана вісь.

Кавʼярня рахує середній чек по кварталах: 96, 98, 101 і 104 гривні. Зростання за рік — з 96 до 104, тобто на 8.3 %. Якщо почати вісь y не з нуля, а, скажімо, з 95, стовпчики стануть заввишки 1, 3, 6 і 9 одиниць — і останній виявиться вдевʼятеро вищим за перший. Дані ті самі. Висновок, який зробить читач, — різний.

Інтерактив 2 · Де починається вісь

Повзунок рухає лише нижню межу осі. Числа не змінюються жодного разу — праворуч завжди той самий графік із нулем для порівняння.

справжнє зростання
виглядає як
Q4 вищий за Q1
перебільшення
Не роби з цього правила «вісь завжди з нуля». Для стовпчиків — так, майже завжди: довжина стовпчика і є те, що ми порівнюємо. Для лінії часового ряду — часто ні: температура повітря або курс валюти від нуля перетворюються на пряму горизонтальну смужку, у якій не видно нічого. Правило чесне й просте: якщо нуль на графіку є, читач порівнює довжини; якщо нуля немає, це треба бачити з першого погляду — підписаною віссю й помітним розривом, а не дрібним шрифтом у кутку.

Обрізана вісь — найвідоміший спосіб, але не єдиний. Ще два трапляються не рідше.

Нерівний крок по горизонталі. Якщо виміри зроблено 1, 2, 3, 10 і 30 числа, а на осі вони стоять на однаковій відстані, то графік показує неіснуючу рівномірність: три дні перетворюються на такий самий проміжок, як двадцять. Ліки — передавати справжні значення по x, а не порядкові номери: тоді matplotlib сам розставить точки на правильній відстані.

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

Схема 4 · Один графік до і після

Ті самі пʼять чисел — продані порції за тиждень. Ліворуч звичайні помилки, праворуч виправлено, унизу перелік того, що саме змінили.

Жодна зі змін не торкнулася даних. Числа в обох половинах однакові — 610, 540, 420, 180 і 90 порцій. Змінилось тільки те, як вони показані, і разом із цим змінився висновок: ліворуч еспресо виглядає у 3.4 раза більшим за чай, праворуч видно справжню різницю — у 2.3 раза.

09 / pandasЗвʼязок із pandas

Таблиця з теми 34 вміє малювати себе сама. Метод plot є і в DataFrame, і в Series, і всередині він кличе той самий matplotlib:

import pandas as pd продажі = pd.DataFrame({"день": дні, "виторг": виторг}) продажі.plot(x="день", y="виторг") # лінія продажі.plot(kind="hist", y="виторг", bins=12) # гістограма продажі.groupby("день_тижня")["виторг"].mean().plot(kind="bar")

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

Важлива деталь: df.plot() повертає Axes — той самий обʼєкт, про який була вся ця лекція. Тому доробити результат можна звичайними методами, а можна навпаки — попросити pandas малювати в уже готову область:

fig, ax = plt.subplots(figsize=(9, 4)) продажі.plot(x="день", y="виторг", ax=ax, label="цього року") ax.set_ylabel("виторг, грн") ax.set_title("Кавʼярня «Дві ложки»") fig.savefig("звіт.png", dpi=150, bbox_inches="tight")

Практичний поділ виходить такий. Досліджуєш — пиши df.plot(): він короткий і сам підписує. Готуєш картинку, яку хтось побачить, — бери fig, ax: тобі знадобиться контроль над розміром, шрифтами, межами й збереженням, а через df.plot() усе це доводиться передавати домовленостями про іменовані аргументи.

10 / ГлибшеДля тих, кому мало

Чому plt взагалі влаштований так дивно. Модуль pyplot — це шар сумісності з MATLAB, написаний як машина станів. Усередині нього живе список аркушів і вказівник на поточний; plt.plot насправді робить приблизно plt.gcf().gca().plot(...), де gcf — «дай поточний аркуш», а gca — «дай поточну область» (обидві створюють обʼєкт, якщо його ще немає). Це глобальний змінюваний стан з усіма його властивостями: зручний в інтерактивній сесії, де ти — єдиний, хто його чіпає, і небезпечний у скрипті, бібліотеці чи тесті, де порядок викликів не гарантований.

Витік памʼяті, який ловлять усі. Кожен plt.subplots() реєструє аркуш у глобальному списку pyplot, і збирач сміття його не забере, бо посилання є. Скрипт, який у циклі малює тисячу графіків і зберігає їх у файли, спокійно зʼїдає всю памʼять. Ліки — закривати аркуш явно після збереження:

for місяць in місяці: fig, ax = plt.subplots() ax.plot(дані[місяць]) fig.savefig(f"{місяць}.png", dpi=150) plt.close(fig) # без цього аркуш лишається в памʼяті назавжди

Бекенди. matplotlib відділяє «що намалювати» від «куди намалювати». Пристрій виводу називається бекендом: TkAgg малює у вікно, module://matplotlib_inline.backend_inline — у клітинку зошита, Agg — просто в памʼять, без жодного вікна. На сервері без графічного середовища треба саме Agg, інакше програма впаде на спробі відкрити вікно:

import matplotlib matplotlib.use("Agg") # ДО import matplotlib.pyplot import matplotlib.pyplot as plt

Стилі й шрифти. Вигляд за замовчуванням задається словником plt.rcParams — розмір шрифту, товщина ліній, палітра, розмір аркуша. Змінювати кожен параметр окремо рідко потрібно: є готові набори, plt.style.use("seaborn-v0_8-whitegrid") або plt.style.use("ggplot"). Окрема поширена морока — кирилиця: якщо підписи виводяться квадратиками, потрібен шрифт із кириличними літерами, наприклад plt.rcParams["font.family"] = "DejaVu Sans" (він іде в комплекті з matplotlib і кирилицю має).

А що таке seaborn. Це надбудова над matplotlib, яка вміє в один рядок те, що тут вимагає пʼяти — статистичні графіки з групуванням прямо з DataFrame; вона повертає ті самі Axes, тож усе, що ти вивчив, лишається чинним.

11 / ПідсумокЩо забрати з теми — і що далі

Уся тема тримається на одній думці: графік — це не ілюстрація до аналізу, це його перший крок. Числа стискають дані й тим самим ховають форму; око бачить форму миттєво й безкоштовно.

На цьому курс Python закінчується. Ти пройшов шлях від першого запуску інтерпретатора до масивів, таблиць і графіків — тобто до повного набору інструментів, якими щодня працюють з даними. Далі починається інша розмова.

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

Курс машинного навчання → Від «що сталося» до «що станеться»: як із тих самих таблиць і графіків народжується модель, яка робить передбачення, і як зрозуміти, чи можна їй вірити. перейти до курсу
Далі в практиці. У practice.ipynb ти власноруч порахуєш статистики чотирьох наборів і доведеш assert-ом, що вони збігаються до другого знака, а потім намалюєш їх і побачиш різницю; побудуєш усі чотири типи графіка на тому самому виторзі кавʼярні; підпишеш осі, заголовок і легенду; зіб'єш чотири графіки в сітку 2×2; збережеш картинку у файл із двома різними dpi й порівняєш розміри в байтах; намалюєш те саме через df.plot(); і зробиш пару «обрізана вісь проти чесної», щоб на своїх очах побачити, у скільки разів змінюється враження від тих самих чисел.

Далі в темі

Теорію прочитано. Тепер закріпи її на практиці.