import pandas as pd import numpy as np import streamlit as st import tools STEP_2 = STEP_3 = STEP_4 = STEP_5 = STEP_6 = False hide_menu_style = """ """ st.markdown(hide_menu_style, unsafe_allow_html=True) st.set_page_config( page_title="A/B Tests", page_icon="📈", initial_sidebar_state="expanded" ) st.title('A/B tests lab') st.image('images/main.jpg') st.write( """ *Внедрять компании новый сервис или нет? Как принять правильное решение?* *Поможет А/В-тестирование.* A/B-тестирование, или сплит-тестирование (англ. A/B testing; Split testing, от англ. «разделять») — техника проверки гипотез. Позволяет оценить, как изменение сервиса или продукта повлияет на пользователей. Проводится так: аудиторию делят на две группы — контрольную (A) и тестовую (В). Группа A видит начальный сервис, без изменений. Группа B получает новую версию, которую и нужно протестировать. Эксперимент длится фиксированное время или по количеству пользователей. В ходе тестирования собираются данные о поведении пользователей в разных группах. Если ключевая метрика в тестовой группе выросла по сравнению с контрольной, новую функциональность внедряют. """ ) st.image('images/ab-structure.png', width=700) st.write( """ Кому нужно A/B-тестирование 1. _Продакт-менеджеры_ могут тестировать изменения ценовых моделей, направленные на повышение доходов, или оптимизацию части воронки продаж для увеличения конверсии. 2. _Маркетологи_ могут тестировать изображения, призывы к действию (call-to-action) или практически любые другие элементы маркетинговой кампании или рекламы с точки зрения улучшения метрик. 3. _Продуктовые дизайнеры_ могут тестировать дизайнерские решения (например, цвет кнопки оформления заказа) или использовать результаты тестирования для того, чтобы перед внедрением определить, будет ли удобно пользоваться новой функцией. """ ) st.markdown( """ Вот шесть шагов, которые нужно пройти, чтобы провести тестирование. В некоторые из пунктов включены примеры тестирования страницы регистрации выдуманного стартапа. """ ) with st.expander('Шаг 1. Определите цели', expanded=True): st.write( """ Определите основные бизнес-задачи вашей компании и убедитесь, что цели A/B-тестирования с ними совпадают. Например, можем выпустить обновление приложения и проверить на маленькой группе, что обновление не портит пользовательский опыт. Если метрики не падают, можем выкатывать обновление на всех. """ ) purpose = st.radio( 'Цели', options=[ 'Занять делом скучающих сотрудников', 'Решить проблему пользователей', 'Снизить риски при значительных изменениях', 'Обеспечить статистически значимые улучшения' ] ) if 'Занять делом скучающих сотрудников' in purpose: st.error( """ Этой цели мы безусловно добьемся, но бизнесу от этого легче не станет. """ ) if 'Решить проблему пользователей' in purpose: st.info( """ Посетители приходят на сайт с конкретной целью: больше узнать о продукте или услуге, что-то купить, изучить тему или просто поглазеть. При этом пользователи с разными целями сталкиваются с общими проблемами. Например, кнопка «Купить» расположена неудобно и её сложно найти. Такие нюансы формируют негативный пользовательский опыт (пользоваться сайтом неудобно) и влияют на конверсию. Это актуально для всех сфер: будь то электронная коммерция, туризм, SaaS, образование, СМИ или издательский бизнес. """ ) st.error('Да, но сегодня мы будем добиваться другой цели. Выберите другую.') if 'Снизить риски при значительных изменениях' in purpose: st.info( """ Рекомендуем вносить небольшие и последовательные изменения вместо того, чтобы одновременно делать редизайн всей страницы. Так снизится вероятность ухудшения коэффициента конверсии. A/B-тесты позволяют получать хороший результат и при этом вносить лишь небольшие изменения, что приводит к увеличению ROI. В качестве примера приведём изменения в описании продукта. Вы можете сделать A/B-тест, когда нужно удалить или обновить описание продукта, но при этом не знаете, как посетители будут реагировать на это. Другой пример модификации с низким риском — добавление новой функции. A/B-тест поможет сделать результат внедрения более предсказуемым. """ ) st.error('Да, но сегодня мы будем добиваться другой цели. Выберите другую.') if 'Обеспечить статистически значимые улучшения' in purpose: st.info( """ A/B-тестирование полностью основано на данных и не оставляет места для догадок. Поэтому можно легко определить «победителя» и «проигравшего» на основе статистически значимых улучшений: показателей времени на странице, число запросов пробников, количество брошенных корзин, CTR. """ ) st.success('Да, попробуем добиться статистически значимого улучшения метрики.') STEP_2 = True if STEP_2: with st.expander('Шаг 2. Определите метрику', expanded=True): st.write( """ На данном этапе необходимо определить метрику, на которую вы будете смотреть, чтобы понять, является ли новая версия сайта более успешной, чем изначальная. Обычно в качестве такой метрики берут коэффициент конверсии, но можно выбрать и промежуточную метрику вроде показателя кликабельности (CTR). """ ) metrick = st.radio( 'Цели', options=[ 'Обеспечить лучшую окупаемость инвестиций (ROI)', 'Уменьшить показатель отказов', 'Повысить конверсию', ] ) if 'Обеспечить лучшую окупаемость инвестиций (ROI)' in metrick: st.info( """ Маркетологи знают, каким дорогим бывает качественный трафик. A/B-тестирование позволяет эффективно использовать существующий трафик и помогает повысить конверсию без затрат на привлечение нового. Иногда даже незначительные изменения влияют на конверсию. """ ) st.error('Сегодня мы будем тестировать не эту метрику. Выберите другую.') if 'Уменьшить показатель отказов' in metrick: st.info( """ Для оценки эффективности сайта важно отслеживать показатель отказов. Люди покидают сайт по разным причинам: слишком много вариантов товара, несоответствие ожиданиям и другие. Поскольку сайты различаются по аудиториям и целям, нет универсального надёжного способа определения показателя отказов. Но решение есть: в каждом случае поможет A/B-тестирование. Можно протестировать несколько вариантов расположения элементов на сайте и найти оптимальное решение. """ ) st.error('Сегодня мы будем тестировать не эту метрику. Выберите другую.') if 'Повысить конверсию' in metrick: st.info( """ Конверсия — один из главных терминов в маркетинге. Не считая конверсию, сложно оценить эффективность маркетинга и работать с воронкой продаж. Конверсия показывает, какой процент пользователей или потенциальных клиентов совершили целевое действие: оставили заявку, купили товар, подписались на рассылку и так далее. """ ) st.success('Правильно! Именно эту метрику мы и будем оптимизировать') STEP_3 = True if STEP_3: with st.expander('Шаг 3. Разработайте гипотезу', expanded=True): st.write( """ Затем нужно разработать гипотезу о том, что именно поменяется, и, соответственно, что вы хотите проверить. Нужно понять, каких результатов вы ожидаете и какие у них могут быть обоснования. Нужно определить две гипотезы, которые помогут понять, является ли наблюдаемая разница между версией A (изначальной) и версией B (новой, которую вы хотите проверить) случайностью или результатом изменений, которые вы произвели. * _Нулевая гипотеза_ предполагает, что результаты, А и В на самом деле не отличаются и что наблюдаемые различия случайны. Мы надеемся опровергнуть эту гипотезу. * _Альтернативная гипотеза_ — это гипотеза о том, что B отличается от A, и вы хотите сделать вывод об её истинности. Решите, будет ли это односторонний или двусторонний тест. Односторонний тест позволяет обнаружить изменение в одном направлении, в то время как двусторонний тест позволяет обнаружить изменение по двум направлениям (как положительное, так и отрицательное). """ ) st.radio( "Тип теста", options=["Односторонний", "Двусторонний"], index=0, key="hypothesis", help="Односторонний тест позволяет обнаружить изменение в одном направлении, в то время как двусторонний тест позволяет обнаружить изменение по двум направлениям (как положительное, так и отрицательное). ", ) STEP_4 = True if STEP_4: with st.expander('Шаг 4. Подготовьте эксперимент', expanded=True): st.write( """ 1. _Создайте новую версию (B)_, отражающую изменения, которые вы хотите протестировать. 2. _Определите контрольную и экспериментальную группы_. Каких пользователей вы хотите протестировать: всех пользователей на всех платформах или только пользователей из одной страны? Определите группу испытуемых, отобрав их по типам пользователей, платформе, географическим показателям и т.п. Затем определите, какой процент исследуемой группы составляет контрольная группа (группа, видящая версию A), а какой процент — экспериментальная группа (группа, видящая версию B). Обычно эти группы одинакового размера. 3. _Убедитесь, что пользователи будут видеть версии A и B в случайном порядке_. Это значит, у каждого пользователя будет равный шанс получить ту или иную версию. 4. _Определите уровень статистической значимости (α)_. Это уровень риска, который вы принимаете при ошибках первого рода (отклонение нулевой гипотезы, если она верна), обычно α = 0.05. Это означает, что в 5% случаев вы будете обнаруживать разницу между A и B, которая на самом деле обусловлена случайностью. Чем ниже выбранный вами уровень значимости, тем ниже риск того, что вы обнаружите разницу, вызванную случайностью. 5. _Определите минимальный размер выборки_. Калькулятор есть [здесь](https://vwo.com/tools/ab-test-sample-size-calculator/). Он рассчитывают размер выборки, необходимый для каждой версии. На размер выборки влияют разные параметры и ваши предпочтения. Наличие достаточно большого размера выборки важно для обеспечения статистически значимых результатов. 6. _Определите временные рамки_. Калькулятор есть [здесь](https://vwo.com/tools/ab-test-duration-calculator/). Возьмите общий размер выборки, необходимый вам для тестирования каждой версии, и разделите его на ваш ежедневный трафик. Так вы получите количество дней, необходимое для проведения теста. Как правило, это одна или две недели. У A/B-теста есть проблема подглядывания (англ. peeking problem): общий результат искажается, если новые данные поступают в начале эксперимента. Каждый, даже небольшой фрагмент новых данных, велик относительно уже накопленных — статистическая значимость достигается за короткий срок. """ ) st.image('images/peeking_problem.png', width=670) st.write( """ На графике разница конверсии между сегментами, полученная в результате смоделированного A/B-теста. Данные собирали из одной генеральной совокупности, и различий в выборочных средних быть не должно. Но из-за флуктуаций (от лат. fluctuatio, колебание) в первые дни тестирования была достигнута статистическая значимость. Если бы это был реальный, а не смоделированный тест, принятое по достижении статистической значимости решение было бы неверным. Чтобы избежать проблемы подглядывания, размер выборки определяют ещё до начала теста. """ ) st.slider( "Уровень значимости (α)", min_value=0.01, max_value=0.10, value=0.05, step=0.01, key="alpha", help="Это уровень риска, который вы принимаете при ошибках первого рода (отклонение нулевой гипотезы, если она верна), обычно α = 0.05.", ) ab_test_duration = st.select_slider(label='Выберите длительность A/B теста в днях', options=range(3, 31)) mean_traff = st.number_input(label='Укажите, среднюю посещаемость сайта в сутки', min_value=150) ab_test_sample_size = st.select_slider(label=f'Укажите размер выборки для группы B (при 20% от средней посещаемости в день, максимальный размер выборки для группы B - {int(mean_traff * 0.2 * ab_test_duration)})', options=range(60, int(mean_traff * 0.2 * ab_test_duration) + 1)) st.write(f'Выбрано ~{int((ab_test_sample_size / ab_test_duration) / mean_traff * 100)}% от средней посещаемости в сутки.') STEP_5 = True if STEP_5: with st.expander('Шаг 5. Проведите эксперимент', expanded=True): st.write( """ Помните о важных шагах, которые необходимо выполнить: 1. Обсудите параметры эксперимента с исполнителями. 2. Выполните запрос на тестовой закрытой площадке, если она у вас есть. Это поможет проверить данные. Если ее нет, проверьте данные, полученные в первый день эксперимента. 3. В самом начале проведения тестирования проверьте, действительно ли оно работает. 4. И наконец, не смотрите на результаты! Преждевременный просмотр результатов может испортить статистическую значимость. """ ) with st.form(key='start_ab'): start_test = st.form_submit_button('Провести тест') if start_test: st.write("Посмотрим на проведенный тест") df = tools.get_dataset(ab_test_sample_size, ab_test_duration) visitors_a = df[df['group'] == 'old_version'].shape[0] visitors_b = df[df['group'] == 'new_version'].shape[0] conversions_a = df.groupby(['group', 'converted']).agg('count')['user_id'][3] conversions_b = df.groupby(['group', 'converted']).agg('count')['user_id'][1] st.write(df.sample(7)) st.plotly_chart(tools.get_plotly_converted_hist(df), use_container_width=True) STEP_6 = True if STEP_6: with st.expander('Шаг 6. Проанализируйте результаты', expanded=True): st.write( """ Вам нужно получить данные и рассчитать значения выбранной ранее метрики успеха для обеих версий (A и B) и разницу между этими значениями. Если не было никакой разницы в целом, вы также можете сегментировать выборку по платформам, типам источников, географическим параметрам и т.п., если это применимо. Вы можете обнаружить, что версия B работает лучше или хуже для определенных сегментов. Проверьте статистическую значимость. Статистическая теория, лежащая в основе этого подхода, объясняется здесь, но основная идея в том, чтобы выяснить, была ли разница в результатах между A и B связана с изменениями или это результат случайности либо естественных изменений. Это определяется путем сравнения тестовых статистических данных (и полученного p-значения) с вашим уровнем значимости. Если p-значение меньше уровня значимости, то можно отвергнуть нулевую гипотезу, если имеются доказательства для альтернативы. Если p-значение больше или равно уровню значимости, мы не можем отвергнуть нулевую гипотезу о том, что A и B не отличаются друг от друга. """ ) tools.calculate_significance( conversions_a, conversions_b, visitors_a, visitors_b ) mcol1, mcol2 = st.columns(2) with mcol1: st.metric( "Разница", value=f"{(st.session_state.crb - st.session_state.cra):.3g}%", delta=f"{(st.session_state.crb - st.session_state.cra):.3g}%", ) with mcol2: st.metric("Различие статзначимо?", value=st.session_state.significant) results_df = pd.DataFrame( { "Group": ["A", "B"], "Conversion": [st.session_state.cra, st.session_state.crb], } ) tools.plot_chart(results_df) table = pd.DataFrame( { "Converted": [conversions_a, conversions_b], "Total": [visitors_a, visitors_b], "% Converted": [st.session_state.cra, st.session_state.crb], }, index=pd.Index(["A", "B"]), ) st.write(table.style.format(formatter={("% Converted"): "{:.3g}%"})) metrics = pd.DataFrame( { "p-value": [st.session_state.p], "z-score": [st.session_state.z], "uplift": [st.session_state.uplift], }, index=pd.Index(["Metrics"]), ) st.write( metrics.style.format( formatter={("p-value", "z-score"): "{:.3g}", ("uplift"): "{:.3g}%"} ) .applymap(tools.style_negative, props="color:red;") .apply(tools.style_p_value, props="color:red;", axis=1, subset=["p-value"]) ) st.plotly_chart(tools.get_fig(df), use_container_width=True)