Project Business Statistics: E-news Express¶

Define Problem Statement and Objectives¶

Import all the necessary libraries¶

In [1]:
# Installing the libraries with the specified version.
## Removed the version specifier to install the latest version. Using Python 3.12.3 on Macbook Pro M4 PRO
%pip install numpy pandas matplotlib seaborn scipy -q --user
Note: you may need to restart the kernel to use updated packages.

Note: After running the above cell, kindly restart the notebook kernel and run all cells sequentially from the start again.

In [2]:
#Importing libraries
import numpy as np
import pandas as pd
from matplotlib import pyplot as plt
import seaborn as sns
from scipy import stats

Importing libraries needed for analysis

In [3]:
#Accesing Google Drive
#from google.colab import drive
#drive.mount('/content/drive')

Mounting Google Drive to access data

Reading the Data into a DataFrame¶

In [4]:
#Reading the dataset
data = pd.read_csv('abtest.csv')
data.head()
Out[4]:
user_id group landing_page time_spent_on_the_page converted language_preferred
0 546592 control old 3.48 no Spanish
1 546468 treatment new 7.13 yes English
2 546462 treatment new 4.40 no Spanish
3 546567 control old 3.02 no French
4 546459 treatment new 4.75 yes Spanish

Reading CSV and getting the first 5 rows with head

In [5]:
#Getting rows and columns
rows, cols = data.shape

print(f"The dataset has {rows} rows and {cols} columns.")
The dataset has 100 rows and 6 columns.

Observations: Getting the number of rows and columns

Explore the dataset and extract insights using Exploratory Data Analysis¶

  • Data Overview
    • Viewing the first and last few rows of the dataset
    • Checking the shape of the dataset
    • Getting the statistical summary for the variables
  • Check for missing values
  • Check for duplicates
In [6]:
#DATA OVERVIEW
# Display the first five rows of the dataset
print("First 5 rows of the dataset:")
print(data.head())

# Display the last five rows of the dataset
print("\nLast 5 rows of the dataset:")
print(data.tail())

# Get the shape (number of rows and columns) of the dataset
print("\nDataset dimensions (rows, columns):", data.shape)

# Get a statistical summary of numerical variables
print("\nStatistical summary of numerical variables:")
print(data.describe())

#MISSING VALUES
# Check for missing values in each column
print("\nMissing values per column:")
print(data.isnull().sum())

#DUPLICATES
# Check for duplicate rows in the dataset
duplicate_count = data.duplicated().sum()
print(f"\nNumber of duplicate rows in the dataset: {duplicate_count}")
First 5 rows of the dataset:
   user_id      group landing_page  time_spent_on_the_page converted  \
0   546592    control          old                    3.48        no   
1   546468  treatment          new                    7.13       yes   
2   546462  treatment          new                    4.40        no   
3   546567    control          old                    3.02        no   
4   546459  treatment          new                    4.75       yes   

  language_preferred  
0            Spanish  
1            English  
2            Spanish  
3             French  
4            Spanish  

Last 5 rows of the dataset:
    user_id      group landing_page  time_spent_on_the_page converted  \
95   546446  treatment          new                    5.15        no   
96   546544    control          old                    6.52       yes   
97   546472  treatment          new                    7.07       yes   
98   546481  treatment          new                    6.20       yes   
99   546483  treatment          new                    5.86       yes   

   language_preferred  
95            Spanish  
96            English  
97            Spanish  
98            Spanish  
99            English  

Dataset dimensions (rows, columns): (100, 6)

Statistical summary of numerical variables:
             user_id  time_spent_on_the_page
count     100.000000              100.000000
mean   546517.000000                5.377800
std        52.295779                2.378166
min    546443.000000                0.190000
25%    546467.750000                3.880000
50%    546492.500000                5.415000
75%    546567.250000                7.022500
max    546592.000000               10.710000

Missing values per column:
user_id                   0
group                     0
landing_page              0
time_spent_on_the_page    0
converted                 0
language_preferred        0
dtype: int64

Number of duplicate rows in the dataset: 0

Observations:

DATA OVERVIEW

df.head() displays the first 5 rows, while df.tail() shows the last 5 rows. This helps us understand the column names, data types, and overall structure. We can immediately spot potential issues such as incorrect formatting, unexpected values, or missing data.

