Розвідка даних

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

Тобі передали файл із дошки оголошень про вживані телефони: 1 212 рядків, вісім стовпців, супровідний лист на три речення. «Ціна в гривнях, памʼять у гігабайтах, остання колонка — чи виявилось оголошення шахрайським». Файл відкривається, помилок немає, усе на місці. Здається, можна працювати.

Не можна. У цій таблиці зараз лежать: 101 порожня ціна, причому порожня не випадково; 12 рядків, які просто продубльовані; стовпець із памʼяттю, який pandas прочитав як текст; шість оголошень дорожчих за 70 000 грн, з яких частина справжня, а частина — одруки; і один стовпець, звʼязаний із відповіддю настільки добре, що ним хочеться одразу скористатись — і саме тому не можна.

Нічого з цього не видно з опису файлу. Усе це видно за пʼятнадцять хвилин, якщо знати, куди дивитись. Це і є розвідувальний аналіз даних (exploratory data analysis, EDA) — і ця тема саме про нього.

МотивДані не такі, як їх описали

У темі 02 ми домовились, що навчання з учителем — це таблиця з ознаками й таргетом. Тоді таблиця була ідеалізована: рівні стовпці, охайні числа. У житті вона така майже ніколи, і причин рівно три.

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

Дані зберігали через кілька систем. Числа проходять через експорт у таблицю, вивантаження у файл, читання назад — і на кожному кроці «128» може перетворитись на «128 ГБ», а порожнє поле — на текст «немає».

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

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

Уся тема — на одній таблиці. Ось вона.

стовпецьщо цетип
модельназва телефона, шість різних моделейтекст
рікрік випуску, від 2017 до 2024ціле
станнове, дуже добре, добре, задовільнетекст
памʼять_гбобсяг памʼяті: 64, 128, 256 або 512?
вік_акаунтаскільки днів існує акаунт продавцяціле
скаргскільки скарг надійшло на це оголошенняціле
цінаціна в оголошенні, гривеньдробове
шахрайськетаргет: 1 — оголошення виявилось шахрайським0 або 1

Знак питання в третьому рядку не помилка верстки. Це те, що ми зараз побачимо.

Що треба знати заздалегідь. Механіку pandas — DataFrame, Series, читання файлів, loc і iloc, фільтр умовою, groupby, fillna — ми розібрали в темі 39 курсу Python «Pandas: таблиці». Тут ми жодного разу не пояснюємо, як викликати метод. Ми говоримо лише про те, що читати в його відповіді. Графіки — на matplotlib з теми 40 курсу Python.

ОглядПерший погляд: пʼять команд

Перше знайомство з будь-якою таблицею — це пʼять викликів. Вони завжди ті самі, і робити їх треба навіть тоді (особливо тоді), коли ти впевнений, що знаєш, що всередині. Цінність не в командах, а в питаннях, які до кожної з них поставлені.

shape — чи стільки рядків, скільки я очікував. Наша таблиця дає (1212, 8). Якщо в листі було сказано «вивантаження за рік, близько тисячі оголошень» — усе гаразд. Якщо було сказано «40 тисяч» — далі можна не дивитись, спершу треба розібратись, куди поділись 39 тисяч рядків. Це найдешевша перевірка з усіх і найчастіше пропущена.

head() — чи значення схожі на те, що обіцяє назва стовпця. Не «які тут числа», а саме це. У нашій таблиці другий рядок одразу показує NaN у стані, а четвертий — памʼять, записану як 256 ГБ. Обидві дрібниці видно неозброєним оком у перших пʼятьох рядках, і обидві коштуватимуть годину, якщо їх не помітити.

dtypes — чи всі числові стовпці справді числові. Ось що віддає наша таблиця:

модель object рік int64 стан object памʼять_гб object ← має бути число вік_акаунта int64 скарг int64 ціна float64 шахрайське int64

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

У нас таких рядків 215 із 1212: у них памʼять записана як 128 ГБ замість 128. І тут ховається справжня пастка. Найочевидніша реакція — «зведімо колонку до чисел» — виглядає так:

