Экономим рекламный бюджет с помощью кластерного анализа площадок в рекламных сетях Яндекса

Реклама
Анализ данных
Автор

Александр Коршаков

Дата публикации

29 марта 2024 г.

Кластерный анализ

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

Кластерный анализ проводится в несколько этапов:

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

Рассмотрим каждый этап подробнее.


Подготовим данные

Подготовим данные для кластеризации. Оставим площадки:

  • по которым были затраты
  • уберем минус площадки (площадки, которые были отминусованы в ходе оптимизации проекта)

Кластеризацию проведем по параметрам:

  • Cost — затраты на площадку
  • Goals — кол-во достигнутых целей на площадку
  • Imp — кол-во показов
  • Clicls — кол-во переходов (кликов)
  • Bounces — кол-во отказов
  • Session — кол-во сессий
  • CPA — стоимость обращения
  • CPC — стоимость клика
  • SR — коэф. сеанса, отношение сессии к кликам
  • BR - коэф. отказов

Данные параметры наиболее важны для анализа качества площадки, на которой происходит показ объявления.

В итоге всех преобразований получаем таблицу для кластерного анализа.

Код
df <- data_proc |>  
  filter(CampaignId %in% "99895250") |> 
  reframe(
    across(where(is.numeric), sum), 
    .by = Placement
    ) |> 
  mutate(
    Cost = round(Cost),
    Imp = round(Impressions),
    BR = if_else(Bounces == 0 | Sessions == 0, 0, round(Bounces / Sessions, 1)),
    SR = if_else(Clicks == 0 | Sessions == 0, 0, round(Sessions / Clicks, 1)),
    CPA = if_else(Cost == 0 | Goals == 0, 0, round(Cost / Goals)),
    CPC = if_else(Cost == 0 | Clicks == 0, 0, round(Cost / Clicks))
    ) |>  
  select(
    Placement,
    Cost,
    Goals,
    Imp,
    Clicks,
    Bounces,
    Sessions,
    CPA,
    CPC,
    SR,
    BR
    ) |> 
  filter(
    Cost > 0,
    !grepl(stop_place_v, Placement)
    )

Таблица: Данные для анализа

Cost Goals Imp Clicks Bounces Sessions CPA CPC SR BR
1plus1tv.ru 36 0 63 9 2 2 0 4 0.2 1.0
93.ru 210 1 441 14 6 16 210 15 1.1 0.4
allserial.ag 12 0 358 3 2 2 0 4 0.7 1.0
amedia.online 11 0 128 2 1 2 0 6 1.0 0.5
ar.com.develup 15 0 6 1 0 1 0 15 1.0 0.0
armfilms.space 31 0 27 3 0 0 0 10 0.0 0.0


Проведем стандартизацию данных для повышения качества анализа.

Таблица: Стандартизированная таблица

Cost Goals Imp Clicks Bounces Sessions CPA CPC SR BR
1plus1tv.ru -0.0300722 -0.0940711 -0.0520325 0.0654478 0.0382431 -0.0308824 -0.0625495 -0.9305891 -1.0463487 1.4843830
93.ru 0.2868610 3.7794466 -0.0084711 0.1427832 0.2762003 0.3087894 2.1230391 0.0862317 0.8446429 0.1150861
allserial.ag -0.0737872 -0.0940711 -0.0180362 -0.0273546 0.0382431 -0.0308824 -0.0625495 -0.9305891 0.0042022 1.4843830
amedia.online -0.0756086 -0.0940711 -0.0445418 -0.0428217 -0.0212462 -0.0308824 -0.0625495 -0.7457126 0.6345328 0.3433023
ar.com.develup -0.0683228 -0.0940711 -0.0586013 -0.0582888 -0.0807355 -0.0551447 -0.0625495 0.0862317 0.6345328 -0.7977784
armfilms.space -0.0391795 -0.0940711 -0.0561812 -0.0273546 -0.0807355 -0.0794070 -0.0625495 -0.3759596 -1.4665691 -0.7977784


Поиск оптимального числа кластеров

  • Автоматический подбор номеров кластера по 30 индексам.
  • Метод кластеризации k-means.
  • Используем «манхетенское» расстояние для расчётов.
Among all indices: 
===================
* 2 proposed  0 as the best number of clusters
* 1 proposed  1 as the best number of clusters
* 4 proposed  2 as the best number of clusters
* 11 proposed  3 as the best number of clusters
* 1 proposed  4 as the best number of clusters
* 4 proposed  5 as the best number of clusters
* 2 proposed  7 as the best number of clusters
* 1 proposed  9 as the best number of clusters

Conclusion
=========================
* According to the majority rule, the best number of clusters is  3 .

Вывод

