Аномалии в бизнес-показателях

Чем больше и сложнее бизнес, тем больше показателей и параметров. Однажды вы поймете, что их невозможно отследить своими глазами. Уменьшение количества показателей или параметров помешает нам отслеживать все аспекты бизнеса или анализировать агрегированные данные, которые могут сгладить или скрыть аномалии. Обнаружение любых аномалий после фактического возникновения может быть пропущено или иметь значительный промежуток времени. Поэтому мы должны немедленно реагировать, чтобы скорее узнать о событии, определить его причины и понять, что с этим делать. Для этого мы можем использовать систему обнаружения аномалий, идентифицировать аномальные значения, собирать соответствующие события централизованно и контролировать гораздо большее количество показателей и параметров, чем позволяют человеческие возможности.

В этой статье под бизнес-метриками мы имеем в виду количественные показатели, которые регулярно измеряем и используем для отслеживания и оценки эффективности бизнес-процесса. Существует огромное количество бизнес-показателей: от обычных до уникальных. Последние специально разработаны и используются в одной компании или даже одной из ее команд. Хочу отметить, что бизнес-метрики имеют размеры- это подразумевает возможность сверления структуры метрики. Например, количество сеансов на веб-сайте может иметь размеры: типы браузеров, каналы, страны, рекламные кампании и т.д. Наличие большого количества параметров на метрику, с одной стороны обеспечивает всесторонний подробный анализ, а с другой – делает его поведение более сложным.
Аномалии являются ненормальными значениями бизнес-показателей. Мы не можем требовать, чтобы аномалии были чем-то плохим или полезным для бизнеса. Мы должны рассматривать их как сигнал о том, что были некоторые события, которые существенно повлияли на бизнес-процесс. Наша цель – определить причины и возможные последствия таких событий и немедленно отреагировать. С точки зрения бизнеса лучше найти такие события, чем игнорировать их.

Система обнаружения аномалий также сигнализирует об изменениях, ожидаемых пользователем. В качестве примера можно привести нерегулярную рекламную кампанию e-mail-рассылки и ожидание роста трафика на целевой странице с того же канала. Получение сигнала о росте трафика полезно с точки зрения подтверждения того, что работа проделана.

На сегодняшний день ряд аналитических инструментов имеют встроенные системы для обнаружения аномалий. Например, в Google Analytics есть такая система.

Однако, если вы:

1. Собираете необработанные данные, которые не передаются аналитическому инструменту со встроенной системой обнаружения аномалий,
2. Хотите выбирать и комбинировать алгоритмы для определения аномалий,
3. Хотите настроить параметры системы (например, установить более высокий или более низкий порог для значений, которые будут определены как аномалии).
Вам понадобится собственная система обнаружения аномалий. Мы изучим четыре подхода для выявления аномалий в бизнес-метриках с использованием языка R. Я также предполагаю, что мы имеем дело с немечеными данными, т.е. мы не знаем, были ли исторические ценности аномалиями.

Для практического примера я извлек веб-данные, которые выглядят как таблица:

Таблица с данными для исследования

Еще я добавил агрегированное значение для каждой даты и метрику «количество целей за сеанс». Мы можем визуализировать метрики на временной оси отдельно следующим кодом:

Код на R

library(tidyverse)
library(reshape2)
library(lubridate)
library(purrrlyr)
library(ggrepel)
# devtools::install_github("twitter/AnomalyDetection")
library(AnomalyDetection)
# install.packages("IsolationForest", repos="http://R-Forge.R-project.org")
library(IsolationForest)
 
 
# loading raw data
df <- read_csv('data.csv') 
 
# summarizing metrics by dates
df_tot <- df %>%
        group_by(date) %>%
        summarise(sessions = sum(sessions),
                  goals = sum(goals)) %>%
        ungroup() %>%
        mutate(channel = 'total')
 
# bindind all together
df_all <- rbind(df, df_tot) %>%
        mutate(goals_per_session = ifelse(goals > 0, round(goals / sessions, 2), 0))
 