pd.to_numeric(дошка["памʼять_гб"], errors="coerce") # 215 нових NaN

errors="coerce" означає «те, що не зміг прочитати, зроби пропуском». Ця команда не падає, не попереджає й нічого не пише в консоль. Вона просто мовчки перетворює 215 цілком справжніх значень на порожнечу — 17,7 % таблиці. Правильна дія — спершу прибрати суфікс, і лише потім переводити в число:

памʼять = дошка["памʼять_гб"].str.replace(" ГБ", "") памʼять = pd.to_numeric(памʼять) # 0 нових NaN

info() — де дірки. Він друкує для кожного стовпця кількість непорожніх значень, і її треба порівнювати з першим рядком виводу. У нас 1212 entries, а в колонці «ціна» стоїть 1111 non-null — отже, 101 порожня. У колонці «стан» — 1164, отже 48 порожніх. Решта стовпців повні.

describe() — чи числа взагалі бувають такими. Його читають не зверху вниз, а трьома парами рядків. Ось вивід для ціни:

count 1111.0 ← менше за 1212: тут дірки mean 6508.8 ← середнє std 8674.0 ← розкид більший за середнє min 130.0 ← 130 грн за телефон? 25 % 2180.0 50 % 4010.0 ← медіана в півтора раза менша за середнє 75 % 7700.0 max 95000.0 ← 95 000 грн за вживаний телефон?

Пара перша: count проти кількості рядків. Різниця — це пропуски, і вона тут є.

Пара друга: min і max проти здорового глузду. Не «чи це можливо в принципі», а «чи буває таке на дошці вживаних телефонів». 130 грн — буває, це майже брухт. 95 000 грн теж буває, але на ці рядки доведеться подивитись окремо.

Пара третя: mean проти 50 %. Середнє 6 509, медіана 4 010. Коли ці два числа розходяться в півтора раза, у даних або довгий хвіст, або кілька дуже великих значень, або помилки введення. У нас, як зʼясується, всі три.

І шоста команда, яку варто дописати до цих пʼятьох: duplicated().sum(). Вона дає 12. Дванадцять рядків у таблиці повторюються повністю, до останнього символу. Найчастіша причина — оголошення подали двічі або вивантаження злилося з двох файлів. Дублікати не ламають нічого одразу, але вони тихо збільшують вагу тих самих обʼєктів у будь-яких підрахунках.

Що бачу у виводі → про що це говорить

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

бачупро що це говоритьщо зробити
Числовий стовпець має тип objectдесь є нечислове значення — суфікс, пробіл між тисячами, кома замість крапки, слово «немає»подивитись на унікальні значення, розібрати текст, лише потім переводити в число
count менший за кількість рядківу стовпці є пропускизʼясувати, у яких саме рядках, і чи це випадково
Середнє помітно більше за медіанурозподіл скошений праворуч, є довгий хвістдивитись на медіану, а не на середнє; побудувати гістограму
std більше за meanрозкид величезний, у стовпці змішані різні за природою обʼєктирозділити за категорією і порахувати окремо
min дорівнює нулю там, де нуль неможливийнулем позначили відсутність значеннязамінити нулі на NaN перед будь-якими підрахунками
max у сотні разів більший за медіануабо справжній рідкісний обʼєкт, або зайвий нуль при введенніподивитись на ці рядки цілком, з усіма стовпцями
Кількість унікальних значень дорівнює кількості рядківце ідентифікатор, а не ознакаприбрати зі списку ознак
duplicated().sum() більше нулярядки повторюються повністюзрозуміти, звідки повтори, перш ніж їх прибирати
Останній рядок — найпідступніший. Прибрати дублікати одним викликом легко, але спершу треба знати, чому вони зʼявились. Якщо це збій вивантаження — їх видаляють. Якщо продавець справді подав те саме оголошення двічі — це поведінка, і вона може бути ознакою.

ПропускиПропуски: скільки, де і чому

Порахувати пропуски вміє один рядок: дошка.isna().sum(). У нас 101 у ціні й 48 у стані. Це відповідь на питання «скільки» — і саме тут більшість зупиняється. А найважливіше питання інше: чому саме ці рядки.

