<jupyter_start><jupyter_text>Analyze A/B Test Results For E-Commerce Website Table of Contents- [Introduction](intro)- [Part I - Probability](probability)- [Part II - A/B Test](ab_test)- [Part III - Regression](regression) IntroductionA/B testing is one of the common technique used for analyzing which web designs drive more traffic. Many companies use A/B testing to test feature changes, layouts and even colors to increase a metric that measures a interest from the users.For this project, I will be working to understand the results of an A/B test run by an e-commerce website.  Your goal is to work through this notebook to help the company understand if they should implement the new page, keep the old page, or perhaps run the experiment longer to make their decision. Part I - ProbabilityTo get started, let's import our libraries.<jupyter_code>import pandas as pd
import numpy as np
import random
import matplotlib.pyplot as plt
import statsmodels.api as sm
from statsmodels.stats.proportion import proportions_ztest
%matplotlib inline

random.seed(42)<jupyter_output>/opt/conda/lib/python3.6/site-packages/statsmodels/compat/pandas.py:56: FutureWarning: The pandas.core.datetools module is deprecated and will be removed in a future version. Please use the pandas.tseries module instead.
  from pandas.core import datetools<jupyter_text>`1.` Now, read in the `ab_data.csv` data. Store it in `df`. a. Read in the dataset and take a look at the top few rows here:<jupyter_code># read data
df = pd.read_csv('ab_data.csv')
df.head()<jupyter_output><empty_output><jupyter_text>b. Use the cell below to find the number of rows in the dataset.<jupyter_code>print('Number of rows: {}'.format(df.shape[0]))
print('Number of columns: {}'.format(df.shape[1]))<jupyter_output>Number of rows: 294478
Number of columns: 5<jupyter_text>c. The number of unique users in the dataset.<jupyter_code>print('{} unique users'.format(df.user_id.nunique()))<jupyter_output>290584 unique users<jupyter_text>d. The proportion of users converted.<jupyter_code>print('Proportion of users converted = {:.5f}%'.format(df.converted.sum() / df.shape[0] * 100))<jupyter_output>Proportion of users converted = 11.96592%<jupyter_text>e. The number of times the `new_page` and `treatment` don't match.<jupyter_code>num_not_match = df[((df.landing_page == 'new_page') & (df.group != 'treatment')) | 
               ((df.landing_page == 'old_page') & (df.group != 'control'))].shape[0]
print("The number of times the new_page and treatment don't match is {}".format(num_not_match))<jupyter_output>The number of times the new_page and treatment don't match is 3893<jupyter_text>f. Do any of the rows have missing values?<jupyter_code>print('Number of rows with missing values = {}'.format(df.isnull().any(axis = 1).sum()))<jupyter_output>Number of rows with missing values = 0<jupyter_text>`2.` For the rows where **treatment** does not match with **new_page** or **control** does not match with **old_page**, we cannot be sure if this row truly received the new or old page.<jupyter_code># remove all rows where the number of times the new_page and treatment don't match
index_for_no_match = df[((df.landing_page == 'new_page') & (df.group != 'treatment')) | 
                        ((df.landing_page == 'old_page') & (df.group != 'control'))].index
