Изучите результаты A/B-тестирования, проведенного веб-сайтом электронной коммерции.
A/B-тесты очень часто выполняются аналитиками данных и учеными данных. Важно, чтобы вы получили некоторую практику работы с этими трудностями.
В рамках этого проекта — программы Udacity Data Analyst для наностепеней — вы будете работать над тем, чтобы понять результаты A/B-тестирования, проведенного веб-сайтом электронной коммерции. Компания разработала новую веб-страницу, чтобы попытаться увеличить количество пользователей, которые «конвертируют», то есть количество пользователей, которые решили заплатить за продукт компании. Цель состоит в том, чтобы помочь компании понять, должны ли они внедрить эту новую страницу, оставить старую страницу или, возможно, провести эксперимент дольше, чтобы принять решение.
Оглавление:
- Часть I. Вероятность
- Часть II — A/B-тестирование
- Часть III. Регрессионный подход
- Часть IV. Заключение
Данные: ab_data.csv
#импортировать библиотеки
import pandas as pd import numpy as np import random import matplotlib.pyplot as plt %matplotlib inline random.seed(42)
Часть I. Вероятность
#Прочитайте данные `ab_data.csv`. Сохраните его в `df`.
df = pd.read_csv('ab_data.csv') df.head()
#Количество строк в наборе данных.
df.shape
#Количество уникальных пользователей в наборе данных.
df.nunique()
#Доля конверсионных пользователей.
df.converted.mean()
#количество раз, когда new_page и обработка не совпадают.
first_df = len(df.query(‘group == “treatment” and landing_page == “old_page”’)) second_df = len(df.query(‘group == “control” and landing_page == “new_page”’)) e = first_df + second_df print(e)
#проверка отсутствия значения.
missing = df.isnull().sum()
Для строк, в которых обработка не соответствует new_page или control не соответствует old_page, мы нельзя быть уверенным, действительно ли эта строка получила новую или старую страницу.
#создайте новый набор данных, соответствующий спецификациям, и сохраните новый фрейм данных в df2.
df2 = df.drop(df.query(‘group == “treatment” and landing_page != “new_page” | group == “control” and landing_page != “old_page”’).index)
#тест
df2[((df2[‘group’] == ‘treatment’) == (df2[‘landing_page’] == ‘new_page’)) == False].shape[0]
#Уникальные user_id в df2.
df2.user_id.nunique()
#повторяется user_id в df2 иинформации о строке
duplicated_user = df2[df2['user_id'].duplicated()] df2[df2['user_id'].duplicated(keep=False)]
#удалить одну строку с повторяющимся user_id.
df2 = df2.drop([2893])
#тест
df2[df2['user_id'].duplicated()].any()
#вероятность отдельной конверсии независимо от страницы, которую он получает.
df2.converted.mean()
#вероятность того, что человек совершит конверсию так, как в контрольной группе.
p_control = df2.query('group == "control"').converted.mean() p_control
#вероятность того, что индивидуум обратится в другую группу.
p_treatment = df2.query('group == "treatment"').converted.mean() p_treatment
#вероятность того, что пользователь получил новую страницу.
p_newpage = df2.query('landing_page == "new_page"').shape[0]/df2.shape[0] p_newpage
#Имеются ли достаточные доказательства того, что новая лечебная страница приводит к большему количеству конверсий?
obs_diff = p_treatment - p_control print('Observed difference is: {}'.format(obs_diff))
Пока нельзя сказать, что новая страница лечения приводит к большему количеству конверсий, у меня нет достаточных доказательств в поддержку этого утверждения. Данные показывают, что p_control = 12% и p_treatment = 11%. Другими словами, коэффициенты конверсии экспериментальных и контрольных групп слишком близки друг к другу, чтобы иметь четкое представление. Разница между ними составляет всего 0,001, как видно из obs_diff выше. Вероятность получения новой страницы или старой страницы выходит 50–50%. Чтобы принять решение об обработке страницы, мне нужно проверить нулевую гипотезу и попытаться получить больше доказательств.
Часть II — A/B-тестирование
#если вы хотите предположить, что старая страница лучше, если новая страница не окажется определенно лучше при частоте ошибок типа I, равной 5%, ваши нулевая и альтернативная гипотезы должны быть
Односторонний Т-тест:
H_0: 𝑝𝑜𝑙𝑑 ≥ 𝑝𝑛𝑒𝑤
H_1: 𝑝𝑜𝑙𝑑 < 𝑝𝑛𝑒𝑤
(𝑝𝑜𝑙𝑑 и 𝑝𝑛𝑒𝑤 — коэффициенты конверсии для старых и новых страниц)
#предположим, что согласно нулевой гипотезе, 𝑝𝑛𝑒𝑤 и 𝑝𝑜𝑙𝑑 имеют «истинные» показатели успешности, равные конверсиям, независимо от страницы
(Предположим, что pnew = pold и размер выборки = размер выборки ab_data)
#коэффициент конвертации для 𝑝𝑛𝑒𝑤 и 𝑝𝑜𝑙𝑑 при нулевом значении
p_new = df2.converted.mean() p_old = df2.converted.mean() print('p_new:' , p_new , 'p_old:', p_old)
#nnewи nold
n_new = len(df2.query('landing_page == "new_page"')) n_old = len(df2.query('landing_page == "old_page"')) print('n_new:' , n_new , 'n_old:', n_old)
#Смоделируйте nnew транзакции с коэффициентом конверсии 𝑝𝑛𝑒𝑤 при нулевом значении. Сохраните эти nnew 1 и 0 в new_page_converted.
new_page_converted = np.random.binomial(1, p_new, n_new) new_page_converted.mean()
#Смоделируйте nold транзакции с коэффициентом конверсии 𝑝𝑜𝑙𝑑 при нулевом значении. Сохраните эти nold 1 и 0 в old_page_converted.
old_page_converted = np.random.binomial(1, p_old, n_old) old_page_converted.mean()
#𝑝𝑛𝑒𝑤-𝑝старый
difference = new_page_converted.mean() — old_page_converted.mean() print(‘Simulated difference is: {}’.format(difference))
#p_diffs
p_diffs = [] new_converted_simulation = np.random.binomial(n_new, p_new, 10000)/n_new old_converted_simulation = np.random.binomial(n_old, p_old, 10000)/n_old p_diffs = new_converted_simulation — old_converted_simulation p_diffs = np.array(p_diffs)
#построить гистограмму
plt.hist(p_diffs) plt.title('New-old probability diffs simulation') plt.xlabel('p_diffs'); null_vals = np.random.normal(0, p_diffs.std(), p_diffs.size) plt.hist(null_vals) plt.axvline(x=obs_diff, c=’red’);
Создаваемый график должен иметь нормальное распределение. Также статистика, которую я вычислил выше, называется в научных исследованиях «p-value». Это произошло из нулевого распределения, p-значение = 0,91 > 0,05. Так что не отвергайте нулевую гипотезу 𝑝𝑛𝑒𝑤 = 𝑝𝑜𝑙𝑑.
Пусть nold и nnew обозначают количество строк, связанных со старой и новой страницами соответственно.
import statsmodels.api as sm convert_old = df2.query("landing_page == 'old_page' and converted == 1").shape[0] convert_new = df2.query("landing_page == 'new_page' and converted == 1").shape[0] n_old = df2.query("landing_page == 'old_page'").shape[0] n_new = df2.query("landing_page == 'new_page'").shape[0] z_score, p_value = sm.stats.proportions_ztest([convert_old, convert_new], [n_old, n_new], alternative = “smaller”) z_score, p_value print(‘Z-score: {}’.format(z_score)) print(‘P-value of Z-Test: {}’.format(p_value))
Вычисления z-показателя и p-значения из тестовой статистики показывают, что результаты не опровергают нулевую гипотезу. Кроме того, оба p-значения равны 0,189 при расчете без вычисления «альтернативы». Они согласны с выводами, сделанными в предыдущей части.
Часть III. Регрессионный подход
Я хочу предсказывать категоричные ответы, поэтому буду использовать логистическую регрессию.
df2[[‘a_page’, ‘ab_page’]] = pd.get_dummies(df2[‘group’]) #create dummies #logistic regression model df2['intercept']= 1 logit_mod =sm.Logit(df2['converted'],df2[['intercept', 'a_page']]) #fit the model results = logit_mod.fit()
p-значение ~ 0,19 ›› ошибка типа 1 (Статистически отклонение несущественно.)
np.exp(0.015) #to exponentiate
Люди в 1,015 раза чаще конвертируют страницу, если все остальное остается неизменным.
Я предположил, что 𝑝𝑛𝑒𝑤 = 𝑝𝑜𝑙𝑑 в этом двустороннем Т-тесте в части III.
Предыдущий анализ представлял собой односторонний Т-тест и предполагал 𝑝𝑛𝑒𝑤 › 𝑝𝑜𝑙𝑑 в части II.
Нулевая и альтернативная гипотезы для двустороннего Т-теста:
H_0: 𝑝𝑛𝑒𝑤 — 𝑝𝑜𝑙𝑑 = 0
H_1: 𝑝𝑛𝑒𝑤 — 𝑝𝑜𝑙𝑑 != 0
Нулевая и альтернативная гипотезы для одностороннего Т-теста:
H_0: 𝑝𝑛𝑒𝑤 — 𝑝𝑜𝑙𝑑 ≤ 0
H_1: 𝑝𝑛𝑒𝑤 — 𝑝𝑜𝑙𝑑 > 0
P-значение для обоих из них составляет 0,1899 при двустороннем тестировании без указания альтернативы.
В некоторых случаях фактор может быть очень важным и критическим для влияния на результат. В таком случае считают, что добавление фактора улучшает модель. Я могу попытаться понять, важно это или нет, вычислив коэффициент корреляции, а затем принять решение оставить его или удалить. С другой стороны, добавление слишком большого количества факторов создало бы проблему переобучения и вводящие в заблуждение результаты. Кроме того, есть еще один недостаток, заключающийся в том, что мультиколлинеарность может возникнуть, если эти дополнительные факторы коррелируют.
#индивидуальные факторы страны и страницы при переходе
#merging datasets countries_df = pd.read_csv(‘./countries.csv’) df_new=countries_df.set_index(‘user_id’).join(df2.set_index(‘user_id’), how=’inner’)#creating dummy variables df_new[[‘UK’, ‘US’, ‘CA’]] = pd.get_dummies(df_new.country)
#логит-модель
model = sm.Logit(df_new.converted, df_new[[‘intercept’, ‘ab_page’, ‘UK’, ‘CA’]]) #logit model results_new = model.fit() #fitting the model
Значения p указывают на отсутствие влияния страны на конверсию.
#взаимодействие между страницей и страной, чтобы увидеть, существенно ли это влияет на конверсию
df_new[‘ab_UK’] = df_new[‘ab_page’] * df_new[‘UK’] df_new[‘ab_US’] = df_new[‘ab_page’] * df_new[‘US’] df_new[‘ab_CA’] = df_new[‘ab_page’] * df_new[‘CA’]
#Логит-модель
lmodel = sm.Logit(df_new[“converted”], df_new[[“intercept”, “ab_page”, “UK”, “CA”, “ab_UK”, “ab_CA”]])
#Подбор логит-модели
results_factor = lmodel.fit() 1/np.exp(results_factor.params) #to exponentiate
Согласно статистическим результатам, p-значения ab_UK и ab_CA превышают ошибку типа I (0,05). Если считать все остальные постоянными, вероятность конверсии у пользователя из Великобритании в 1,08 раза выше, а у пользователя из ЦА вероятность конверсии выше в 1,03 раза. Их влияние действительно мало. Более того, результаты недостаточно надежны, чтобы говорить о значительном влиянии на конверсию отдельных факторов страны и страницы.
Заключение
Все результаты A/B-тестирования показывают, что результаты не являются достаточными доказательствами для отклонения нулевой гипотезы. Решение должно заключаться в том, что лучше не обновлять сайт с old_page на new_page. Поскольку никаких изменений не произойдет, это будет означать потерю времени и усилий.
Подробности и полная версия:
Дополнительные материалы на PlainEnglish.io. Подпишитесь на нашу бесплатную еженедельную рассылку новостей. Подпишитесь на нас в Twitter и LinkedIn. Присоединяйтесь к нашему сообществу Discord.