Річ у тім, що пропуск буває трьох дуже різних сортів, і плутати їх дорого.

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

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

рядкискількичеснихшахрайськихчастка шахрайських
ціна вказана1 11197213912.5 %
ціни немає101445756.4 %

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

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

дошка["ціни_немає"] = дошка["ціна"].isna().astype(int)

Порівняй це зі стовпцем «стан». Там пропусків 48, і частка шахрайських серед них 12.5 % проти 16.3 % у решті таблиці. Це різниця в межах випадковості, і переконатись легко: 12.5 % від 48 рядків — це рівно шість оголошень, тож одне оголошення туди чи сюди зсуває частку більш ніж на два пункти. Уся різниця між групами — 3.8 пункту, тобто менша за дві такі сходинки. Порівняй із ціною: там у групі 101 рядок, а розрив 44 пункти — стільки випадковість не намалює. Пропуск у стані ні про що не говорить; пропуск у ціні говорить майже все. Один рядок коду розрізняє ці випадки, і зробити його треба до того, як заповнювати дірки.

Пропуск як ознака

Ті самі 1 212 рядків, поділені двічі: за пропуском ціни й за пропуском стану. Висота стовпчика — частка шахрайських оголошень у групі.

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

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

ФормаФорма розподілу

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

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

Скільки кошиків — стільки й висновків

Ціни з нашої таблиці, 1 111 оголошень із вказаною ціною. Вісь обрізана на 25 000 грн — вище лежать 33 оголошення, і про них окрема розмова в наступному розділі. Рожева лінія — середнє, бірюзова — медіана.

середнє, ₴—
медіана, ₴—
у піку—
ширина кошика—
Прожени повзунок в обидва краї. На трьох кошиках усе зліва — один стовпчик на 864 оголошення, і висновок «найтиповіша ціна — біля нуля»: це неправда, її створила ширина кошика у вісім тисяч гривень. Пік відліплюється від нуля лише на одинадцятому кошику. На двадцяти він стоїть там, де він і є насправді — між 1 250 і 2 500 грн, — і добре видно довгий хвіст праворуч. На вісімдесяти сусідні стовпчики стрибають на десятки відсотків, а місцями й удвічі: це вже не форма розподілу, а кількість оголошень, яка випадково потрапила в конкретний відрізок. Робочий діапазон широкий, але не безмежний: приблизно від 15 до 40 кошиків на тисячу значень.

Форма, яку ми бачимо, зветься скошеною праворуч (right-skewed): пік зліва, довгий хвіст справа. Так виглядає майже все, що не буває відʼємним і має верхню межу лише теоретично — ціни, зарплати, час очікування, розмір міста. Протилежна форма, симетричний горбик, у природних даних трапляється рідше, ніж здається з підручників.

Саме скошеність розводить середнє й медіану, і це варто проговорити повільно, бо плутанина тут коштує дорого.

крок 1 · що таке кожне з чисел

Середнє — сума всіх цін, поділена на кількість. Кожне оголошення вносить свою суму цілком: телефон за 95 000 грн важить у сумі стільки ж, скільки двадцять телефонів по 4 750 грн.

Медіана — значення, яке стоїть посередині відсортованого списку. Їй байдуже, наскільки велике найбільше число: важливо лише, що воно більше за середину.

крок 2 · що вийшло на наших даних
середнє = 6 509   ·   медіана = 4 010

Розрив у 1.62 раза. Перевіримо, звідки він: скільки оголошень дешевші за середнє?

763 з 1 111  =  68.7 %
крок 3 · висновок

Понад дві третини оголошень дешевші за «середнє оголошення». Отже, речення «середня ціна на дошці — 6 509 грн» описує обʼєкт, якого майже не буває: воно правдиве арифметично й хибне по суті. Речення «половина оголошень дешевша за 4 010 грн» описує саме те, що є.

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

ВикидиВикиди й межа

