Новости и статьи об искусственном интеллекте и нейросетях. Мы собираем и обрабатываем самую актуальную информацию из мира AI. О проекте

Статьи

Детокс данных: подготовка к реальным хаотичным наборам

Статья разбирает проект NoBroker и четыре приёма работы с реальными беспорядочными данными: пропуски, выбросы, несоответствия, типы. На примерах кода показывают, как очищать наборы без потерь, чтобы модели работали стабильно. Документация и проверки — ключ к воспроизводимости.

15 декабря 2025 г.
12 мин
9
Иллюстрация детокса данных

Введение

Каждый специалист по данным не раз просиживал часы за отладкой модели, чтобы в итоге выяснить: проблема не в алгоритме, а в пустом значении в строке номер 47 832. Соревнования на Kaggle навевают мысль, будто данные всегда приходят в идеальном виде — чистые CSV-файлы с точными метками и без дисбаланса классов. На практике всё обстоит иначе.

В этой статье на примере реального проекта по данным разберём четыре полезных приёма, чтобы научиться справляться с беспорядочными наборами из жизни.

Проект NoBroker: тест на хаос реальных данных

NoBroker — индийская компания в сфере prop-tech, которая объединяет владельцев недвижимости и арендаторов напрямую, без посредников-брокеров.

Логотип или интерфейс NoBroker

Этот проект по данным используют на собеседованиях для найма специалистов по data science в NoBroker.

Задача — создать модель, которая предскажет количество взаимодействий с объектом недвижимости за заданный период. Полностью проект здесь не разбираем, но он поможет выявить приёмы работы с беспорядочными реальными данными.

В проекте три набора данных:

  • property_data_set.csv
    • Детали объектов: тип, расположение, удобства, площадь, аренда и другие характеристики жилья.
  • property_photos.tsv
    • Фотографии объектов.
  • property_interactions.csv
    • Метки времени взаимодействий с объектами.

Чистые данные на собеседованиях против production-данных: проверка на деле

Наборы для интервью — отполированные, сбалансированные и предсказуемые. А production-данные? Это сплошной бардак: пропуски, дубликаты, разные форматы и ошибки, которые вылезут в пятницу вечером, сломав пайплайн.

Возьмём набор NoBroker — реальный беспорядок из 28 888 объектов в трёх таблицах. С виду нормально, но при ближайшем рассмотрении: 11 022 пропущенных URL фото (38% данных), испорченные JSON-строки с лишними слешами и прочее.

Вот грань между чистотой и хаосом. Чистые данные учат строить модели, а production-данные — выживать в борьбе.

Разберём четыре приёма для тренировки.

Сравнение чистых и реальных данных

Приём 1: работа с пропусками

Пропуски — не просто раздражение, а момент для решений. Удалить строку? Заполнить средним? Отметить как неизвестно? Всё зависит от причины пропусков и того, сколько данных можно потерять.

В наборе NoBroker три вида пропусков. В столбце photo_urls их 11 022 из 28 888 строк — 38% данных. Вот код проверки.

pics.isna().sum()

Результат:

Количество пропусков в photo_urls

Удалить эти строки — значит потерять ценные записи об объектах. Лучше посчитать отсутствие фото как ноль. Вот решение.

def correction(x):     if x is np.nan or x == 'NaN':         return 0  # Missing photos = 0 photos     else:         return len(json.loads(x.replace('\\', '').replace('{title','{"title'))) pics['photo_count'] = pics['photo_urls'].apply(correction)

Для числовых столбцов вроде total_floor (23 пропуска) и категориальных вроде building_type (38 пропусков) применили заполнение. Числа — средним, категории — модой.

for col in x_remain_withNull.columns:     x_remain[col] = x_remain_withNull[col].fillna(x_remain_withNull[col].mean()) for col in x_cat_withNull.columns:     x_cat[col] = x_cat_withNull[col].fillna(x_cat_withNull[col].mode()[0])

Первое правило: не удаляй на автомате — думай!

Понять паттерн пропусков. URL фото пропущены не случайно.

Приём 2: поиск выбросов

Выброс не всегда ошибка, но всегда повод насторожиться.

Представьте объект с 21 ванной, возрастом 800 лет или площадью 40 000 кв. футов. Либо мечта, либо ошибка ввода.

В наборе NoBroker полно таких флагов. Диаграммы размаха выявили экстремумы: возраст свыше 100 лет, площадь за 10 000 кв. футов, депозиты больше 3,5 млн. Некоторые — люкс, большинство — опечатки.

df_num.plot(kind='box', subplots=True, figsize=(22,10)) plt.show()

Результат:

Диаграммы размаха для числовых столбцов

Решение — удаление по межквартильному размаху (IQR), простая статистика: флаги за пределами 2 IQR.

Сначала функция для удаления.

def remove_outlier(df_in, col_name):     q1 = df_in[col_name].quantile(0.25)     q3 = df_in[col_name].quantile(0.75)     iqr = q3 - q1     fence_low  = q1 - 2 * iqr     fence_high = q3 + 2 * iqr     df_out = df_in.loc[(df_in[col_name] <= fence_high) & (df_in[col_name] >= fence_low)]     return df_out # Note: Multiplier changed from 1.5 to 2 to match implementation.

Применяем к числовым столбцам.

