Каждый, кто приходит в Аналитику/Data Science, рано или поздно (чаще рано чем поздно) сталкивается с необходимостью очистки данных. Будь то при разработке или корректировке дашбордов, или при выгрузке данных заказчику, предиктивной аналитике и т.д. Я занимаюсь очисткой и подготовкой данных именно для предиктивной аналитики (подготовка данных для последующей работы с ML).
Анализируя и сравнивая сегодняшнего меня со мной три года назад, в очистке данных, сегодняшний я - не стал увереннее за результат, но стал более требовательным и критичным в этом отношении. Очистка данных — это не просто нудная работа. Это навык, полное противоречий и стратегических решений. Здесь нет «правильно» и «неправильно». Есть только «подходит для этой задачи» и «всё испортит к буям чертям». Сегодня мы поговорим именно об этих дилеммах, когда на каждой развилке приходится делать выбор, и от этого выбора зависит судьба всего проекта.
Статья обещает быть длинной, но полезной и думаю интересной (но это не точно)🎱!
Дилемма №1: Пропуски — удалить нельзя заполнить
*запятую ставьте сами👆🏽
Это первое, с чем мы сталкиваемся. В столбце зияют дыры — NaN, Null, пустые ячейки. Что делать? Мозг сразу предлагает два пути, и оба кажутся логичными.
Путь А: С глаз долой, из данных вон! (Удаление)
Самое простое решение: если в строке есть хотя бы один пропуск — удаляем всю строку. Или, если в столбце слишком много пропусков, — удаляем весь столбец. Грубо, да, но это вариант, редко когда используемый, но всё же.
Когда это хороший выбор?
- У вас прорва данных. Если у вас миллион строк, а пропусков всего тысяча (0.1%), то их удаление никак не повредит общей картине. Модель даже не заметит потери бойца. Это не призыв к действию, а скорее влияние действия на результат. Решать решателю🤓.
- Пропуск в целевой переменной. Если вы пытаетесь предсказать отток, а в столбце is_churn у вас пропуск, то эта строка для обучения модели бесполезна. Смело удаляйте.
- Данные пропущены абсолютно случайно. Если пропуски не зависят ни от каких других данных (например, случайный сбой при выгрузке), их удаление не внесёт систематической ошибки (bias).
В чём подвох? А что, если пропуски не случайны? Представьте, вы анализируете анкеты, и у людей с высоким доходом пропущено поле «зарплата». Если вы удалите все эти строки, ваша модель будет обучаться только на людях с низким и средним доходом и сделает совершенно неверные выводы. Вы своими руками исказите реальность.
Итог: Удаление — это быстро, но рискованно. Вы теряете информацию, которая может быть важна.
Путь Б: Операция «Импутация» (Заполнение)
Второй путь — не удалять, а заполнить пропуски. И вот тут начинается самое интересное, потому что способов заполнения — вагон и маленькая тележка🛒.
1. Заполнение средним/медианой/модой — классика жанра
Это первое, что приходит в голову.
- Для чисел: пропуски в столбце «возраст» можно заполнить средним возрастом.
- Для чисел с выбросами: пропуски в «зарплате» лучше заполнить медианой, потому что пара олигархов в данных сильно сместит среднее вверх, а медиана к этому устойчива.
- Для категорий: пропуски в столбце «город» можно заполнить модой — самым часто встречающимся городом.
Плюсы:
- Просто и быстро. Одна строка кода в pandas.
- Сохраняет размер датасета.
Минусы:
- Уменьшает дисперсию. Вы добавляете кучу одинаковых значений, и разброс данных становится меньше, чем в реальности.
- Искажает связи. Вы игнорируете зависимости между столбцами. Может, пропуск в доходе был у человека без высшего образования**? Заполнив его средним, вы создаёте фантомного «среднего» человека, которого в природе не существует.
**это просто пример, наличие или отсутствие у человека высшего образования ничего о нем не говорит и зачастую мало влияет на зп, если только этот человек не работаете в образовательном учреждении. Тут могла быть ваша реклама😄
Пример (на пайтоне):
# Плохо, если есть выбросы
df['salary'].fillna(df['salary'].mean(), inplace=True)
# Лучше для данных с выбросами
df['age'].fillna(df['age'].median(), inplace=True)
# Для категорий
df['city'].fillna(df['city'].mode()[0], inplace=True)
2. Заполнение константой («Неизвестно», 0, -999)
Иногда лучше честно признаться, что мы не знаем. Мы можем создать отдельную категорию «Unknown» для города или заполнить числовой пропуск значением, которого точно нет в данных (вроде -999 для возраста).
Плюсы:
- Модель сможет сама понять, что отсутствие данных — это важный признак. Иногда тот факт, что человек не указал доход, говорит о нём больше, чем сам доход.
- Не вносит ложной информации.
Минусы:
- Линейные модели могут воспринять -999 как очень маленькое число и сделать неверные выводы. Древовидные модели (случайный лес, градиентный бустинг) справятся с этим лучше.
3. Продвинутые методы (для тех, кто пишет на Пайтон и возможно пробовал fine-tuning моделей)
Можно предсказать пропущенные значения с помощью модели (например, линейной регрессии) на основе других столбцов или использовать методы вроде KNN-импутации (заполнять пропуск, глядя на «соседей» — похожие строки).
Плюсы:
- Гораздо точнее, сохраняет связи в данных.
Минусы:
- Сложно, долго, можно случайно «подсмотреть» в тестовые данные и получить слишком оптимистичные результаты (data leakage).
Вердикт по пропускам: Нет серебряной пули. Начинайте с простого (медиана/мода), но всегда думайте: «А не вру ли я своей модели, делая так?». Иногда честное «Неизвестно» лучше, чем красивая, но лживая цифра.
Дилемма №2: Выбросы — враг или тайный VIP-клиент?
Вы смотрите на данные о покупках и видите заказ на 999 999 рублей, когда средний чек — 2000. Или видите пользователя с возрастом 150 лет. Что это? Ошибка ввода или самый ценный клиент в истории компании?🥲
Путь А: Сжечь его! (Удаление/Ограничение)
Если вы уверены, что это ошибка (возраст > 120, дата регистрации в будущем), то ответ очевиден — удаляем или исправляем, если можем.
Если же значение теоретически возможно, но очень редкое, можно его «усмирить»:
- Ограничение (Capping): Все значения выше 99-го перцентиля заменяем на значение этого 99-го перцентиля. Так мы не удаляем строку, но уменьшаем влияние выброса.
Когда это хорошо?
- Для моделей, чувствительных к выбросам (линейная регрессия, k-means). Один аномальный заказ может полностью «увести» линию регрессии в сторону.
В чём подвох? Вы своими руками удаляете самые интересные события! Этот заказ на миллион мог быть реальной сделкой с корпоративным клиентом. Удалив его, вы лишаете бизнес возможности найти и привлечь таких же «китов». В анализе мошенничества выбросы — это вообще самое главное, это и есть те транзакции, которые мы ищем.
Путь Б: Изучить и лелеять (Оставить как есть)
Иногда выброс — это не шум, а сигнал.
Когда это хорошо?
- Для моделей, устойчивых к выбросам (деревья решений, случайный лес). Им по большому счёту всё равно, насколько велико аномальное значение.
- Когда вы занимаетесь поиском аномалий, инсайтов, а не просто строите предсказательную модель.
Вердикт по выбросам: Никогда не удаляйте выбросы на автомате. Ваш девиз: «Исследуй, а потом решай». Посмотрите на эту строку целиком. Кто этот клиент? Когда был сделан заказ? Может, стоит позвонить в отдел продаж и спросить? Один такой звонок может дать больше инсайтов, чем недели ковыряния в данных.
Дилемма №3: Редкие категории — объединить в «Other» или оставить?
В столбце «город» у вас Москва, Питер, ещё пара миллионников и 500 деревень, в каждой из которых по одному пользователю. Если сделать One-Hot Encoding, вы получите 500+ новых столбцов, что сведёт с ума любую модель («проклятие размерности»).
Путь А: Всех под одну гребёнку (Объединение в «Other»)
Логичное решение: оставляем топ-5 городов, а все остальные заменяем на категорию «Other».
top_cities = df['city'].value_counts().nlargest(5).index
df['city_grouped'] = df['city'].apply(lambda x: x if x in top_cities else 'Other')
Плюсы:
- Сильно уменьшает количество признаков.
- Модель становится проще и работает быстрее.
Минусы:
- Вы теряете информацию. А что, если одна из этих «деревень» — это элитный коттеджный посёлок, жители которого приносят 80% выручки? Объединив их с другими, вы теряете этот ценный сигнал.
Путь Б: Оставить всё как есть
Можно использовать другие методы кодирования (например, Target Encoding), которые не создают кучу столбцов.
Плюсы:
- Сохраняем всю информацию.
Минусы:
- Высокий риск переобучения. Модель может запомнить, что единственный пользователь из «Нижних Грязей» всегда покупает дорогой товар, и сделать из этого неверный глобальный вывод.
Вердикт по категориям: Начинайте с объединения, это почти всегда безопасно. Но если модель работает плохо, возможно, стоит вернуться и посмотреть, не скрывается ли в категории «Other» что-то ценное.
Типичные грабли, от которых никто не застрахован (и я тоже)
- Слепое df.dropna(): Самое страшное, что можно сделать. Вы можете случайно удалить 70% данных, потому что в каком-то второстепенном столбце были пропуски.
- Заполнение до разделения выборки: Вы сначала заполнили пропуски средним по всему датасету, а потом разделили его на train и test. Поздравляю, вы допустили утечку данных (data leakage)! Информация из тестовой выборки (её среднее) просочилась в обучающую. Правильно: сначала делим, потом заполняем пропуски на train (используя среднее train), а затем этим же значением заполняем пропуски на test.
- Не смотреть на данные глазами: Перед тем как писать код, просто отсортируйте столбец и посмотрите на минимальные/максимальные значения, на самые частые категории. Это займёт 5 минут, но спасёт от часов отладки.
Что в итоге?
Как видите, очистка данных — это не свод жёстких правил, а серия компромиссов. Наша задача как аналитика — не просто выполнить механическую работу, а понять контекст.
- Какая у нас бизнес-задача? Если ищем мошенников, то выбросы — наши друзья. Если прогнозируем средний чек, то — враги.
- Какую модель мы будем использовать? Для линейной регрессии данные нужно готовить гораздо тщательнее, чем для градиентного бустинга.
Главный инструмент аналитика — это не fillna или dropna. Это его мозг и знание предметной области. Так что в следующий раз, столкнувшись с грязными данными, не спешите писать код. Сначала подумайте. И помните: идеальных данных не бывает, но бывают хорошо продуманные решения🥸.
Полезное:
статья "Как нейросеть заполнила характеристики для 15к+ товаров..."
статья "10 функций Pandas, которые я использую каждый день"
статья "Статистика для аналитиков данных: основные концепции и их применение"
Я не претендую на истину в последней инстанции, просто рассказываю, как иду по пути аналитика. Спасибо, что дочитали! 😎 Подписывайтесь 👇👇👇, лайкайте 👍🏽👍🏽, и пишите в комментах, какие самые безумные дилеммы при очистке данных встречались у вас!