Analyze A/B Test Results

This project will assure you have mastered the subjects covered in the statistics lessons. The hope is to have this project be as comprehensive of these topics as possible. Good luck!

Table of Contents

Introduction

A/B tests are very commonly performed by data analysts and data scientists. It is important that you get some practice working with the difficulties of these

For this project, you 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.

As you work through this notebook, follow along in the classroom and answer the corresponding quiz questions associated with each question. The labels for each classroom concept are provided for each question. This will assure you are on the right track as you work through the project, and you can feel more confident in your final submission meeting the criteria. As a final check, assure you meet all the criteria on the RUBRIC.

Part I - Probability

To get started, let's import our libraries.

In [1]:
#Import Statements
import pandas as pd
import numpy as np
import random
import matplotlib.pyplot as plt

%matplotlib inline

#We are setting the seed to assure you get the same answers on quizzes as we set up
random.seed(42)

1. Now, read in the ab_data.csv data. Store it in df. Use your dataframe to answer the questions in Quiz 1 of the classroom.

a. Read in the dataset and take a look at the top few rows here:

In [2]:
#Importing DataSet
df = pd.read_csv('ab_data.csv')

#Showing First rows of ab_dataSet (df)
df.head()
Out[2]:
user_id timestamp group landing_page converted
0 851104 2017-01-21 22:11:48.556739 control old_page 0
1 804228 2017-01-12 08:01:45.159739 control old_page 0
2 661590 2017-01-11 16:55:06.154213 treatment new_page 0
3 853541 2017-01-08 18:28:03.143765 treatment new_page 0
4 864975 2017-01-21 01:52:26.210827 control old_page 1

b. Use the below cell to find the number of rows in the dataset.

In [3]:
#Shape can be used to see the dimensions of the DataSet
df.shape
Out[3]:
(294478, 5)

c. The number of unique users in the dataset.

In [4]:
#Finding the unique Users and storing it variable Total
total = df['user_id'].nunique()
total
Out[4]:
290584

d. The proportion of users converted.

In [5]:
#Finding users converted and Proportion of users who their respecitve proprtions
converted = df.query('converted == 1')['user_id'].count()
prop_converted = (converted) / len(df)
prop_converted
Out[5]:
0.11965919355605512

e. The number of times the new_page and treatment don't line up.

In [6]:
#Analysing the Data - Intermediate Step 
df.groupby(['group','landing_page']).count()
Out[6]:
user_id timestamp converted
group landing_page
control new_page 1928 1928 1928
old_page 145274 145274 145274
treatment new_page 145311 145311 145311
old_page 1965 1965 1965
In [7]:
#Looking at the above data we gather the following values.
treatment_oldPage = df[(df['group'] == 'treatment') & (df['landing_page'] == 'old_page')]
control_newpage = df[(df['group'] == 'control') & (df['landing_page'] == 'new_page')]

ans = len(treatment_oldPage) + len(control_newpage)

ans_df = pd.concat([treatment_oldPage, control_newpage])


ans
Out[7]:
3893

f. Do any of the rows have missing values?

In [8]:
#Checking for missing values in the dataSet
df.isnull().values.any()
Out[8]:
False

2. For the rows where treatment is not aligned with new_page or control is not aligned with old_page, we cannot be sure if this row truly received the new or old page. Use Quiz 2 in the classroom to provide how we should handle these rows.

a. Now use the answer to the quiz to create a new dataset that meets the specifications from the quiz. Store your new dataframe in df2.

In [9]:
#Copying Dataframe according to above mentioned condition
df2 = df

# Remove incriminating rows
mismatch_index = ans_df.index
df2 = df2.drop(mismatch_index)
In [10]:
# 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]
Out[10]:
0

3. Use df2 and the cells below to answer questions for Quiz3 in the classroom.

a. How many unique user_ids are in df2?

In [11]:
#Find Uniqe Users 
df2['user_id'].nunique()
Out[11]:
290584
In [12]:
#Check if any non Unique Users
len(df2)-len(df2.user_id.unique())
Out[12]:
1

b. There is one user_id repeated in df2. What is it?

In [13]:
# Refrence : https://stackoverflow.com/questions/15247628/how-to-find-duplicate-names-using-pandas

df2.set_index('user_id').index.get_duplicates()
Out[13]:
[773192]

c. What is the row information for the repeat user_id?

In [14]:
df2.query('user_id == 773192')
Out[14]:
user_id timestamp group landing_page converted
1899 773192 2017-01-09 05:37:58.781806 treatment new_page 0
2893 773192 2017-01-14 02:55:59.590927 treatment new_page 0

