Всё началось с использования ML в BigQuery — оказалось это совсем не больно, и очень эффективно.  Мы в GFN.RU используем модель K-Means для поиска аномалий в работе сервиса. Ведь невозможно кожаному мешку смотреть десятки графиков по тысячам игр ежедневно. Пусть электрический болван подсказывает куда нужно глянуть.  В поиске аномалий с таким подходом есть проблема: если метрика является метрикой второго и более высокого порядка, то K-Means может сообщить об аномалии, хотя, аномалии как таковой нет, а есть шум. Пример метрики второго порядка — среднее время пользователя на сайте. Очевидно, она зависит от количества пользователей. Таким образом, перед тем, как скармливать метрики в K-Means, их нужно отфильтровать от шумов.  Если по простому - определить на базе какой минимальной выборки нужно считать AVG чтоб ему верить. Это можно сделать различными способами, но я, сторонник использования минимума инструментов — раз уж BQ, то BQ, и нужно, чтоб инструмент был по максимуму универсален - поменяли выборку метрики, а всё остальное осталось таким же. BQ полностью подходит для этой задачи. Итак, 0-гипотеза — метрика изменяется в допустимых пределах (за базу возьмем 5%).  Необходимо найти количество замеров, при которых изменение метрики будет находиться в нужных пределах. При этом, сделать это в виде нескольких тестов, чтоб более точно проверить гипотезу. Начнем с выборки данных : Здесь всё просто и понятно - взяли данные, перемешали их. Теперь нам необходимо эти данные разбить на тесты (группы). Я для этого использую метод остатка от деления. Берем какое-либо значение, делим на необходимое количество групп. Остаток от деления будет номером группы. В данном случае в качестве исходного значения брем номер строки в отсортированном списке и делим на 1000 групп. На выходе получили :  Значение метрики. Номер строки (он нам дальше понадобится). Номер группы (от 1 до 1000) или номер теста. Теперь нам нужно найти в каждой группе значение среднего для разного количества элементов. Первое, что приходит в голову - конечно - сделать несколько циклов и посчитать. Но, в SQL циклы не приветствуются. Выражение должно векторизоваться, и тут нам на помощь приходит скользящее среднее.  Мы внутри каждой группы считаем скользящее среднее по значениям от начала группы и до её конца. В итоге, скользящее среднее для 10-й строки - это будет среднее внутри группы из 10 элементов, для 20-й строки - для 20 элементов и так далее. Следующим этапом мы считаем расхождение скользящего среднего между текущей и предыдущей строчкой. Процент расхождения и есть процент шума. Итак, в avg_div у нас есть процент расхождения среднего между двумя соседними показателями. Осталось его округлить, обрезать и найти в какой обычно строке из 1000 тестов у нас появляется допустимое расхождение. Собственно и всё : округлили процент расхождений, нашли среднюю строчку в которой достигается нужное расхождение.  Все метрики второго порядка, которые основаны на данных с меньшим количеством - просто отбрасываем - т.к. они с высокой долей вероятности будут обусловлены шумом.  Само собой метрики первого порядка нужно анализировать отдельно - совсем не значит, что если у нас по какой-то группе замер из 2 единиц, то это не аномалия - точнее среднее - конечно же шум и не аномалия, но то, что сегодня 2 единицы данных - вполне может быть аномалией, если обычно ежедневно по 200 единиц. На закуску: готовая к использованию сторка: https://github.com/GFNRussia/bqmlalerts/blob/main/stat_sig.sql