У хвості розподілу лежать викиди (outliers) — значення, які помітно відірвались від решти. Найпоширеніший спосіб їх позначити не потребує ніяких припущень про форму розподілу і зветься правилом міжквартильного розмаху. Порахуймо його руками на наших числах.

крок 1 · квартилі

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

Q1 = 2 180   ·   Q3 = 7 700
крок 2 · міжквартильний розмах

Це просто відстань між ними — ширина смуги, у якій живе середня половина оголошень. Позначають IQR (interquartile range).

IQR = 7 700 − 2 180 = 5 520

Чому саме середня половина: вона не залежить від того, що коїться в хвостах. Хоч постав найдорожчий телефон у мільйон гривень — Q1 і Q3 не зрушать. Тобто лінійка, якою ми міряємо викиди, сама від викидів не залежить. Це і є вся ідея правила.

крок 3 · межі

Від квартилів відкладають півтора розмаху в кожен бік:

верхня = 7 700 + 1.5 · 5 520 = 15 980
нижня = 2 180 − 1.5 · 5 520 = −6 100

Нижня межа вийшла відʼємною, а ціна відʼємною не буває — отже, знизу викидів немає жодного. Усе цікаве вгорі: 78 оголошень дорожчі за 15 980 грн, це 7.0 % тих 1 111 рядків, у яких ціна взагалі вказана.

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

Де провести межу

Кожна риска внизу — одне оголошення. Зверху — «скринька»: прямокутник від Q₁ до Q₃, лінія посередині — медіана, вуса — до поточної межі. Оголошення за межею позначені рожевим.

верхня межа, ₴—
викидів—
частка рядків із ціною—
серед них шахрайських—
Множник не має правильного значення — його має рішення. На 0.5 за межею опиняється кожне шосте оголошення, і слово «викид» втрачає сенс. На 3.0 лишається 35 рядків — це вже справді окремі випадки. Але жодне з цих чисел не каже, що з ними робити: правило вміє лише показати пальцем.

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

модельрікстанпамʼятьскаргціна
Gamma X2017нове512095 000
Gamma X2017нове512091 000
Gamma X2017нове512088 000
Beta 12 Pro2021добре256083 500
Gamma X2017нове512082 000
Gamma X Ultra2017добре256076 000

Чотири рядки з шести — це та сама комбінація: Gamma X 2017 року, стан «нове», 512 гігабайтів. Запакований флагман семирічної давності, який ніколи не вмикали. За такі речі колекціонери платять, і 90 000 грн тут не помилка, а ринок. Прибрати ці рядки означає викинути справжнє явище, яке в даних є.

Два інші рядки — Beta 12 Pro у стані «добре» за 83 500 і Gamma X Ultra 2017 року за 76 000 — на тій самій висоті, але сусідні стовпці їх не підтримують. Телефон у стані «добре» не коштує як чотири нових. Це майже напевно зайвий нуль при введенні: 8 350 стало 83 500, 7 600 стало 76 000. Дві однакові за величиною ціни, і два протилежні рішення — причому розрізняє їх не ціна, а решта рядка.

Правило, що економить дні. Рішення «видалити чи лишити» змістове, а не технічне. Число за межею — привід відкрити рядок цілком і спитати, чи буває таке насправді. Автоматичне видалення всього за межею 1.5·IQR прибрало б у нас 78 рядків — і разом з ними чотирьох колекційних, увесь дорогий кінець ринку і чималу частину шахрайських оголошень із завищеною ціною.

І ще один наслідок, який видно тільки на числах. Викиди спотворюють не лише середнє — вони ламають підрахунки звʼязків. Для моделі Gamma X кореляція між роком випуску й ціною дорівнює 0.06: наче звʼязку немає взагалі. Прибираємо чотири колекційні рядки зі 122 — і та сама кореляція стає 0.80. Чотири рядки зі ста двадцяти двох перевернули висновок.

ЗвʼязкиЗвʼязки між ознаками

Кожен стовпець ми подивились окремо. Тепер попарно: чи повʼязані вони між собою і чи повʼязані з таргетом. Стандартний інструмент — кореляція Пірсона, число від −1 до +1.

