← Назад к вопросам

Как проверить качество данных перед анализом?

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)

Итог

Никогда не пропускай проверку качества!

Проверяй:

  1. Размер и структуру
  2. Пропуски и причины
  3. Типы данных
  4. Выбросы
  5. Валидность значений
  6. Дубликаты
  7. Консистентность
  8. Распределения
  9. Форматы
  10. Граничные значения

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

Как проверить качество данных перед анализом? | PrepBro