Тобі передали файл із дошки оголошень про вживані телефони: 1 212 рядків, вісім стовпців, супровідний лист на три речення. «Ціна в гривнях, памʼять у гігабайтах, остання колонка — чи виявилось оголошення шахрайським». Файл відкривається, помилок немає, усе на місці. Здається, можна працювати.
Не можна. У цій таблиці зараз лежать: 101 порожня ціна, причому порожня не випадково; 12 рядків, які просто продубльовані; стовпець із памʼяттю, який pandas прочитав як текст; шість оголошень дорожчих за 70 000 грн, з яких частина справжня, а частина — одруки; і один стовпець, звʼязаний із відповіддю настільки добре, що ним хочеться одразу скористатись — і саме тому не можна.
Нічого з цього не видно з опису файлу. Усе це видно за пʼятнадцять хвилин, якщо знати, куди дивитись. Це і є розвідувальний аналіз даних (exploratory data analysis, EDA) — і ця тема саме про нього.
МотивДані не такі, як їх описали
У темі 02 ми домовились, що навчання з учителем — це таблиця з ознаками й таргетом. Тоді таблиця була ідеалізована: рівні стовпці, охайні числа. У житті вона така майже ніколи, і причин рівно три.
Дані збирали не для тебе. Колонку «стан телефона» заповнює продавець у вікні з випадним списком — і половина вибирає перший пункт, бо швидше. Колонку «скарг» проставляє служба підтримки. Ніхто з них не думав про майбутню модель.
Дані зберігали через кілька систем. Числа проходять через експорт у таблицю, вивантаження у файл, читання назад — і на кожному кроці «128» може перетворитись на «128 ГБ», а порожнє поле — на текст «немає».
Дані змінювались у часі. Торік поле «памʼять» було вільним текстом, цього року стало списком. У файлі лежать обидва варіанти, і ніхто про це не напише.
Тому розвідка — не етап звітності, а страховка, і головний її аргумент — ціна помилки. Дірку в даних, помічену зараз, ти виправляєш за годину. Ту саму дірку, помічену після навчання моделі, ти виправляєш разом з усім, що встиг на ній побудувати: обробкою, ознаками, поділом на навчальну й тестову частини, висновками, які вже комусь показав. Звідси настрій теми, майже неввічливий: ми шукаємо неприємності, поки вони дешеві.
Уся тема — на одній таблиці. Ось вона.
| стовпець | що це | тип |
|---|---|---|
| модель | назва телефона, шість різних моделей | текст |
| рік | рік випуску, від 2017 до 2024 | ціле |
| стан | нове, дуже добре, добре, задовільне | текст |
| памʼять_гб | обсяг памʼяті: 64, 128, 256 або 512 | ? |
| вік_акаунта | скільки днів існує акаунт продавця | ціле |
| скарг | скільки скарг надійшло на це оголошення | ціле |
| ціна | ціна в оголошенні, гривень | дробове |
| шахрайське | таргет: 1 — оголошення виявилось шахрайським | 0 або 1 |
Знак питання в третьому рядку не помилка верстки. Це те, що ми зараз побачимо.
DataFrame,
Series, читання файлів, loc і iloc, фільтр умовою,
groupby, fillna — ми розібрали в
темі 39 курсу Python «Pandas: таблиці». Тут ми жодного
разу не пояснюємо, як викликати метод. Ми говоримо лише про те, що читати
в його відповіді. Графіки — на matplotlib
з теми 40 курсу Python.ОглядПерший погляд: пʼять команд
Перше знайомство з будь-якою таблицею — це пʼять викликів. Вони завжди ті самі, і робити їх треба навіть тоді (особливо тоді), коли ти впевнений, що знаєш, що всередині. Цінність не в командах, а в питаннях, які до кожної з них поставлені.
shape — чи стільки рядків, скільки я очікував. Наша
таблиця дає (1212, 8). Якщо в листі було сказано «вивантаження за рік, близько
тисячі оголошень» — усе гаразд. Якщо було сказано «40 тисяч» — далі можна не дивитись,
спершу треба розібратись, куди поділись 39 тисяч рядків. Це найдешевша перевірка з усіх
і найчастіше пропущена.
head() — чи значення схожі на те, що обіцяє назва стовпця.
Не «які тут числа», а саме це. У нашій таблиці другий рядок одразу показує NaN
у стані, а четвертий — памʼять, записану як 256 ГБ. Обидві дрібниці видно
неозброєним оком у перших пʼятьох рядках, і обидві коштуватимуть годину, якщо їх не
помітити.
dtypes — чи всі числові стовпці справді числові. Ось що
віддає наша таблиця:
object у pandas означає «щось, що я не зміг звести до числа» — зазвичай
текст. Для «моделі» й «стану» це нормально, для «памʼять_гб» — тривожний знак. Тип не
буває «майже правильним»: досить одного нечислового значення, щоб уся
колонка стала текстом. Тобто object у числовій колонці — це не властивість
даних, а слід події: десь у файлі є рядок, який випадає із загального правила.
У нас таких рядків 215 із 1212: у них памʼять записана як 128 ГБ замість
128. І тут ховається справжня пастка. Найочевидніша реакція — «зведімо
колонку до чисел» — виглядає так:
errors="coerce" означає «те, що не зміг прочитати, зроби пропуском». Ця
команда не падає, не попереджає й нічого не пише в консоль. Вона просто мовчки перетворює
215 цілком справжніх значень на порожнечу — 17,7 % таблиці. Правильна дія — спершу
прибрати суфікс, і лише потім переводити в число:
info() — де дірки. Він друкує для кожного стовпця
кількість непорожніх значень, і її треба порівнювати з першим рядком виводу. У нас
1212 entries, а в колонці «ціна» стоїть 1111 non-null — отже,
101 порожня. У колонці «стан» — 1164, отже 48 порожніх. Решта стовпців повні.
describe() — чи числа взагалі бувають такими. Його читають
не зверху вниз, а трьома парами рядків. Ось вивід для ціни:
Пара перша: 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 111 | 972 | 139 | 12.5 % |
| ціни немає | 101 | 44 | 57 | 56.4 % |
Різниця в чотири з половиною рази. Серед оголошень без ціни шахрайських більше половини, серед решти — восьма частина. Це вже не статистична дрібниця, це поведінка: шахрай не називає ціну, бо ціна — те місце, де його оголошення виглядає підозріло.
Звідси головний висновок розділу: сам факт пропуску буває ознакою. Не значення, якого немає, а те, що його немає. Технічно це один додатковий стовпець:
Порівняй це зі стовпцем «стан». Там пропусків 48, і частка шахрайських серед них 12.5 % проти 16.3 % у решті таблиці. Це різниця в межах випадковості, і переконатись легко: 12.5 % від 48 рядків — це рівно шість оголошень, тож одне оголошення туди чи сюди зсуває частку більш ніж на два пункти. Уся різниця між групами — 3.8 пункту, тобто менша за дві такі сходинки. Порівняй із ціною: там у групі 101 рядок, а розрив 44 пункти — стільки випадковість не намалює. Пропуск у стані ні про що не говорить; пропуск у ціні говорить майже все. Один рядок коду розрізняє ці випадки, і зробити його треба до того, як заповнювати дірки.
Пропуск як ознака
Ті самі 1 212 рядків, поділені двічі: за пропуском ціни й за пропуском стану. Висота стовпчика — частка шахрайських оголошень у групі.
Способи заповнити дірки існують, і їх багато: середнє, медіана, найчастіше значення, значення сусіднього рядка, окрема категорія «невідомо», передбачення за іншими стовпцями. Вибір між ними — окрема розмова, і вона буде в темі про передобробку. Поки що досить двох правил. Перше: перш ніж заповнювати, перевір, чи пропуск випадковий — одним порівнянням, як щойно. Друге: заповнення середнім, зроблене не думаючи, — найпоширеніша помилка новачка. Воно не додає інформації, зате робить розподіл штучно вужчим і ховає той факт, що значення взагалі не було.
ФормаФорма розподілу
describe() віддає сім чисел, і цього мало. Сім чисел не розрізняють
розподіл з одним піком і розподіл з двома, а це різні світи. Тому наступний крок після
таблиці — гістограма: вісь ціни ріжуть на однакові відрізки (кошики) і
рахують, скільки оголошень потрапило в кожен.
Гістограма має одну властивість, про яку мовчать: її висновок залежить від кількості кошиків. Це не тонкість для педантів — від цього залежить, що ти скажеш про дані вголос.
Скільки кошиків — стільки й висновків
Ціни з нашої таблиці, 1 111 оголошень із вказаною ціною. Вісь обрізана на 25 000 грн — вище лежать 33 оголошення, і про них окрема розмова в наступному розділі. Рожева лінія — середнє, бірюзова — медіана.
Форма, яку ми бачимо, зветься скошеною праворуч (right-skewed): пік зліва, довгий хвіст справа. Так виглядає майже все, що не буває відʼємним і має верхню межу лише теоретично — ціни, зарплати, час очікування, розмір міста. Протилежна форма, симетричний горбик, у природних даних трапляється рідше, ніж здається з підручників.
Саме скошеність розводить середнє й медіану, і це варто проговорити повільно, бо плутанина тут коштує дорого.
Середнє — сума всіх цін, поділена на кількість. Кожне оголошення вносить свою суму цілком: телефон за 95 000 грн важить у сумі стільки ж, скільки двадцять телефонів по 4 750 грн.
Медіана — значення, яке стоїть посередині відсортованого списку. Їй байдуже, наскільки велике найбільше число: важливо лише, що воно більше за середину.
Розрив у 1.62 раза. Перевіримо, звідки він: скільки оголошень дешевші за середнє?
Понад дві третини оголошень дешевші за «середнє оголошення». Отже, речення «середня ціна на дошці — 6 509 грн» описує обʼєкт, якого майже не буває: воно правдиве арифметично й хибне по суті. Речення «половина оголошень дешевша за 4 010 грн» описує саме те, що є.
Означає це не «медіана краща», а дещо точніше. Медіана відповідає на питання «яке оголошення типове». Середнє відповідає на питання «скільки грошей на дошці всього». Якщо тобі треба закупити партію телефонів — рахуй середнє, бо платити доведеться суму. Якщо треба сказати покупцеві, на що розраховувати, — кажи медіану. Помилка починається тоді, коли середнім відповідають на друге питання.
ВикидиВикиди й межа
У хвості розподілу лежать викиди (outliers) — значення, які помітно відірвались від решти. Найпоширеніший спосіб їх позначити не потребує ніяких припущень про форму розподілу і зветься правилом міжквартильного розмаху. Порахуймо його руками на наших числах.
Квартилі — це три значення, що ділять відсортований список на
чотири рівні за кількістю частини. Нам потрібні перший і третій: нижче Q1
лежить чверть оголошень, нижче Q3 — три чверті.
describe() уже їх надрукував:
Це просто відстань між ними — ширина смуги, у якій живе середня половина оголошень. Позначають IQR (interquartile range).
Чому саме середня половина: вона не залежить від того, що коїться в хвостах. Хоч постав найдорожчий телефон у мільйон гривень — Q1 і Q3 не зрушать. Тобто лінійка, якою ми міряємо викиди, сама від викидів не залежить. Це і є вся ідея правила.
Від квартилів відкладають півтора розмаху в кожен бік:
нижня = 2 180 − 1.5 · 5 520 = −6 100
Нижня межа вийшла відʼємною, а ціна відʼємною не буває — отже, знизу викидів немає жодного. Усе цікаве вгорі: 78 оголошень дорожчі за 15 980 грн, це 7.0 % тих 1 111 рядків, у яких ціна взагалі вказана.
Множник 1.5 — домовленість, а не закон природи. Він зручний і поширений, але нічим не священний: на скошених даних, як наші, він завжди позначає багато рядків справа, бо припускає приблизну симетрію, якої тут немає. Тому корисно подивитись, як число викидів залежить від множника.
Де провести межу
Кожна риска внизу — одне оголошення. Зверху — «скринька»: прямокутник від Q₁ до Q₃, лінія посередині — медіана, вуса — до поточної межі. Оголошення за межею позначені рожевим.
І тепер найважливіше в цьому розділі. Викид — не синонім помилки. Правило міжквартильного розмаху не знає нічого про телефони; воно знає лише, що число велике. Подивімось на шість найдорожчих оголошень цілком, з усіма стовпцями:
| модель | рік | стан | памʼять | скарг | ціна |
|---|---|---|---|---|---|
| Gamma X | 2017 | нове | 512 | 0 | 95 000 |
| Gamma X | 2017 | нове | 512 | 0 | 91 000 |
| Gamma X | 2017 | нове | 512 | 0 | 88 000 |
| Beta 12 Pro | 2021 | добре | 256 | 0 | 83 500 |
| Gamma X | 2017 | нове | 512 | 0 | 82 000 |
| Gamma X Ultra | 2017 | добре | 256 | 0 | 76 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. Дві однакові за величиною ціни, і два протилежні рішення — причому розрізняє їх не ціна, а решта рядка.
І ще один наслідок, який видно тільки на числах. Викиди спотворюють не лише середнє — вони ламають підрахунки звʼязків. Для моделі Gamma X кореляція між роком випуску й ціною дорівнює 0.06: наче звʼязку немає взагалі. Прибираємо чотири колекційні рядки зі 122 — і та сама кореляція стає 0.80. Чотири рядки зі ста двадцяти двох перевернули висновок.
ЗвʼязкиЗвʼязки між ознаками
Кожен стовпець ми подивились окремо. Тепер попарно: чи повʼязані вони між собою і чи повʼязані з таргетом. Стандартний інструмент — кореляція Пірсона, число від −1 до +1.
Словами вона означає ось що: наскільки добре хмару точок можна замінити однією прямою. +1 — точки лежать на прямій, що йде вгору. −1 — на прямій, що йде вниз. 0 — прямої, яка щось описує, немає. Одна команда рахує всі пари одразу:
| пара стовпців | кореляція | як це читати |
|---|---|---|
| скарг ↔ шахрайське | 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 230 | 42.9 % |
| друга | 1 230 – 1 820 | 18.9 % |
| третя | 1 820 – 2 510 | 12.6 % |
| середина | 4 010 – 5 060 | 2.7 % |
| девʼята | 9 040 – 13 550 | 7.2 % |
| найдорожчі | понад 13 550 | 18.0 % |
Від 42.9 % до 2.7 % і назад до 18.0 %. Звʼязок величезний — просто він не прямий, а U-подібний: підозрілі й дуже дешеві оголошення (приманка «неймовірна знижка»), і дуже дорогі (виманити велику передоплату). Пряма, проведена крізь таку хмару, лежить горизонтально, і кореляція чесно повідомляє нуль. Вона не помилилась — вона відповіла на своє питання, а ми поставили інше.
Кореляція проти звʼязку
Пʼять пар стовпців із нашої таблиці. Точки — оголошення; вісь цін обрізана, найдорожчі оголошення в кадр не потрапляють. Жирна ламана — типове значення по вертикальній осі всередині кожної десятої частини за горизонтальною: медіана ціни, а для колонки з нулів і одиниць — частка одиниць. Там, де вертикальна вісь двійкова, точки трохи розсунуті, щоб не злипались.
Звідси практичне правило: нульова кореляція означає «прямої немає», а не «звʼязку немає». Перш ніж викреслювати ознаку через маленьке число, подивись на неї очима або порахуй середнє таргета по кошиках — це десять рядків коду й одна хвилина.
Скарги і шахрайство: 0.887. Третій рядок, і найважчий. Почнімо з загального: кореляція не означає причину. Це фраза, яку всі чули й майже ніхто не застосовує, тож ось вона на нашій дошці.
Скарги й шахрайство рухаються разом — але скарга не робить оголошення шахрайським. Причина йде у зворотний бік: оголошення було шахрайським, тому люди й поскаржились. Той самий фокус із памʼяттю та ціною: більша памʼять корелює з вищою ціною, але значна частина цієї кореляції — не про памʼять, а про те, що моделі з великою памʼяттю в принципі дорожчі. Кореляція бачить, що дві величини рухаються разом, і не бачить, хто кого рухає — та чи не рухає їх обидві щось третє.
ТаргетПогляд на таргет
Окремо й уважно варто подивитись на сам таргет — стовпець, який ми збираємось передбачати. Питання тут два: скільки в нього класів і чи рівні вони за розміром.
Класи нерівні: на кожне шахрайське оголошення припадає пʼять чесних. Це називають дисбалансом класів, і він тут не крайній — буває й один до тисячі. Але навіть таке співвідношення вже змінює все, що ми знаємо про вимірювання якості.
Згадай тему 05. Модель, яка не дивиться на ознаки взагалі й називає чесними геть усі оголошення, вгадає в 83.8 % випадків. Виглядає це як пристойний результат — і при цьому шахраїв упіймано рівно нуль. Саме тому дисбаланс треба знати до того, як обрана метрика: він визначає, з чим взагалі порівнювати будь-яке майбутнє число. Precision і recall ставлять правильні питання — скільки з упійманих справді шахраї і скільки шахраїв упіймано, — а частка правильних відповідей на нерівних класах просто бреше.
Нуль відліку, який задає дисбаланс
Угорі — з чого складається таргет. Унизу — що на цьому складі отримає модель, яка взагалі не дивиться на ознаки.
Другий погляд на таргет — розподіл ознак усередині кожного класу. Найкорисніше порівняння в нашій таблиці таке:
| група | оголошень | медіанна ціна |
|---|---|---|
| чесні | 1 016 | 4 235 грн |
| шахрайські | 196 | 1 940 грн |
Колонка «оголошень» тут — це розмір класу цілком; медіану ж рахують лише по тих рядках, де ціна вказана, тобто по 972 чесних і 139 шахрайських. Медіанне шахрайське оголошення більш ніж удвічі дешевше за медіанне чесне. Але ми вже знаємо, що цим справа не вичерпується: серед найдорожчої десятої частини шахрайських теж 18 %. Тобто одне число («шахраї дешевші») правдиве й недостатнє — і зловити це вдалось лише тому, що ми подивились на розподіл, а не на середнє.
Заодно з таргетом варто перевірити ще одну річ — чи не змінюється частка класів у часі. Якщо у вивантаженні за перше півріччя шахрайських 8 %, а за друге 24 %, то це вже не одна задача, а дві, і будь-який поділ на навчальну й тестову частини треба робити з оглядкою на дату.
ВитікВитік даних
Повернімось до першого рядка таблиці кореляцій. Скарги повʼязані з таргетом на 0.887 — це надзвичайно багато. Подивімось на цю пару впритул. Найпростіший спосіб — розкласти рядки по клітинках: скільки їх у кожному поєднанні «є скарга / немає скарги» з «чесне / шахрайське». Така табличка називається таблицею спряженості (contingency table), і читають її як карту: цікаві не самі числа, а порожні клітинки.
| чесних | шахрайських | |
|---|---|---|
| скарг немає | 994 | 0 |
| скарг ≥ 1 | 22 | 196 |
Правило «якщо є хоч одна скарга — оголошення шахрайське» ловить усі 196 шахрайських оголошень і помиляється лише на 22 чесних. Точність такого правила — 89.9 % серед позначених, і жодного пропущеного шахрая. Одна колонка, жодного навчання, майже ідеальний результат.
Це не удача. Це витік даних (data leakage) — ситуація, коли в ознаки потрапила інформація, якої в момент передбачення ще не існує.
Подумай, коли зʼявляється скарга. Оголошення публікують — скарг нуль. Хтось надсилає передоплату й не отримує телефон — надходить скарга. Служба підтримки розбирається й ставить мітку «шахрайське». Тобто скарга і мітка зʼявляються в один і той самий момент і з однієї й тієї самої події. Модель, навчена на скаргах, у робочому режимі отримає нове оголошення, у якого скарг нуль завжди — і назве чесними всі. Її чудова якість на історичних даних не переживе першого ж дня.
Чому «скарг» не можна брати в ознаки
Вісь часу одного оголошення. Угорі — момент, у який модель має дати відповідь. Унизу — момент, у який зʼявляється значення в колонці «скарг».
Витік підступний саме тим, що зовні виглядає як успіх. Він не дає помилки, не пише попереджень і не псує метрику — він її покращує. Тому в роботі з даними діє правило, яке варто вивчити напамʼять:
Розвідка ловить витік краще за будь-яку іншу перевірку — і ловить його рано, до навчання, коли виправлення коштує один рядок. Три сигнали, за якими його видно:
- Аномально висока кореляція з таргетом. Значення понад 0.8 у справжній задачі трапляється рідко. Наші 0.887 — саме такий випадок.
- Ознака, яка виглядає як наслідок, а не причина. Скарги, повернення, статус «заблоковано», дата закриття — усе це описує те, що вже сталося.
- Ідеальний поділ у таблиці спряженості. Нуль у клітинці «чесні зі скаргами» був би вироком; у нас там 22, і це майже так само підозріло.
Що з цим робити? Викинути колонку «скарг» зі списку ознак — і не з таблиці. Вона чудово годиться для аналізу заднім числом («скільки шахрайських оголошень ми пропустили до першої скарги»), просто не годиться для передбачення. Це той самий принцип, що й у темі 03: усе, що модель бачить під час навчання, має бути доступним і в момент застосування — інакше вимірювання не має сенсу. І це продовження розмови з теми 02 про те, чим ознака відрізняється від відповіді, записаної іншими словами.
ПідсумокЧекліст розвідки
Зберімо всю тему в послідовність, яку можна виконати механічно на будь-якій новій таблиці. Порядок не випадковий: кожен крок спирається на попередній, і кожен наступний дорожчий.
Чекліст розвідки даних
Пʼятнадцять пунктів, приблизно пʼятнадцять хвилин. Праворуч — те, на що дивитись у відповіді.
corr(), а рятує від тижня роботи над моделлю, яка ніколи не
запрацює. Пункти 1–4 роблять на будь-якій таблиці взагалі; 5–10 — на кожному
числовому стовпці, який планується в ознаки.Одна вимога, яка не вміщається в чекліст, але важливіша за половину його пунктів: записуй, що знайшов. Не в памʼять, а текстом — прямо в зошит, поруч із кодом. Через два тижні ти не згадаєш, чому в колонці ціни зʼявився стовпець «ціни_немає» і чому «скарг» немає серед ознак. А той, хто відкриє твій зошит замість тебе, не згадає й поготів.
ДаліЩо далі
Ми пройшли таблицю наскрізь і жодного разу нічого в ній не змінили. Це не випадковість і не економія — це порядок дій. Спершу дивимось, потім вирішуємо. Ось із чим ми виходимо з цієї таблиці:
- 12 дублікатів — зрозуміти походження, найімовірніше прибрати;
- стовпець памʼяті потребує розбору тексту, інакше 215 значень зникнуть мовчки;
- 101 пропуск у ціні — не випадковий, потрібна окрема колонка «ціни_немає»;
- 48 пропусків у стані — випадкові, можна заповнювати;
- 78 викидів за правилом 1.5·IQR, з яких щонайменше два — одруки, а чотири — справжні колекційні телефони;
- ціна повʼязана з таргетом U-подібно, тому лінійні прикидки на ній працюватимуть погано;
- колонка «скарг» — витік, зі списку ознак прибрати.
Кожен пункт цього списку — завдання для передобробки: наступної теми, де ми навчимось прибирати дублікати без втрати сенсу, заповнювати пропуски свідомо, кодувати текстові колонки числами й приводити ознаки до спільного масштабу. А тема після неї — інженерія ознак: там із тієї самої таблиці зʼявляться нові стовпці, і U-подібний звʼязок ціни з таргетом перестане бути проблемою.
Але головне з цієї теми не залежить від жодної наступної. Таблиця, яку тобі дали, — не факт, а чиясь розповідь про факти. Перевіряти цю розповідь треба до того, як на ній щось будувати. Пʼятнадцять хвилин зараз — це те, що не доведеться переробляти місяць потому.