Словами вона означає ось що: наскільки добре хмару точок можна замінити однією прямою. +1 — точки лежать на прямій, що йде вгору. −1 — на прямій, що йде вниз. 0 — прямої, яка щось описує, немає. Одна команда рахує всі пари одразу:

дошка[числові_стовпці].corr()
пара стовпцівкореляціяяк це читати
скарг ↔ шахрайське0.887майже ідеальний звʼязок — і саме тому підозрілий
рік ↔ ціна0.249новіші дорожчі, але слабко
памʼять ↔ ціна0.209більше памʼяті — трохи дорожче
вік акаунта ↔ шахрайське−0.101у шахраїв акаунти молодші
вік акаунта ↔ ціна−0.030звʼязку немає
ціна ↔ шахрайське−0.012звʼязку немає?

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

Рік і ціна: 0.249. Здається, вік телефона майже не впливає на ціну, що безглуздо. Причина в тому, що ми змішали шість різних моделей: Alfa A5 2024 року коштує дешевше за Gamma X Ultra 2017-го, і ця різниця забиває залежність від року. Візьмімо одну модель і лише чесні оголошення — Beta 12, 171 рядок — і порахуймо ту саму кореляцію: 0.840. Звʼязок був увесь час; кореляція по всій таблиці його не показала, бо всередині таблиці лежать шість різних ринків.

Ціна і шахрайство: −0.012. Практично нуль. Спокуса тут очевидна — викреслити ціну зі списку корисних ознак і піти далі. Це була б помилка, і ось чому. Порахуймо частку шахрайських окремо в кожній десятій частині цін, від найдешевших до найдорожчих:

десята частина за ціноюмежі, грнчастка шахрайських
найдешевшідо 1 23042.9 %
друга1 230 – 1 82018.9 %
третя1 820 – 2 51012.6 %
середина4 010 – 5 0602.7 %
девʼята9 040 – 13 5507.2 %
найдорожчіпонад 13 55018.0 %

Від 42.9 % до 2.7 % і назад до 18.0 %. Звʼязок величезний — просто він не прямий, а U-подібний: підозрілі й дуже дешеві оголошення (приманка «неймовірна знижка»), і дуже дорогі (виманити велику передоплату). Пряма, проведена крізь таку хмару, лежить горизонтально, і кореляція чесно повідомляє нуль. Вона не помилилась — вона відповіла на своє питання, а ми поставили інше.

Кореляція проти звʼязку

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

кореляція—
рядків—
низ ламаної—
верх ламаної—
Порівняй третю кнопку з четвертою. В обох кореляція практично нульова: −0.030 і −0.012. Але в третій ламана справді пласка — звʼязку немає, і кореляція права. У четвертій ламана падає з 42.9 % до 2.7 % і повертається до 18.0 % — звʼязок є, величезний, а кореляція його не бачить. Одне й те саме число означає в цих двох випадках протилежні речі, і розрізняє їх лише картинка. Перші дві кнопки показують інший бік того самого: ламана там майже однакова, а кореляція відрізняється втричі, бо міряє вона не силу залежності, а те, наскільки тісно точки тиснуться до прямої.

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

Скарги і шахрайство: 0.887. Третій рядок, і найважчий. Почнімо з загального: кореляція не означає причину. Це фраза, яку всі чули й майже ніхто не застосовує, тож ось вона на нашій дошці.

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

ТаргетПогляд на таргет

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

дошка["шахрайське"].value_counts() 0 1016 ← чесні, 83.8 % 1 196 ← шахрайські, 16.2 %

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

Згадай тему 05. Модель, яка не дивиться на ознаки взагалі й називає чесними геть усі оголошення, вгадає в 83.8 % випадків. Виглядає це як пристойний результат — і при цьому шахраїв упіймано рівно нуль. Саме тому дисбаланс треба знати до того, як обрана метрика: він визначає, з чим взагалі порівнювати будь-яке майбутнє число. Precision і recall ставлять правильні питання — скільки з упійманих справді шахраї і скільки шахраїв упіймано, — а частка правильних відповідей на нерівних класах просто бреше.