# visualizing metrics
ggplot(df_all, aes(x = date)) +
        theme_minimal() + 
        facet_wrap(~ channel) +
        geom_line(aes(y = sessions), color = 'blue') +
        geom_line(aes(y = goals), color = 'red')
 
ggplot(df_all, aes(x = date, y = goals_per_session)) +
        theme_minimal() + 
        facet_wrap(~ channel) +
        geom_line(color = 'darkgreen')

[свернуть]

Визуализация таблицы с данными

Как вы видите, у нас есть данные с августа по ноябрь. В конце сентября был значительный всплеск в трех из пяти каналов (прямые заходы, органика, и трафик с соц сетей) и в целом. В трафике с соц сетей и прямом есть менее значительные всплески.

Ниже приведены различные паттерны в метрике «goals_per_session» (количество целей за сеанс):

Количество достигнутых целей в день в разрезе каналов

Давайте посмотрим, что мы можем сделать с этим примером:

№1 Модельный подход

Идея заключается в следующем: мы создаем модель, основанную на исторических данных и наблюдениях. Аномалии буду там, где модель наиболее ошибочна.

На практике мы измеряем бизнес-показатели на регулярной основе, как правило, ежедневно. Значит показатели имеют характер временных рядов. Мы можем использовать модель временного ряда, и если предсказанное значение существенно отличается от фактического значения, то мы обнаруживаем аномалию. Этот подход хорош для показателей с явными сезонными колебаниями. В нашем примере это количество сеансов и целей для основных каналов. Для показателя «goal_per_session» этот подход может быть не столь эффективным.

Есть много пакетов для моделирования временных рядов в R, но, учитывая нашу цель поиска аномалий, я рекомендую использовать один из готовых решений, например пакет AnomalyDetection.

Начнем с простого примера анализа прямого трафика:

Код на R

##### time series modeling #####
 
# simple example
df_ts <- df_all %>%
        # the package works with POSIX date format
        mutate(date = as.POSIXct(date, origin = "1970-01-01", tz = "UTC"))
 
df_ts_ses <- df_ts %>%
        dcast(., date ~ channel, value.var = 'sessions')
df_ts_ses[is.na(df_ts_ses)] <- 0
 
# example with Direct channel
AnomalyDetectionTs(df_ts_ses[, c(1, 3)], max_anoms = 0.05, direction = 'both', e_value = TRUE, plot = TRUE) # 5% of anomalies
AnomalyDetectionTs(df_ts_ses[, c(1, 3)], max_anoms = 0.1, direction = 'both', e_value = TRUE, plot = TRUE) # 10% of anomalies

[свернуть]

Аномалии в количестве достигнутых целей за день при модельном подходеАномалии в количестве достигнутых целей за день при модельном подходе

Мы можем изменить количество аномалий, изменив порог процента аномалий.

Чтобы масштабировать этот подход ко всем метрикам, мы можем сопоставить функцию со всеми параметрами всех показателей со следующим кодом:

Код на R

# scaled example
df_ts <- df_all %>%
         
        # removing some metrics
        select(-goals_per_session) %>%
         
        # the package works with POSIX date format
        mutate(date = as.POSIXct(date, origin = "1970-01-01", tz = "UTC")) %>%
         
        # melting data frame
        melt(., id.vars = c('date', 'channel'), variable.name = 'metric') %>%
        mutate(metric_dimension = paste(metric, channel, sep = '_')) %>%
         
        # casting
        dcast(., date ~ metric_dimension, value.var = 'value')
df_ts[is.na(df_ts)] <- 0
 
# anomaly detection algorithm as a function
df_ts_anom <- map2(df_ts %>% select(1), df_ts %>% select(c(2:ncol(.))),
              function(df1, df2) {
                      AnomalyDetectionTs(data.frame(df1, df2), max_anoms = 0.1, direction = 'both', e_value = TRUE)
                      }
)
 
