
Мы частенько шутим с коллегами , что любые действия можно поделить на «обезьяньи» и «smart». Обезьянья работа - это когда ты что-то делаешь на автомате, не включая мозг и не думая. Smart-работа требует вдумчивости, внимательности и анализа.
В этой статье разберем пять ключевых этапов EDA (Exploratory data analysis) — от сбора данных до нормализации — в разрезе этих двух концепций: «обезьяньей» (когда делаем «на автомате») и smart (когда анализируем, проверяем и выбираем осознанно).
Почему предобработка данных так важна?
Представьте, что вы печете торт. Машинное обучение — это процесс выпечки по сложному рецепту. А данные — это ваши ингредиенты. Можно взять прогорклое масло, муку с жуками и сахар пополам с солью — и очень старательно, по всем правилам замесить тесто и отправить его в печь. Будет ли торт вкусным? Нет. Он будет катастрофой. Алгоритм (печь) лишь исполнит код (рецепт), но не сможет волшебным образом исправить испорченные данные (ингредиенты).
В машинном обучении этот принцип называется «Garbage In — Garbage Out» (мусор на входе — мусор на выходе). Самый продвинутый бустинг или нейросеть не вытащат сигнал из данных, где пропуски заполнены как попало, а выбросы искажают всю статистику. Все, что вы сделаете на «обезьяньем» автопилоте на этапе очистки и подготовки, станет фундаментом вашей модели. И если этот фундамент кривой, то вся конструкция — точность, интерпретируемость, надежность — рано или поздно рухнет.
Поэтому предобработка — это не скучная обязательная повинность перед «настоящей работой». Это и есть самая настоящая работа, где закладывается 80% успеха. Это тот самый момент, когда вы решаете: испечете ли вы шедевр или несъедобную подошву. Давайте же разберемся, как правильно «отбирать муку» и «проверять яйца».
Этап 1: Сбор данных
Обезьяний сбор
import pandas as pd
df = pd.read_csv('your_data.csv')
Датафрейм прочитали, и дело с концом.
Smart-сбор
Перед тем как прочитать датафрейм, мы анализируем что вообще в нем лежит. Например, в данных есть специальные символы и кириллица, у которых может слететь кодировка. Либо в качестве разделителей использованы не стандартные запятые, а что-то другое, и когда мы откроем наш датафрейм, там будет один большой столбец. Либо в этой таблице больше нескольких миллионов строк, и при загрузке наш Jupyter Notebook упадет с ошибкой Out of memory. Поэтому рассмотрим несколько атрибутов, чтобы избежать этих проблем.
Пример
df = pd.read_csv(
'data.csv',
sep=',', # выбор разделителя
encoding='utf-8', # для корректого отображения символов
usecols = [‘col1’, ‘col2’], # при чтении выбираем только нужные колонки
parse_dates=['date_column'], # Список столбцов для парсинга как даты
dtype={'category_col': 'category'}, # тип данных для столбца
na_values=['', 'NULL', 'N/A'], # Дополнительные значения для распознавания как NaN
nrows=1000 # считать определенное количество строк
)
Этап 2. Обработка пропусков
Обезьянья обработка
df.fillna(mean_value, inplace = True)
Заполняем все пропуски средним значением

Smart-обработка
Перед тем заполнить пропуски, надо подумать не испортит ли это исходные данные. Обезьяна заполнит средним значением все пропуски, даже не посмотрев какие фичи у нее есть, поэтому в столбце «flag 1/0», у нее появится значение 0.5.
Иногда None вообще не нужно заполнять, потому что они несут в себе смысл. Допустим вы хотите собрать фичу актуальную только для физического лица. Вы делаете left join к выборке где есть и фл, и юл, и ваша фича приджойнится только к фл. Сделав заполнение пропусков, получится так, что у вас эта фича теперь заполнена и для фл, и для юл, и теряется весь ее смысл.
Если вы все-таки хотите заполнить None в вашей выборке, то можно сделать это «выбросом». Например, в столбце «возраст» заменить пропуски на «-1», тогда модель будет понимать, что это выброс.
Для столбцов с какой-либо суммой, можно заменить пропущенные значения на 0.
df.isnull().sum() - количество Nan
df['column'].fillna(df['column'].mean()) - заполнение Nan средним значением столбца
df['column'].replace({None:-1}, inplace = True)
Этап 3. Обработка выбросов
Обезьянья обработка
Отсутствует.
Либо удалим все строки, где есть хотя бы 1 выброс.
df.dropna(how='any')
Smart-обработка
Для начала:
df.describe() - смотрим мин, макс, среднее значения и отклонение
df.loc[col].value_counts() - для категориальных столбцов уникальные значения
df.dtypes - сверяем типы данных
Для столбцов float и int строим гистограммы и box-plot, смотрим распределения. Обычно подозрительные значения видно на графике. Обязательно исследуем наши предполагаемые выбросы, может быть это на самом деле нормальные значения и удалять их не нужно.
Как читать и интерпретировать боксплот (Box-plot)