d. Remove one of the rows with a duplicate user_id, but keep your dataframe as df2.

In [15]:
#Dropping one of the duplicate Rows
df2=df2[(df2["user_id"]!= 773192) & (df2["timestamp"] != "2017-01-09 05:37:58.781806")]
print("Drop True")
Drop True
In [16]:
#Checking if Drop Was successful
df2.set_index('user_id').index.get_duplicates()
Out[16]:
[]

4. Use df2 in the below cells to answer the quiz questions related to Quiz 4 in the classroom.

a. What is the probability of an individual converting regardless of the page they receive?

In [17]:
#Probabaility of a User Converting 
df2.query('converted == 1').user_id.count()/df2.user_id.count()
Out[17]:
0.11959749882133504

b. Given that an individual was in the control group, what is the probability they converted?

e. Use the results in the previous two portions of this question to suggest if you think there is evidence that one page leads to more conversions? Write your response below.

In [18]:
# Probability of control group converting
df2[((df2.group == "control") & (df2.converted == 1))].user_id.count()/df2.query('group == "control"').user_id.count()
Out[18]:
0.1203863045004612

c. Given that an individual was in the treatment group, what is the probability they converted?

In [19]:
# Probability of treatment group converting
df2[((df2.group == "treatment") & (df2.converted == 1))].user_id.count()/df2.query('group == "treatment"').user_id.count()
Out[19]:
0.11880888313869065

d. What is the probability that an individual received the new page?

In [20]:
# Probability an individual recieved new page
df2.query('landing_page == "new_page"').user_id.count()/df2.user_id.count()
Out[20]:
0.50006022375706771

e. Consider your results from a. through d. above, and explain below whether you think there is sufficient evidence to say that the new treatment page leads to more conversions.

From section 1d, we know that roughly 0.12% of users converted overall.

Looking at the data in general, we had several columns (which we removed on the way) for which the initial data didn't line up with the group. As we don't have enough information for a re-matching, we removed those columns in section 3d, which could actually contribute to a distortion of the overall picture.

However, looking at the probabilities by group in sections 4b and 4c, we found that given that an individual belongs to the treatment group (or the control group respectively), the conversion probabilitiy is about 0.12% for both cases. Since we know from section 4d that the probability that an inidividual received the new pages lies roughly at about 50%, both groups are more or less of same size, so the probability values are indeed comparable.

Summarising, have enough evidence that one page leads to more conversions

Part II - A/B Test

Notice 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.

Put your answer here.

The null hypothesis $H_0$: $\hspace{2cm}$ $p_{new} - p_{old} \leq 0$

The alternative hypothesis $H_1$: $\hspace{0.9cm}$ $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. If this doesn't make complete sense right now, don't worry - you are going to work through the problems below to complete this problem. You can use Quiz 5 in the classroom to make sure you are on the right track.

a. What is the convert rate for $p_{new}$ under the null?

In [21]:
#Calculate probability of conversion for new page
p_new = df2[df2['landing_page']=='new_page']['converted'].mean()
p_new
Out[21]:
0.11880888313869065

b. What is the convert rate for $p_{old}$ under the null?

In [22]:
# Calculate probability of conversion for old page
p_old = df2[df2['landing_page']=='old_page']['converted'].mean()
p_old
Out[22]:
0.1203863045004612
In [23]:
# Take the mean of these two probabilities
p_mean = np.mean([p_new, p_old])
p_mean
Out[23]:
0.11959759381957592
In [24]:
# Calc. differences in probability of conversion for new and old page (not under H_0)
p_diff = p_new-p_old
p_diff
Out[24]:
-0.0015774213617705535

c. What is $n_{new}$?

In [25]:
n_New= df.query('group == "treatment" and landing_page == "new_page" ').user_id.count()
# 
# n_New = df2['landing_page'].value_counts()
n_New
#For some reason this value is up by 1
Out[25]:
145311

d. What is $n_{old}$?

In [26]:
n_Old = df.query('group == "control" and landing_page == "old_page" ').user_id.count()
n_Old
Out[26]:
145274

e. Simulate $n_{new}$ transactions with a convert rate of $p_{new}$ under the null. Store these $n_{new}$ 1's and 0's in new_page_converted.

In [27]:
new_page_converted = np.random.choice([1, 0], size=n_New, p=[p_mean, (1-p_mean)])
new_page_converted.mean()
Out[27]:
0.11857326699286358

f. Simulate $n_{old}$ transactions with a convert rate of $p_{old}$ under the null. Store these $n_{old}$ 1's and 0's in old_page_converted.