# extracting results
df_ts_anom <- lapply(df_ts_anom, function(x){
        data.frame(x[["anoms"]])
})
 
# adding metric names and binding all metrics into data frame
names(df_ts_anom) <- colnames(df_ts[, -1])
df_ts_anom <- do.call('rbind', df_ts_anom)

[свернуть]

№2 Статистический подход

С статистической точки зрения, аномалии являются крайними значениями или выбросами. Для определения таких значений существует ряд способов и соответствующих функций в R. Использование определенных критериев должно быть основано на свойствах выборки.

Я применю статистический подход для анализа  «goal_per_session» в нашем примере. Этот индикатор близок к нормальному распределению по более популярным каналам, поэтому можно использовать межквартильное расстояние для определения выбросов.

Код на R

ggplot(df_all, aes(x = goals_per_session)) +
        theme_minimal() + 
        facet_wrap(~ channel) +
        geom_histogram(binwidth = 0.01)

[свернуть]

Гистограмма распределения параметра "Количество целей за день"

На практике, является ли значение выбросом менее интересно, чем слишком высокое или низкое значение по сравнению с другими днями. В таком случае можно использовать нижний и верхний 5% процентиль (0-5% и 95-100% диапазонов). Результаты визуализируются с помощью следующего кода:

Код на R

df_stat_anom <- df_all %>%
         
        # select the metrics
        select(-sessions, -goals) %>%
         
        group_by(channel) %>%
        mutate(is_low_percentile = ifelse(goals_per_session <= quantile(goals_per_session, probs = 0.05), TRUE, FALSE), is_high_percentile = ifelse(goals_per_session >= quantile(goals_per_session, probs = 0.95), TRUE, FALSE),
                        
               is_outlier = case_when(
                       goals_per_session < quantile(goals_per_session, probs = 0.25) - 1.5 * IQR(goals_per_session) | goals_per_session > quantile(goals_per_session, probs = 0.75) + 1.5 * IQR(goals_per_session) ~
                               TRUE,
                       TRUE ~ FALSE)
        ) %>%
        ungroup()
 
ggplot(df_stat_anom, aes(x = date, y = goals_per_session)) +
        theme_minimal() + 
        facet_wrap(~ channel) +
        geom_line(color = 'darkblue') +
        geom_point(data = df_stat_anom[df_stat_anom$is_outlier == TRUE, ], color = 'red', size = 5, alpha = 0.5) +
        geom_point(data = df_stat_anom[df_stat_anom$is_low_percentile == TRUE, ], color = 'blue', size = 2) +
        geom_point(data = df_stat_anom[df_stat_anom$is_high_percentile == TRUE, ], color = 'darkred', size = 2)

[свернуть]

Аномалии и высокие-низкие процентили

Я отметил 5% высокие и низкие процентили небольшими красными и синими точками, а выбросы – более светлыми красными точками. Можно сдвинуть порог процентиля и межквартильного расстояния (изменив коэффициент, равный 1,5).

№3 Метрический подход

Этот подход определяет, насколько далеко одно наблюдение от других в пространстве. Очевидно, что типичное наблюдение расположено близко к другому, а аномальное – дальше. В метрическом подходе расстояние Махаланобиса хорошо подходит для анализа бизнес-показателей. Особенность подхода расстояний Махаланобиса от Евклидова в том, что он учитывает корреляцию между переменными. С точки зрения обнаружения аномалий это имеет следующий эффект: если сеансы со всех каналов синхронно выросли в два раза, то этот случай может иметь такую ​​же оценку аномалии, как если количество сеансов увеличилось в 1,5 раза только с одного канала.

В этом случае наблюдение представляет собой день, который описывается различными метриками и их размерами. Соответственно, мы можем смотреть на эту статистику под разными углами. Например, был ли день аномальным с точки зрения структуры определенной метрики, если структура трафика по каналам была типичной. С другой стороны, можно видеть, был ли день ненормальным с точки зрения определенных показателей, например, количества сеансов и целей.