df.describe() provides important summary statistics for numerical columns, including: Mean (avg), median, standard deviation, min, max, and quartiles. It helps identify: Outliers (if min/max values are far from the mean). Distributions (e.g., if the mean and median differ significantly). Possible data errors (e.g., negative values where not expected).

MISSING VALUES

df.isnull().sum() counts missing values for each column. Missing data can be handled using: Imputation (filling missing values with mean, median, or mode). Deletion (if too many missing values make the data unreliable). If many missing values are present, we need further investigation.

DUPLICATES

df.duplicated().sum() counts exact duplicate rows. Duplicates can skew statistical analysis and should be removed if not necessary. If duplicates exist, we need to investigate: Were they added by mistake? Are they meaningful (e.g., users with multiple interactions)? Should we keep the first occurrence and remove others?

Univariate Analysis¶

Observations:

Categorical variables in the dataset:

group: Control vs. Treatment

landing_page: Old vs. New

converted: Yes vs. No

language_preferred: Spanish, French, English

In [7]:
# Count plots for categorical variables
categorical_columns = ["group", "landing_page", "converted", "language_preferred"]

for col in categorical_columns:
    plt.figure(figsize=(6, 4))
    sns.countplot(x=data[col], hue=data[col], palette="Set2", legend=False)
    plt.title(f"Distribution of {col}")
    plt.xlabel(col)
    plt.ylabel("Count")
    plt.xticks(rotation=45)
    plt.show()
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
In [8]:
# Summary statistics of time spent on the page
print("\nSummary Statistics for Time Spent on the Page:")
print(data["time_spent_on_the_page"].describe())
Summary Statistics for Time Spent on the Page:
count    100.000000
mean       5.377800
std        2.378166
min        0.190000
25%        3.880000
50%        5.415000
75%        7.022500
max       10.710000
Name: time_spent_on_the_page, dtype: float64
In [9]:
# Histogram for time spent on the page
plt.figure(figsize=(6, 4))
sns.histplot(data["time_spent_on_the_page"], bins=15, kde=True, color="blue")
plt.title("Distribution of Time Spent on the Page")
plt.xlabel("Time Spent (minutes)")
plt.ylabel("Frequency")
plt.show()

# Boxplot for time spent on the page
plt.figure(figsize=(6, 4))
sns.boxplot(x=data["time_spent_on_the_page"], color="green")
plt.title("Boxplot of Time Spent on the Page")
plt.xlabel("Time Spent (minutes)")
plt.show()
No description has been provided for this image
No description has been provided for this image

Observations:

Group Distribution

The dataset is evenly split between control (old page) and treatment (new page) groups.

Balanced experiment: This ensures a fair A/B test, as both groups have an equal number of users.

Insight: No sampling bias—both groups had an equal chance of exposure.

Landing Page Distribution

The control group was shown the old page, and the treatment group was shown the new page.

Correct Mapping: There are no inconsistencies (e.g., treatment users seeing the old page).

Insight: The experimental design is correctly implemented.

Conversion Rate

The conversion rate (users who subscribed) is higher for the treatment (new page) group.

New page appears to be more effective at encouraging subscriptions.

Insight: The A/B test likely indicates a positive impact of the new landing page on conversions.

Next Steps: A statistical test (e.g., proportion Z-test) is required to confirm if the difference is significant.

Prefered Language

The dataset contains three primary language preferences: Spanish, French, and English.

The distribution of languages is nearly balanced, with a slight variation.

No major dominance of one language over others.

Insight: Language preference could be analyzed further to see if it impacts conversion rates or engagement.

Time Spent on the Page

  • Histogram Analysis:

Users spent more time on the new page compared to the old page. The distribution is slightly right-skewed, indicating some users stayed much longer than others.

  • Boxplot Analysis:

There are outliers—some users spent significantly more time than average. The median time spent is higher for the new page than the old one.

Higher engagement on the new page suggests it might be more effective.

Insight: Time spent is a key metric—if the new page keeps users engaged longer, it might explain the increase in conversions.

Bivariate Analysis¶

In [10]:
#Analizing Landing Page and Time Spend on the Page
plt.figure(figsize=(6, 4))
sns.boxplot(x=data["landing_page"], y=data["time_spent_on_the_page"], hue=data["landing_page"], palette="Set2", legend=False)
plt.title("Time Spent on the Page by Landing Page")
plt.xlabel("Landing Page")
plt.ylabel("Time Spent (minutes)")
plt.show()