df2 = df.drop(index = index_for_no_match)
# Double Check all of the correct rows were removed - this should be 0
df2[((df2['group'] == 'treatment') == (df2['landing_page'] == 'new_page')) == False].shape[0]<jupyter_output><empty_output><jupyter_text>`3.` Let's investigate the data frame after the change performed in previous step. a. How many unique **user_id**s are in **df2**?<jupyter_code>print('{} unique users'.format(df2.user_id.nunique()))<jupyter_output>290584 unique users<jupyter_text>b. There is one **user_id** repeated in **df2**.  What is it?<jupyter_code>print('The one user_id repeated in df2 is {}'.format(df2[df2.user_id.duplicated()].iloc[0, 0]))<jupyter_output>The one user_id repeated in df2 is 773192<jupyter_text>c. What is the row information for the repeat **user_id**?<jupyter_code>df2[df2.user_id.duplicated()]<jupyter_output><empty_output><jupyter_text>d. Remove **one** of the rows with a duplicate **user_id**, but keep your dataframe as **df2**.<jupyter_code># remove duplicte user_id as row values are consistent
df2 = df2.drop(index = df2[df2.user_id.duplicated()].index)
# verify for duplicate user ids
df2.user_id.duplicated().sum()<jupyter_output><empty_output><jupyter_text>`4.` Find probabilites using **df2**a. What is the probability of an individual converting regardless of the page they receive?<jupyter_code>prob_regardless_page = (df.converted == 1).mean()
print('Probability of converting regardless of page is {:.5f}'.format(prob_regardless_page))<jupyter_output>Probability of converting regardless of page is 0.11966<jupyter_text>b. Given that an individual was in the `control` group, what is the probability they converted?<jupyter_code>prob_control_group = ((df.group == 'control') & (df.converted == 1)).sum() / (df.group == 'control').sum()
print('Probability of converting given that individual is in control group is {:.5f}'.format(prob_control_group))<jupyter_output>Probability of converting given that individual is in control group is 0.12040<jupyter_text>c. Given that an individual was in the `treatment` group, what is the probability they converted?<jupyter_code>prob_treatment_group = ((df.group == 'treatment') & (df.converted == 1)).sum() / (df.group == 'treatment').sum()
print('Probability of converting given that individual is in treatment group is {:.5f}'.format(prob_treatment_group))<jupyter_output>Probability of converting given that individual is in treatment group is 0.11892<jupyter_text>d. What is the probability that an individual received the new page?<jupyter_code>prob_indi_recieved_page = (df.landing_page == 'new_page').mean()
print('probability that an individual received the new page is {:.5f}'.format(prob_indi_recieved_page))<jupyter_output>probability that an individual received the new page is 0.50000<jupyter_text>e. Consider your results from parts (a) through (d) above, and explain below whether you think there is sufficient evidence to conclude that the new treatment page leads to more conversions. **Let me bring down my results over here so that it will be easy to interpret**- Probability of converting regardless of page is `0.11966`.- Probability of converting given that individual is in control group is `0.12040`.- Probability of converting given that individual is in treatment group is `0.11892`.- probability that an individual received the new page is `0.50000`.From whatever evidence I have from above results, I believe that implementing new page will not increase the conversion rate. **_However there isn't sufficient imformation to conclude this for sure because the difference in probability is very less._**  Part II - A/B TestNotice that because of the time stamp associated with each event, you could technically run a hypothesis test continuously as each observation was observed.  However, then the hard question is do you stop as soon as one page is considered significantly better than another or does it need to happen consistently for a certain amount of time?  How long do you run to render a decision that neither page is better than another?  These questions are the difficult parts associated with A/B tests in general.  `1.` For now, consider you need to make the decision just based on all the data provided.  If you want to assume that the old page is better unless the new page proves to be definitely better at a Type I error rate of 5%, what should your null and alternative hypotheses be?  You can state your hypothesis in terms of words or in terms of **$p_{old}$** and **$p_{new}$**, which are the converted rates for the old and new pages. $$H_0: p_{new} - p_{old} \le 0$$$$H_1: p_{new} - p_{old} > 0$$ `2.` Assume under the null hypothesis, $p_{new}$ and $p_{old}$ both have "true" success rates equal to the **converted** success rate regardless of page - that is $p_{new}$ and $p_{old}$ are equal. Furthermore, assume they are equal to the **converted** rate in **ab_data.csv** regardless of the page. Use a sample size for each page equal to the ones in **ab_data.csv**.  Perform the sampling distribution for the difference in **converted** between the two pages over 10,000 iterations of calculating an estimate from the null.  Use the cells below to provide the necessary parts of this simulation. a. What is the **conversion rate** for $p_{new}$ under the null?<jupyter_code>p_new = (df2.converted == 1).mean()
print('The conversion rate for p_new under the null is {:.5f}'.format(p_new))<jupyter_output>The conversion rate for p_new under the null is 0.11960<jupyter_text>b. What is the **conversion rate** for $p_{old}$ under the null?<jupyter_code>p_old = (df2.converted == 1).mean()
print('The conversion rate for p_old under the null is {:.5f}'.format(p_old))<jupyter_output>The conversion rate for p_old under the null is 0.11960<jupyter_text>c. What is $n_{new}$, the number of individuals in the treatment group?<jupyter_code>n_new = df2[df2.group == 'treatment'].shape[0]
print('The number of individuals in the treatment group is {}'.format(n_new))<jupyter_output>The number of individuals in the treatment group is 145310<jupyter_text>d. What is $n_{old}$, the number of individuals in the control group?<jupyter_code>n_old = df2[df2.group == 'control'].shape[0]
print('The number of individuals in the treatment group is {}'.format(n_old))<jupyter_output>The number of individuals in the treatment group is 145274<jupyter_text>e. Simulate $n_{new}$ transactions with a conversion rate of $p_{new}$ under the null.  Store these $n_{new}$ 1's and 0's in **new_page_converted**.<jupyter_code>new_page_converted = np.random.binomial(1, p_new, n_new)<jupyter_output><empty_output><jupyter_text>f. Simulate $n_{old}$ transactions with a conversion rate of $p_{old}$ under the null.  Store these $n_{old}$ 1's and 0's in **old_page_converted**.<jupyter_code>old_page_converted = np.random.binomial(1, p_old, n_old)<jupyter_output><empty_output><jupyter_text>g. Find $p_{new}$ - $p_{old}$ for your simulated values from part (e) and (f).<jupyter_code>obs_diff = new_page_converted.mean() - old_page_converted.mean()  
obs_diff<jupyter_output><empty_output><jupyter_text>h. Create 10,000 $p_{new}$ - $p_{old}$ values using the same simulation process you used in parts (a) through (g) above. Store all 10,000 values in a NumPy array called **p_diffs**.<jupyter_code># simulate for 10000 times
p_diffs = []
for i in range(10000):
    new_page_converted = np.random.binomial(1, p_new, n_new)
    old_page_converted = np.random.binomial(1, p_old, n_old)
    p_diffs.append(new_page_converted.mean() - old_page_converted.mean())