Нам необходимо применить порог того, какое расстояние будет использоваться в качестве критерия аномалии. Для этого мы можем использовать точное значение или объединить его со статистическим подходом. В следующем примере я проанализировал структуру (размеры) сеансов по датам и отмеченным значениям, которые находятся в 95-100% процентилях:

Код на R

##### 3 Metric approach #####
 
# Mahalanobis distance function
maha_func <- function(x) {
        x <- x %>% select(-1)
        x <- x[, which(
                round(colMeans(x), 4) != 0 &
                apply(x, MARGIN = 2, FUN = sd) != 0)
               ]
        round(mahalanobis(x, colMeans(x), cov(x)), 2)
}
 
df_ses_maha <- df_all %>%
        # select the metrics
        select(-goals, -goals_per_session) %>%
        # casting
        dcast(., date ~ channel, value.var = 'sessions') %>%
        # remove total values
        select(-total)
df_ses_maha[is.na(df_ses_maha)] <- 0
 
# adding Mahalanobis distances
df_ses_maha$m_dist <- maha_func(df_ses_maha)
 
df_ses_maha <- df_ses_maha %>%
        mutate(is_anomaly = ifelse(ecdf(m_dist)(m_dist) >= 0.95, TRUE, FALSE))
         
# visualization
df_maha_plot <- df_ses_maha %>% select(-m_dist, -is_anomaly) %>% melt(., id.vars = 'date')
 
df_maha_plot <- full_join(df_maha_plot, df_maha_plot, by = 'date') %>%
        left_join(., df_ses_maha %>% select(date, m_dist, is_anomaly), by = 'date')
 
# color palette
cols <- c("#4ab04a", "#eec73a", "#ffd73e", "#f05336", "#ce472e")
mv <- max(df_maha_plot$m_dist)
 
ggplot(df_maha_plot, aes(x = value.x, y = value.y, color = m_dist)) +
        theme_minimal() +
        facet_grid(variable.x ~ variable.y) +
         
        scale_color_gradientn(colors = cols, limits = c(min(df_maha_plot$m_dist), max(df_maha_plot$m_dist)),
                              breaks = c(0, mv),
                              labels = c("0", mv),
                              guide = guide_colourbar(ticks = T, nbin = 50, barheight = .5, label = T, barwidth = 10)) +
         
        geom_point(aes(color = m_dist), size = 2, alpha = 0.4) +
         
        geom_text_repel(data = subset(df_maha_plot, is_anomaly == TRUE),
                        aes(label = as.character(date)),
                        fontface = 'bold', size = 2.5, alpha = 0.6,
                        nudge_x = 200, direction = 'y', hjust = 1, segment.size = 0.2,
                        max.iter = 10) +
        theme(legend.position = 'bottom',
              legend.direction = 'horizontal',
              panel.grid.major = element_blank())

[свернуть]

Диаграмма пересечений параметров при метрическом подходе

Как вы можете видеть на диаграмме, которая представляет собой набор пересечений всех параметров, даты, которые были обнаружены как аномалии, расположены дальше в пространстве на большинстве пересечений.

№4 Подход машинного обучения

Это тоже хороший способ обнаружения аномалий, но он требует большего внимания при настройке параметров. В этой статье я хочу упомянуть алгоритм изолирующего леса, который является вариацией Random Forest, его идея заключается в следующем: алгоритм создает случайные деревья, пока каждый объект не находится в отдельном листе, и если в данных имеются выбросы, они будут изолированы на ранних стадиях (на низкой глубине дерева). Затем для каждого наблюдения мы вычисляем среднее значение глубины листьев, в которое оно попадает, и, основываясь на этом значении, мы решаем, является ли это аномалией.

Как и в метрическом подходе, мы можем оценивать наблюдения (даты в нашем примере) по-разному и выбирать порог аномалии. Я использовал тот же метод, что и для подхода метрического подхода:

Код на R

