# Φόρτωση δεδομένων
set.seed(42)
ads <- read_csv("marketing_AB.csv") |>
janitor::clean_names() |>
mutate(
group = factor(test_group, levels = c("psa", "ad")),
converted = as.integer(converted)
)
## New names:
## Rows: 588101 Columns: 7
## ── Column specification
## ──────────────────────────────────────────────────────── Delimiter: "," chr
## (2): test group, most ads day dbl (4): ...1, user id, total ads, most ads hour
## lgl (1): converted
## ℹ Use `spec()` to retrieve the full column specification for this data. ℹ
## Specify the column types or set `show_col_types = FALSE` to quiet this message.
## • `` -> `...1`
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 <chr> "Monday", "Tuesday", "Tuesday", "Tuesday", "Friday", "Sa…
## $ 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, …
# Παράμετροι πειράματος
n_control <- 8000 # μέγεθος ομάδας ελέγχου
n_treatment <- 8000 # μέγεθος πειραματικής ομάδας
p_control <- 0.08 # baseline conversion rate
p_treatment <- 0.10 # μετά την αλλαγή (true effect = +2%)
# Δημιουργία ενός tibble «experiment» με στήλες:
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))
)
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 <chr> "control", "control", "control", "control", "control", "cont…
## $ converted <int> 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 0, …
# Υπολογισμός ανά ομάδα: n, conversions, conversion_rate, standard error (se) και 95% CI (ci_lower, ci_upper)
summary_stats <- experiment |>
group_by(group) |>
summarise(
n = n(),
conversions = sum(converted),
conversion_rate = mean(converted),
se = sqrt(conversion_rate * (1 - conversion_rate) / n),
ci_lower = conversion_rate - 1.96 * se,
ci_upper = conversion_rate + 1.96 * se
)
summary_stats
## # A tibble: 2 × 7
## group n conversions conversion_rate se ci_lower ci_upper
## <chr> <int> <int> <dbl> <dbl> <dbl> <dbl>
## 1 control 8000 677 0.0846 0.00311 0.0785 0.0907
## 2 treatment 8000 764 0.0955 0.00329 0.0891 0.102
# Οπτικοποίηση με ggplot2 (geom_col + geom_errorbar)
ggplot(summary_stats, aes(x = group, y = conversion_rate, fill = group)) +
geom_col(width = 0.5, 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.5, size = 5, fontface = "bold") +
scale_y_continuous(labels = percent, limits = c(0, 0.16)) +
scale_fill_manual(values = c("control" = "#00cc00",
"treatment" = "#3b82f6")) +
labs(
title = "A/B Test: Πιθανότητα conversion ανά ομάδα",
subtitle = "Με 95% διαστήματα εμπιστοσύνης",
x = NULL, y = "Conversion probability"
) +
theme_minimal(base_size = 13) +
theme(legend.position = "none")
# Διεξαγωγή ελέγχου υποθέσεων με prop.test()
conversions <- c(summary_stats$conversions[1], summary_stats$conversions[2])
visitors <- c(summary_stats$n[1], summary_stats$n[2])
test_result <- prop.test(
x = conversions,
n = visitors,
conf.level = 0.95,
correct = FALSE
)
test_result
##
## 2-sample test for equality of proportions without continuity correction
##
## data: conversions out of 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
# Χειρωνακτική επαλήθευση
## 1. Pooled estimate
p_pool <- sum(conversions) / sum(visitors)
## 2. Pooled standard error
se_pool <- sqrt(p_pool * (1 - p_pool) *
(1/visitors[1] + 1/visitors[2]))
## 3. Διαφορά και διάστημα εμπιστοσύνης
delta <- summary_stats$conversion_rate[2] - summary_stats$conversion_rate[1]
m <- 1.96 * se_pool
cat(sprintf("Pooled p̂ = %.4f\n", p_pool))
## Pooled p̂ = 0.0901
cat(sprintf("Pooled SE = %.4f\n", se_pool))
## Pooled SE = 0.0045
cat(sprintf("δ = %.4f\n", delta))
## δ = 0.0109
cat(sprintf("95%% CI for δ: [%.4f, %.4f]\n", delta - m, delta + m))
## 95% CI for δ: [0.0020, 0.0197]
Παρατηρείται ότι:
Δηλαδή, οι τιμές είναι αντίθετες. Αύτο σημβαίνει επειδη η CI του prop.test() προκυπτει από p1-p2 (όπου p1:p_control και p2:p_treatment), ενώ το το CI από την χειρωνακτική επαλήθευση προκύπτει απο delta <- summary_stats\(conversion_rate[2] - summary_stats\)conversion_rate[1] = p2 - p1
# Power Analysis — υπολόγισε με pwr.2p.test()
## h = Cohen's effect size για δύο αναλογίες
effect_size <- ES.h(p1 = 0.10, p2 = 0.08)
cat(sprintf("Cohen's h = %.4f\n", effect_size))
## Cohen's h = 0.0700
## Υπολογισμός απαιτούμενου μεγέθους δείγματος ανά ομάδα
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
Θα χρειαζόμασταν >3205 δείγματα για power = 80% και α = 0.05. Με το δείγμα μας των 8000 υπερππληρόυμε τις προυποθέσεις
# Invariants check
## 1. Αναλογία στις ομάδες
ads |>
count(group) |>
mutate(pct = n / sum(n))
## # A tibble: 2 × 3
## group n pct
## <fct> <int> <dbl>
## 1 psa 23524 0.0400
## 2 ad 564577 0.960
## 2. Κατανομή ανά ημέρα εβδομάδας
ads |>
count(group, most_ads_day) |>
group_by(group) |>
mutate(pct = n / sum(n)) |>
ggplot(aes(x = most_ads_day, y = pct, fill = group)) +
geom_col(position = "dodge") +
scale_y_continuous(labels = percent) +
labs(title = "Invariant check: κατανομή ανά ημέρα",
x = NULL, y = "% της ομάδας") +
theme_minimal()
Note: Η αναλογία ad/psa δεν είναι 50/50 ,είναι 0.04/0.96
# Οπτικοποίηση της κατανομής ανά ημέρα εβδομάδας (most_ads_day)
ads_summary <- ads |>
group_by(group) |>
summarise(
n = n(),
conversions = sum(converted),
conversion_rate = mean(converted),
se = sqrt(conversion_rate * (1 - conversion_rate) / n)
) |>
mutate(
ci_lower = conversion_rate - 1.96 * se,
ci_upper = conversion_rate + 1.96 * se
)
ads_summary
## # A tibble: 2 × 7
## group n conversions conversion_rate se ci_lower ci_upper
## <fct> <int> <int> <dbl> <dbl> <dbl> <dbl>
## 1 psa 23524 420 0.0179 0.000863 0.0162 0.0195
## 2 ad 564577 14423 0.0255 0.000210 0.0251 0.0260
Την μεγιστη ανισορροπία έχει κατα φθίνουσα σειρά:
# Υπολογισμός conversion rate, SE, 95% CI ανά ομάδα
test <- prop.test(
x = ads_summary$conversions,
n = ads_summary$n,
correct = FALSE
)
broom::tidy(test) |>
select(estimate1, estimate2, statistic, p.value,
conf.low, conf.high)
## # A tibble: 1 × 6
## estimate1 estimate2 statistic p.value conf.low conf.high
## <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
## 1 0.0179 0.0255 54.3 1.71e-13 -0.00943 -0.00595
# Segmentation — conversion rate ανά ημέρα εβδομάδας
# Conversion rate ανά ημέρα και ομάδα
ads_by_day <- ads |>
group_by(most_ads_day, group) |>
summarise(
n = n(),
conversion_rate = mean(converted),
se = sqrt(conversion_rate * (1 - conversion_rate) / n),
.groups = "drop"
)
ggplot(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 = conversion_rate - 1.96 * se,
ymax = conversion_rate + 1.96 * se,
fill = group),
alpha = 0.15, color = NA) +
scale_y_continuous(labels = percent_format(accuracy = 0.1)) +
scale_color_manual(values = c("psa" = "#00cc00", "ad" = "#3b82f6")) +
scale_fill_manual(values = c("psa" = "#00cc00", "ad" = "#3b82f6")) +
labs(title = "Conversion rate ανά ημέρα εβδομάδας",
subtitle = "Με 95% διαστήματα εμπιστοσύνης",
x = NULL, y = "Conversion rate") +
theme_minimal(base_size = 13)
Η ημέρα με τη μεγαλύτερη οπτική διαφορά μεταξύ ομάδων είναι η Τρίτη (Tuesday) λόγο της μέγιστης απόστασης μεταξύ καμπύλων
# Επιχειρηματική απόφαση
delta_min <- 0.005 # κατώφλι επιχειρηματικής ουσίας
conf_int <- broom::tidy(test)
lift_pct <- (conf_int$estimate2 - conf_int$estimate1) /
conf_int$estimate1 * 100
cat(sprintf("Absolute lift: %.2f ποσοστ. μονάδες\n",
(conf_int$estimate2 - conf_int$estimate1) * 100))
## Absolute lift: 0.77 ποσοστ. μονάδες
cat(sprintf("Relative lift: %+.1f%%\n", lift_pct))
## Relative lift: +43.1%
cat(sprintf("95%% CI for difference: [%.4f, %.4f]\n",
-conf_int$conf.low, -conf_int$conf.high))
## 95% CI for difference: [0.0094, 0.0060]
# Όρισε δ_min = 0.005 (κατώφλι επιχειρηματικής ουσίας)
# Υπολόγισε το absolute lift και το relative lift
# Ποια από τις 6 περιπτώσεις CI ισχύει; (A/B/C/D/E/F)
# Ποια η τελική σύστασή σου;