In [28]:
old_page_converted = np.random.choice([1, 0], size=n_Old, p=[p_mean, (1-p_mean)])
old_page_converted.mean()
Out[28]:
0.11914038300039924

g. Find $p_{new}$ - $p_{old}$ for your simulated values from part (e) and (f).

In [29]:
new_page_converted.mean()-old_page_converted.mean()
Out[29]:
-0.00056711600753565905

h. Simulate 10,000 $p_{new}$ - $p_{old}$ values using this same process similarly to the one you calculated in parts a. through g. above. Store all 10,000 values in a numpy array called p_diffs.

In [30]:
p_diffs = []

# Re-run simulation 10,000 times
# trange creates an estimate for how long this program will take to run
for i in range(10000):
    new_page_converted = np.random.choice([1, 0], size=n_New, p=[p_mean, (1-p_mean)])
    old_page_converted = np.random.choice([1, 0], size=n_Old, p=[p_mean, (1-p_mean)])
    p_diff = new_page_converted.mean()-old_page_converted.mean()
    p_diffs.append(p_diff)

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.

In [31]:
# Plot histogram
plt.hist(p_diffs, bins=25)
plt.title('Simulated Difference of New Page and Old Page Converted Under the Null')
plt.xlabel('Page difference')
plt.ylabel('Frequency')
plt.axvline(x=(p_new-p_old), color='r', linestyle='dashed', linewidth=1, label="Real difference")
plt.axvline(x=(np.array(p_diffs).mean()), color='g', linestyle='dashed', linewidth=1, label="Simulated difference")
plt.legend()
plt.show()

j. What proportion of the p_diffs are greater than the actual difference observed in ab_data.csv?

In [32]:
p_diff = p_old - p_new 
p_diff
Out[32]:
0.0015774213617705535
In [33]:
# Find proportion of p_diffs greater than the actual difference
greater_than_diff = [i for i in p_diffs if i > p_diff]
In [34]:
# Calculate values
print("Actual difference:" , p_diff)

p_greater_than_diff = len(greater_than_diff)/len(p_diffs)

print('Proportion greater than actual difference:', p_greater_than_diff)

print('As a percentage: {}%'.format(p_greater_than_diff*100))
Actual difference: 0.0015774213617705535
Proportion greater than actual difference: 0.0953
As a percentage: 9.53%

k. In words, explain 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?

If our sample conformed to the null hypothesis then we'd expect the proportion greater than the actual difference to be 0.5. However, we calculate that almost 90% of the population in our simulated sample lies above the real difference which does not only suggest that the new page does not do significantly better than the old page, it might even be worse!

The value we computed in j is the diffrence between both the p old and p new. This can be used to compare the value to the p critical to see if it is relevant The value is very small hence the alternate hypothesis is rejected

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.

In [35]:
#Import statsmodels
import statsmodels.api as sm

# Calculate number of conversions
# Some of these values were defined ealier in this notebook: n_old and n_new

convert_old = len(df2[(df2['landing_page']=='old_page')&(df2['converted']==1)])
convert_new = len(df2[(df2['landing_page']=='new_page')&(df2['converted']==1)])

print("convert_old:", convert_old, 
      "\nconvert_new:", convert_new,
      "\nn_old:", n_Old,
      "\nn_new:", n_New)
D:\Anaconda3\lib\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
convert_old: 17489 
convert_new: 17264 
n_old: 145274 
n_new: 145311

m. Now use stats.proportions_ztest to compute your test statistic and p-value. Here is a helpful link on using the built in.

In [36]:
# Find z-score and p-value
z_score, p_value = sm.stats.proportions_ztest(count=[convert_new, convert_old],nobs=[n_New, n_Old],alternative='smaller')
print("z-score:", z_score,
     "\np-value:", p_value)
z-score: -1.31160753391 
p-value: 0.0948262948594

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.?

It seems that the differences between the lines shown in the histogram above is -1.31 standard deviations. The p-value is roughly 19.0% which is the probability that this result is due to random chance, this is not enough evidence to reject the null hypothesis and thus we fail to do so.

Part III - A regression approach

1. In this final part, you will see that the result you acheived in the previous A/B test can also be acheived 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?

We can use Logistic Regression and will be using the sm module for the same.

The null hypothesis $H_0$: $\hspace{2cm}$ $p_{new} - p_{old} \leq 0$

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 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.