Нуль відліку, який задає дисбаланс

Угорі — з чого складається таргет. Унизу — що на цьому складі отримає модель, яка взагалі не дивиться на ознаки.

83.8 % правильних відповідей тут не результат, а розмір більшого класу. Найдурніша можлива модель — константа «усе чесне» — дає рівно цю цифру, не подивившись на жодну ознаку. Тому дисбаланс і треба знати до вибору метрики: він задає нуль відліку, з яким порівнюють будь-яке майбутнє число. Модель, яка дала 85 %, обіграла константу на 1.2 пункту, а не «майже не помиляється».

Другий погляд на таргет — розподіл ознак усередині кожного класу. Найкорисніше порівняння в нашій таблиці таке:

групаоголошеньмедіанна ціна
чесні1 0164 235 грн
шахрайські1961 940 грн

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

Заодно з таргетом варто перевірити ще одну річ — чи не змінюється частка класів у часі. Якщо у вивантаженні за перше півріччя шахрайських 8 %, а за друге 24 %, то це вже не одна задача, а дві, і будь-який поділ на навчальну й тестову частини треба робити з оглядкою на дату.

ВитікВитік даних

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

чеснихшахрайських
скарг немає9940
скарг ≥ 122196

Правило «якщо є хоч одна скарга — оголошення шахрайське» ловить усі 196 шахрайських оголошень і помиляється лише на 22 чесних. Точність такого правила — 89.9 % серед позначених, і жодного пропущеного шахрая. Одна колонка, жодного навчання, майже ідеальний результат.

Це не удача. Це витік даних (data leakage) — ситуація, коли в ознаки потрапила інформація, якої в момент передбачення ще не існує.

Подумай, коли зʼявляється скарга. Оголошення публікують — скарг нуль. Хтось надсилає передоплату й не отримує телефон — надходить скарга. Служба підтримки розбирається й ставить мітку «шахрайське». Тобто скарга і мітка зʼявляються в один і той самий момент і з однієї й тієї самої події. Модель, навчена на скаргах, у робочому режимі отримає нове оголошення, у якого скарг нуль завжди — і назве чесними всі. Її чудова якість на історичних даних не переживе першого ж дня.

Чому «скарг» не можна брати в ознаки

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

Уся суть — у відстані між двома позначками. Колонка, значення якої визначається пізніше за момент передбачення, у навчальній таблиці виглядає як найкорисніша ознака на світі. У робочому режимі вона порожня. Перевірка одна й дуже проста: чи знав би я це значення тоді, коли треба відповідати?

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

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

Розвідка ловить витік краще за будь-яку іншу перевірку — і ловить його рано, до навчання, коли виправлення коштує один рядок. Три сигнали, за якими його видно:

Що з цим робити? Викинути колонку «скарг» зі списку ознак — і не з таблиці. Вона чудово годиться для аналізу заднім числом («скільки шахрайських оголошень ми пропустили до першої скарги»), просто не годиться для передбачення. Це той самий принцип, що й у темі 03: усе, що модель бачить під час навчання, має бути доступним і в момент застосування — інакше вимірювання не має сенсу. І це продовження розмови з теми 02 про те, чим ознака відрізняється від відповіді, записаної іншими словами.

ПідсумокЧекліст розвідки

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

Чекліст розвідки даних

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

Пункт 11 — той, заради якого варто пройти решту. Він коштує один виклик corr(), а рятує від тижня роботи над моделлю, яка ніколи не запрацює. Пункти 1–4 роблять на будь-якій таблиці взагалі; 5–10 — на кожному числовому стовпці, який планується в ознаки.

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

ДаліЩо далі

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

Кожен пункт цього списку — завдання для передобробки: наступної теми, де ми навчимось прибирати дублікати без втрати сенсу, заповнювати пропуски свідомо, кодувати текстові колонки числами й приводити ознаки до спільного масштабу. А тема після неї — інженерія ознак: там із тієї самої таблиці зʼявляться нові стовпці, і U-подібний звʼязок ціни з таргетом перестане бути проблемою.

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