Положение медианы
Медиана в центре ящика → симметричное распределение
Медиана ближе к Q1 → смещение вправо (положительная асимметрия)
Медиана ближе к Q3 → смещение влево (отрицательная асимметрия)
Длина ящика и усов
Короткий ящик → небольшая вариативность данных
Длинный ящик → большая вариативность данных
Длинный нижний ус → есть много малых значений
Длинный верхний ус → есть много больших значений
Ключевые выводы из боксплота
1. Центральная тенденция - где находится "типичное" значение (медиана)
2. Разброс данных - насколько значения варьируются (длина ящика и усов)
3. Асимметрия - смещено ли распределение в одну сторону
4. Выбросы - есть ли экстремальные значения
5. Сравнение групп - как распределения разных групп соотносятся друг с другом
Еще шокирующий факт: график box-plot - это гистограмма сверху. Живите с этим.
Этап 4. Кодировка категориальных признаков
Обезьянья кодировка
Все кодируем с помощью One-hot encoding. Матрица признаков размножилась в разы, потому что в одном из категориальных столбцов было около 100 уникальных значений.
Smart-кодировка
Выбираем метод кодировки в зависимости от исходных данных.
Некоторые уникальные значения можно объединить в классы, и только после этого использовать One-Hot Encoding. Для порядковых категориальных признаков можно использовать Label Encoding. Если много уникальных значений и есть связь между целевой переменной и признаком, то можно использовать Target Encoding - он кодирует средним значением целевой переменной. Если важна частота встречаемости: Frequency Encoding.
Этап 5. Нормализация данных
Обезьянья нормализация
Не глядя используем Standard Scaler - на то он и Standard, что тут думать?
Smart-нормализация
Действительно, признаки в разных масштабах могут искажать работу алгоритмов, но далеко не все алгоритмы чувствительны к масштабам.
Например, для деревьев и наивных байесовских классификаторов нормализация необязательна.
Тем не менее, нормализация СТРОГО ОБЯЗАТЕЛЬНА, если у вас:
1. Алгоритм, основанных на расстояниях (KNN, K-means, SVM, DBSCAN)
2. Алгоритмы, с градиентным спуском (Нейросети)
3. Алгоритмы с регуляризацией (Lasso, Ridge, Elastic Net) - для справедливого штрафа коэффициентов
4. Методы снижения размерности (PCA, LDA)
Нормализация также РЕКОМЕНДУЕТСЯ для:
1. Линейной регрессии - для численной стабильности и интерпретируемости коэффициентов
2. Любых алгоритмов - как лучшая практика предобработки данных
Гайд по выбору методов нормализации
1. StandardScaler
Когда использовать: Универсальный выбор, алгоритмы с расстояниями (KNN, SVM), градиентный спуск
Диапазон: μ=0, σ=1
Плюсы: Сохраняет распределение, подходит для большинства случаев
Минусы: Чувствителен к выбросам
2. MinMaxScaler
Когда использовать: Нейросети, изображения, когда нужен фиксированный диапазон
Диапазон: [0, 1] или [-1, 1]
Плюсы: Фиксированный диапазон, интуитивный
Минусы: Очень чувствителен к выбросам
3. RobustScaler
Когда использовать: Данные с выбросами, реальные бизнес-метрики, финансовые данные
Диапазон: Зависит от данных
Плюсы: Устойчив к выбросам, надежный
Минусы: Нет фиксированного диапазона
4. MaxAbsScaler
Когда использовать: Текстовые данные, разреженные матрицы, рекомендательные системы
Диапазон: [-1, 1]
Плюсы: Сохраняет разреженность, быстрый
Минусы: Чувствителен к выбросам
Быстрый выбор:
Нет выбросов → StandardScaler
Есть выбросы → RobustScaler
Нейросети/изображения → MinMaxScaler
Тексты/разреженные данные → MaxAbsScaler
Об авторе
Меня зовут Наталья, я senior data scientist в Сбере. Специализируюсь на fintech-задачах. Люблю разбирать сложные концепции на простые составляющие — как в этой статье.