In [37]:
df3 = df2 # Clone dataframe in case of a mistake
In [38]:
df3['intercept'] = pd.Series(np.zeros(len(df3)), index=df3.index)
df3['ab_page'] = pd.Series(np.zeros(len(df3)), index=df3.index)
In [39]:
# Find indexes that need to be changed for treatment group
index_to_change = df3[df3['group']=='treatment'].index

# Change values
df3.set_value(index=index_to_change, col='ab_page', value=1)
df3.set_value(index=df3.index, col='intercept', value=1)

# Change datatype
df3[['intercept', 'ab_page']] = df3[['intercept', 'ab_page']].astype(int)

# Move "converted" to RHS
df3 = df3[['user_id', 'timestamp', 'group', 'landing_page', 'ab_page', 'intercept', 'converted']]
D:\Anaconda3\lib\site-packages\ipykernel\__main__.py:5: FutureWarning: set_value is deprecated and will be removed in a future release. Please use .at[] or .iat[] accessors instead
D:\Anaconda3\lib\site-packages\ipykernel\__main__.py:6: FutureWarning: set_value is deprecated and will be removed in a future release. Please use .at[] or .iat[] accessors instead
In [40]:
# Check everything  has worked
df3[df3['group']=='treatment'].head()
Out[40]:
user_id timestamp group landing_page ab_page intercept converted
2 661590 2017-01-11 16:55:06.154213 treatment new_page 1 1 0
3 853541 2017-01-08 18:28:03.143765 treatment new_page 1 1 0
6 679687 2017-01-19 03:26:46.940749 treatment new_page 1 1 1
8 817355 2017-01-04 17:58:08.979471 treatment new_page 1 1 1
9 839785 2017-01-15 18:11:06.610965 treatment new_page 1 1 1

c. Use statsmodels to import your regression model. Instantiate the model, and fit the model using the two columns you created in part b. to predict whether or not an individual converts.

In [41]:
# Set up logistic regression
logit = sm.Logit(df3['converted'], df3[['ab_page', 'intercept']])

# Calculate results
result=logit.fit()
Optimization terminated successfully.
         Current function value: 0.366119
         Iterations 6

d. Provide the summary of your model below, and use it as necessary to answer the following questions.

In [42]:
result.summary2() # result.summary() wasn't working for some reason, but this one does
Out[42]:
Model: Logit No. Iterations: 6.0000
Dependent Variable: converted Pseudo R-squared: 0.000
Date: 2018-02-28 18:01 AIC: 212780.0972
No. Observations: 290583 BIC: 212801.2565
Df Model: 1 Log-Likelihood: -1.0639e+05
Df Residuals: 290581 LL-Null: -1.0639e+05
Converged: 1.0000 Scale: 1.0000
Coef. Std.Err. z P>|z| [0.025 0.975]
ab_page -0.0150 0.0114 -1.3102 0.1901 -0.0374 0.0074
intercept -1.9888 0.0081 -246.6690 0.0000 -2.0046 -1.9730

e. What is the p-value associated with ab_page? Why does it differ from the value you found in Part II?

