Σε αυτή την εργασία αναλύεται ένα A/B test για νέα διαφημιστική
καμπάνια. Η ομάδα control είδε ουδέτερο μήνυμα, δηλαδή
psa, ενώ η ομάδα treatment είδε τη νέα
διαφήμιση, δηλαδή ad.
Το βασικό ερώτημα είναι:
Η νέα διαφήμιση αυξάνει πραγματικά τις μετατροπές ή η διαφορά που παρατηρούμε μπορεί να οφείλεται στην τύχη;
Θα εξετάσουμε το ερώτημα με δύο τρόπους:
marketing_AB.csv από το
Kaggle.packages <- c("tidyverse", "pwr", "broom", "scales", "janitor")
missing_packages <- packages[!sapply(packages, requireNamespace, quietly = TRUE)]
if (length(missing_packages) > 0) {
install.packages(missing_packages)
}
invisible(lapply(packages, library, character.only = TRUE))set.seed(42)
data_path <- params$data_file
if (!file.exists(data_path)) {
stop("Δεν βρέθηκε το αρχείο marketing_AB.csv. Βάλτε το CSV στον ίδιο φάκελο με το .Rmd ή αλλάξτε το params$data_file στο YAML header.")
}
ads <- read_csv(data_path, show_col_types = FALSE) |>
janitor::clean_names() |>
mutate(
group = factor(test_group, levels = c("psa", "ad")),
converted = as.integer(converted),
most_ads_day = factor(
most_ads_day,
levels = c("Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday")
)
)
glimpse(ads)## Rows: 588,101
## Columns: 8
## $ x1 <dbl> 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16…
## $ user_id <dbl> 1069124, 1119715, 1144181, 1435133, 1015700, 1137664, 11…
## $ test_group <chr> "ad", "ad", "ad", "ad", "ad", "ad", "ad", "ad", "ad", "a…
## $ converted <int> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0,…
## $ total_ads <dbl> 130, 93, 21, 355, 276, 734, 264, 17, 21, 142, 209, 47, 6…
## $ most_ads_day <fct> Monday, Tuesday, Tuesday, Tuesday, Friday, Saturday, Wed…
## $ most_ads_hour <dbl> 20, 22, 18, 10, 14, 10, 13, 18, 19, 14, 11, 13, 20, 13, …
## $ group <fct> ad, ad, ad, ad, ad, ad, ad, ad, ad, ad, ad, ad, ad, ad, …
Το dataset περιέχει παρατηρήσεις χρηστών, την ομάδα στην οποία
ανήκουν (psa ή ad), καθώς και το binary
outcome converted, όπου:
converted = 1: ο χρήστης έκανε conversion.converted = 0: ο χρήστης δεν έκανε conversion.Αρχικά δημιουργούμε ένα απλό προσομοιωμένο A/B test. Η ομάδα control έχει baseline conversion rate 8%, ενώ η treatment ομάδα έχει πραγματικό conversion rate 10%. Άρα το πραγματικό effect που θέλουμε να εντοπίσουμε είναι +2 ποσοστιαίες μονάδες.
# Παράμετροι πειράματος
n_control <- 8000
n_treatment <- 8000
p_control <- 0.08
p_treatment <- 0.10
experiment <- tibble(
user_id = 1:(n_control + n_treatment),
group = c(
rep("control", n_control),
rep("treatment", n_treatment)
),
converted = c(
rbinom(n_control, 1, p_control),
rbinom(n_treatment, 1, p_treatment)
)
) |>
mutate(group = factor(group, levels = c("control", "treatment")))
glimpse(experiment)## Rows: 16,000
## Columns: 3
## $ user_id <int> 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 1…
## $ group <fct> control, control, control, control, control, control, contro…
## $ converted <int> 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 0, …
Υπολογίζουμε για κάθε ομάδα:
summary_stats <- experiment |>
group_by(group) |>
summarise(
n = n(),
conversions = sum(converted),
conversion_rate = mean(converted),
se = sqrt(conversion_rate * (1 - conversion_rate) / n),
.groups = "drop"
) |>
mutate(
ci_lower = conversion_rate - 1.96 * se,
ci_upper = conversion_rate + 1.96 * se
)
summary_statsΗ μεταβλητή conversion_rate είναι η βασική μετρική του
A/B test. Όσο υψηλότερη είναι, τόσο μεγαλύτερο ποσοστό χρηστών έκανε
conversion.
ggplot(summary_stats, aes(x = group, y = conversion_rate, fill = group)) +
geom_col(width = 0.55, alpha = 0.85) +
geom_errorbar(
aes(ymin = ci_lower, ymax = ci_upper),
width = 0.15,
linewidth = 0.8
) +
geom_text(
aes(label = percent(conversion_rate, accuracy = 0.1)),
vjust = -1.3,
size = 5,
fontface = "bold"
) +
scale_y_continuous(labels = percent, limits = c(0, 0.13)) +
scale_fill_manual(values = c("control" = "#6b7280", "treatment" = "#3b82f6")) +
labs(
title = "Simulated A/B Test: Conversion rate ανά ομάδα",
subtitle = "Με 95% διαστήματα εμπιστοσύνης",
x = NULL,
y = "Conversion rate"
) +
theme_minimal(base_size = 13) +
theme(legend.position = "none")Από το γράφημα βλέπουμε αν η treatment ομάδα έχει υψηλότερο conversion rate από την control ομάδα και πόση αβεβαιότητα υπάρχει γύρω από κάθε εκτίμηση.
prop.test()Ο στατιστικός έλεγχος που χρησιμοποιούμε είναι έλεγχος δύο αναλογιών.
Οι υποθέσεις είναι:
sim_clicks <- summary_stats |>
arrange(group) |>
pull(conversions)
sim_visitors <- summary_stats |>
arrange(group) |>
pull(n)
test_result <- prop.test(
x = sim_clicks,
n = sim_visitors,
conf.level = 0.95,
correct = FALSE
)
test_result##
## 2-sample test for equality of proportions without continuity correction
##
## data: sim_clicks out of sim_visitors
## X-squared = 5.7725, df = 1, p-value = 0.01628
## alternative hypothesis: two.sided
## 95 percent confidence interval:
## -0.019744875 -0.002005125
## sample estimates:
## prop 1 prop 2
## 0.084625 0.095500
sim_test_tidy <- broom::tidy(test_result) |>
select(estimate1, estimate2, statistic, p.value, conf.low, conf.high)
sim_test_tidyΠροσοχή: επειδή η σειρά είναι control,
treatment, το prop.test() δίνει confidence
interval για τη διαφορά:
\[ control - treatment \]
Για να το ερμηνεύσουμε ως effect της treatment ομάδας, κοιτάμε αντίστροφα:
\[ treatment - control \]
Υπολογίζουμε χειρωνακτικά το pooled estimate, το pooled standard error, τη διαφορά και το 95% confidence interval.
p_control_hat <- summary_stats |>
filter(group == "control") |>
pull(conversion_rate)
p_treatment_hat <- summary_stats |>
filter(group == "treatment") |>
pull(conversion_rate)
n_control_hat <- summary_stats |>
filter(group == "control") |>
pull(n)
n_treatment_hat <- summary_stats |>
filter(group == "treatment") |>
pull(n)
# 1. Pooled estimate
p_pool <- sum(sim_clicks) / sum(sim_visitors)
# 2. Pooled standard error
se_pool <- sqrt(
p_pool * (1 - p_pool) *
(1 / n_control_hat + 1 / n_treatment_hat)
)
# 3. Difference: treatment - control
delta_sim <- p_treatment_hat - p_control_hat
# 4. 95% CI με pooled SE
margin_pool <- 1.96 * se_pool
manual_ci_pooled <- c(delta_sim - margin_pool, delta_sim + margin_pool)
# 5. Unpooled Wald CI, χρήσιμο για σύγκριση
se_unpooled <- sqrt(
p_control_hat * (1 - p_control_hat) / n_control_hat +
p_treatment_hat * (1 - p_treatment_hat) / n_treatment_hat
)
manual_ci_unpooled <- c(
delta_sim - 1.96 * se_unpooled,
delta_sim + 1.96 * se_unpooled
)
# Το prop.test CI είναι για control - treatment, άρα το αντιστρέφουμε
prop_ci_treatment_minus_control <- c(
-sim_test_tidy$conf.high,
-sim_test_tidy$conf.low
)
ci_comparison_sim <- tibble(
method = c("Manual pooled", "Manual unpooled Wald", "prop.test inverted"),
estimate_delta = c(delta_sim, delta_sim, delta_sim),
ci_low = c(manual_ci_pooled[1], manual_ci_unpooled[1], prop_ci_treatment_minus_control[1]),
ci_high = c(manual_ci_pooled[2], manual_ci_unpooled[2], prop_ci_treatment_minus_control[2])
)
ci_comparison_simΤο χειρωνακτικό CI με pooled SE μπορεί να μη συμπίπτει απόλυτα με το
CI του prop.test(), επειδή το prop.test()
χρησιμοποιεί διαφορετική προσέγγιση για το confidence interval. Το
pooled SE χρησιμοποιείται κυρίως για τον έλεγχο της μηδενικής υπόθεσης,
όπου υποθέτουμε ότι οι δύο αναλογίες είναι ίσες.
Το power analysis απαντά στο ερώτημα:
Πόσους χρήστες χρειαζόμασταν ανά ομάδα για να έχουμε 80% πιθανότητα να εντοπίσουμε διαφορά από 8% σε 10%, αν αυτή η διαφορά όντως υπάρχει;
effect_size <- ES.h(p1 = p_treatment, p2 = p_control)
sample_size_calc <- pwr.2p.test(
h = effect_size,
sig.level = 0.05,
power = 0.80,
alternative = "two.sided"
)
sample_size_calc##
## Difference of proportion power calculation for binomial distribution (arcsine transformation)
##
## h = 0.069988
## n = 3204.715
## sig.level = 0.05
## power = 0.8
## alternative = two.sided
##
## NOTE: same sample sizes
required_n_per_group <- ceiling(sample_size_calc$n)
actual_n_per_group <- n_control
tibble(
required_n_per_group = required_n_per_group,
actual_n_per_group = actual_n_per_group,
required_total_n = 2 * required_n_per_group,
actual_total_n = n_control + n_treatment
)Στο προσομοιωμένο παράδειγμα τρέξαμε 8000 χρήστες ανά ομάδα. Το απαιτούμενο δείγμα για power 80% είναι περίπου 3205 χρήστες ανά ομάδα. Άρα το simulated experiment έχει αρκετά μεγαλύτερο δείγμα από το ελάχιστο απαιτούμενο.
Πριν κοιτάξουμε το conversion rate, ελέγχουμε αν οι ομάδες φαίνονται ισορροπημένες ως προς μεταβλητές που δεν πρέπει να επηρεάζονται από το treatment. Αυτό λέγεται invariant check.
ad και psaggplot(group_balance, aes(x = group, y = pct, fill = group)) +
geom_col(width = 0.55, alpha = 0.85) +
geom_text(
aes(label = percent(pct, accuracy = 0.1)),
vjust = -0.5,
size = 5,
fontface = "bold"
) +
scale_y_continuous(labels = percent, limits = c(0, 1)) +
scale_fill_manual(values = c("psa" = "#6b7280", "ad" = "#3b82f6")) +
labs(
title = "Αναλογία χρηστών ανά ομάδα",
subtitle = "Έλεγχος αν το split είναι κοντά στο 50/50",
x = NULL,
y = "% χρηστών"
) +
theme_minimal(base_size = 13) +
theme(legend.position = "none")Αν το split δεν είναι 50/50, αυτό δεν σημαίνει απαραίτητα ότι το πείραμα είναι άκυρο. Μπορεί η καμπάνια να σχεδιάστηκε με άνιση κατανομή. Ωστόσο, το μικρότερο control group δίνει μικρότερη ακρίβεια στην εκτίμηση του baseline conversion rate.
day_balance <- ads |>
count(group, most_ads_day) |>
group_by(group) |>
mutate(pct = n / sum(n)) |>
ungroup()
day_balanceggplot(day_balance, aes(x = most_ads_day, y = pct, fill = group)) +
geom_col(position = "dodge", alpha = 0.85) +
scale_y_continuous(labels = percent) +
scale_fill_manual(values = c("psa" = "#6b7280", "ad" = "#3b82f6")) +
labs(
title = "Invariant check: κατανομή ανά ημέρα εβδομάδας",
subtitle = "Οι αναλογίες ανά ημέρα πρέπει να είναι σχετικά παρόμοιες μεταξύ των ομάδων",
x = NULL,
y = "% της κάθε ομάδας",
fill = "Group"
) +
theme_minimal(base_size = 13) +
theme(axis.text.x = element_text(angle = 35, hjust = 1))day_imbalance <- day_balance |>
select(group, most_ads_day, pct) |>
pivot_wider(names_from = group, values_from = pct) |>
mutate(
diff_ad_minus_psa = ad - psa,
abs_diff = abs(diff_ad_minus_psa)
) |>
arrange(desc(abs_diff))
day_imbalanceΟ παραπάνω πίνακας δείχνει σε ποιες ημέρες υπάρχει η μεγαλύτερη
διαφορά στην κατανομή των χρηστών μεταξύ ad και
psa. Μεγάλη πρακτική ανισορροπία θα μπορούσε να
δημιουργήσει bias, ειδικά αν η ημέρα εβδομάδας σχετίζεται με την
πιθανότητα conversion.
prop.test()ads_summary <- ads |>
group_by(group) |>
summarise(
n = n(),
conversions = sum(converted),
conversion_rate = mean(converted),
se = sqrt(conversion_rate * (1 - conversion_rate) / n),
.groups = "drop"
) |>
mutate(
ci_lower = conversion_rate - 1.96 * se,
ci_upper = conversion_rate + 1.96 * se
)
ads_summaryggplot(ads_summary, aes(x = group, y = conversion_rate, fill = group)) +
geom_col(width = 0.55, alpha = 0.85) +
geom_errorbar(
aes(ymin = ci_lower, ymax = ci_upper),
width = 0.15,
linewidth = 0.8
) +
geom_text(
aes(label = percent(conversion_rate, accuracy = 0.01)),
vjust = -1.1,
size = 5,
fontface = "bold"
) +
scale_y_continuous(labels = percent, limits = c(0, max(ads_summary$ci_upper) * 1.25)) +
scale_fill_manual(values = c("psa" = "#6b7280", "ad" = "#3b82f6")) +
labs(
title = "Real A/B Test: Conversion rate ανά ομάδα",
subtitle = "Με 95% διαστήματα εμπιστοσύνης",
x = NULL,
y = "Conversion rate"
) +
theme_minimal(base_size = 13) +
theme(legend.position = "none")real_clicks <- ads_summary |>
arrange(group) |>
pull(conversions)
real_visitors <- ads_summary |>
arrange(group) |>
pull(n)
real_test <- prop.test(
x = real_clicks,
n = real_visitors,
correct = FALSE
)
real_test_tidy <- broom::tidy(real_test) |>
select(estimate1, estimate2, statistic, p.value, conf.low, conf.high)
real_test_tidyΕπειδή η σειρά των ομάδων είναι psa, ad, το
estimate1 είναι το conversion rate του psa και
το estimate2 είναι το conversion rate του ad.
Το confidence interval του prop.test() είναι για τη
διαφορά:
\[ psa - ad \]
Για επιχειρηματική ερμηνεία, όμως, μας ενδιαφέρει:
\[ ad - psa \]
real_effect <- tibble(
psa_rate = real_test_tidy$estimate1,
ad_rate = real_test_tidy$estimate2,
absolute_lift = ad_rate - psa_rate,
relative_lift = absolute_lift / psa_rate,
ci_low_ad_minus_psa = -real_test_tidy$conf.high,
ci_high_ad_minus_psa = -real_test_tidy$conf.low,
p_value = real_test_tidy$p.value
)
real_effect |>
mutate(
psa_rate = percent(psa_rate, accuracy = 0.01),
ad_rate = percent(ad_rate, accuracy = 0.01),
absolute_lift = percent(absolute_lift, accuracy = 0.01),
relative_lift = percent(relative_lift, accuracy = 0.1),
ci_low_ad_minus_psa = percent(ci_low_ad_minus_psa, accuracy = 0.01),
ci_high_ad_minus_psa = percent(ci_high_ad_minus_psa, accuracy = 0.01)
)Με βάση το πραγματικό dataset, το conversion rate της ομάδας
ad είναι μεγαλύτερο από το conversion rate της ομάδας
psa. Το p-value δείχνει αν αυτή η διαφορά είναι στατιστικά
σημαντική.
ads_by_day <- ads |>
group_by(most_ads_day, group) |>
summarise(
n = n(),
conversions = sum(converted),
conversion_rate = mean(converted),
se = sqrt(conversion_rate * (1 - conversion_rate) / n),
.groups = "drop"
) |>
mutate(
ci_lower = conversion_rate - 1.96 * se,
ci_upper = conversion_rate + 1.96 * se
)
ads_by_dayggplot(
ads_by_day,
aes(x = most_ads_day, y = conversion_rate, color = group, group = group)
) +
geom_line(linewidth = 1.2) +
geom_point(size = 3) +
geom_ribbon(
aes(ymin = ci_lower, ymax = ci_upper, fill = group),
alpha = 0.15,
color = NA
) +
scale_y_continuous(labels = percent_format(accuracy = 0.1)) +
scale_color_manual(values = c("psa" = "#6b7280", "ad" = "#3b82f6")) +
scale_fill_manual(values = c("psa" = "#6b7280", "ad" = "#3b82f6")) +
labs(
title = "Conversion rate ανά ημέρα εβδομάδας",
subtitle = "Με 95% διαστήματα εμπιστοσύνης",
x = NULL,
y = "Conversion rate",
color = "Group",
fill = "Group"
) +
theme_minimal(base_size = 13) +
theme(axis.text.x = element_text(angle = 35, hjust = 1))day_lift <- ads_by_day |>
select(most_ads_day, group, conversion_rate) |>
pivot_wider(names_from = group, values_from = conversion_rate) |>
mutate(
absolute_lift = ad - psa,
relative_lift = absolute_lift / psa
) |>
arrange(desc(absolute_lift))
day_lift |>
mutate(
psa = percent(psa, accuracy = 0.01),
ad = percent(ad, accuracy = 0.01),
absolute_lift = percent(absolute_lift, accuracy = 0.01),
relative_lift = percent(relative_lift, accuracy = 0.1)
)Η ημέρα με τη μεγαλύτερη απόλυτη διαφορά μεταξύ ad και
psa είναι η Tuesday. Αυτό σημαίνει ότι
εκεί η καμπάνια φαίνεται να έχει τη μεγαλύτερη διαφορά conversion rate
σε σχέση με το control.
Ορίζουμε ως ελάχιστο επιχειρηματικά ουσιαστικό effect:
\[ \delta_{min} = 0.005 \]
Δηλαδή, για να προτείνουμε υλοποίηση/κλιμάκωση της καμπάνιας, δεν αρκεί απλώς να υπάρχει στατιστικά σημαντική διαφορά. Θέλουμε το lift να είναι τουλάχιστον 0.5 ποσοστιαίες μονάδες.
delta_min <- 0.005
absolute_lift <- real_effect$absolute_lift
relative_lift <- real_effect$relative_lift
ci_low <- real_effect$ci_low_ad_minus_psa
ci_high <- real_effect$ci_high_ad_minus_psa
p_value <- real_effect$p_value
classify_ci <- function(ci_low, ci_high, delta_min) {
if (ci_low > delta_min) {
return("A")
} else if (ci_low > 0 && ci_high > delta_min) {
return("B")
} else if (ci_low > 0 && ci_high <= delta_min) {
return("C")
} else if (ci_low <= 0 && ci_high >= delta_min) {
return("D")
} else if (ci_low <= 0 && ci_high > 0 && ci_high < delta_min) {
return("E")
} else if (ci_high <= 0) {
return("F")
} else {
return("Unclassified")
}
}
ci_case <- classify_ci(ci_low, ci_high, delta_min)
case_explanation <- case_when(
ci_case == "A" ~ "Το 95% CI είναι ολόκληρο πάνω από το επιχειρηματικό κατώφλι. Η καμπάνια είναι στατιστικά και επιχειρηματικά ισχυρή.",
ci_case == "B" ~ "Το 95% CI είναι πάνω από το 0, αλλά ακουμπά/διασχίζει το επιχειρηματικό κατώφλι. Υπάρχει στατιστική σημαντικότητα, αλλά η επιχειρηματική ουσία έχει αβεβαιότητα.",
ci_case == "C" ~ "Το 95% CI είναι πάνω από το 0 αλλά κάτω από το επιχειρηματικό κατώφλι. Υπάρχει στατιστική σημαντικότητα, αλλά όχι αρκετή επιχειρηματική ουσία.",
ci_case == "D" ~ "Το 95% CI περιλαμβάνει και το 0 και το επιχειρηματικό κατώφλι. Το αποτέλεσμα είναι αβέβαιο/inconclusive.",
ci_case == "E" ~ "Το 95% CI περιλαμβάνει το 0 και βρίσκεται κάτω από το επιχειρηματικό κατώφλι. Δεν υπάρχει καθαρή ένδειξη για ουσιαστικό θετικό effect.",
ci_case == "F" ~ "Το 95% CI είναι κάτω από το 0. Η καμπάνια φαίνεται πιθανώς επιβλαβής.",
TRUE ~ "Δεν ταξινομήθηκε."
)
recommendation <- case_when(
ci_case == "A" ~ "Σύσταση: Υλοποίηση/κλιμάκωση της διαφημιστικής καμπάνιας.",
ci_case == "B" ~ "Σύσταση: Προσεκτική υλοποίηση ή επιπλέον test, επειδή υπάρχει στατιστικό lift αλλά όχι πλήρης βεβαιότητα για το επιχειρηματικό κατώφλι.",
ci_case == "C" ~ "Σύσταση: Όχι άμεση υλοποίηση, εκτός αν το κόστος είναι πολύ χαμηλό.",
ci_case == "D" ~ "Σύσταση: Συνέχιση/επανάληψη του test για περισσότερα δεδομένα.",
ci_case == "E" ~ "Σύσταση: Μη υλοποίηση με τα τρέχοντα δεδομένα.",
ci_case == "F" ~ "Σύσταση: Διακοπή της καμπάνιας ή επανασχεδιασμός.",
TRUE ~ "Σύσταση: Χρειάζεται περαιτέρω έλεγχος."
)
business_summary <- tibble(
delta_min = delta_min,
absolute_lift = absolute_lift,
relative_lift = relative_lift,
ci_low = ci_low,
ci_high = ci_high,
p_value = p_value,
ci_case = ci_case,
explanation = case_explanation,
recommendation = recommendation
)
business_summary |>
mutate(
delta_min = percent(delta_min, accuracy = 0.01),
absolute_lift = percent(absolute_lift, accuracy = 0.01),
relative_lift = percent(relative_lift, accuracy = 0.1),
ci_low = percent(ci_low, accuracy = 0.01),
ci_high = percent(ci_high, accuracy = 0.01)
)Η τελική κατηγορία CI είναι A.
Το 95% CI είναι ολόκληρο πάνω από το επιχειρηματικό κατώφλι. Η καμπάνια
είναι στατιστικά και επιχειρηματικά ισχυρή.
Σύσταση: Υλοποίηση/κλιμάκωση της διαφημιστικής
καμπάνιας.
Το p-value του πραγματικού A/B test είναι:
Εφόσον p-value < 0.05, απορρίπτουμε τη μηδενική
υπόθεση H₀. Αυτό σημαίνει ότι υπάρχει στατιστικά σημαντική διαφορά στο
conversion rate μεταξύ psa και ad.
Πιο συγκεκριμένα, το conversion rate του ad είναι 2.55%,
ενώ το conversion rate του psa είναι 1.79%.
prop.test(); Αν όχι,
γιατί;Στο simulated μέρος, το χειρωνακτικό CI με pooled SE δεν είναι
απαραίτητο να συμπίπτει απόλυτα με το CI του
prop.test().
Ο λόγος είναι ότι το pooled SE βασίζεται στην υπόθεση της H₀, δηλαδή
ότι οι δύο αναλογίες είναι ίσες. Είναι κατάλληλο για τον υπολογισμό του
test statistic. Το confidence interval, όμως, μπορεί να υπολογιστεί με
διαφορετική προσέγγιση, όπως κάνει το prop.test().
Άρα περιμένουμε τα αποτελέσματα να είναι κοντά, αλλά όχι ακριβώς ίδια.
Στο simulated A/B test, το power analysis έδειξε ότι χρειαζόμασταν περίπου 3205 χρήστες ανά ομάδα για power 80% και α = 0.05.
Εμείς τρέξαμε 8000 χρήστες ανά ομάδα, δηλαδή 16000 συνολικά.
Αυτό σημαίνει ότι το πείραμα έχει περισσότερο δείγμα από το ελάχιστο απαιτούμενο. Άρα έχει αρκετή στατιστική ισχύ για να εντοπίσει μια διαφορά της τάξης των 2 ποσοστιαίων μονάδων.
Η πραγματική ανάλυση δείχνει ότι η ομάδα που είδε τη νέα διαφήμιση
(ad) έχει υψηλότερο conversion rate από την ομάδα control
(psa). Το αποτέλεσμα είναι στατιστικά σημαντικό, επειδή το
p-value είναι μικρότερο από 0.05.
Επιπλέον, το absolute lift είναι 0.77% και το 95% CI για το lift είναι [0.60%, 0.94%]. Εφόσον το confidence interval είναι πάνω από το επιχειρηματικό κατώφλι 0.50%, η σύσταση είναι θετική.
Τελική σύσταση: η fintech startup μπορεί να υλοποιήσει ή να κλιμακώσει τη νέα διαφημιστική καμπάνια, υπό την προϋπόθεση ότι το κόστος απόκτησης/conversion παραμένει αποδεκτό και ότι δεν υπάρχουν άλλοι επιχειρησιακοί περιορισμοί.