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

Какие проверки выполняешь для данных?

1.0 Junior🔥 251 комментариев
#Pandas и обработка данных

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI26 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Проверки качества данных (Data Validation)

Качество данных — основание любого аналитического вывода. Я всегда следую принципу: "Не доверяй данным, пока не проверишь". Вот мой набор проверок, которые я выполняю для каждого датасета.

1. Проверки на полноту (Completeness)

Проблема: Пропущенные значения (NULL) могут исказить выводы.

-- Проверка: какой процент NULL в каждой колонке
SELECT 
    'users' as table_name,
    COUNT(*) as total_rows,
    COUNT(*) FILTER (WHERE id IS NULL) as null_id,
    COUNT(*) FILTER (WHERE email IS NULL) as null_email,
    COUNT(*) FILTER (WHERE created_at IS NULL) as null_created_at,
    COUNT(*) FILTER (WHERE name IS NULL) as null_name,
    ROUND(100.0 * COUNT(*) FILTER (WHERE email IS NULL) / NULLIF(COUNT(*), 0), 2) as null_email_pct,
    ROUND(100.0 * COUNT(*) FILTER (WHERE created_at IS NULL) / NULLIF(COUNT(*), 0), 2) as null_created_at_pct
FROM users;

-- Результат: если null_email_pct > 1%, это красный флаг

Алертинг: NULL > 5% обычно означает проблему в источнике данных (сломанный API, bad ETL).

2. Проверки на дубликаты (Uniqueness)

Проблема: Дубликаты приводят к переоценке метрик.

-- Проверка: есть ли дубликаты в primary key
SELECT 
    user_id,
    COUNT(*) as occurrence_count
FROM users
GROUP BY user_id
HAVING COUNT(*) > 1
LIMIT 10;

-- Если результат непустой — есть дубликаты!

-- Глубже: проверка дубликатов по email (должен быть уникален)
SELECT 
    email,
    COUNT(*) as user_count
FROM users
GROUP BY email
HAVING COUNT(*) > 1
ORDER BY user_count DESC;

-- Проверка: могут ли быть "скрытые" дубликаты (разные ID, но тот же email)
WITH email_groups AS (
    SELECT 
        email,
        ARRAY_AGG(DISTINCT id) as user_ids,
        COUNT(DISTINCT id) as unique_user_ids
    FROM users
    WHERE email IS NOT NULL
    GROUP BY email
)
SELECT * FROM email_groups WHERE unique_user_ids > 1 LIMIT 10;

Рекомендация: Дубликатов быть не должно. Если есть — нужно разобраться в причине.

3. Проверки на диапазон (Range Validation)

Проблема: Невозможные значения (цена < 0, возраст 200 лет, дата в будущем).

-- Проверка: цены не могут быть отрицательными
SELECT 
    order_id,
    amount,
    created_at
FROM orders
WHERE amount < 0  -- это ошибка!
LIMIT 10;

-- Проверка: даты заказа в будущем
SELECT 
    COUNT(*) as future_orders_count
FROM orders
WHERE created_at > CURRENT_TIMESTAMP;

-- Проверка: возраст пользователя в допустимом диапазоне
SELECT 
    user_id,
    EXTRACT(YEAR FROM AGE(birth_date)) as age,
    birth_date
FROM users
WHERE EXTRACT(YEAR FROM AGE(birth_date)) < 13
   OR EXTRACT(YEAR FROM AGE(birth_date)) > 120;

-- Проверка: количество покупок (может ли быть отрицательным?)
SELECT 
    user_id,
    SUM(CASE WHEN quantity < 0 THEN quantity ELSE 0 END) as negative_qty_total
FROM order_items
GROUP BY user_id
HAVING SUM(CASE WHEN quantity < 0 THEN quantity ELSE 0 END) != 0
LIMIT 5;

Рекомендация: Каждая колонка имеет бизнес-диапазон. Выход за пределы — это ошибка.

4. Проверки на консистентность (Consistency)

Проблема: Данные в разных таблицах не согласуются между собой.

