← Назад к вопросам
Как проверить качество данных перед анализом?
1.0 Junior🔥 171 комментариев
#Pandas и обработка данных
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI26 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Проверка качества данных (Data Quality Assessment)
Проверка качества данных — это критический первый шаг перед анализом. Некачественные данные приводят к неправильным выводам. Фраза "garbage in, garbage out" (мусор на входе, мусор на выходе) в аналитике абсолютно справедлива.
Что такое качество данных?
Качественные данные должны быть:
- Полнота (Completeness) — нет/минум пропусков
- Точность (Accuracy) — данные соответствуют реальности
- Консистентность (Consistency) — нет противоречий
- Валидность (Validity) — соответствуют правилам и формату
- Уникальность (Uniqueness) — нет дубликатов
- Своевременность (Timeliness) — данные актуальны
Шаг 1: Базовая разведка (Exploratory Data Analysis)
import pandas as pd
import numpy as np
# Загрузка данных
df = pd.read_csv('data.csv')
# Базовая информация
print(f"Размер датасета: {df.shape}") # (1000 строк, 15 колонок)
print(f"\nИнформация о колонках:")
print(df.info()) # Типы данных, не-NULL counts
print(f"\nОсновная статистика:")
print(df.describe()) # mean, std, min, max для числовых
print(f"\nПервые 5 строк:")
print(df.head())
print(f"\nПоследние 5 строк:")
print(df.tail())
Шаг 2: Анализ пропусков (Missing Values)
# Количество пропусков
print("Пропуски по колонкам:")
print(df.isnull().sum())
# Процент пропусков
print("\nПроцент пропусков:")
print((df.isnull().sum() / len(df) * 100).round(2))
# Строки с пропусками
print(f"\nВсего строк с пропусками: {df.isnull().any(axis=1).sum()}")
# Анализ пропусков (зависимость)
print("\nКорреляция пропусков между колонками:")
print(df.isnull().corr())
# Если коррелируют → пропуски системные (MAR), не случайные (MCAR)
# Визуализация
import seaborn as sns
sns.heatmap(df.isnull(), yticklabels=False, cbar=True)
Шаг 3: Проверка типов данных
# Неправильные типы данных
print("Типы данных:")
print(df.dtypes)
# Примеры проблем:
# - Дата загружена как строка (object), не как datetime
# - Число загружено как строка (object) из-за специальных символов
# - Категория загружена как число (int64)
# Исправления:
df['date'] = pd.to_datetime(df['date']) # Строка → Date
df['salary'] = df['salary'].str.replace('$', '').astype(float) # Строка → Float
df['category'] = df['category'].astype('category') # Int → Category
Шаг 4: Проверка на выбросы (Outliers)
# Метод 1: Межквартильный размах (IQR)
Q1 = df['salary'].quantile(0.25)
Q3 = df['salary'].quantile(0.75)
IQR = Q3 - Q1
lower_bound = Q1 - 1.5 * IQR
upper_bound = Q3 + 1.5 * IQR
outliers = df[(df['salary'] < lower_bound) | (df['salary'] > upper_bound)]
print(f"Выбросы (IQR): {len(outliers)} строк")
print(outliers)
# Метод 2: Z-score
from scipy import stats
z_scores = np.abs(stats.zscore(df['salary'].dropna()))
outliers_z = z_scores > 3 # Более 3 сигм от среднего
print(f"Выбросы (Z-score): {outliers_z.sum()} строк")
# Визуализация
import matplotlib.pyplot as plt
plt.boxplot(df['salary'])
plt.title('Box Plot: Salary')
plt.show()
# Точки вне усов — выбросы
Шаг 5: Проверка валидности значений
# Диапазоны значений
print("Диапазоны:")
print(f"Возраст: {df['age'].min()} - {df['age'].max()}")
# Если видишь age = 999 или -10 → ошибка
# Невозможные значения
invalid_ages = df[df['age'] < 0] # Отрицательный возраст
invalid_ages = df[df['age'] > 150] # Слишком старый
if len(invalid_ages) > 0:
print(f"⚠️ Найдено {len(invalid_ages)} невозможных значений возраста")
# Проверка диапазонов для известных переменных
valid_genders = ['M', 'F', 'Other']
invalid_gender = df[~df['gender'].isin(valid_genders)]
if len(invalid_gender) > 0:
print(f"⚠️ Неизвестный пол: {invalid_gender['gender'].unique()}")
Шаг 6: Проверка дубликатов
# Полные дубликаты
print(f"Полные дубликаты: {df.duplicated().sum()}")
print(df[df.duplicated(keep=False)]) # Показать все (keep=False)
# Дубликаты по ключевой колонке (например, ID)
print(f"\nДубликаты по ID: {df.duplicated(subset=['id']).sum()}")
dup_ids = df[df.duplicated(subset=['id'], keep=False)]
print(dup_ids.sort_values('id'))
# Удаление дубликатов
df_clean = df.drop_duplicates(subset=['id'])
print(f"\nЭтап дубликатов: {len(df)} → {len(df_clean)} строк")
Шаг 7: Проверка консистентности
# Противоречивые значения
# Пример: дата смерти раньше даты рождения
df_invalid = df[df['death_date'] < df['birth_date']]
if len(df_invalid) > 0:
print(f"⚠️ Найдено {len(df_invalid)} противоречивых дат")
# Пример: сумма частей не равна итогу
df['sum_check'] = df['part1'] + df['part2'] + df['part3']
df_mismatch = df[df['sum_check'] != df['total']]
if len(df_mismatch) > 0:
print(f"⚠️ Несоответствие сумм в {len(df_mismatch)} строках")
# Логические противоречия
# Статус "умер" с датой смерти в будущем
df_logic = df[(df['status'] == 'deceased') & (df['death_date'] > pd.Timestamp.today())]
Шаг 8: Проверка распределения
import matplotlib.pyplot as plt
import seaborn as sns
# Гистограмма
for col in df.select_dtypes(include=[np.number]).columns:
plt.figure(figsize=(10, 4))
plt.subplot(1, 2, 1)
df[col].hist(bins=50)
plt.title(f'Distribution: {col}')
plt.subplot(1, 2, 2)
df[col].boxplot()
plt.title(f'Boxplot: {col}')
plt.show()
# Проверка нормальности (Shapiro-Wilk тест)
from scipy.stats import shapiro
stat, p_value = shapiro(df['salary'].dropna())
if p_value < 0.05:
print(f"Распределение НЕ нормальное (p={p_value:.4f})")
else:
print(f"Распределение примерно нормальное")
Шаг 9: Проверка граничных значений
# Значения на границах диапазона
print("Минимальные значения:")
print(df.min())
print("\nМаксимальные значения:")
print(df.max())
# Часто граничные значения используются как коды ошибок:
# 999, 9999, -1, 0 для отсутствия информации
# Проверь, что это реальные значения, а не коды ошибок!
Шаг 10: Проверка формата
# Формат текстовых полей
print(f"Длина email: {df['email'].str.len().describe()}")
# Нормальный email: 10-100 символов
# Формат телефонов
phone_valid = df['phone'].str.match(r'^\+?1?\d{9,15}$')
if not phone_valid.all():
print(f"⚠️ {(~phone_valid).sum()} некорректных телефонов")
# Формат кодов (ZIP, постальные коды)
zip_valid = df['zip'].str.match(r'^\d{5}$')
if not zip_valid.all():
print(f"⚠️ {(~zip_valid).sum()} некорректных ZIP кодов")
Комплексный чек-лист в коде
def check_data_quality(df, name='Dataset'):
"""Комплексная проверка качества данных"""
print(f"\n{'='*60}")
print(f"Data Quality Report: {name}")
print(f"{'='*60}")
# Размер
print(f"\n1. SIZE:")
print(f" Rows: {len(df)}, Columns: {len(df.columns)}")
# Пропуски
print(f"\n2. MISSING VALUES:")
missing = df.isnull().sum()
if missing.sum() > 0:
print(f" Total missing: {missing.sum()} ({missing.sum()/df.size*100:.2f}%)")
for col in missing[missing > 0].index:
pct = missing[col] / len(df) * 100
print(f" - {col}: {missing[col]} ({pct:.1f}%)")
else:
print(" ✓ No missing values")
# Дубликаты
print(f"\n3. DUPLICATES:")
dup_count = df.duplicated().sum()
print(f" Complete duplicates: {dup_count}")
# Типы данных
print(f"\n4. DATA TYPES:")
print(f" {df.dtypes.value_counts().to_dict()}")
# Статистика
print(f"\n5. STATISTICS:")
for col in df.select_dtypes(include=[np.number]).columns:
q1 = df[col].quantile(0.25)
q3 = df[col].quantile(0.75)
iqr = q3 - q1
outlier_count = ((df[col] < q1 - 1.5*iqr) | (df[col] > q3 + 1.5*iqr)).sum()
if outlier_count > 0:
print(f" ⚠️ {col}: {outlier_count} potential outliers")
print(f"\n{'='*60}\n")
# Использование
check_data_quality(df, 'Sales Data')
Решения при обнаружении проблем
# Проблема → Решение
# 1. Пропуски
# Решение: dropna(), fillna(), imputation
df = df.dropna(subset=['critical_column'])
df['age'].fillna(df['age'].median(), inplace=True)
# 2. Выбросы
# Решение: удалить, заменить, трансформировать
df = df[(df['salary'] >= lower_bound) & (df['salary'] <= upper_bound)]
# 3. Неправильный тип
# Решение: преобразовать
df['date'] = pd.to_datetime(df['date'])
# 4. Дубликаты
# Решение: удалить (keep='first'/'last')
df = df.drop_duplicates(subset=['id'], keep='first')
# 5. Невозможные значения
# Решение: удалить или заменить на NULL
df.loc[df['age'] > 150, 'age'] = np.nan
Документирование качества
# Создай отчёт о качестве данных
quality_report = {
'date_checked': pd.Timestamp.today(),
'total_rows': len(df),
'total_missing': df.isnull().sum().sum(),
'duplicate_rows': df.duplicated().sum(),
'issues_found': [
'Salary: 15 missing values (1.5%)',
'Age: 3 impossible values (>150)',
'Email: 50 duplicates'
],
'actions_taken': [
'Removed 3 impossible ages',
'Filled missing salary with median',
'Kept duplicate emails (for investigation)'
]
}
import json
with open('data_quality_report.json', 'w') as f:
json.dump(quality_report, f, indent=2, default=str)
Итог
Никогда не пропускай проверку качества!
Проверяй:
- Размер и структуру
- Пропуски и причины
- Типы данных
- Выбросы
- Валидность значений
- Дубликаты
- Консистентность
- Распределения
- Форматы
- Граничные значения
Документируй все найденные проблемы и действия, которые ты предпринял. Это поможет другим аналитикам понять, на какие данные можно полагаться, а какие требуют осторожности при интерпретации результатов.