Методом голосования по 30 индексам алгоритм предлагает использовать 3 или 5 кластеров.


Кластеризация методом k-means

График: Визуальный анализ кластеризации

Код
# визуализация
fviz_cluster(
  fit,
  stand = FALSE,
  data = df_st, 
  geom = c('point', 'text'),
  labelsize = 5
  ) +
  theme_minimal() +
  labs(title = NULL) +
  theme(
    legend.position = "top",
    legend.title = element_text(size = 10, color = "gray40"),
    legend.text = element_text(size = 10, color = "gray40"),
    axis.title = element_text(size = 8, color = "gray40"),
    axis.text = element_text(size = 8, color = "gray50")
    )

Вывод

Видим сильно отличающийся кластер №1, в который вошел один сайт. В левом углу графика сосредоточены остальные кластеры.

Данный график лучше рассматривать вместе с табличными данными результата кластеризации.


Анализ результатов

Смотрим характеристики кластеров.

Таблица: Кластеры

Код
# присвоим метки набору данных и выведем значения по кластерам
df$Cluster <- as.character(fit$cluster)

# cоздаем новый столбец из названия строк
df$Placement <- rownames(df)  

# сбрасываем названия строк
rownames(df) <- NULL

# создадим таблицу данных
view_clust <- df |> 
  reframe(
    across(c(1:6), sum),
    n = n(),
    .by = Cluster
    ) |> 
  mutate(
    BR = if_else(Bounces == 0 | Sessions == 0, 0, round(Bounces / Sessions, 1)),
    SR = if_else(Clicks == 0 | Sessions == 0, 0, round(Sessions / Clicks, 1)),
    CTR = if_else(Clicks == 0 | Imp == 0, 0, round(Clicks / Imp * 100, 2)),
    CPA = if_else(Cost == 0 | Goals == 0, 0, round(Cost / Goals)),
    CPC = if_else(Cost == 0 | Clicks == 0, 0, round(Cost / Clicks)),
    CR = if_else(Clicks == 0 | Goals == 0, 0, round(Goals / Clicks * 100, 2))
    ) |> 
  select(
    Cluster,
    n,
    Cost,
    Goals,
    Imp,
    Clicks,
    Sessions,
    CPC,
    CPA,
    CTR,
    CR,
    BR,
    SR
  )

# создаем таблицу с помощью библиотеке DT
datatable(
  view_clust,
  rownames = FALSE,
  extensions = c(
    'Buttons',
    'ColReorder',
    'FixedHeader'),
  escape  = FALSE,
  class   = 'compact nowrap',
  options = list(
    pageLength = 10,
    dom        = 'Btpli',
    language   = list(url = 'https:://cdn.datatables.net/plug-ins/1.13.6/i18n/ru.json'),
    buttons    = c('csv', 'excel'),
    colReorder = TRUE,
    fixedHeader = TRUE)
  )


Вывод

В кластер №1 вошла одна площадка, которая больше всех расходует бюджет и имеет приемлемые характеристики.

Кластер №2 содержит площадки с повышенным коэф. отказов. Необходимо рассмотреть данный кластер подробнее.

Кластеры №3 и №5 содержат площадки с хорошими характеристиками.

Площадки в кластере №4 можно смело минусовать, так как на них теряются клики, т.е. скорее всего с этих площадок идет бот-трафик.

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


Таблица для ручного анализа кластеров


Таблица содержит столбцы:

  • Cluster — номер кластера
  • Placement — название площадки
  • n — кол-во площадок в кластере
  • Cost — потрачено денег на площадку
  • Goals — кол-во достигнутых целей на площадке
  • Imp — кол-во показов на площадке
  • Clicks — кол-во переходов с площадки
  • Session — кол-во сеансов с площадки
  • CPC — стоимость клика
  • CPA — стоимость конверсии
  • CTR — коэффициента кликабельности объявлений
  • CR — коэффициент конверсии из клика в достигнутую цель
  • BR — коэффициенто отказа
  • SR — коэффициент сеанса (Session / Clicks). Показывает потерю клику на площадках. Если значение ниже 0.6, что означает потерю 40% кликов, то площадку можно минусовать.


Поиск минус площадок.

Минус-площадки — площадки которые не подходят для проекта, например, приложения.

С помощью данного анализа можно быстро найти минус-площадки и запретить по ним показы, чтобы не слить бюджет.