-- Проверка: все order_id из order_items должны быть в orders
SELECT COUNT(*) as orphan_items
FROM order_items oi
LEFT JOIN orders o ON oi.order_id = o.id
WHERE o.id IS NULL;  -- если результат > 0, есть проблема

-- Проверка: сумма в order должна равняться сумме items
WITH order_totals AS (
    SELECT 
        o.id as order_id,
        o.amount as order_total,
        SUM(oi.quantity * oi.unit_price) as items_total,
        ABS(o.amount - SUM(oi.quantity * oi.unit_price)) as difference
    FROM orders o
    LEFT JOIN order_items oi ON o.id = oi.order_id
    GROUP BY o.id, o.amount
)
SELECT * FROM order_totals
WHERE difference > 0.01  -- допускаем небольшие различия из-за rounded
LIMIT 10;

-- Проверка: дата создания заказа не может быть позже даты обновления
SELECT COUNT(*) as inconsistent_dates
FROM orders
WHERE created_at > updated_at;

5. Проверки на внешние зависимости (Foreign Key Validation)

-- Проверка: все user_id должны существовать в таблице users
SELECT COUNT(*) as invalid_user_refs
FROM orders o
LEFT JOIN users u ON o.user_id = u.id
WHERE u.id IS NULL;

-- Проверка в обе стороны: есть ли пользователи без заказов (это нормально, но полезно знать)
SELECT COUNT(*) as users_without_orders
FROM users u
LEFT JOIN orders o ON u.id = o.user_id
WHERE o.id IS NULL;

6. Проверки на аномалии (Outliers)

Проблема: Экстремальные значения могут быть как реальными (VIP клиент), так и ошибками.

-- Проверка: выявляем выбросы используя IQR метод
WITH order_stats AS (
    SELECT 
        PERCENTILE_CONT(0.25) WITHIN GROUP (ORDER BY amount) as q1,
        PERCENTILE_CONT(0.75) WITHIN GROUP (ORDER BY amount) as q3,
        PERCENTILE_CONT(0.75) WITHIN GROUP (ORDER BY amount) - 
        PERCENTILE_CONT(0.25) WITHIN GROUP (ORDER BY amount) as iqr
    FROM orders
    WHERE amount > 0
)
SELECT 
    o.order_id,
    o.amount,
    CASE 
        WHEN o.amount < (qs.q1 - 1.5 * qs.iqr) THEN 'LOW_OUTLIER'
        WHEN o.amount > (qs.q3 + 1.5 * qs.iqr) THEN 'HIGH_OUTLIER'
        ELSE 'NORMAL'
    END as anomaly_flag
FROM orders o
CROSS JOIN order_stats qs
WHERE o.amount < (qs.q1 - 1.5 * qs.iqr) 
   OR o.amount > (qs.q3 + 1.5 * qs.iqr)
LIMIT 20;

-- Проверка: резкие скачки в ежедневные метрики
WITH daily_metrics AS (
    SELECT 
        DATE(created_at) as date,
        COUNT(*) as daily_orders,
        SUM(amount) as daily_revenue,
        LAG(SUM(amount)) OVER (ORDER BY DATE(created_at)) as prev_day_revenue
    FROM orders
    GROUP BY DATE(created_at)
)
SELECT 
    date,
    daily_orders,
    daily_revenue,
    prev_day_revenue,
    ROUND(100.0 * (daily_revenue - prev_day_revenue) / NULLIF(prev_day_revenue, 0), 2) as day_over_day_change_pct
FROM daily_metrics
WHERE ABS(100.0 * (daily_revenue - prev_day_revenue) / NULLIF(prev_day_revenue, 0)) > 50  -- скачок > 50%
ORDER BY date DESC;

7. Проверки на форматирование (Format Validation)

Проблема: Email содержит опечатки, номер телефона неправильного формата.