Apparently the p-value associated with ab_page is 0.1899, which is slightly lower than the p-value I calculated using the z-test above. The reason why the value is lower is because I added an intercept which is meant to account for error if my memory is correct. This means that this value is more accurate. (As in, it's probably closer to the true p-value)

However, this p-value is still much too high to reject the null hypothesis.

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?

There are certainly disadvantages to adding too many features into your analysis. When do you regression or categorization analysis you want to have features which have large impacts on outcome, small impacts are usually not influencial and should be left for the intercept.

I believe there's a statistic which accounts for this, some sort of corrected R² value (in linear regression at least) which will give lower outputs if "useless" features are added.

However, only one feature was chosen to determine whether a user would convert (beside the intercept) so a couple of added features wouldn't hurt. I would imagine some features like the time spent looking at page and the date the page was designed might be some interesting features to add. The longer a customer spends on a page the more they are likely to be content with it and unwilling to change, it could also be the case that really old pages will not work well and people will want an updated version.

g. Now along with testing if the conversion rate changes for different pages, also add an effect based on which country a user lives. You will need to read in the countries.csv dataset and merge together your datasets on the approporiate rows. Here are the docs for joining tables.

Does it appear that country had an impact on conversion? Don't forget to create dummy variables for these country columns - Hint: You will need two columns for the three dummy variables. Provide the statistical output as well as a written response to answer this question.

In [43]:
countries_df = pd.read_csv('./countries.csv')
df_new = countries_df.set_index('user_id').join(df2.set_index('user_id'), how='inner')
countries_df.head()
Out[43]:
user_id country
0 834778 UK
1 928468 US
2 822059 UK
3 711597 UK
4 710616 UK
In [44]:
# Creating dummy variables
df_dummy = pd.get_dummies(data=countries_df, columns=['country'])

# Performing join
df4 = df_dummy.merge(df3, on='user_id') # df.join is depricated AFAIK

# Sorting columns
df4 = df4[['user_id', 'timestamp', 'group', 'landing_page', 
           'ab_page', 'country_CA', 'country_UK', 'country_US',
           'intercept', 'converted']]

# Fix Data Types
df4[['ab_page', 'country_CA', 'country_UK', 'country_US','intercept', 'converted']] =\
df4[['ab_page', 'country_CA', 'country_UK', 'country_US','intercept', 'converted']].astype(int)

df4.head()
Out[44]:
user_id timestamp group landing_page ab_page country_CA country_UK country_US intercept converted
0 834778 2017-01-14 23:08:43.304998 control old_page 0 0 1 0 1 0
1 928468 2017-01-23 14:44:16.387854 treatment new_page 1 0 0 1 1 0
2 822059 2017-01-16 14:04:14.719771 treatment new_page 1 0 1 0 1 1
3 711597 2017-01-22 03:14:24.763511 control old_page 0 0 1 0 1 0
4 710616 2017-01-16 13:14:44.000513 treatment new_page 1 0 1 0 1 0

h. Though you have now looked at the individual factors of country and page on conversion, we 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.

Provide the summary results, and your conclusions based on the results.

In [45]:
### Fit Your Linear Model And Obtain the Results

# Create logit_countries object
logit_countries = sm.Logit(df4['converted'], 
                           df4[['country_UK', 'country_US', 'intercept']])

# Fit
result2 = logit_countries.fit()
Optimization terminated successfully.
         Current function value: 0.366117
         Iterations 6
In [46]:
# Show results
result2.summary2()
Out[46]:
Model: Logit No. Iterations: 6.0000
Dependent Variable: converted Pseudo R-squared: 0.000
Date: 2018-02-28 18:01 AIC: 212780.5787
No. Observations: 290583 BIC: 212812.3176
Df Model: 2 Log-Likelihood: -1.0639e+05
Df Residuals: 290580 LL-Null: -1.0639e+05
Converged: 1.0000 Scale: 1.0000
Coef. Std.Err. z P>|z| [0.025 0.975]
country_UK 0.0507 0.0284 1.7863 0.0740 -0.0049 0.1064
country_US 0.0408 0.0269 1.5180 0.1290 -0.0119 0.0935
intercept -2.0375 0.0260 -78.3639 0.0000 -2.0885 -1.9866
In [47]:
# Create logit_countries object
logit_countries2 = sm.Logit(df4['converted'], 
                           df4[['ab_page', 'country_UK', 'country_US', 'intercept']])

# Fit
result3 = logit_countries2.fit()
Optimization terminated successfully.
         Current function value: 0.366114
         Iterations 6
In [48]:
# Show results
result3.summary2()
Out[48]:
Model: Logit No. Iterations: 6.0000
Dependent Variable: converted Pseudo R-squared: 0.000
Date: 2018-02-28 18:01 AIC: 212780.8724
No. Observations: 290583 BIC: 212823.1910
Df Model: 3 Log-Likelihood: -1.0639e+05
Df Residuals: 290579 LL-Null: -1.0639e+05
Converged: 1.0000 Scale: 1.0000
Coef. Std.Err. z P>|z| [0.025 0.975]
ab_page -0.0149 0.0114 -1.3062 0.1915 -0.0373 0.0075
country_UK 0.0506 0.0284 1.7835 0.0745 -0.0050 0.1063
country_US 0.0408 0.0269 1.5163 0.1294 -0.0119 0.0935
intercept -2.0300 0.0266 -76.2489 0.0000 -2.0822 -1.9779

When adding everything together it seems that the p-values for all featues has increased. The z-score for the intercept is incredibly large though which is interesting.

Conclusions

Analyzing the dataset we can see there is quite a diffrence between the conversion rates for both the pages. Although there is not enough evidence to completely reject the null hypothesis. Also, it seems that the new page does worse than the old page.

It was also found that the conversion rate is not dependant on counties, data shows that UK and US behave almost the same.

I would recommend that the e-commerce company spend their money on trying to improve their website before trying again. This can be in something more than the design of the said webpage. The Below Mentioned recommendations maybe used

  • Target a diffrent demographic of people
  • Add new categories of products/material which may find it more intresting to customers.
  • Changes made in AB tests are not significant enough to make a noticeable diffrence
  • There are other issues in the products, pricing, etc which are not letting the business grow