df = dataset.copy() for col in df_num.columns:     if col in ['gym', 'lift', 'swimming_pool', 'request_day_within_3d', 'request_day_within_7d']:         continue  # Skip binary and target columns     df = remove_outlier(df, col) print(f"Before: {dataset.shape[0]} rows") print(f"After: {df.shape[0]} rows") print(f"Removed: {dataset.shape[0] - df.shape[0]} rows ({((dataset.shape[0] - df.shape[0]) / dataset.shape[0] * 100):.1f}% reduction)")

Результат:

Результат удаления выбросов

После чистки набор сжался с 17 386 строк до 15 170 — минус 12,7%, зато модель в порядке. Компромисс окупился.

Для целевых вроде request_day_within_3d вместо удаления — обрезка. Значения свыше 10 ставим в 10, чтобы не искажать предсказания. Вот код с сравнением.

def capping_for_3days(x):     num = 10     return num if x > num else x df['request_day_within_3d_capping'] = df['request_day_within_3d'].apply(capping_for_3days) before_count = (df['request_day_within_3d'] > 10).sum() after_count = (df['request_day_within_3d_capping'] > 10).sum() total_rows = len(df) change_count = before_count - after_count percent_change = (change_count / total_rows) * 100 print(f"Before capping (>10): {before_count}") print(f"After capping (>10): {after_count}") print(f"Reduced by: {change_count} ({percent_change:.2f}% of total rows affected)")

Результат:

Результат обрезки для request_day_within_3d

Распределение чище, модель лучше, отладки меньше.

Приём 3: дубликаты и несоответствия

Дубликаты просты — df.drop_duplicates(). Несоответствия сложнее. Например, JSON-строка, изуродованная несколькими системами.

В NoBroker photo_urls должен был хранить JSON-массивы, но там каша: без кавычек, экранированные слеши, лишние символы.

text_before = pics['photo_urls'][0] print('Before Correction: \n\n', text_before)

До исправления:

Испорченный JSON в photo_urls

Исправление — цепочка замен строк перед парсингом. Вот код.

text_after = text_before.replace('\\', '').replace('{title', '{"title').replace(']"', ']').replace('],"', ']","') parsed_json = json.loads(text_after)

Результат:

Исправленный JSON после парсинга

JSON стал валидным. Не самый элегантный способ манипуляций со строками, но работает.

Несоответствия повсюду: даты как строки, опечатки в категориях, ID как float. Решение — стандартизация, как с JSON.

Приём 4: проверка типов данных и схемы

Всё начинается при загрузке. Узнавать потом, что даты — строки, а числа — объекты, — потеря времени.

В проекте NoBroker типы проверяли сразу при чтении CSV с параметрами pandas. Вот код.

data = pd.read_csv('property_data_set.csv') print(data['activation_date'].dtype) data = pd.read_csv('property_data_set.csv', parse_dates=['activation_date'], infer_datetime_format=True, dayfirst=True) print(data['activation_date'].dtype)

Результат:

Сравнение типов данных до и после парсинга дат

То же для взаимодействий.

interaction = pd.read_csv('property_interactions.csv', parse_dates=['request_date'], infer_datetime_format=True, dayfirst=True)

Это не просто хорошая практика — необходимость для расчётов вроде разницы дат активации и запроса.

num_req['request_day'] = (num_req['request_date'] - num_req['activation_date']) / np.timedelta64(1, 'D')

Проверки схемы спасут от изменений структуры, но данные ещё дрейфуют — распределение меняется со временем. Можно имитировать, варьируя пропорции входов и проверяя, ловит ли модель сдвиги.

Фиксация шагов очистки

Через три месяца забудешь, зачем обрезал request_day_within_3d до 10. Через полгода коллега сломает пайплайн, убрав фильтр выбросов. Через год модель в проде рухнет без объяснений.

Документация обязательна. Это грань между воспроизводимым пайплайном и скриптом на удачу.

В проекте NoBroker все трансформации фиксировали в комментариях кода и разделах ноутбука с оглавлением и объяснениями.

# Assignment # Read and Explore All Datasets # Data Engineering Handling Pics Data Number of Interactions Within 3 Days Number of Interactions Within 7 Days Merge Data # Exploratory Data Analysis and Processing # Feature Engineering Remove Outliers One-Hot Encoding MinMaxScaler Classical Machine Learning Predicting Interactions Within 3 Days Deep Learning # Try to correct the first Json # Try to replace corrupted values then convert to json # Function to correct corrupted json and get count of photos

Версионный контроль тоже ключ. Отслеживай изменения в логике очистки. Сохраняй промежуточные наборы. Веди чейнджлог экспериментов и успехов.

Цель не идеал, а ясность. Не объяснишь решение — не защитишь модель при сбое.

Итоги

Идеальные данные — миф. Лучшие специалисты по данным не бегут от беспорядка, а укрощают его. Они находят пропуски до обучения.

Определяют выбросы до влияния на предсказания. Проверяют схемы перед джойнами таблиц. Фиксируют всё, чтобы следующий не начинал с нуля.

Настоящий эффект рождается не из совершенства, а из умения работать с ошибками и строить рабочее решение.

Так что при виде пропусков, сломанных строк и выбросов в наборе не пугайтесь. Это шанс проявить мастерство на реальных данных.

Горячее

Загружаем популярные статьи...