p_diffs = np.array(p_diffs)<jupyter_output><empty_output><jupyter_text>i. Plot a histogram of the **p_diffs**.  Does this plot look like what you expected?  Use the matching problem in the classroom to assure you fully understand what was computed here.<jupyter_code>plt.hist(p_diffs);<jupyter_output><empty_output><jupyter_text>j. What proportion of the **p_diffs** are greater than the actual difference observed in **ab_data.csv**?<jupyter_code># calculate actual observed difference
p_diff_orig = df[df['landing_page'] == 'new_page']['converted'].mean() -  df[df['landing_page'] == 'old_page']['converted'].mean()
(p_diffs > p_diff_orig).mean()<jupyter_output><empty_output><jupyter_text>k. Please explain using the vocabulary you've learned in this course what you just computed in part **j.**  What is this value called in scientific studies?  What does this value mean in terms of whether or not there is a difference between the new and old pages? - The value that is computed in step j is called as `p-value` which is defined as **_if null hypothesis is true, the probability of obtaining the observed statistic or one or more extreme in favour of the alternative hypothesis._**- The `p-value` is dependent on alternative hypothesis. Based on the alternative hypothesis we shade the region and find the area under the shaded region to obtain p-value.- In my test the `p-value` is `0.9124` which indicates that I failed to reject the null at a type 1 error of `5%`. I can say that the new page is equal to the old page in terms of conversion rate at this null hypothesised value. However we cannot say for sure whether the new page is worse than old page. l. We could also use a built-in to achieve similar results.  Though using the built-in might be easier to code, the above portions are a walkthrough of the ideas that are critical to correctly thinking about statistical significance. Fill in the below to calculate the number of conversions for each page, as well as the number of individuals who received each page. Let `n_old` and `n_new` refer the the number of rows associated with the old page and new pages, respectively.<jupyter_code>import statsmodels.api as sm

