Spaces:
Runtime error
Runtime error
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 | |
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) | |