А зачем вообще нужно это сегментирование? Да потому что пользователи очень разные. Допустим, приложение бесплатное. Два пользователя скачали его. У одного на счету 0 рублей и он никогда ничего не купит, а другой сделал покупок в приложении на 1500 рублей. Один ходит пешком, а другой ездит на Бентли. Очевидно, что подход к этим людям должен быть разным.
Зачем делать AB-тестирование? Чтобы проверять свои гипотезы о том, как оптимизировать продажи. Какой баннер лучше продает — с котиком или собачкой? Разделяем аудиторию на две равные части, одним показываем котика, другим собачку. Сравниваем продажи, делаем выводы, меняем поведение приложения без апдейта.
На рынке есть готовые системы, которые решают озвученные задачи. Например:
- swrve.com — “In terms of pricing Swrve is between $2000 and $9000 payable monthly, with an annual subscription. Cost is based on the number of custom segments and concurrant a/b tests you would like to run.”
- www.localytics.com — “ENTERPRISE Starts at $1,790 per month, all apps (volume-based pricing).”
Я здесь акцент делаю на стоимости. С радостью использовал бы одно из них, если бы стоило дешевле. Расскажите, если знаете похожие дешевые или даже бесплатные системы.
Дальше я расскажу как быстро и дешево построить свое собственное решение. Для него не понадобится делать сервер с базой данных, что существенно упрощает задачу.
Конфигурация
Введем для приложения понятие “конфигурация” (“конфиг”). Здесь мы будем хранить все настройки приложения — что и кому показывать. Конфиг — это статический JSON файл, одинаковый для всех пользователей. Конфиг надо выложить на какой-нибудь
Далее я буду рассказывать на примере баннеров, хотя применять можно для любого аспекта, вплоть до цветов кнопочек. Наша конфигурация выглядит так:
{
"banners": [
{
"if": "$user_type == ‘geek’",
"link": "http://habrahabr.ru",
"image": "habrahabr.jpg"
},
{
"if": "$user_type == 'has_money'",
"link": "http://buyall.com",
"image": "buyall.jpg"
}
]}
Здесь у баннера кроме очевидных “image” и “link”, есть еще поле “if”. Оно ключевое для сегментирования пользователей. Так мы задаем, кому какой баннер показывать.
Рекомендую при скачивании конфига отключать кеширование, чтобы всегда была последняя версия файла. Также есть смысл шифровать конфиг, чтобы некоторые хитрые пользователи не стали настраивать приложение под свои нужды.
Переменные пользователя
Зададим в приложении “переменные пользователя”, которые мы сможем использовать в выражениях “if” в баннерах. Делается это просто, примерно так:
- (void)onProductBought:(NSString*)productId {
// Запоминаем, что пользователь сделал покупку
NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults];
[defaults setBool:YES forKey:productId];
[defaults synchronize];
}
- (NSDictionary*)userVars {
// Составляем переменные по покупкам
NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults];
NSMutableDictionary* vars = [NSMutableDictionary new];
for (NSString* productId in PRODUCT_IDS) {
vars[productId] = [NSNumber numberWithBool:[defaults boolForKey:productId]];
}
return vars;
}
Переменные представляют собой словарь ключей и значений. Что именно туда включать, каждый выбирает сам, исходя из своих нужд. В переменных можно хранить статус по покупкам, тип пользователя (определяется на основе разных данных), активность в соцсетях, тип устройства (ipad, iphone), версия приложения и т.д.
На первом запуске приложения также создаем булевые переменные для AB-тестирования:
- (void)onAppStarted {
NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults];
if (![defaults objectForKey:@"install_date"]) {
[defaults setObject:[NSDate date] forKey:@"install_date"];
[defaults setBool:(arc4random() % 2) == 0 forKey:@"test_a"];
[defaults setBool:(arc4random() % 2) == 0 forKey:@"test_b"];
[defaults setBool:(arc4random() % 2) == 0 forKey:@"test_c"];
[defaults synchronize];
}
}
Трех переменных достаточно, чтобы провести три независимых теста одновременно. Например одна переменная отвечает за цвет фона баннера (синий или красный), вторая — за цвет текста баннера (белый или черный), третья — за сам текст баннера (нейтральный или эпатажный).
Выражения из переменных
На основе переменных пользователя мы можем составлять выражения. Это удобно делать с помощью стандартных средств iOS — предикатов. Подробнее об этом написано здесь.
В результате можно составлять гибкие условия. Например:
- $user_type == ‘geek’ && $test_a == 1 — баннер для гиков из тестовой группы N2
- $purchases_count > 2 && $test_a == 0 — баннер для сделавших больше 2 покупок из тестовой группы N1
Такие выражения вычисляются очень просто:
- (BOOL)evaluatePredicate:(NSString*)predicateStr {
NSDictionary* userVars = [self userVars];
@try {
NSPredicate* predicate = [NSPredicate predicateWithFormat:predicateStr];
return [predicate evaluateWithObject:nil substitutionVariables:userVars];
}
@catch (NSException *exception) {
NSLog(@"Exception! %@", exception);
return NO;
}
}
В моем случае с баннерами, я считаю выражение “if” и если условие выполняется, то скачиваю и показываю баннер.
Система аналитики
Для AB-тестирования обязательно нужна система аналитики. Для этого мы можем использовать бесплатную Flurry. Тогда вместо обычного:
[Flurry logEvent:@”PRODUCT_BOUGHT” withParameters:@{ @”product_id”: productId } ];
мы будем использовать:
[Flurry logEvent:@”PRODUCT_BOUGHT” withParameters:[self paramsWithUserVars:@{ @”product_id”: productId }] ];
- (void)paramsWithUserVars:(NSDictionary*)params {
NSMutableDictionary* newParams = [[NSMutableDictionary alloc] initWithDictionary:params];
NSDictionary* userVars = [self userVars];
for (NSString* var in userVars) {
newParams[var] = userVars[var];
}
return newParams;
}
При работе с аналитикой необходимо учитывать некоторые ограничения. Для события Flurry количество параметров не может превышать 10. Поэтому лишние переменные из параметров стоит отсекать. Также Flurry собирает информацию по сессиям. Поэтому, чтобы считать количество пользователей, надо не посылать некоторые события дважды.
Push Notifications
Важным инструментом маркетинга являются push notifications (пуши). К ним мы также хотим применять сегментирование. Чтобы не раздражать пушем “купи” тех, кто уже купил.
Нам подойдет бесплатное решение для рассылки пушей — Urban Airship (UA). Оно бесплатное до 1 млн. пушей в месяц, что вполне достаточно для небольших проектов. В UA есть механизм тегов и сегментов. Теги дают возможность пометить пользователя меткой (строкой). Сегменты позволяют составлять выражения из тегов и отправлять пуши только в заданный сегмент. Для удобства в качестве тегов будем использовать те же переменные пользователя, только в другом виде. Вместо { @“product_id”: 1 } будет @”product_id_1”.
NSMutableArray* pushTags = [NSMutableArray new];
NSDictionary* userVars = [self userVars];
for (NSString* var in userVars) {
NSString* tag = [NSString stringWithFormat:@"%@_%@", var, userVars[var]];
[pushTags addObject:tag];
}
[UAPush shared].tags = pushTags;
[[UAPush shared] updateRegistration];
Каждое изменение переменной необходимо отслеживать и обновлять в библиотеке UA.
Далее на сайте UA в разделе Audience > Segments можно создать необходимые сегменты. Для этого там есть web-конструктор. Как дополнительный бонус UA посчитает приблизительную аудиторию каждого сегмента.
Посылать пуши на все устройства можно прямо на сайте UA. На сегменты посылать можно только через API. Видимо это сделано как дополнительный стимул перейти на платный сервис. Но мы воспользуемся утилитой curl и напишем небольшой скрипт:
# Задаем идентификатор и пароль приложения
APP_AUTH=<App Key>:<App Master Secret>
# Задаем сообщение
PUSH_MESSAGE='"aps":{"alert":"Привет пользователь!","sound":"default"},"banner":"welcome.jpg"'
# Получаем список всех сегментов (нам надо узнать id сегмента)
curl -X GET -u "$APP_AUTH" -H 'Content-Type: application/json' https://go.urbanairship.com/api/segments/
# Посылаем пуш на сегмент
curl -X POST -u "$APP_AUTH" -H 'Content-Type: application/json' -d '{"segments":["$SEGMENT_ID"],"ios":{$PUSH_MESSAGE}}' https://go.urbanairship.com/api/push/segments
Перед отсылкой пуша на сегмент, рекомендую проверять его сначала на одном устройстве:
curl -X POST -u "$APP_AUTH" -H 'Content-Type: application/json' -d '{"device_tokens":["$TEST_DEVICE_TOKEN"],$PUSH_MESSAGE}' https://go.urbanairship.com/api/push/
Заключение
Описанная система не идеальна, местами неудобна в применении, но тем не менее решает поставленную задачу для небольших проектов (300.000 пользователей — ок). Реализовать ее можно за день и при правильном использовании это вложение окупится очень быстро. Содержание системы не требует денег (кроме
Автор: AndrewKovzel