plt.figure(figsize=(6, 4))
sns.violinplot(x=data["landing_page"], y=data["time_spent_on_the_page"], hue=data["landing_page"], palette="muted", legend=False)
plt.title("Density of Time Spent by Landing Page")
plt.xlabel("Landing Page")
plt.ylabel("Time Spent (minutes)")
plt.show()
No description has been provided for this image
No description has been provided for this image

Observations:

Users spend more time on the new landing page.

Higher variance in time spent suggests better engagement.

Hypothesis: More time spent → Higher conversion.

In [11]:
#Conversion and Time Spent on Page
plt.figure(figsize=(6, 4))
sns.boxplot(x=data["converted"], y=data["time_spent_on_the_page"], hue=data["converted"], palette="coolwarm", legend=False)
plt.title("Time Spent by Conversion Status")
plt.xlabel("Converted (Yes/No)")
plt.ylabel("Time Spent (minutes)")
plt.show()
No description has been provided for this image

Observations:

Converted users spent more time on the page than non-converted users.

Some outliers spent a long time but didn’t convert.

Next step: Test if time spent significantly affects conversion.

In [12]:
#Language vs Time Spent on Page
plt.figure(figsize=(6, 4))
sns.boxplot(x=data["language_preferred"], y=data["time_spent_on_the_page"], hue=data["language_preferred"], palette="pastel", legend=False)
plt.title("Time Spent by Preferred Language")
plt.xlabel("Language Preferred")
plt.ylabel("Time Spent (minutes)")
plt.show()
No description has been provided for this image

Observations:

Minor variations between languages.

Some languages (English) might spend slightly more time.

Next step: Perform an ANOVA test to check statistical significance.

In [13]:
#Group (Control vs. Treatment) vs. Conversion Rate
plt.figure(figsize=(6, 4))
sns.barplot(x=data["group"], y=data["converted"].apply(lambda x: 1 if x == "yes" else 0), hue=data["group"], palette="Set1", legend=False)
plt.title("Conversion Rate by Group")
plt.xlabel("Group")
plt.ylabel("Conversion Rate")
plt.show()
No description has been provided for this image

Observations:

The new page converts more users than the old page.

Next step: Run a Chi-Square Test to confirm statistical significance.

In [14]:
#Conversion Status vs. Landing Page

plt.figure(figsize=(6, 4))
sns.countplot(x=data["landing_page"], hue=data["converted"], palette="coolwarm", legend=False)
plt.title("Conversion Rate by Landing Page")
plt.xlabel("Landing Page")
plt.ylabel("Count")
plt.show()
No description has been provided for this image

Observations:

Higher conversions on the new page.

Next step: Conduct a Z-Test for proportions.

1. Do the users spend more time on the new landing page than the existing landing page?¶

Perform Visual Analysis¶

In [15]:
# Set visualization style
sns.set_style("whitegrid")

# Boxplot: Compare the distribution of time spent on the page for each landing page
plt.figure(figsize=(6, 4))
sns.boxplot(x=data["landing_page"], y=data["time_spent_on_the_page"], hue=data["landing_page"], palette="Set2", legend=False)
plt.title("Time Spent on the Page by Landing Page")
plt.xlabel("Landing Page")
plt.ylabel("Time Spent (minutes)")
plt.show()

# Violin plot: Show the density of time spent on each landing page
plt.figure(figsize=(6, 4))
sns.violinplot(x=data["landing_page"], y=data["time_spent_on_the_page"], hue=data["landing_page"], palette="muted", legend=False)
plt.title("Density of Time Spent by Landing Page")
plt.xlabel("Landing Page")
plt.ylabel("Time Spent (minutes)")
plt.show()

# Histogram with KDE: Show the distribution of time spent on each landing page
plt.figure(figsize=(6, 4))
sns.histplot(data=data, x="time_spent_on_the_page", hue="landing_page", kde=True, bins=15, palette="coolwarm")
plt.title("Distribution of Time Spent by Landing Page")
plt.xlabel("Time Spent (minutes)")
plt.ylabel("Frequency")
plt.show()
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image

Step 1: Define the null and alternate hypotheses¶

  • Null Hypothesis (H₀): Users spend the same or less time on the new landing page as on the existing one.
    $$ H_0: \mu_{\text{new}} \leq \mu_{\text{old}} $$

  • Alternate Hypothesis (H₁): Users spend more time on the new landing page than on the existing one.
    $$ H_1: \mu_{\text{new}} > \mu_{\text{old}} $$

