На этот раз с кодом в R
Я уже рассказывал вам о том, что бид-менеджеры не эффективны, но и я сам не осознавал насколько! Также в статье рассказано об оптимальной ставке в новом аукционе и будет экскурс в R.
Большинство моих читателей уже знают почему бидменеджеры не работают и об оптимальных ставках в Директе. Поэтому, по ходу пьесы, я расскажу, как пользоваться языком программирования R (это просто и полезно).
Бидменеджеры
Есть два типа программ оптимизирующих ставки в контекстной рекламе:
- Бидменеджеры. Они основываются на стоимости позиций из интерфейса Директа (хотя в самой справке Директа написано, что эти цифры ничего не значат).
- Оптимизаторы конверсий. Они ориентируются на конверсию, средний чек и прочее.
Сейчас после смены алгоритма аукциона в Яндекс.Директ, пиарщики бидменеджеров хватаются за последнюю соломинку: «между блоками не действует VCG, поэтому, бидменеджеры работают». Разрушим этот миф.
Почему мы должны доказывать обратное? Производитель лекарств проводит их клинические испытания и доказывает, что лекарство работает.
Бидменеджеров штук 20, и они уже существуют лет 10. Почему никто не показал ни одного вменяемого кейса? Для сравнения посмотрим на оптимизаторы конверсий. У К50 на сайте 5 кейсов.
Сейчас в комментах меня начнут спрашивать: «Почему К50 не сделает кейс который доказывает что биддеры не работают?»
К50 доказательство этого факта ничего не принесет. К50:Оптимизатор для очень больших клиентов (минимальная абонплата 50.000 рублей), а бид-менеджеры использует мелкие клиенты. Единственный оптимизатор конверсии для мелких клиентов это мой бесплатный Bid-Expert.
R
R это довольно простой язык программирования. Он предназначен для исследования данных (big-data) или для моделирования процессов связанных со случайными величинами.
Лет через пять знания R будет обязательным требованием для Senior-PPC, поскольку контекстная реклама и интернет-маркетинг во многом объясняются теорией вероятности. Более того, для исследований больших данных R очень полезен.
Первым делом нужно установить R и R-Studio. Это просто. Затем запускаем R-Studio и пишем код. Чтобы код выполнить нужно его выделить и нажать ctrl+enter.
Модель
Уже никто не спорит, что внутри блоков действует VCG, в котором оптимальная ставка это ценность клика. Нужно доказать, что и между блоками биддеры не работают. Для этого напишем простую модель на R, которая отражает переходы между блоками.
Допустим у нас есть ключевое слово. Для простоты предположим, что у нас стратегия в блоке «по минимальной цене» и почти все клики из спецразмещения или гарантии (динамику не учитываем).
Допустим CTR в гарантии 1%, а в СР —10%. Пускай мы торгуем чайниками, покупаем за 200$, а продаем за 300$, конверсии = 3%. Максимальная сумма которую мы можем заплатить за клик (ценность клика) = 3%*(300$-200$)=3$.
srCTR = 0.1 # CTR спеца 10% garCTR = 0.01 # CTR гарантии 1% clickValue = 3 # Ценность клика = 3$
У ключевого слова есть показы. В одной из прошлых статей я показал, что стоимость входа в блоки у разных показов разная.
nImpressions = 100500 # число показов у ключа srPrice = runif(nImpressions, 1, 6) #Стоимость входа в спец garPrice = runif(nImpressions, 0.1, 4)#Стоимость входа в гарантию
Этот код генерирует 100500 показов у ключа. Почему так много? Поскольку чем их больше тем выше точность. Тем меньшее влияние случайности на наши результаты. Но вы можете убедится, что и на меньшем числе показов наша модель даст аналогичные результаты.
Функция runif(nImpressions, 1, 6), генерирует 100500 случайных чисел от 1 до 6. Мы в будущем поменяем диапазоны и заменим функции распределения, чтобы убедиться, что на результат не влияют входящие данные.
Массивы
Преимущество R перед другими языками программирования в том, что там очень простая работа с массивами. Мы можем производить большинство операций без использования циклов.
Вместо такого кода:
for(i in 1:nImpressions){ srPrice[i]=srPrice[i]*2 }
Можно написать просто:
srPrice=srPrice * 2
Есть функции которые считают сумму (sum()) и среднее (mean()) по массиву.
Сравнение
Это один из самых крутых приемов в R. Если вы освоите его, то большинство вещей будите делать вдвое быстрее.
Также можно сравнивать элементы массивов srPrice>garPrice вернет массив из единиц (истина) и нулей (ложь). Поэтому долю показов в спеце при ставке в 2 можно вычислить так: mean(srPrice<2).
Допустим нам нужно вычислить средний CPC в СР при ставке в 2$. Мы попадаем в гарантию во всех показах у которых стоимость входа меньше 2. Таких показов sum(srPrice<2).
Выражение (srPrice<2)*srPrice, равно 1*srPrice=srPrice для всех показов у которых srPrice<2, а для всех остальных 0*srPrice=0.
Нам нужно найти среднее по всем ненулевым элементам этого массива. Берем сумму этого массива и делим на число ненулевых элементов:
Так образом мы вычислим средний CPC в спеце при ставке 2.
Число кликов
Теперь вычислим сколько кликов мы получим при ставке bid.
Нужно вычислить число показов в СР и умножить на CTR в СР.
srCTR*sum(bid>srPrice)
В гарантию мы попадаем когда выполняются одновременно 2 условия. Первое мы не попадаем в СР. Второе стоимость входа в гарантию меньше . Поэтому формула такая.
garCTR*sum((bid<srPrice)*(bid>garPrice))
Просуммируем клики с двух блоков:
clicks = srCTR*sum(bid>srPrice) + garCTR*sum((bid<srPrice)*(bid>garPrice))
Расход и прибыль
Расход аналогично считается
expense = srCTR*sum((bid>srPrice)*srPrice) + garCTR*sum((bid<srPrice)*(bid>garPrice) *garPrice)
Прибыль суммарная ценность купленных кликов минус расход
profit = clicks * clickValue - expense
График
Давайте посмотрим как меняется наша прибыль в зависимости от ставки от 0.01 до 5:
srCTR = 0.1 # CTR спеца garCTR = 0.01 # CTR гарантии clickValue = 3 # Ценность клика nImpressions = 100500 # число показов у ключа srPrice = runif(nImpressions, 1, 6) garPrice = runif(nImpressions, 0.1, 4) bids = c() # массив со ставками profits = c() # массив с прибылями max = 0 # макс. прибыль optBid = 0 # оптимальная ставка for(i in 1:500){ bid = i/100 #ставка bids[i] = bid clicks = srCTR*sum(bid>srPrice) + garCTR*sum((bid<srPrice)*(bid>garPrice)) expense = srCTR*sum((bid>srPrice)*srPrice) + garCTR*sum((bid<srPrice)*(bid>garPrice) *garPrice) profit = clicks * clickValue - expense profits[i]=profit if(profit>max){ max = profit optBid = bid } } plot(x=bids,y=profits)
Чтобы вывести на экран оптимальную ставку нужно просто написать optBid. Получится 2.9.
Берем формулу оптимальной ставки из этой статьи и получаем формулу
k = mean(srCTR/garCTR) ((k-1)*clickValue + mean(garPrice))/k
И получаем 2.905. Может это конечно, совпадение. Поменяем, начальные условия.
srPrice = runif(nImpressions, 2, 5) garPrice = runif(nImpressions, 0.5, 2.5)
Получаем совсем другой график:
Но формула снова верна: 2.85 против 2.8502 по формуле.
Гамма распределение
У нас график получился некрасивым (не гладким). Это поскольку мы использовали равномерное распределение, которое не гладкое и практически не встречается в природе.
Заменим его на другое распределение. Можно на нормальное (но оно может выдавать отрицательные числа). Поэтому возьмем гамму (оно генерирует положительные числа):
srPrice = rgamma(nImpressions, 7, 2) #среднее 7/2 = 3.5 garPrice = rgamma(nImpressions, 1, 2) #среднее 1/2 = 0.5
Получаем 2.8 против 2.799821 по формуле.
Промежуточные выводы
- Формула работает (если кто-то все-еще сомневается — поиграйтесь в с начальными условиями)
- Оптимальная ставка не зависит от стоимости входа в СР. Это видно по формуле. Ни внутри блоков (там VCG), ни снаружи.
- Программы которые на нее ориентируются работают в минус.
- Все эти выводы легко опровергнуть, просто подобрав цифры, где формула не верна (см. в конце статьи код с учетом, того что гарантия может быть недоступной).
Внутри блоков оптимальная ставка clickValue (это доказанное свойство VCG аукциона) между блоков ((k-1)*clickValue + mean(garPrice))/k. Итого оптимальная ставка будет где-то между ними.
При k=10 и малом стоимости входа между блоками оптимальная ставка около 0.9 * clickValue. Итоговую ставку можно оценить как 0.95 * clickValue.
Однако, в большинстве случаев нужно вычитать НДС и разделить на 1.18. Получим 0.805 * clickValue.
Эффективность
Теперь оценим на эффективность стратегий в процентах. Максимальную прибыль возьмем за 100% эффективности. Например, 50% это значит, что прибыль вдвое ниже чем у максимальной стратегии.
Алгоритм 0: искренняя ставка
Самый простой алгоритм. Устанавливаем ставку равную ценности клика.
Поскольку мы уже посчитали прибыль для разный ставок и записали их в массив profits, то можно «вспомнить» прибыль, которая была при ставке равной ценности клика:
profit_a0 = profits[round(clickValue*100)]
Считаем эффективность mean(profit_a0)/max
Получаем 98.5%
Алгоритм 1: по формуле
В формуле есть средняя стоимость входа в гарантию, которая нам неизвестна. Нам из интерфейса Директа известна только стоимость входа в блок при запросе точно соответствующем ключевому слову, в самом дорогом регионе. Грубо говоря, стоимость в случайном показе.
Алгоритм: берем вместо mean(garPrice) циферку «вход в гарантию» из интерфейса Директа.
Мы можем подставить в формулу вместо mean(garPrice) любое из этих чисел garPrice[1], garPrice[2], … Посчитав его имитацией циферки в интерфейсе Яндекса.
bid_a1 = ((k-1)*clickValue + garPrice)/k
profit_a1 = profits[round(bid_a1*100)]
mean(profit_a1)/max
Мы получим 100500 возможных ставок сделанных по этому алгоритму в зависимости от того, данные какого показа были в интерфейсе Директа. И 100500 возможных значений прибыли. Потом считаем среднюю эффективность.
Получим 99.8%
Алгоритм 2: бидменеджер
Типовой алгоритм бидменеджера:
- Если цена_входа_в_СР+0.01<3, то устанавливаем ставку цена_входа_в_СР+0.01
- В противном случае, если цена_входа_в_гарантию+0.01<3, то устанавливаем ставку цена_входа_в_гарантию+0.01
- В противном случае устанавливаем 0.01.
bid_a2 = ((srPrice + 0.01 < clickValue) * (srPrice) + (srPrice + 0.01 > clickValue) * (garPrice + 0.01 < clickValue) * (garPrice) + 0.01) profit_a2 = profits[round(bid_a2*100)] mean(profit_a2)/max
Получим 55%. Т.е. прибыли вдвое меньше! Причем это даже без учета того, что внутри блоков происходит.
Алгоритм 2.2: бидменеджер 2
Меняем действие в третьем случае. Вместо минимальной ставки (0.01) ставим ценность клика (3).
- Если цена_входа_в_СР+0.01<3, то устанавливаем ставку цена_входа_в_СР+0.01
- В противном случае, если цена_входа_в_гарантию+0.01<3, то устанавливаем ставку цена_входа_в_гарантию+0.01
- В противном случае устанавливаем 3.
bid_a22 = ((srPrice + 0.01 < clickValue) * (srPrice + 0.01) +(srPrice + 0.01 > clickValue) * (garPrice + 0.01 < clickValue) * (garPrice + 0.01) +(srPrice + 0.01 > clickValue) * (garPrice + 0.01 > clickValue) * clickValue) profit_a22 = profits[round(bid_a22*100)] mean(profit_a22)/max
Получим 56%.
Исходный код
Вы можете скачать исходный код, в нем много комментариев, защита от некоторых ситуаций (когда гарантия недоступна например), а также алгоритмы реализованы в 2 видах (в том числе и в цикле).
Выводы
Эти входящие данные я специально не подбирал — первое на чем я проверил. При разных входящих данных, потери из-за бидменеджера разные. Но всегда бидменеджер это худший алгоритм, а первый алгоритм (формула) — лучший.
- Бидменеджеры это куча усилий, чтобы вдвое сократить свою прибыль. Проще просто вырубить показы по четным часам или не отвечать на половину звонков
- Они проигрывают даже искренней ставке (тупо установить максимум того, что вы готовы потратить)
- Все это касается и ручной перебивки
Однако, не все сервисы одинаковые, есть оптимизаторы конверсии. Они работают совершенно по другому принципы. Оптимизаторы , благодаря математике, прогнозируют вероятность конверсии (и средний чек) и ориентируясь на эти данные делают ставки.
Например, мой бесплатный Bid-Expert.