Найти в Дзене

Очистка и подготовка данных для прогнозирования. Шпаргалка

Оглавление

Каждый, кто приходит в Аналитику/Data Science, рано или поздно (чаще рано чем поздно) сталкивается с необходимостью очистки данных. Будь то при разработке или корректировке дашбордов, или при выгрузке данных заказчику, предиктивной аналитике и т.д. Я занимаюсь очисткой и подготовкой данных именно для предиктивной аналитики (подготовка данных для последующей работы с ML).

Анализируя и сравнивая сегодняшнего меня со мной три года назад, в очистке данных, сегодняшний я - не стал увереннее за результат, но стал более требовательным и критичным в этом отношении. Очистка данных — это не просто нудная работа. Это навык, полное противоречий и стратегических решений. Здесь нет «правильно» и «неправильно». Есть только «подходит для этой задачи» и «всё испортит к буям чертям». Сегодня мы поговорим именно об этих дилеммах, когда на каждой развилке приходится делать выбор, и от этого выбора зависит судьба всего проекта.

Статья обещает быть длинной, но полезной и думаю интересной (но это не точно)🎱!

Дилемма №1: Пропуски — удалить нельзя заполнить

*запятую ставьте сами👆🏽

Это первое, с чем мы сталкиваемся. В столбце зияют дыры — NaN, Null, пустые ячейки. Что делать? Мозг сразу предлагает два пути, и оба кажутся логичными.

Путь А: С глаз долой, из данных вон! (Удаление)

Самое простое решение: если в строке есть хотя бы один пропуск — удаляем всю строку. Или, если в столбце слишком много пропусков, — удаляем весь столбец. Грубо, да, но это вариант, редко когда используемый, но всё же.

Когда это хороший выбор?

  1. У вас прорва данных. Если у вас миллион строк, а пропусков всего тысяча (0.1%), то их удаление никак не повредит общей картине. Модель даже не заметит потери бойца. Это не призыв к действию, а скорее влияние действия на результат. Решать решателю🤓.
  2. Пропуск в целевой переменной. Если вы пытаетесь предсказать отток, а в столбце is_churn у вас пропуск, то эта строка для обучения модели бесполезна. Смело удаляйте.
  3. Данные пропущены абсолютно случайно. Если пропуски не зависят ни от каких других данных (например, случайный сбой при выгрузке), их удаление не внесёт систематической ошибки (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» что-то ценное.

Типичные грабли, от которых никто не застрахован (и я тоже)

  1. Слепое df.dropna(): Самое страшное, что можно сделать. Вы можете случайно удалить 70% данных, потому что в каком-то второстепенном столбце были пропуски.
  2. Заполнение до разделения выборки: Вы сначала заполнили пропуски средним по всему датасету, а потом разделили его на train и test. Поздравляю, вы допустили утечку данных (data leakage)! Информация из тестовой выборки (её среднее) просочилась в обучающую. Правильно: сначала делим, потом заполняем пропуски на train (используя среднее train), а затем этим же значением заполняем пропуски на test.
  3. Не смотреть на данные глазами: Перед тем как писать код, просто отсортируйте столбец и посмотрите на минимальные/максимальные значения, на самые частые категории. Это займёт 5 минут, но спасёт от часов отладки.

Что в итоге?

Как видите, очистка данных — это не свод жёстких правил, а серия компромиссов. Наша задача как аналитика — не просто выполнить механическую работу, а понять контекст.

  • Какая у нас бизнес-задача? Если ищем мошенников, то выбросы — наши друзья. Если прогнозируем средний чек, то — враги.
  • Какую модель мы будем использовать? Для линейной регрессии данные нужно готовить гораздо тщательнее, чем для градиентного бустинга.

Главный инструмент аналитика — это не fillna или dropna. Это его мозг и знание предметной области. Так что в следующий раз, столкнувшись с грязными данными, не спешите писать код. Сначала подумайте. И помните: идеальных данных не бывает, но бывают хорошо продуманные решения🥸.

Полезное:

статья "Как нейросеть заполнила характеристики для 15к+ товаров..."

статья "10 функций Pandas, которые я использую каждый день"

статья "Статистика для аналитиков данных: основные концепции и их применение"

Я не претендую на истину в последней инстанции, просто рассказываю, как иду по пути аналитика. Спасибо, что дочитали! 😎 Подписывайтесь 👇👇👇, лайкайте 👍🏽👍🏽, и пишите в комментах, какие самые безумные дилеммы при очистке данных встречались у вас!