-- Проверка: email в правильном формате
SELECT 
    user_id,
    email,
    CASE 
        WHEN email NOT LIKE '%@%.%' THEN 'INVALID_FORMAT'
        WHEN email LIKE '% %' THEN 'CONTAINS_SPACES'
        WHEN email = LOWER(email) AND email LIKE '%@%' THEN 'VALID'
        ELSE 'SUSPICIOUS'
    END as email_status
FROM users
WHERE email IS NOT NULL
AND email NOT LIKE '%@%.%'  -- стандартная проверка
LIMIT 20;

-- Проверка: URL ссылки
SELECT 
    id,
    profile_url
FROM users
WHERE profile_url IS NOT NULL
  AND profile_url NOT LIKE 'http://%'
  AND profile_url NOT LIKE 'https://%'
LIMIT 10;

8. Проверки на статистическую аномалию (Statistical Tests)

import pandas as pd
from scipy import stats

# Загруженные данные
df = pd.read_sql("SELECT amount FROM orders WHERE created_at >= CURRENT_DATE - INTERVAL '7 days'", conn)

# Проверка на нормальность распределения (Shapiro-Wilk тест)
statistic, p_value = stats.shapiro(df['amount'])
if p_value < 0.05:
    print(f"Распределение НЕ нормальное (p={p_value:.4f})")
else:
    print(f"Распределение нормальное (p={p_value:.4f})")

# Проверка на статистический скачок
mean_7d = df['amount'].mean()
std_7d = df['amount'].std()
z_scores = abs((df['amount'] - mean_7d) / std_7d)
outliers = df[z_scores > 3]  # значения, которые отстоят на 3 стандартных отклонения
print(f"Выявлено {len(outliers)} потенциальных аномалий")

9. Дашборд для мониторинга качества данных

-- Единая витрина для мониторинга качества
WITH data_quality_checks AS (
    SELECT 
        'users' as table_name,
        'NULL_CHECK' as check_type,
        ROUND(100.0 * COUNT(*) FILTER (WHERE email IS NULL) / COUNT(*), 2) as metric_value,
        CASE WHEN COUNT(*) FILTER (WHERE email IS NULL) > COUNT(*) * 0.01 THEN 'FAIL' ELSE 'PASS' END as status
    FROM users
    
    UNION ALL
    
    SELECT 
        'orders',
        'NEGATIVE_AMOUNTS',
        COUNT(*)::numeric,
        CASE WHEN COUNT(*) > 0 THEN 'FAIL' ELSE 'PASS' END
    FROM orders
    WHERE amount < 0
    
    UNION ALL
    
    SELECT 
        'orders',
        'ORPHAN_RECORDS',
        COUNT(*)::numeric,
        CASE WHEN COUNT(*) > 0 THEN 'FAIL' ELSE 'PASS' END
    FROM orders o
    LEFT JOIN users u ON o.user_id = u.id
    WHERE u.id IS NULL
    
    UNION ALL
    
    SELECT 
        'order_items',
        'NULL_CHECK',
        ROUND(100.0 * COUNT(*) FILTER (WHERE order_id IS NULL) / COUNT(*), 2),
        CASE WHEN COUNT(*) FILTER (WHERE order_id IS NULL) > 0 THEN 'FAIL' ELSE 'PASS' END
    FROM order_items
)
SELECT * FROM data_quality_checks ORDER BY status DESC, table_name;

Мой чеклист проверок

Завтрак (перед использованием данных):

  • NULL-значения < 1%
  • Нет дубликатов в ID
  • Нет значений вне диапазона
  • Консистентность между таблицами

После получения новых данных:

  • Количество записей совпадает с ожиданиями
  • Нет резких скачков (день на день > 50%)
  • Формат данных корректный
  • Временные метки в правильном порядке

При больших изменениях метрик:

  • Запросить source данных у инженеров
  • Проверить логи ETL на ошибки
  • Сравнить с независимым источником (Google Analytics, платежный шлюз)
  • Провести root cause analysis

Главный принцип: Не работай с данными, в которых ты не уверен. Лучше потратить час на проверку, чем представить неправильный отчёт в совет директоров.

Какие проверки выполняешь для данных? | PrepBro