Привет! Меня зовут Черняховский Денис, и я Data Engineer. Я достаточно продолжительное время работаю с данными и увлекаюсь математической статистикой. Совсем недавно решил поискать в интернете, как другие опытные дата-инженеры исследуют качество данных при помощи статистики, и обнаружил, что никак… пум-пум-пум. А далее обнаружил, что проблема уходит корнями гораздо глубже, чем может показаться.
В этой статье я постараюсь рассказать:
почему дата-инженерам необходимо использовать статистику и почему её не используют
проведём тесты на реальных примерах данных
разберём проблему межпрофессионального разрыва компетенций между дата-инженерами и аналитиками
Почему инженеру данных стоит использовать статистику?
Разберём, какой базовый набор проверок/валидаций использует типичный дата-инженер и инженер качества данных.
Типичный чек-лист на проде:
NOT NULL
UNIQUE
REFERENTIAL INTEGRITY
row_count_today >= row_count_yesterday
max(updated_at) >= now() – 1h
revenue > 0
И какие-то прочие атомарные проверки
Это бинарные правила: либо сломалось, либо нет. Те же, кто работает с качеством данных, ежедневно сталкиваются с проблемой, когда бинарные проверки не показывают проблем «Тихие ошибки» (Silent Failures), но аналитики и заказчик прибегают с горящими глазами и кричат, что всё сломано.
А статистика — это вероятностное мышление. Статистика всегда покажет проблему и покажет её первой, если данная проблема имеет место быть.
Почему инженеры не используют статистику в валидации данных?
Статистика «не орёт», когда что-то пошло не так
Пример:
COUNT(*) = 0 — АЛЕРТ
mean + 3σ уехало — «Ну… вроде странно, но не факт»
В проде любят чёткие сигналы, а не «подозрения». Хотя, если подумать, мы можем превратить статистику в бинарный алерт через Thresholds (пороги).
Пример: Если
abs(mean - median) > 20%, то мы не просто пишем в лог, а останавливаем пайплайн или кидаем Critical Alert.
Нет готовых «коробочных» паттернов
Для схем и пайплайнов есть:
Airflow
dbt tests
Great Expectations
Soda
А вот для стат методов:
drift distribution
stability index
autocorrelation
quantile tracking
— почти нет стандартов.
Поэтому большинство просто говорят:
«Не трогаем, потому что непонятно, как поддерживать».
Разберём простейшее, но мощное применение статистики в отслеживании выбросов (ошибочные данные, которые сильно отличаются от допустимых), такое как среднее и медианное значение, их сравнение и интерпретацию.
Да, вы можете жёстко задать валидацию по допустимым диапазонам, например a < x ≤ b. Но очень часто вы не располагаете информацией о допустимом диапазоне в силу большой дисперсии данных и сезонности отрасли.
Перейдём к делу!
1. Что такое среднее в статистике?

Среднее (арифметическое) в статистике — это мера центральной тенденции, рассчитываемая как сумма всех числовых значений в выборке, делённая на их общее количество.
Например, вам необходимо узнать среднюю зарплату по отделу, тогда:
(10 + 8 + 7 + 5) / 4 = 7,5.
Получается, средняя зарплата по отделу — 7,5 чего-то там. И это выглядит корректно, если предоставить такую статистику заказчику.
Но что, если в данных будет выброс?
(1000 + 8 + 7 + 5) / 4 = 255.
Получается, что средняя зарплата по отделу = 255, и не важно, что вы получаете 5–7 арбузиков за рабочий день, а ваш начальник — целую фуру арбузиков. Что-то здесь не так, даже интуитивно, правда?
2. Что такое медиана?