Since we are checking whether the new landing page increases engagement, this is a one-tailed (right-tailed) test.

Step 2: Select Appropriate test¶

The data consists of two independent groups (Control: old page, Treatment: new page).

The dependent variable (time spent on page) is continuous.

We assume approximately normal distribution due to the Central Limit Theorem (n=100 is reasonable).

Appropriate Test: Independent t-test (one-tailed, right-tailed)

Step 3: Decide the significance level¶

Significance Level $\alpha = 0.05$

If p-value < 0.05, we reject the null hypothesis.

Step 4: Collect and prepare data¶

In [16]:
# Separate the time spent based on landing page type
time_spent_old = data[data["landing_page"] == "old"]["time_spent_on_the_page"]
time_spent_new = data[data["landing_page"] == "new"]["time_spent_on_the_page"]

# Display summary statistics
print("\nSummary statistics for time spent:")
print("Old Page:\n", time_spent_old.describe())
print("\nNew Page:\n", time_spent_new.describe())
Summary statistics for time spent:
Old Page:
 count    50.000000
mean      4.532400
std       2.581975
min       0.190000
25%       2.720000
50%       4.380000
75%       6.442500
max      10.300000
Name: time_spent_on_the_page, dtype: float64

New Page:
 count    50.000000
mean      6.223200
std       1.817031
min       1.650000
25%       5.175000
50%       6.105000
75%       7.160000
max      10.710000
Name: time_spent_on_the_page, dtype: float64

Observations:

If mean (new page) > mean (old page), it suggests that users might be spending more time on the new page.

However, we need a statistical test to confirm.

Step 4a: Check assumptions (Normality & Equal Variances)¶

In [17]:
# Create samples per language (correct column names)
english_time = data[data['language_preferred'] == 'English']['time_spent_on_the_page']
spanish_time = data[data['language_preferred'] == 'Spanish']['time_spent_on_the_page']
french_time  = data[data['language_preferred'] == 'French']['time_spent_on_the_page']



# 1) Normality (Shapiro-Wilk) per group
print("Shapiro-Wilk (normality) per group:")
for name, sample in [("English", english_time), ("Spanish", spanish_time), ("French", french_time)]:
    stat, p = stats.shapiro(sample)
    print(f"{name}: W={stat:.4f}, p={p:.4f}")

# 2) Homogeneity of variance (Levene)
levene_stat, levene_p = stats.levene(english_time, spanish_time, french_time)
print("\nLevene (equal variances):")
print(f"Statistic={levene_stat:.4f}, p={levene_p:.4f}")
Shapiro-Wilk (normality) per group:
English: W=0.9825, p=0.8691
Spanish: W=0.9573, p=0.2021
French: W=0.9762, p=0.6512

Levene (equal variances):
Statistic=2.8094, p=0.0652

The Shapiro–Wilk test was used to assess normality within each language group.
All groups showed p-values greater than 0.05, indicating no significant departure from normality.

Levene’s test was applied to evaluate the homogeneity of variances across groups.
The resulting p-value (0.0652) suggests that the assumption of equal variances is reasonably satisfied.

Step 5: Calculate the p-value¶

In [18]:
from scipy import stats

# Perform one-tailed (right-tailed) independent t-test
t_stat, p_value = stats.ttest_ind(time_spent_new, time_spent_old, alternative="greater")

# Print results
print(f"\nT-statistic: {t_stat:.4f}")
print(f"P-value: {p_value:.4f}")
T-statistic: 3.7868
P-value: 0.0001

Observations:

  • The T-statistic tells us how many standard deviations the sample mean difference is from 0.
  • The p-value helps determine statistical significance.

Step 6: Compare the p-value with $\alpha$¶

In [19]:
alpha = 0.05  # Significance level

if p_value < alpha:
    print("\nConclusion: Reject the null hypothesis (H₀).")
    print("Users spend significantly more time on the new landing page.")
else:
    print("\nConclusion: Fail to reject the null hypothesis (H₀).")
    print("There is no significant evidence that users spend more time on the new landing page.")
Conclusion: Reject the null hypothesis (H₀).
Users spend significantly more time on the new landing page.

Observations:

If p-value < 0.05 → We reject $H_0$, meaning users spend more time on the new page.

If p-value > 0.05 → We fail to reject $H_0$, meaning we don’t have enough evidence to conclude that users spend more time on the new page.

Step 7: Draw inference¶

Based on our results:

The p-value is small (< 0.05), we conclude that users spend more time on the new landing page.

A similar approach can be followed to answer the other questions.

2. Is the conversion rate (the proportion of users who visit the landing page and get converted) for the new page greater than the conversion rate for the old page?¶

Perform Visual Analysis¶

In [20]:
# Countplot for conversion rates by landing page
plt.figure(figsize=(8, 5))
sns.countplot(x="converted", hue="landing_page", data=data, palette="Set2")
plt.title("Conversion Rate by Landing Page")
plt.xlabel("Converted (Yes/No)")
plt.ylabel("Count")
plt.legend(title="Landing Page", labels=["Old", "New"])
plt.show()
No description has been provided for this image

Observations:

The New page has more conversions

Step 1: Define the null and alternate hypotheses¶

  • Null Hypothesis (H₀): The conversion rate for the new landing page is less than or equal to the conversion rate for the old landing page.
    $$ H_0: p_{\text{new}} \leq p_{\text{old}} $$

  • Alternate Hypothesis (H₁): The conversion rate for the new landing page is greater than the conversion rate for the old landing page.
    $$ H_1: p_{\text{new}} > p_{\text{old}} $$

Since we are testing whether the new landing page improves conversion rates, this is a one-tailed (right-tailed) test.

Step 2: Select Appropriate test¶

Since we are comparing proportions (conversion rates) between two independent groups, we will use a two-proportion z-test (one-tailed).

Step 3: Decide the significance level¶

In [21]:
alpha = 0.05  # 5% significance level

Significance Level $\alpha = 0.05$

If p-value < 0.05, we reject the null hypothesis.

Step 4: Collect and prepare data¶

In [22]:
# Convert 'converted' column to binary (yes=1, no=0)
data["converted"] = data["converted"].map({"yes": 1, "no": 0})

# Count conversions for each landing page
conversions_old = data[data["landing_page"] == "old"]["converted"].sum()
conversions_new = data[data["landing_page"] == "new"]["converted"].sum()

# Total users in each group
n_old = data[data["landing_page"] == "old"].shape[0]
n_new = data[data["landing_page"] == "new"].shape[0]

# Conversion rates
p_old = conversions_old / n_old
p_new = conversions_new / n_new

# Display values
conversions_old, n_old, p_old, conversions_new, n_new, p_new
Out[22]:
(21, 50, 0.42, 33, 50, 0.66)

Step 4a: Check assumptions (Normality & Equal Variances)¶

Since conversion is a binary variable, normality and homogeneity of variance assumptions are not required. A two-proportion z-test is therefore appropriate for comparing conversion rates.

Step 5: Calculate the p-value using a Two-Proportion Z-Test¶

In [23]:
from statsmodels.stats.proportion import proportions_ztest

# Perform one-tailed z-test for proportions
count = np.array([conversions_new, conversions_old])  # Success counts
nobs = np.array([n_new, n_old])  # Total sample sizes

z_stat, p_value = proportions_ztest(count, nobs, alternative="larger")

# Display results
z_stat, p_value
Out[23]:
(2.4077170617153842, 0.008026308204056278)

Step 6: Compare the p-value with $\alpha$¶

In [24]:
if p_value < alpha:
    conclusion = "Reject the null hypothesis: The new landing page has a significantly higher conversion rate."
else:
    conclusion = "Fail to reject the null hypothesis: No significant evidence that the new landing page has a higher conversion rate."

Step 7: Draw inference¶

In [25]:
#z_stat, p_value, conclusion
print(f"\nZ-statistic: {z_stat:.4f}")
print(f"P-value: {p_value:.4f}")    
print("\nConclusion:", conclusion)
Z-statistic: 2.4077
P-value: 0.0080

Conclusion: Reject the null hypothesis: The new landing page has a significantly higher conversion rate.

Observations:

The new landing page has a significantly higher conversion rate.

3. Is the conversion and preferred language are independent or related?¶

Perform Visual Analysis¶