convert_old = ((df.converted == 1) & (df.landing_page == 'old_page')).sum()
convert_new = ((df.converted == 1) & (df.landing_page == 'new_page')).sum()
n_old = (df.landing_page == 'old_page').sum()
n_new = (df.landing_page == 'new_page').sum()<jupyter_output><empty_output><jupyter_text>m. Now use `stats.proportions_ztest` to compute your test statistic and p-value.  [Here](https://docs.w3cub.com/statsmodels/generated/statsmodels.stats.proportion.proportions_ztest/) is a helpful link on using the built in.<jupyter_code># perform z_test
z_score, p_value = proportions_ztest([convert_old, convert_new], [n_old, n_new], alternative = 'smaller')
print('p_value: {}'.format(p_value))
print('z_score: {}'.format(z_score))<jupyter_output>p_value: 0.9143962454534289
z_score: 1.3683341399998907<jupyter_text>n. What do the z-score and p-value you computed in the previous question mean for the conversion rates of the old and new pages?  Do they agree with the findings in parts **j.** and **k.**? - We should note that in the earlier setup the hypotheses where `one sided` while in the `z_test` the hypotheses are `two sided`. - The `p_value` of `0.914` still suggests that I failed to reject the null at the type 1 error rate of `5%`. That is the new page is same as the old page in terms of conversion rate.- While the `z_score` of `1.3683` tell us how far from mean of distribution a point lies and this value is used for calculating the `p_value`.- Finally, these values agree with the findings in parts j and k.  Part III - A regression approach`1.` In this final part, you will see that the result you achieved in the A/B test in Part II above can also be achieved by performing regression. a. Since each row is either a conversion or no conversion, what type of regression should you be performing in this case? - **Logistic Regression** b. The goal is to use **statsmodels** to fit the regression model you specified in part **a.** to see if there is a significant difference in conversion based on which page a customer receives. However, you first need to create in df2 a column for the intercept, and create a dummy variable column for which page each user received.  Add an **intercept** column, as well as an **ab_page** column, which is 1 when an individual receives the **treatment** and 0 if **control**.<jupyter_code>df2.head()
df2['intercept'] = 1
df2['ab_page'] = df2.group.map({'treatment': 1, 'control': 0})
df2.head()<jupyter_output><empty_output><jupyter_text>c. Use **statsmodels** to instantiate your regression model on the two columns you created in part b., then fit the model using the two columns you created in part **b.** to predict whether or not an individual converts.<jupyter_code># perform logistic regression on data
model = sm.Logit(df2['converted'], df2[['intercept', 'ab_page']])
results = model.fit()<jupyter_output>Optimization terminated successfully.
         Current function value: 0.366118
         Iterations 6<jupyter_text>d. Provide the summary of your model below, and use it as necessary to answer the following questions.<jupyter_code>results.summary2()<jupyter_output><empty_output><jupyter_text>e. What is the p-value associated with **ab_page**? Why does it differ from the value you found in **Part II**? - The `p-value` associated with `ab_page` is `0.1899` and it is different from what we have in part 2 because of the following reason:  - The setup for hypothesis test in part 2 is one tailed that is$$H_0: p_{new} - p_{old} \le 0$$$$H_1: p_{new} - p_{old} > 0$$  - While the setup for hypothesis considered by regression model is two tailed that is$$H_0: p_{new} - p_{old} = 0$$$$H_1: p_{new} - p_{old} \not= 0$$- So inorder to get the same `p-value` we have do the following:>$pvalue_{onetailed} = 1 - \frac{0.1899}{2} = 0.91$  f. Now, you are considering other things that might influence whether or not an individual converts.  Discuss why it is a good idea to consider other factors to add into your regression model.  Are there any disadvantages to adding additional terms into your regression model? - It is good idea to consider other factors to add in regression model to understand how other factors would effect the results. There will be number of factors that correlate to the response variable that your predicting and hence considering them can yeild different results.- But considering mutliple factors can bring in the following potential problems:  - Multicollinearity  - Non-linearity of the response-predictor relationships  - Correlation of error terms  - Outliers g. Now along with testing if the conversion rate changes for different pages, also add an effect based on which country a user lives in. I will need to read in the **countries.csv** dataset and merge together your datasets on the appropriate rows.  [Here](https://pandas.pydata.org/pandas-docs/stable/generated/pandas.DataFrame.join.html) are the docs for joining tables. Does it appear that country had an impact on conversion?<jupyter_code># read countries file
df_country = pd.read_csv('countries.csv')
df_country.head()
# join df2 dataframe with df_country dataframe
df_new = df2.join(df_country.set_index('user_id'), on = 'user_id')
df_new.head()
df_new.country.unique()
# 0, 1 encoding for country attribute
df_new[['US', 'CA', 'UK']] = pd.get_dummies(df_new['country'])
df_new.head()
# conversion rate for US
us_conv = ((df_new.country == 'US') & (df_new.converted == 1)).sum() / (df_new.country == 'US').sum()
print('The conversion rate when page is viewed from US is {}'.format(us_conv))
# conversion rate for CA
ca_conv = ((df_new.country == 'CA') & (df_new.converted == 1)).sum() / (df_new.country == 'CA').sum()
print('The conversion rate when page is viewed from CA is {}'.format(ca_conv))
# conversion rate for UK
uk_conv = ((df_new.country == 'UK') & (df_new.converted == 1)).sum() / (df_new.country == 'UK').sum()
print('The conversion rate when page is viewed from UK is {}'.format(uk_conv))<jupyter_output>The conversion rate when page is viewed from UK is 0.12059448568984076<jupyter_text>- It seems like country has impact on conversion rate as it varies from country to country.- Highest being for `UK` with approximately `12%` and least being for `US` with approximately `11.9%`. h. Though I have now looked at the individual factors of country and page on conversion, I would now like to look at an interaction between page and country to see if there significant effects on conversion.  Create the necessary additional columns, and fit the new model.<jupyter_code>df_new.head()
# perform logistic regression on data with countries
logi_model = sm.Logit(df_new['converted'], df_new[['intercept', 'ab_page', 'US', 'CA']])
results = logi_model.fit()
results.summary2()
# interpreting logistic regression model coefficients 
1/np.exp(-0.0149), 1/np.exp(-0.0408), np.exp(0.0099)
# find end and start of test
df_new.timestamp.max(), df_new.timestamp.min()
from datetime import datetime

# calculate test duration
fmt = '%Y-%m-%d %H:%M:%S'
tstamp1 = datetime.strptime('2017-01-24 13:41:54', fmt)
tstamp2 = datetime.strptime('2017-01-02 13:42:05', fmt)

td = tstamp1 - tstamp2
print('The test is conducted for just over {} days'.format(td.days))<jupyter_output>The test is conducted for just over 21 days
{'fraction_non_alphanumeric': 0.10232855177834545, 'fraction_numerical': 0.019453723717822755, 'mean_word_length': 5.279148673658236, 'pattern_counts': {'":': 0, '<': 113, '<?xml version=': 0, '>': 117, 'https://': 2, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '33', 'dir': 'jupyter-structured-clean-dedup', 'id': '106753', 'license': 'MIT', 'n_tokens_mistral': 6351, 'n_tokens_neox': 5992, 'n_words': 2507, 'path': 'Analyze_ab_test_results_notebook.ipynb', 'repo_name': 'ManideepTelukuntla/AnalyzeABTestResultsForECommerceWebsite', 'size': '20356'}