##### 4 Isolation Forest #####
df_ses_if <- df_all %>%
        # select the metrics
        select(-goals, -goals_per_session) %>%
        # casting
        dcast(., date ~ channel, value.var = 'sessions') %>%
        # remove total values
        select(-total)
df_ses_if[is.na(df_ses_if)] <- 0
 
# creating trees
if_trees <- IsolationTrees(df_ses_if[, -1])
 
# evaluating anomaly score
if_anom_score <- AnomalyScore(df_ses_if[, -1], if_trees)
 
# adding anomaly score
df_ses_if$anom_score <- round(if_anom_score$outF, 4)
 
df_ses_if <- df_ses_if %>%
        mutate(is_anomaly = ifelse(ecdf(anom_score)(anom_score) >= 0.95, TRUE, FALSE))
 
# visualization
df_if_plot <- df_ses_if %>% select(-anom_score, -is_anomaly) %>% melt(., id.vars = 'date')
 
df_if_plot <- full_join(df_if_plot, df_if_plot, by = 'date') %>%
        left_join(., df_ses_if %>% select(date, anom_score, is_anomaly), by = 'date')
 
# color palette
cols <- c("#4ab04a", "#eec73a", "#ffd73e", "#f05336", "#ce472e")
mv <- max(df_if_plot$anom_score)
 
ggplot(df_if_plot, aes(x = value.x, y = value.y, color = anom_score)) +
        theme_minimal() +
        facet_grid(variable.x ~ variable.y) +
         
        scale_color_gradientn(colors = cols, limits = c(min(df_if_plot$anom_score), max(df_if_plot$anom_score)),
                              breaks = c(0, mv),
                              labels = c("0", mv),
                              guide = guide_colourbar(ticks = T, nbin = 50, barheight = .5, label = T, barwidth = 10)) +
         
        geom_point(aes(color = anom_score), size = 2, alpha = 0.4) +
         
        geom_text_repel(data = subset(df_if_plot, is_anomaly == TRUE),
                        aes(label = as.character(date)),
                        fontface = 'bold', size = 2.5, alpha = 0.6,
                        nudge_x = 200, direction = 'y', hjust = 1, segment.size = 0.2,
                        max.iter = 10) +
        theme(legend.position = 'bottom',
              legend.direction = 'horizontal',
              panel.grid.major = element_blank())

[свернуть]

Диаграмма пересечений параметров при подходе машинного обучения

Результаты немного отличаются от метрического подхода, но почти одинаковы.

Заключение

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

  • Бизнес меняется со временем, поэтому мы делаем все это. Изменения отражаются как в значениях показателей, так и в размерах, с помощью которых отслеживаются эти показатели (например, рекламные кампании, лендинги и т. д. могут быть изменены, добавлены, удалены). Чтобы быть реалистичным, я рекомендую работать со сдвижным историческим окном (или даже несколькими окнами), например, использовать данные за последние 30, 60, 90,… дней.
  • Для количественных показателей совет состоит в том, чтобы не комбинировать все показатели вместе. Поскольку, во-первых, это может уменьшить способность идентифицировать аномалию, если затронут только одно параметр. Во-вторых, будет труднее понять, почему этот день считается аномальным.
  • Некоторые наблюдения можно идентифицировать как аномалии с помощью нескольких подходов. Мы можем применять множество различных алгоритмов для определения того, какие наблюдения являются аномалиями путем голосования. Это можно сделать, например, с помощью этого пакета.

Обнаружение аномалий в бизнес-метриках помогает бизнесу быть «настороже» и своевременно реагировать на неожиданные события. А автоматическая система обнаружения аномалий позволяет значительно расширить диапазон показателей и их размеры, отслеживать многие аспекты бизнеса. Существует огромное количество подходов, методов и алгоритмов обнаружения аномалий, эта статья предназначена для ознакомления с некоторыми из них, я надеюсь, что это поможет вам предпринять первые шаги для обнаружения аномалий для вашей компании.

Автор – Сергей Брыль, английский оригинал статьи тут.

Добавить комментарий

Ваш e-mail не будет опубликован.