Медиана в статистике — это серединное значение упорядоченного набора данных, которое делит его ровно пополам: 50 % значений находятся ниже медианы и 50 % — выше.
Для нечётного числа данных медиана — это число в середине ряда; для чётного — среднее арифметическое двух центральных чисел. Это устойчивая мера центральной тенденции, менее чувствительная к «выбросам» (экстремальным значениям), чем среднее арифметическое.
Как считать медиану:
Упорядочить элементы в списке по возрастанию.
Посчитать количество элементов в списке.
а) Если число элементов нечётное — найти число, стоящее посередине.
б) Если число элементов чётное — найти два числа, которые находятся посередине, сложить их и результат разделить пополам.
Попробуем посчитать медиану зарплаты по отделу с жадным начальником:
4, 5, 7, 8, 1000
Делаем всё как в алгоритме выше и получаем медиану = 7.
Замечаете? Значение медианы очень близко к нашему среднему в первом рассмотренном случае.
Посчитав разницу между средним и медианой, можно понять, доверять ли тем данным, которые мы загружаем в наш DM-слой, или стоит начинать копать.
Но давайте перейдём к реальным примерам. Немного «потрогаем» статистику в валидации качества данных.
У нас есть таблица заказов orders и таблица услуг с ценами order_items.
create table dq_demo.order_items ( dt Date, order_id UInt64, item_id UInt64, qty UInt16, price Float64, created_at DateTime ) engine = MergeTree PARTITION BY dt ORDER BY (dt, order_id, item_id) SETTINGS index_granularity = 8192; create table dq_demo.orders ( dt Date, order_id UInt64, user_id UInt64, source LowCardinality(String), city LowCardinality(String), revenue Float64, created_at DateTime ) engine = MergeTree PARTITION BY dt ORDER BY (dt, order_id) SETTINGS index_granularity = 8192;
Наполним эти таблицы данными
INSERT INTO dq_demo.orders SELECT toDate(now()) - (15 - d) AS dt, (d * 1000000) + number AS order_id, 1 + (number % 20000) AS user_id, multiIf(number % 3 = 0, 'web', number % 3 = 1, 'ios', 'android') AS source, multiIf(number % 4 = 0, 'Moscow', number % 4 = 1, 'SPB', number % 4 = 2, 'Kazan', 'Other') AS city, round(200 + (randNormal(0, 1) * 40) + (number % 10) * 3, 2) AS revenue, now() - toIntervalDay(15 - d) - toIntervalSecond(number % 86400) AS created_at FROM numbers(50000) -- 50k заказов в день CROSS JOIN (SELECT arrayJoin(range(1, 15)) AS d); INSERT INTO dq_demo.order_items SELECT o.dt, o.order_id, (o.order_id * 10) + item_idx AS item_id, 1 + (rand() % 3) AS qty, round(exp(4.2 + randNormal(0, 1) * 0.35), 2) AS price, o.created_at + toIntervalSecond(item_idx) AS created_at FROM dq_demo.orders o CROSS JOIN (SELECT arrayJoin(range(1, 1 + (rand() % 4))) AS item_idx) WHERE o.dt < toDate(now());
И посчитаем при помощи скл запроса среднее, медиану и разницу между ними
Как будто всё отлично, придраться не к чему — разница между медианой и средним минимальная.
Но давайте попробуем сделать выброс: вставим число побольше в наше значение revenue = 21400, когда среднее по данному заказу около 214.

Выполняем запрос, который описали выше, и видим…

Видим, что разница между средним и медианой в нашем случае огромна. Я бы не стал отдавать такие данные заказчику как минимум, а перепроверил бы слой raw.
Этот случай с разницей между средним арифметическим и медианой примитивен. На данный момент существует огромное количество статистических методов, которые можно применить к вашим данным и более точечно искать проблемы. Но данный пример показывает, как полезна статистика даже в её самом примитивном варианте, а также показывает, что статистические методы наиболее практично применять вместе, а не по отдельности.
Почему этим на практике почти никто не занимается?
Потому что это зона между ролями:
слишком «математично» для DE
слишком «инфраструктурно» для аналитика
И именно здесь появляется Data Quality Engineer / Analytics Engineer нового поколения.
Изначально я хотел написать одну статью, но, начав её, понял, что одной статьёй здесь не обойтись. Поэтому эта статья — лишь вводная в мир анализа качества данных при помощи статистики. Будут ещё статьи про:
квантили и хвосты распределений
drift и PSI
корреляции и бизнес-инварианты
где я постараюсь наиболее полезно и практично показать, что дата-инженерия и статистика — неотделимые сферы деятельности и что дата-инженерам пора брать её на вооружение, чтобы не остаться на уровне валидаций данных 2010 года.
Если мы хотим делать устойчивые DWH и витрины, статистика — не опция, а инструмент первой необходимости.
А какие методы статистики вы используете в своих пайплайнах? Поделитесь вашими решениями!