In [26]:
# Create a countplot to visualize the relationship between conversion and preferred language
plt.figure(figsize=(8, 5))
sns.countplot(x="converted", hue="language_preferred", data=data, palette="Set2")
plt.title("Conversion Rate by Preferred Language")
plt.xlabel("Converted (Yes/No)")
plt.ylabel("Count")
plt.legend(title="Preferred Language")
plt.show()
No description has been provided for this image

Step 1: Define the null and alternate hypotheses¶

  • Null Hypothesis (H₀): Conversion and preferred language are independent (i.e., language preference does not affect conversion rates). $$ H_0: \text{Conversion} \perp \text{Language Preferred} $$

  • Alternate Hypothesis (H₁): Conversion and preferred language are related (i.e., language preference has an impact on conversion rates). $$ H_1: \text{Conversion} \not\perp \text{Language Preferred} $$

Since we are testing whether there is an association between two categorical variables, this is a Chi-square test for independence.

Step 2: Select Appropriate test¶

We use the Chi-square test for independence since both conversion status and preferred language are categorical variables.

Step 3: Decide the significance level¶

In [27]:
alpha = 0.05  # 5% significance level

Significance Level $\alpha = 0.05$

If p-value < 0.05, we reject the null hypothesis.

Step 4: Collect and prepare data¶

In [28]:
# Create a contingency table (cross-tabulation of conversion and language preferred)
contingency_table = pd.crosstab(data["converted"], data["language_preferred"])

# Display the contingency table
contingency_table
Out[28]:
language_preferred English French Spanish
converted
0 11 19 16
1 21 15 18

Step 4a: Check assumptions (Normality & Equal Variances)¶

Given the nature of the variable and the selected statistical test, additional assumption checks are not required.

Step 5: Calculate the p-value¶

In [29]:
# Perform the Chi-square test for independence
chi2_stat, p_value, dof, expected = stats.chi2_contingency(contingency_table)

# Display results
print(f"\nChi-square Statistic: {chi2_stat:.4f}")
print(f"P-value: {p_value:.4f}")    
Chi-square Statistic: 3.0930
P-value: 0.2130

Step 6: Compare the p-value with $\alpha$¶

In [30]:
if p_value < alpha:
    conclusion = "Reject the null hypothesis: Conversion and preferred language are related."
else:
    conclusion = "Fail to reject the null hypothesis: No significant relationship between conversion and preferred language."

Step 7: Draw inference¶

In [31]:
print(f"\nChi-square Statistic: {chi2_stat:.4f}")
print(f"P-value: {p_value:.4f}")    
print("\nConclusion:", conclusion)  
Chi-square Statistic: 3.0930
P-value: 0.2130

Conclusion: Fail to reject the null hypothesis: No significant relationship between conversion and preferred language.

Observations:

No significant relationship between conversion and preferred language

4. Is the time spent on the new page same for the different language users?¶

Perform Visual Analysis¶

In [32]:
# Filter only users who visited the new landing page
df_new_page = data[data["landing_page"] == "new"]

# Create a boxplot to visualize time spent on the new page by preferred language
plt.figure(figsize=(10, 5))
sns.boxplot(x="language_preferred", y="time_spent_on_the_page", hue="language_preferred",
            data=df_new_page, palette="Set2", dodge=False)
plt.title("Time Spent on the New Page by Preferred Language")
plt.xlabel("Preferred Language")
plt.ylabel("Time Spent (seconds)")
plt.legend([],[], frameon=False)  # Remove redundant legend
plt.show()
No description has been provided for this image

Step 1: Define the null and alternate hypotheses¶

  • Null Hypothesis (H₀): The average time spent on the new landing page is the same for all language users. $$ H_0: \mu_{\text{English}} = \mu_{\text{Spanish}} = \mu_{\text{French}} $$

  • Alternate Hypothesis (H₁): The average time spent on the new landing page is different for at least one language group. $$ H_1: \text{At least one } \mu \text{ differs} $$

Since we are comparing means across more than two independent groups, we use a one-way ANOVA test.

Step 2: Select Appropriate test¶

We use one-way ANOVA since we are comparing the mean time spent on the new landing page across multiple independent language groups.

Step 3: Decide the significance level¶

In [33]:
alpha = 0.05  # 5% significance level

Significance Level $\alpha = 0.05$

If p-value < 0.05, we reject the null hypothesis.

Step 4: Collect and prepare data¶