Код
# подготовка данных для дальнейшего анализа
data <- data_proc |>
  filter(CampaignId %in% "99895250") |> 
  reframe(
    across(where(is.numeric), sum), 
          .by = c(CampaignName, Placement)
          )  |> 
  mutate(
    Cost = round(Cost),
    Imp = round(Impressions),
    SR = if_else(Clicks == 0 | Sessions == 0, 0, round(Sessions / Clicks, 1)),
    BR = if_else(Bounces == 0 | Sessions == 0, 0, round(Bounces / Sessions * 100)),
    CTR = if_else(Clicks == 0 | Impressions == 0, 0, round(Clicks / Impressions * 100, 2)),
    CPA = if_else(Cost == 0 | Goals == 0, 0, round(Cost / Goals)),
    CPC = if_else(Cost == 0 | Clicks == 0, 0, round(Cost / Clicks)),
    CR = if_else(Clicks == 0 | Goals == 0, 0, round(Goals / Clicks * 100, 2))
    ) |>  
  select(
    CampaignName,
    Placement,
    Cost,
    Goals,
    CPA,
    CR,
    Imp,
    Clicks,
    Sessions,
    SR,
    CPC,
    CTR,
    BR
    ) |> 
  arrange(
    CampaignName, 
    Placement
    )

# подготовка данных
minus_pl <- data |>
  filter(
    Cost > 0,
    grepl(stop_place_v, Placement)
  ) |> 
  arrange(Placement)

# создаем таблицу с помощью библиотеке DT
datatable(
  minus_pl,
  rownames = FALSE,
  filter = 'top',
  extensions = c(
    'Buttons',
    'ColReorder',
    'FixedHeader'),
  escape  = FALSE,
  class   = 'display compact nowrap',
  caption = htmltools::tags$caption(
    style = 'caption-side: top; text-align: left; color: gray;', 
    htmltools::h4('Таблица: Минус площадки за весь период')),
  options = list(
    pageLength = 10,
    dom        = 'Btpli',
    language   = list(url = 'https:://cdn.datatables.net/plug-ins/1.13.6/i18n/ru.json'),
    buttons    = c('csv', 'excel'),
    colReorder = TRUE,
    fixedHeader = TRUE)
  ) |> 
  formatStyle(
    columns = 'Cost',
    background = styleColorBar(minus_pl$Cost, '#EEC9E5'),
    backgroundSize = '100% 80%',
    backgroundRepeat = 'no-repeat',
    backgroundPosition = 'center'
  ) |>
  formatStyle(
      columns = 'Clicks',
      background = styleColorBar(minus_pl$Clicks, '#EEC9E5'),
      backgroundSize = '100% 80%',
      backgroundRepeat = 'no-repeat',
      backgroundPosition = 'center'
    ) |>
  formatStyle(
      columns = 'Imp',
      background = styleColorBar(minus_pl$Imp, '#EEC9E5'),
      backgroundSize = '100% 80%',
      backgroundRepeat = 'no-repeat',
      backgroundPosition = 'center'
    )


Потерянные клики

Найдем площадки на которых теряется более 40% сеансов. Такое поведение похоже на бот трафик.

Фильтрация:

  • за весь период
  • из данных исключены минус площадки
  • кол-во переходов с площадки от 5
Код
clicks_pl <- data  |>
  filter(
    !grepl(stop_place_v, Placement),
    SR < 0.6,
    Clicks >= 5
    ) |> 
  arrange(Placement)

# создаем таблицу с помощью библиотеке DT
datatable(
  clicks_pl,
  rownames = FALSE,
  filter = 'top',
  extensions = c(
    'Buttons',
    'ColReorder',
    'FixedHeader'
  ),
  escape = FALSE,
  class = 'display compact nowrap',
  caption = htmltools::tags$caption(
    style = 'caption-side: top;
            text-align: left; 
            color: gray;',
    htmltools::h4('Таблица: Потерянные клики')
  ),
  options = list(
    pageLength = 10,
    dom = 'Btpli',
    language = list(url = 'https:://cdn.datatables.net/plug-ins/1.13.6/i18n/ru.json'),
    buttons = c('csv', 'excel'),
    colReorder = TRUE,
    fixedHeader = TRUE
    )
  ) |>
  formatStyle(
    columns = 'Cost',
    background = styleColorBar(clicks_pl$Cost, '#EEC9E5'),
    backgroundSize = '100% 80%',
    backgroundRepeat = 'no-repeat',
    backgroundPosition = 'center'
  ) |>
  formatStyle(
    columns = 'Clicks',
    background = styleColorBar(clicks_pl$Clicks, '#EEC9E5'),
    backgroundSize = '100% 80%',
    backgroundRepeat = 'no-repeat',
    backgroundPosition = 'center'
  ) |>
  formatStyle(
    columns = 'Sessions',
    background = styleColorBar(clicks_pl$Sessions, '#EEC9E5'),
    backgroundSize = '100% 80%',
    backgroundRepeat = 'no-repeat',
    backgroundPosition = 'center'
  )