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" | |
| ) | |
| hide_menu_style = """ | |
| <style> | |
| footer {visibility: hidden;} | |
| </style> | |
| """ | |
| st.markdown(hide_menu_style, unsafe_allow_html=True) | |
| 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) | |