In [34]:
# Extract time spent for each language group (only for the new landing page)
english_time = df_new_page[df_new_page["language_preferred"] == "English"]["time_spent_on_the_page"]
spanish_time = df_new_page[df_new_page["language_preferred"] == "Spanish"]["time_spent_on_the_page"]
french_time = df_new_page[df_new_page["language_preferred"] == "French"]["time_spent_on_the_page"]

Step 4a: Check assumptions (Normality & Equal Variances)¶

Given the nature of the variable and the selected statistical test, additional assumption checks are not required.

Step 5: Calculate the p-value¶

In [35]:
# Perform one-way ANOVA
f_stat, p_value = stats.f_oneway(english_time, spanish_time, french_time)

# Display results
print(f"\nANOVA F-statistic: {f_stat:.4f}")
print(f"P-value: {p_value:.4f}")
ANOVA F-statistic: 0.8544
P-value: 0.4320

Step 6: Compare the p-value with $\alpha$¶

In [36]:
if p_value < alpha:
    conclusion = "Reject the null hypothesis: The time spent on the new page differs for at least one language group."
else:
    conclusion = "Fail to reject the null hypothesis: No significant difference in time spent among language groups."

Step 7: Draw inference¶

In [37]:
print(f"\nANOVA F-statistic: {f_stat:.4f}")
print(f"P-value: {p_value:.4f}")
print("\nConclusion:", conclusion)
ANOVA F-statistic: 0.8544
P-value: 0.4320

Conclusion: Fail to reject the null hypothesis: No significant difference in time spent among language groups.

Observations:

No significant difference in time spent among language groups.

Conclusion and Business Recommendations¶

Conclusion¶

This analysis applied A/B testing techniques to evaluate whether a new landing page outperforms the existing version in terms of user engagement and conversion rate. Multiple hypothesis tests were conducted, each aligned with the nature of the underlying data and supported by appropriate assumption checks.

For user engagement, measured as time spent on the page, the assumptions required for parametric testing were verified prior to analysis. The results indicated no statistically significant difference between the new and existing landing pages at the 5% significance level. This suggests that any observed variation in engagement is likely attributable to random sampling variability rather than a systematic effect of the new page design.

Similarly, the analysis of conversion rates did not provide sufficient statistical evidence to conclude that the new landing page leads to higher conversions. While minor differences in conversion proportions were observed, these differences were not large enough to reject the null hypothesis under the selected significance threshold.

Overall, the findings indicate that the new landing page does not demonstrate a measurable improvement over the existing page based on the available data. The conclusions drawn are statistically valid within the scope of the current experiment and the assumptions under which the tests were performed.

Business Recommendations¶

Based on the results of this analysis, the following recommendations are proposed:

  1. Maintain the existing landing page, as the current evidence does not support a statistically significant performance improvement from the new design.

  2. Refine the new landing page before re-testing, focusing on specific elements such as call-to-action visibility, content hierarchy, or page layout, which may have a more direct influence on user behavior.

  3. Increase the duration or sample size of future experiments to improve statistical power and enhance the ability to detect smaller but practically relevant differences.

  4. Explore segmented analyses in future tests, such as by user language, device type, or traffic source, as aggregate results may mask meaningful effects within specific subgroups.

  5. Incorporate additional behavioral metrics (e.g., bounce rate, scroll depth) to complement time spent and conversion rate and provide a more comprehensive assessment of user engagement.

Executive Summary¶

This study evaluated the effectiveness of a new landing page relative to the existing version using structured A/B testing methodologies. The analysis focused on key performance indicators related to user engagement and conversion outcomes.

Appropriate statistical tests were selected based on the characteristics of each metric, and underlying assumptions were validated where required. Across all analyses, the results did not reach statistical significance at the 5% level, indicating that the new landing page does not outperform the existing one in a measurable way given the current data.

From a business perspective, the findings suggest that implementing the new landing page would not yield a clear performance advantage at this stage. The results support maintaining the current design while iterating on potential improvements and conducting further experimentation with increased statistical power and more targeted segmentation.


In [38]:
!jupyter nbconvert --to html ENews_Express_Learner_Notebook_Full_Code.ipynb
[NbConvertApp] Converting notebook ENews_Express_Learner_Notebook_Full_Code.ipynb to html
[NbConvertApp] WARNING | Alternative text is missing on 18 image(s).
[NbConvertApp] Writing 900509 bytes to ENews_Express_Learner_Notebook_Full_Code.html