Как я реализовывал А/Б-тестирование на PHP

    Возникла задача проведения сплит-тестирования нашего интернет-магазина. Погуглил на эту тему. Нашел пару сервисов, но по тем или иным причинам они нам не подошли.

    Например, использование тестирования через google analytics не подошло, т.к. мы для ведения статистики используем яндекс.метрику и встраивать еще одну аналитическую систему в сайт не хочется. И к тому же я так понял, что с гуглом есть проблема при тестировании динамических страниц или каких-то их элементов.

    Другие сервисы, типа abtest.ru (который, кстати, почему-то не работает) или подобных ему, тоже не подходят, т.к. там можно изменить лишь внешний вид чего-либо. А нам, например, нужно протестировать сайт на предмет вывода/не вывода той или иной информации из базы (ну как пример, выводить ли краткое описание товара в списке товаров или еще выводить спецификации и т.д.).

    Решил попробовать реализовать А/Б-тестирование собственными силами, дописав в сайт немного php-кода (интернет-магазин самописный, на php).
    Необходимо проверить как будет действовать на конверсию изменение того или иного элемента сайта. Т.е. всех посетителей сайта условно делим на «посетителей-А» и «посетителей-Б». Делим естественно поровну, т.е. каждый первый – это «А», каждый второй – это «Б». В зависимости от того «А» это или «Б», показываем ему соответствующий вариант элемента сайта. По окончании фиксируем оформил ли посетитель заказ или нет

    Код описывать не буду, а лишь кратко поясню что и как делал:

    1. При входе на сайт каждому пользователю в сессию (скажем $_SESSION[‘split’]) записывается значение «А» или «Б». Каждому первому – «А», каждому второму – «Б». Для этого в базе данных имеется специальный «счетчик последнего посетителя», который содержит данные о последнем зашедшем на сайт посетителе. Т.е., зашел посетитель на сайт, ему в $_SESSION[‘split’] записали значение «А» и в базе счетчик тоже изменился на «А». При заходе следующего посетителя на сайт, скрипт берет из базы значение счетчика, и если оно = «А», то посетителю в сессию записывается «Б» и наоборот. Т.е. таким образом, обеспечивается чередование пользователей. Кстати, вопрос: может кто подкажет как реализовать чередуемость посетителей без обращения к БД, как узнать какое значение присваивать посетителю «А» или «Б»? Ведь для этого нужно знать какое значение было у предыдущего. Или же можно не париться и просто назначать рандомно 1-ый или 2-ой вариант и этим рандомом? Разделятся ли в этом случае посетители поровну?

    2. После того как в сессию пользователю записали значение «А» или «Б», то теперь в зависимости от этого значения, пользователю показывается либо первый либо второй вариант тестируемого элемента. Т.е. зашел человек на страницу где находится тестируемый элемент, сразу же осуществляется проверка что записано в $_SESSION[‘split’] и в зависимости от результата показывается 1-ый или 2-ой вариант.

    3. Сохранение результатов. Для этого в базе имеются счетчики для подсчета результатов (для подсчета количества заказов «посетителями-А» и «посетителями-Б». Т.е. для каждого из двух вариантов «А» или «Б» ведется свой счетчик. Если посетитель у которого в $_SESSION[‘split’] сидит значение «А» оформил заказ, то счетчик «А» увеличивается на 1. Если посетитель «Б» оформил заказ, то счетчик «Б» увеличивается на 1.

    По сути получается довольно простой код. Но это и пугает. Кажется что я не учел какие-то тонкости и нюансы процесса А/Б тестирования и все гораздо сложнее и глубже. Ведь возможны различные спорные ситуации. Например, как учитывать посетителей, которые зашли на сайт, затем сайт покинули и снова вернулись? Показывать ли им при повторном посещении сайта тот вариант тестируемого элемента, который им был показан при первом посещении, или же заново учитывать этого посетителя в очереди чередования. И тут уже встает вопрос использования не механизма сессий, а механизма cookies.

    Я написал этот пост чтобы поделиться своей реализацией простейшего А/Б – тестирования и заодно выслушать мнения специалистов по этому поводу. Может будут какие подсказки, замечания и т.д.
    Share post
    AdBlock has stolen the banner, but banners are not teeth — they will be back

    More
    Ads

    Comments 46

      0
      Мне даже как-то неловко вам это говорить, но ngx_http_split_clients_module способ более гибкий и менее ресурсоёмкий.
        0
        Менее ресурсоёмкий по сравнению с хранением переменной в сессии? (которая в 99.9% случаев так и так создана)
          0
          Менее ресурскоёмкий, чем хранение последнего посетителя в базе.
            0
            Если есть задача гарантированно выдавать «не то, что было последнему», то без персистентого хранилища вы не обойдётесь никак. Модуль nginx распределяет пользователей согласно выбранных вероятностей и не гарантирует, что каждому последуюшему посетителю будет выдано не то, что предыдущему.

            Я понимаю, что в реальной жизни такое требование вряд ли будет, но просто решения не эквивалентны. И если требование изменить на стандартные вероятности — тогда получим тот же самый rand() + сессию.
              0
              На больших числах распределение будет равномерное. Проверено.
              Впрочем, вы об этом уже написали, как я погляжу.
                0
                Решение в топике гарантирует поочередную выдачу тестовых ячеек.

                Я с вами соглашаюсь, что база там не нужна, но если нужна гарантированная поочередная выдача ячеек, то решение с равномерной выдачей не годится, по определению.
              0
              Неужели вы думаете, что один дополнительный элементарный запрос к БД завалит магазин нагрузкой?
          0
          Как справедливо сказал автор выше — база вам не нужна, достаточно rand() > 0.5? 'A': 'B' и класть в сессию.
            0
            т.е. если я 100 раз запущу rand() > 0.5? 'A': 'B', то появления A и B будут в равных долях?
              0
              При условии, что rand() возвращает от 0 до 1 — да. Чем больше, тем распределение будет ближе к 1:1

              $results = array('A' => 0, 'B' => 0); $i = 1000; while ($i--) { $results[ mt_rand(1, 100) > 50 ? 'A' : 'B' ]++; } var_dump($results);
                0
                Бррр, как-то странно тэг «code» работает :-S
                  +1
                  Надо использовать <source lang=«php»></source> вместо <code>
                  0
                  При таком подходе разница между A и B может достигать 15%, это существенная разница
                    +1
                    Откуда взята цифра в 15%? Попробуйте запустить код на, к примеру, 1M операциях

                    ideone.com/3F6aO2
                      0
                      Не придирайтесь, я ведь сказал «может» относительно вашего кода в 1000 итераций. Согласен, что распределение зависит от кол-ва операций, но даже на 1М я вижу разницу в 3%.

                      Да, заменив запрос к БД на rand() вы незначительно сократите нагрузку на сервер и немного упростите код, но ценой будет погрешность в анализе. Думаю оно того не стоит.
                        +1
                        3% на 1М запусков? Вы уверены, что правильно посчитали?

                        > незначительно
                        Значительно. Разница между вызовом rand() и Выполнением SQL + DML огромна.
                          0
                          Конечно же это воспроизводится не каждый раз, но я вижу такую цифру (при 1М запусков)
                          var_dump($results['A']/$results['B']);
                          //float 0.97199816833671
                          
                            0
                            В то время как разница считается вот так:

                            var_dump(abs($results['A'] - $results['B']) / 10000);
                            


                            Позапускайте и посмотрите на эту цифру теперь
                              0
                              ps: так или иначе — в течение двух минут запускал свой код автоматически (получилось около 300 запусков), так и не смог получить отношение в 0.97 как у вас. Непруха :-(
                            +1
                            Подождите, а вы хоть понимаете смысл а/б тестирования? Вам не важно, чтоб 500 человек увидели надпись, а 500 не увидели. Вам важно сравнить поведение на больших массивах клиентов.
                              0
                              Так для этого будет сессия. Тут обсуждают как раздать эти сессии посетителям примерно поровну.
                                +2
                                В том то и дело, что здесь почему то уперлись в «равные доли». на самом деле они нафиг не надо. Важно чтоб возрастной, половой, териториальный и т.д. состав групп был одинаковый. Если эти условия выполнены — то пофиг, в равных долях у вас разделилось или нет. Потому что на выходе у вас должно быть чтото типа «Группа А: конверсия 4%», «Группа Б: конверсия 5%».
                                  0
                                  Так одно другого не отменяет: после того как выяснили «возрастной, половой, териториальный и т.д.» признаки посетителя, нужно его по какому-то признаку отнести либо в группу А, либо в Б. Выше совершенно справедливо заметили что рэндом, в качестве этого признака, прекрасно подходит.
                      +1
                      таки вам критично чтоб было 50/50? 49/51 уже никак?
                        0
                        Одна мысль пришла. Можно действительно делить на группы рандомно. И не важно какое соотношение будет. Пусть даже 100/1000. Но посчитать тогда нужно именно ПРОЦЕНТ заказвших в той или иной группе, т.е. внутри группы. Допустим группа А: пришли 100 человек, заказали 10. Группа Б: пришли 1000 человек, заказали 10. Из этого видно, что в А конверсия выше. А:10%, Б: 1%. Т.е. считать имено процентное соотношение заказавших к количеству посетителей внутри одной группы (А или Б).
                          0
                          Т.е. зашел человек, рандомно ему назначили либо А, либо Б. И тут же записали в счетчик «пришедших». «Пришедшие А» и «пришедшие Б». В итоге эти два счетчика могу показывать разные данные. В группе пришедших на сайт «А» оказалось 100 человек. А в группе «пришедших Б» оказалось 1000 человек. Да, группы, благодаря рандому получились разными, с очень большим отличием (хотя это и невозможно, но допустим). Теперь мы должны считать сколько заказов сделали пользователи из группы А и из группы Б. Т.е. для этого тоже нужны два счетчика: «заказавшие А» и «заказавшие Б». И в итоге по результатам тестирования берем отношение (процент) «заказавших А» к «пришедшим А» и «заказавших Б» и «пришедшим Б». Сравниваем полученные проценты. И пофиг нам что группы были разными по количеству.

                          Получается в данном случае мы точно защищаем себя от каких то случайных статистических последовательностей (типа той, что каждый второй посетитель — это женщина). Группы будут очень смешанными и разносторонними по географическому признаку, половому, возрастному и т.д.
                    0
                    я сейчас, видимо, жутко туплю, но почему rand() > 0.5 ??
                    разве rand() генерирует число от 0 до 0,9?
                      0
                      А, теперь понял! Последний вопрос отменяю!
                      +1
                      Тоесть я два раза зашёл — побывал А и Б? Я думаю всё таки стоит как минимум в куку писать. Зарегистрированным и вообще лучше в базу
                        0
                        Вот и я об этом думаю? Не знаю как правильно. Эти тонкости должны быть известны только тем кто глубоко эту тему копал. Кто досконально разбирается в механизмах тестирования, теории вероятности, математике и т.д.
                          0
                          Ну, допустим деление пользователя средствами nginx обеспечивает однозначное попадание одного пользователя в одну группу.
                          Но вам никто не мешает ставить куку и определять по ней дополнительно. Кроме того, зарегистрированный пользователь тоже обладает какой-нибудь кукой или переменной сессии.
                            0
                            Не совсем понимаю в чем проблема использования сессий или кук? Ведь пользователи гарантированно будут делиться на две половины. Разве нет?
                              0
                              Никаких проблем.
                              0
                              О чём разговор? Автор же и так записывает значение в сессию, которая основана на куке
                                0
                                А если у автора 5 000 запросов к сайту в секунду?
                                upd. Не в тот тред.
                                  0
                                  Считаете, что автор проводит тестирование ebay? )
                                  0
                                  И которая как правило удаляется после закрытия браузера. Вы можете попасть в интересную ситуацию, когда пользователь начал использовать сценарий А, закрыл браузер, и продолжил покупку уже по сценарию Б. По сути — это главная притензия к решению.
                                0
                                Несколько дилетантских соображений:
                                1. Вам не нужно, чтобы каждый следующий посетитель попадал в другую группу. Вам нужно, чтобы группы были одного размера (упрощенно, при достаточном объеме групп можно нормировать показатели на размеры группы). На самом деле даже не обязательно тестировать на всей аудитории.
                                2. Разнесение на группы на основе четный/нечетный посетитель может быть даже вредно, особенно при небольшом количестве трафика, т.к. результат может зависеть от паттерна посещения или регистрациии. Например, заходит жена, находит товар, кидает ссылку мужу, который и осуществляет покупку. Если между ними посетителей не было, то жена попадет в одну группу, а муж — в другую.
                                3. Если период тестирования значительно больше времени жизни сессии, то постоянный (или несколько раз возвращающийся человек) может попадать то в одну группу, то в другую. Если при этом для каждой группы есть свой дизайн сайта, то такая «мигалка» может раздражать сама по себе. Так что лучше делать группы персистентными (на период одного тестирования).
                                4. Если выбирать группу для посетителей случайно, то может и не повезти (рандом такой рандом), и в составе групп могут появиться статистически значимые отклонения (по полу, количеству предыдущих покупок, возрасту, географии и т.д.). Если подготавливать группы заранее, то можно попытаться свести такие различия к минимуму. Если разносить посетителей на группы автоматически, то желательно иметь этап пост-обработки данных по окончанию тестирования, чтобы убедиться в том, что в характеристиках групп не было значительных отклонений.
                                  0
                                  Эти «тонкости» становятся понятными после 15 минут раздумий над проблемой (желательно вместе с маркетологом). Знаю, потому что занимался подобным.

                                  На самом деле, Ваш код — явное частное решение проблемы. Как пример, который стоит показывать сообществу — явно не тянет. Ну а если Ваши нужды удовлетворяет — то и ладно.
                                +3
                                Это всё на тему «высшее образование программисту не нужно»
                                  0
                                  тогда уж — на тему «чтоб быть программистом, не нужно знать математику».

                                  А если не холиворить, то программисту нужен некий объём знаний и развитое логическое мышление. А уж где и как он это получил — не важно.
                                  0
                                  Создавал аналогичную систему на php, тогда тоже думал про вариант с cookie и rand(0, 1).
                                  В итоге организовал процесс так:
                                  Существует пустая таблица — список пользователей для тестирования (поля: ip, вариант тестирования A/B).

                                  Пользователь заходит — в таблице в зависимости от количества участников A/B ему выдается вариант A или B, таким образом удалось достичь 50/50%. Добавляется запись в таблицу — ip пользователя и вариант.
                                    0
                                    Т.е. если пользователь покинул сайт и потом вернулся, то при возвращении ему выдается тот же вариант, что и при первом посещении?
                                      0
                                      Да, если у него конечно же не изменился ip адрес.
                                    +1
                                    Я просто оставлю это здесь. ЗБЧ.
                                      0
                                      я для А-B тестирования делал просто mt_rand(0, 1) и клал в куки. Не вижу особого смысла гнаться за ровно 50/50

                                      Only users with full accounts can post comments. Log in, please.