Представьте: вы получили датасет для анализа. 100 тысяч строк. Клиентская база. Нужно сделать сегментацию, построить модель, найти инсайты. Открываете файл и видите:
Email: "ivan@gmailcom" (без точки), "MARIA@YANDEX.RU" (капс), "null", пустые ячейки. Телефоны: "+7 (912) 345-67-89", "89123456789", "912-345-6789" — три разных формата. Даты: "12.03.2024", "2024-03-12", "12 марта 2024". Имена: "Иванов Иван", "ПЕТРОВА МАРИЯ", "сидоров", " Алексей Смирнов " (с пробелами).
Дубликаты: один клиент записан трижды с разными ID. Пропуски: 30% ячеек пустые. Outliers: возраст "250 лет", доход "$99999999". Вы тратите 2 часа на анализ, получаете странные результаты, понимаете — данные грязные. Нужна чистка.
Вы начинаете чистить. Удаляете строки с пропусками. Убираете дубликаты. Через 4 часа датасет уменьшился со 100K до 65K строк. Вы потеряли 35% данных. И вдруг понимаете: а вдруг среди удалённых были ценные клиенты? Важная информация потеряна безвозвратно.
По данным IBM (2023), компании тратят 40-60% времени аналитиков на очистку данных. Gartner оценивает средний ущерб от плохого качества данных в $12.9M на компанию в год. Data scientists говорят: 80% работы — это подготовка данных, только 20% — сами модели.
Проблема не только во времени. Грязные данные приводят к неправильным выводам, плохим бизнес-решениям, провальным ML-моделям. А агрессивная очистка уничтожает паттерны и важную информацию.
Эта статья — практическое руководство. Какие бывают проблемы с данными, как их находить, какие стратегии очистки применять, когда удалять, когда заполнять, когда оставлять как есть. С конкретными примерами кода (Python/SQL), техниками, инструментами, чек-листами. Чтобы данные были чистыми, но информация не терялась.
1. Что такое грязные данные
Определение
Грязные данные (dirty data) — это данные с ошибками, несогласованностью, дубликатами, пропусками или некорректным форматированием, которые делают их непригодными для анализа без предварительной обработки.
Почему данные становятся грязными
Человеческий фактор: Опечатки при вводе, разные стандарты ввода у разных операторов
Слияние источников: Объединили 3 базы с разными форматами — получили хаос
Миграция систем: Переезд со старой CRM на новую, потеря данных при экспорте/импорте
Эволюция схемы данных: Поля менялись со временем, старые записи не обновлялись
Отсутствие валидации: Форма позволяет ввести что угодно, никто не проверяет
Технические сбои: Ошибки записи, неполные транзакции, дублирование при повторных попытках
Устаревание: Клиент сменил телефон 2 года назад, в базе старый
Типы грязных данных
| Тип проблемы | Пример | Частота |
|---|---|---|
| Missing values (пропуски) | NULL, NA, пустые ячейки | 30-50% |
| Дубликаты | Один клиент с 3 записями | 5-15% |
| Несогласованность | "Москва", "москва", "MSK" | 20-40% |
| Outliers | Возраст 250 лет | 1-5% |
| Неправильный тип | Число как строка "123" | 10-20% |
| Неправильный формат | Разные форматы дат | 15-30% |
| Опечатки | "gmailcom" вместо "gmail.com" | 5-10% |
Грязные данные vs Плохие данные
Грязные данные: Правильная информация в неправильном формате. Можно починить.
Плохие данные: Неправильная информация. Нельзя починить, только удалить или заменить.
Пример грязных: "+7 912 345-67-89" → легко очистить до "79123456789"
Пример плохих: email = "asdfgh" → это не email, информация бесполезна
«80% проблем в data science — это не алгоритмы и модели. Это грязные данные» — DJ Patil, бывший Chief Data Scientist США
2. Стоимость грязных данных
Почему это не просто "неудобство", а серьёзная проблема.
Стоимость 1: Потерянное время
Data scientist с зарплатой $100K/год тратит 60% времени на очистку данных.
Расчёт: $100K × 0.6 = $60K в год на одного человека только на очистку
Команда из 5 data scientists = $300K/год только на борьбу с грязными данными.
Стоимость 2: Неправильные решения
Грязные данные → неверный анализ → плохие бизнес-решения.
Пример: Дубликаты клиентов не удалили. Анализ показал: "У нас 100K клиентов". На самом деле 70K. Планировали маркетинг на 100K бюджет, потратили впустую 30%.
Стоимость 3: Провальные ML-модели
Garbage in = garbage out. Модель обученная на грязных данных делает плохие предсказания.
Пример: Outliers не удалили (доход $99999999). Модель кредитного скоринга одобряет кредиты неплатёжеспособным клиентам. Потери в миллионах.
Стоимость 4: Репутационные риски
Отправили рассылку с ошибками в именах/адресах. Клиент получил "Уважаемый NULL!". Репутация компании страдает.
Стоимость 5: Compliance проблемы
GDPR требует правильности персональных данных. Грязные данные = штрафы до €20M или 4% оборота.
Индустриальная статистика
| Метрика | Значение | Источник |
|---|---|---|
| Средний ущерб от плохого качества данных | $12.9M/год | Gartner, 2023 |
| % времени аналитиков на очистку | 40-60% | IBM, 2023 |
| Стоимость bad data в США | $3.1 триллиона/год | IBM, 2020 |
| % проектов ML проваливающихся из-за данных | 85% | Gartner, 2022 |
3. Оценка качества данных
Прежде чем чистить — нужно оценить масштаб проблемы.
Dimension 1: Completeness (полнота)
Сколько данных отсутствует?
Формула: Completeness = (Заполненные ячейки / Всего ячеек) × 100%
Пример: Датасет 10K строк × 20 колонок = 200K ячеек. Пустых 50K. Completeness = 75%.
Бенчмарки:
95-100% — Отлично
80-95% — Хорошо
60-80% — Удовлетворительно
<60% — Плохо
Dimension 2: Accuracy (точность)
Насколько данные соответствуют реальности?
Сложно измерить без ground truth. Методы оценки:
Сэмплинг + ручная проверка (100-200 записей)
Сравнение с внешними источниками
Логические проверки (возраст 0-120, email содержит @)
Dimension 3: Consistency (согласованность)
Одинаковые значения записаны одинаково?
Проверка: Количество уникальных значений для категориальных полей.
Пример: Поле "Город". Ожидаем 100 городов. На самом деле 847 уникальных значений (из-за опечаток, разных написаний).
Dimension 4: Uniqueness (уникальность)
Есть ли дубликаты?
Формула: Duplicate rate = (Дубликаты / Всего записей) × 100%
Dimension 5: Timeliness (актуальность)
Насколько свежие данные?
Проверка: Дата последнего обновления записей. Если 50% записей старше 2 лет — возможно устарели.
Dimension 6: Validity (валидность)
Данные соответствуют формату и ограничениям?
Проверки:
Email содержит @ и домен
Телефон имеет правильную длину
Дата в будущем для DoB (некорректно)
Отрицательная цена (некорректно)
Пример Python для оценки качества:
import pandas as pddf = pd.read_csv('data.csv')# Полнотаcompleteness = (1 - df.isnull().sum().sum() / (len(df) * len(df.columns))) * 100print(f"Completeness: {completeness:.2f}%")# Дубликатыduplicates = df.duplicated().sum()duplicate_rate = (duplicates / len(df)) * 100print(f"Duplicate rate: {duplicate_rate:.2f}%")# Уникальность категориальныхfor col in df.select_dtypes(include='object').columns:print(f"{col}: {df[col].nunique()} уникальных значений")
4. Принципы очистки данных
Не существует универсального способа. Но есть принципы.
Принцип 1: Никогда не модифицируйте исходные данные
Создайте копию. Работайте с копией. Оригинал — это страховка.
df_clean = df.copy() # Всегда
Принцип 2: Документируйте каждый шаг
Что делали, почему, сколько строк удалили/изменили.
Формат:
Шаг 1: Удалили строки где email=NULL (5,234 строк)
Шаг 2: Заменили пропуски в возрасте на median (12,456 строк)
Шаг 3: Удалили дубликаты по email+phone (3,891 строк)
Принцип 3: Понимайте доменную область
Нельзя механически чистить без понимания бизнеса.
Пример: Возраст = 0. Это ошибка? Или это новорожденный? Нужен контекст.
Принцип 4: Начинайте с EDA (Exploratory Data Analysis)
Прежде чем чистить — изучите данные:
Распределения (гистограммы)
Корреляции
Выбросы (boxplots)
Паттерны пропусков (missingno library)
Принцип 5: Итеративный процесс
Очистка → Анализ → Обнаружили новую проблему → Очистка → Анализ...
Не пытайтесь сделать всё за один проход.
Принцип 6: Автоматизируйте, но проверяйте
Пишите скрипты для очистки. Но всегда сэмплируйте результат вручную (100-200 строк). Автоматизация может скрывать ошибки.
Принцип 7: Баланс между чистотой и потерей информации
Идеально чистые данные = пустой датасет (всё удалили).
Нулевая очистка = грязный анализ.
Нужен баланс. Иногда лучше оставить несовершенные данные, чем удалить половину.
5. Работа с пропущенными значениями
Missing values — самая частая проблема.
Типы пропусков
MCAR (Missing Completely At Random): Пропуски случайны, не зависят от других данных.
Пример: Сбой системы привёл к потере данных за 2 часа.
MAR (Missing At Random): Пропуски зависят от других наблюдаемых данных.
Пример: Мужчины реже указывают возраст, чем женщины. Пропуск в возрасте зависит от пола.
MNAR (Missing Not At Random): Пропуск зависит от самого значения.
Пример: Люди с высоким доходом не указывают доход (именно потому что высокий).
Стратегии обработки
Стратегия 1: Удаление
Когда: Пропусков мало (<5%), они MCAR, потеря данных приемлема.
# Удалить строки с любым пропускомdf_clean = df.dropna()# Удалить строки где пропуск в критичных колонкахdf_clean = df.dropna(subset=['email', 'user_id'])# Удалить колонки где >50% пропусковthreshold = len(df) * 0.5df_clean = df.dropna(axis=1, thresh=threshold)
Риски: Потеря информации, смещение выборки (если пропуски не MCAR).
Стратегия 2: Заполнение константой
Когда: Пропуск = отсутствие значения (не ошибка).
# Заполнить 0df['column'].fillna(0)# Заполнить "Unknown"df['city'].fillna('Unknown')# Заполнить -1 (чтобы отличить от настоящих значений)df['age'].fillna(-1)
Стратегия 3: Заполнение статистикой
Когда: Числовые данные, пропуски MAR.
# Mean (среднее) — чувствителен к outliersdf['age'].fillna(df['age'].mean())# Median (медиана) — устойчив к outliersdf['salary'].fillna(df['salary'].median())# Mode (мода) — для категориальныхdf['city'].fillna(df['city'].mode()[0])
Стратегия 4: Forward/Backward fill
Когда: Временные ряды, значения стабильны.
# Forward fill (взять предыдущее значение)df['temperature'].fillna(method='ffill')# Backward fill (взять следующее)df['temperature'].fillna(method='bfill')
Стратегия 5: Интерполяция
Когда: Временные ряды, значения меняются плавно.
# Линейная интерполяцияdf['temperature'].interpolate(method='linear')# Полиномиальнаяdf['sales'].interpolate(method='polynomial', order=2)
Стратегия 6: Заполнение предсказаниями модели
Когда: Сложные зависимости, важные данные.
Обучаете модель (например, KNN, Random Forest) на данных без пропусков, предсказываете пропущенные значения.
Стратегия 7: Оставить как есть
Когда: Модель/алгоритм умеет работать с пропусками (например, XGBoost, LightGBM).
Выбор стратегии: дерево решений
Пропусков <5% в некритичных колонках? → Удалить
Пропусков >50% в колонке? → Удалить колонку
Временной ряд? → Forward/backward fill или интерполяция
Категориальные данные? → Mode или "Unknown"
Числовые с outliers? → Median
Числовые без outliers? → Mean
Сложные зависимости + критичные данные? → Модель
6. Обработка дубликатов
Дубликаты искажают анализ, завышают метрики, ломают модели.
Типы дубликатов
1. Точные дубликаты
Все поля идентичны. Просто обнаружить и удалить.
# Найтиdf[df.duplicated()]# Удалить (оставить первую запись)df_clean = df.drop_duplicates()# Удалить по ключевым колонкамdf_clean = df.drop_duplicates(subset=['email', 'phone'])
2. Частичные дубликаты
Часть полей совпадает, но не все.
Пример:
Строка 1: email=ivan@mail.ru, phone=+79123456789, name=Иван
Строка 2: email=ivan@mail.ru, phone=+79123456780, name=Иван
Email совпадает → вероятно один человек, но телефон разный (обновился?).
Решение: Нужно доменное знание для объединения/выбора.
3. Fuzzy дубликаты
Похожие, но не идентичные из-за опечаток.
Пример:
Иван Иванов
Иван Иваанов (опечатка)
ИВАН ИВАНОВ (капс)
Решение: Fuzzy matching (Levenshtein distance, soundex).
from fuzzywuzzy import fuzz# Сходство строк (0-100)similarity = fuzz.ratio("Иван Иванов", "Иван Иваанов")print(similarity) # 92# Если >90 — считаем дубликатом
Стратегии удаления дубликатов
Стратегия 1: Оставить первую запись
df.drop_duplicates(keep='first')
Стратегия 2: Оставить последнюю (самую свежую)
df.drop_duplicates(keep='last')
Стратегия 3: Объединение информации
Взять лучшее из обеих записей.
Пример:
Строка 1: email=ivan@mail.ru, phone=NULL
Строка 2: email=ivan@mail.ru, phone=+7912...
Объединить: email=ivan@mail.ru, phone=+7912...
Стратегия 4: Оставить наиболее заполненную
Если в одной записи больше заполненных полей — она "качественнее".
7. Стандартизация и нормализация
Стандартизация — приведение к единому формату
1. Текст: uppercase/lowercase
# Lowercasedf['email'] = df['email'].str.lower()# Uppercase для кодовdf['country_code'] = df['country_code'].str.upper()# Title case для имёнdf['name'] = df['name'].str.title()
2. Удаление пробелов
# Убрать пробелы в начале/концеdf['column'] = df['column'].str.strip()# Множественные пробелы → одинdf['column'] = df['column'].str.replace(r'\s+', ' ', regex=True)
3. Даты
# Преобразовать в datetimedf['date'] = pd.to_datetime(df['date'], errors='coerce')# Форматировать единообразноdf['date'] = df['date'].dt.strftime('%Y-%m-%d')
4. Телефоны
import redef clean_phone(phone):# Оставить только цифрыphone = re.sub(r'\D', '', str(phone))# Привести к формату 79XXXXXXXXXif phone.startswith('8'):phone = '7' + phone[1:]return phonedf['phone'] = df['phone'].apply(clean_phone)
5. Email
# Lowercase + trimdf['email'] = df['email'].str.lower().str.strip()# Валидацияemail_pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'df['email_valid'] = df['email'].str.match(email_pattern)
Нормализация — приведение к единой шкале
Для числовых данных, чтобы фичи были на одной шкале (важно для ML).
1. Min-Max Scaling (0 до 1)
from sklearn.preprocessing import MinMaxScalerscaler = MinMaxScaler()df['age_scaled'] = scaler.fit_transform(df[['age']])
2. Standardization (Z-score)
Среднее = 0, стандартное отклонение = 1
from sklearn.preprocessing import StandardScalerscaler = StandardScaler()df['salary_scaled'] = scaler.fit_transform(df[['salary']])
8. Работа с outliers и аномалиями
Outliers могут быть: (1) ошибками данных, (2) редкими, но валидными значениями.
Методы обнаружения
1. IQR (Interquartile Range)
Q1 = df['column'].quantile(0.25)Q3 = df['column'].quantile(0.75)IQR = Q3 - Q1# Outliers: за пределами Q1-1.5*IQR и Q3+1.5*IQRlower_bound = Q1 - 1.5 * IQRupper_bound = Q3 + 1.5 * IQRoutliers = df[(df['column'] < lower_bound) | (df['column'] > upper_bound)]
2. Z-score
Если |Z-score| > 3 — outlier (99.7% данных в пределах ±3σ).
from scipy import statsz_scores = stats.zscore(df['column'])outliers = df[abs(z_scores) > 3]
3. Isolation Forest
ML алгоритм для обнаружения аномалий.
from sklearn.ensemble import IsolationForestiso = IsolationForest(contamination=0.05)df['outlier'] = iso.fit_predict(df[['age', 'salary']])outliers = df[df['outlier'] == -1]
Стратегии обработки
1. Удаление
Если это явные ошибки (возраст 250, зарплата $99999999).
2. Замена на границы (capping/flooring)
# Заменить на 95-й перцентильupper_limit = df['salary'].quantile(0.95)df['salary'] = df['salary'].clip(upper=upper_limit)
3. Трансформация
Log transform для уменьшения влияния outliers.
import numpy as npdf['salary_log'] = np.log1p(df['salary'])
4. Оставить как есть
Если это редкие, но валидные значения (например, миллионеры в датасете доходов). Используйте robust модели (Random Forest, XGBoost).
Решение: проверка на валидность
Outlier или реальное значение?
Доменное знание (возраст 150 лет = ошибка, возраст 95 = редко, но возможно)
Проверка других полей (если возраст 150, но дата рождения = 1990 → ошибка)
Сэмплинг: вручную проверить 50-100 outliers
9. Инструменты для очистки данных
Python библиотеки
1. Pandas
Базовый инструмент для работы с данными.
import pandas as pddf = pd.read_csv('data.csv')df.info() # Обзор типов и пропусковdf.describe() # Статистикаdf.isnull().sum() # Подсчёт пропусков
2. Missingno
Визуализация пропущенных данных.
import missingno as msnomsno.matrix(df) # Матрица пропусковmsno.heatmap(df) # Корреляция пропусков
3. PyJanitor
Удобные функции для очистки.
import janitordf_clean = (df.clean_names() # Стандартизация имён колонок.remove_empty() # Удаление пустых строк/колонок.dropna(subset=['key_column']))
4. Great Expectations
Валидация и тестирование качества данных.
import great_expectations as gedf_ge = ge.from_pandas(df)df_ge.expect_column_values_to_not_be_null('email')df_ge.expect_column_values_to_match_regex('email', r'^.+@.+$')
5. Dedupe
ML для обнаружения дубликатов.
SQL для очистки
-- Удаление дубликатовDELETE FROM usersWHERE id NOT IN (SELECT MIN(id) FROM users GROUP BY email);-- СтандартизацияUPDATE users SET email = LOWER(TRIM(email));-- Замена NULLUPDATE users SET city = 'Unknown' WHERE city IS NULL;
Платформы no-code/low-code
OpenRefine: Бесплатный инструмент для очистки, трансформации
Trifacta Wrangler: Визуальная подготовка данных
Alteryx: Enterprise решение
DataRobot: Автоматическая очистка для ML
Рекомендуемый стек
Для начала достаточно:
Pandas (основа)
Missingno (визуализация пропусков)
Sklearn (нормализация, outliers)
Для продвинутых:
+ Great Expectations (валидация)
+ PyJanitor (удобство)
+ Dedupe (сложные дубликаты)
10. Документирование процесса очистки
Без документации очистка непрозрачна и невоспроизводима.
Что документировать
1. Исходное состояние данных
Количество строк/колонок
% пропусков по колонкам
Количество дубликатов
Типы данных
2. Каждое преобразование
Шаблон записи:
Шаг X: [Название операции]
Действие: Что сделали
Причина: Почему
Результат: Сколько строк затронуто/удалено
Код: Фрагмент кода или SQL
Пример:
Шаг 1: Удаление строк без email
Действие: df.dropna(subset=['email'])
Причина: Email — критичный идентификатор, без него запись бесполезна
Результат: Удалено 5,234 строк (5.2% датасета)
Было/стало: 100,000 → 94,766 строк
3. Итоговое состояние
Финальное количество строк/колонок
% пропусков после очистки
Сколько всего удалено/изменено
4. Assumptions (предположения)
Какие предположения делались при очистке.
Пример: "Предполагаем, что пропуск в колонке 'income' для молодых пользователей означает низкий доход, а не отсутствие данных".
Формат документации
Вариант 1: Markdown файл
data_cleaning_log.md рядом с кодом.
Вариант 2: Jupyter Notebook с markdown ячейками
Код + объяснения в одном файле.
Вариант 3: Комментарии в коде
# Шаг 3: Заполнение пропусков в age медианой# Причина: Пропусков 12%, удаление нежелательно# Median выбран т.к. есть outliers (mean искажён)median_age = df['age'].median()df['age'].fillna(median_age, inplace=True)# Результат: Заполнено 12,456 значений
Воспроизводимость
Документация должна позволять кому-то другому повторить очистку и получить тот же результат.
Для этого:
Версионируйте код (Git)
Фиксируйте версии библиотек (requirements.txt)
Сохраняйте промежуточные состояния (checkpoints)
11. Как не потерять важную информацию
Агрессивная очистка = потеря паттернов и инсайтов.
Проблема 1: Удаление строк с пропусками
Вы удалили все строки где хотя бы одна колонка пустая. Потеряли 40% данных.
Риск: Пропуски могут быть неслучайными (MAR, MNAR). Удаление создаёт selection bias.
Решение:
Удаляйте только по критичным колонкам
Анализируйте паттерны пропусков (есть ли зависимость?)
Создавайте индикаторы пропусков: was_missing=True/False
# Вместо удаления — индикаторdf['age_was_missing'] = df['age'].isnull()df['age'].fillna(df['age'].median(), inplace=True)
Проблема 2: Удаление outliers
Вы удалили всё, что выходит за 3σ. Но это могли быть редкие, но важные случаи.
Пример: VIP клиенты с высоким доходом — outliers по доходу. Удалили их → потеряли важный сегмент.
Решение:
Не удаляйте автоматически
Исследуйте outliers: это ошибки или реальные значения?
Создавайте отдельный флаг: is_outlier=True
Используйте robust методы вместо удаления
Проблема 3: Слишком агрессивная стандартизация
Вы привели "Москва", "moscow", "MSK" к "москва". Но "MSK" могло означать не Москву, а что-то другое (аэропорт, код). Потеряли нюанс.
Решение:
Сохраняйте оригинальную колонку
Создавайте новую колонку с очищенными данными
Проверяйте сэмпл после стандартизации
df['city_original'] = df['city'] # Сохранить оригиналdf['city_clean'] = df['city'].str.lower().str.strip()
Проблема 4: Объединение дубликатов
Два клиента с одинаковым email, но разными адресами. Вы объединили в одну запись, выбрав первый адрес. Но второй адрес мог быть актуальнее.
Решение:
Если есть timestamp — выбирайте по дате (последний = актуальнее)
Если нет — сохраняйте оба значения через разделитель
Создавайте отдельную таблицу для истории изменений
Правило: Сохраняйте сырые данные
Никогда не перезаписывайте исходные файлы. Структура:
data/raw/ — оригинальные данные (неприкасаемые)
data/interim/ — промежуточные состояния
data/processed/ — финальные очищенные данные
Правило: Делайте checkpoints
После каждого крупного шага — сохраняйте промежуточную версию.
df.to_csv('data/interim/01_after_deduplication.csv')df.to_csv('data/interim/02_after_missing_values.csv')df.to_csv('data/interim/03_after_standardization.csv')
Если что-то пошло не так — откатываетесь к предыдущему checkpoint.
12. Валидация после очистки
Очистили данные. Как убедиться, что всё правильно?
Проверка 1: Базовые метрики
# До и послеprint("Было строк:", len(df_original))print("Стало строк:", len(df_clean))print("Удалено:", len(df_original) - len(df_clean))print("% потерь:", (1 - len(df_clean)/len(df_original)) * 100)# Пропускиprint("Пропусков до:", df_original.isnull().sum().sum())print("Пропусков после:", df_clean.isnull().sum().sum())
Проверка 2: Распределения
Сравните распределения до и после. Если они сильно изменились — возможно, вы удалили что-то важное.
import matplotlib.pyplot as pltfig, axes = plt.subplots(1, 2, figsize=(12, 4))df_original['age'].hist(ax=axes[0], bins=50)axes[0].set_title('До очистки')df_clean['age'].hist(ax=axes[1], bins=50)axes[1].set_title('После очистки')
Проверка 3: Сэмплинг
Вручную просмотрите 50-100 случайных строк из очищенных данных.
df_clean.sample(100)
Ищите странности: неожиданные значения, паттерны, которые выглядят неправильно.
Проверка 4: Юнит-тесты на данные
Автоматические проверки инвариантов.
# Проверкиassert df_clean['age'].min() >= 0, "Возраст не может быть отрицательным"assert df_clean['age'].max() <= 120, "Возраст слишком большой"assert df_clean['email'].str.contains('@').all(), "Все email должны содержать @"assert df_clean.duplicated(subset=['user_id']).sum() == 0, "Есть дубликаты user_id"
Проверка 5: Бизнес-логика
Проверьте, что данные соответствуют доменным правилам.
Примеры:
Дата регистрации не может быть позже даты первой покупки
Сумма заказа >= 0
Если status="отменён", то revenue=0
Проверка 6: Сравнение с benchmark
Если у вас есть эталонный датасет (золотой стандарт), сравните:
Accuracy = (Правильно очищенные / Всего) × 100%
13. Реальные кейсы
Кейс 1: Клиентская база e-commerce (100K записей)
Проблемы:
35% пропусков в телефонах
12K дубликатов
Email в разных форматах (капс, пробелы, опечатки)
Адреса несогласованные (сокращения, опечатки в городах)
Решение:
Email: lowercase, trim, валидация regex → удалили 2.3K невалидных
Телефоны: стандартизация до 79XXXXXXXXX → заменили пропуски на "Unknown"
Дубликаты: по email+имя, оставили последнюю запись (самую свежую)
Адреса: fuzzy matching городов → объединили 847 вариантов в 152 города
Результат: Датасет с 100K → 85K строк (качественных). Потеря 15%, но качество выросло с 40% до 92% (по метрике completeness + accuracy).
Кейс 2: Датасет для ML модели предсказания churn (50K пользователей)
Проблемы:
20% пропусков в feature "last_login_days"
Outliers в "session_duration" (до 999999 минут)
Несбалансированность: churn=1 только у 8%
Решение:
last_login_days: создали флаг was_missing, заполнили медианой
session_duration: capping на 99-м перцентиле (180 минут)
Несбалансированность: использовали SMOTE для oversampling minority class
Не удалили ни одной строки (критично для ML)
Результат: Модель обучена на полном датасете (50K), accuracy 89%, precision/recall для churn класса улучшились за счёт правильной обработки outliers и missing values.
Кейс 3: Логи веб-сервера (10M записей)
Проблемы:
IP адреса с bot traffic (30% записей)
Timestamps в разных форматах
URL с query параметрами (тысячи уникальных)
Решение:
Фильтрация ботов: по user-agent и списку известных bot IP
Timestamps: привели к единому формату UTC
URL: очистили query параметры, оставили только path
Агрегация: group by path + hour → сократили до 500K записей
Результат: Из 10M записей получили 7M валидных (после фильтрации ботов) + агрегировали для анализа. Скорость обработки выросла в 20 раз.
14. Что запомнить
Грязные данные — норма, не исключение. Научиться чистить правильно — критичный навык для любого, кто работает с данными.
Ключевые выводы:
40-60% времени уходит на очистку. Это не баг, это фича профессии data analyst/scientist.
Стоимость грязных данных огромна. $12.9M/год средний ущерб на компанию (Gartner). Неправильные решения, провальные модели, потерянное время.
Нет универсального рецепта. Каждый датасет уникален. Нужно доменное знание + здравый смысл + итерации.
Никогда не модифицируйте оригинал. Работайте с копией. Сохраняйте checkpoints. Структура: raw → interim → processed.
Документируйте всё. Что сделали, почему, сколько строк затронуто. Иначе очистка непрозрачна и невоспроизводима.
Баланс: чистота vs потеря информации. Идеально чистые данные = пустой датасет. Агрессивная очистка убивает паттерны.
Сохраняйте индикаторы. was_missing, was_outlier, original_value — помогают не потерять информацию.
Валидация обязательна. Сэмплинг, юнит-тесты, сравнение распределений. Не доверяйте слепо автоматической очистке.
«В data science нет shortcut. Хорошие модели начинаются с хороших данных. Хорошие данные начинаются с правильной очистки» — Andrew Ng, основатель Coursera
Чек-лист: очистка данных
☐ Сохранить исходные данные (raw/) без изменений
☐ Провести EDA: распределения, пропуски, outliers
☐ Оценить качество: completeness, accuracy, consistency
☐ Создать копию для работы
☐ Задокументировать каждый шаг очистки
☐ Обработать пропуски (удалить / заполнить / оставить)
☐ Найти и обработать дубликаты (точные / fuzzy)
☐ Стандартизировать форматы (email, телефоны, даты, текст)
☐ Обработать outliers (исследовать / удалить / cap / оставить)
☐ Создать индикаторы (was_missing, is_outlier)
☐ Сохранить промежуточные версии (checkpoints)
☐ Валидировать результат (сэмплинг, тесты, распределения)
☐ Сохранить финальную версию (processed/)
☐ Написать README с описанием процесса
Начните сегодня:
Загрузите свой датасет в Pandas (10 минут)
Запустите df.info(), df.describe(), df.isnull().sum() (5 минут)
Визуализируйте пропуски через missingno (5 минут)
Создайте документ data_cleaning_log.md (2 минуты)
Выберите 1 проблему (пропуски / дубликаты / outliers) (1 минута)
Примените одну технику из статьи (30 минут)
Задокументируйте шаг (10 минут)
Главный урок: Очистка данных — это искусство баланса. С одной стороны — грязь, ошибки, шум. С другой — ценная информация, паттерны, инсайты. Ваша задача не сделать данные идеальными. Ваша задача — сделать их достаточно чистыми для анализа, не потеряв критичную информацию. Это требует внимания, доменного знания и здравого смысла. Но овладев этим навыком, вы сэкономите месяцы работы и создадите основу для качественного анализа.
А лучшие вакансии для аналитиков данных ищите на hirehi.ru