Σκοπός της παρούσας εργασίας είναι η αξιολόγηση της αποτελεσματικότητας μιας νέας διαφημιστικής καμπάνιας μέσω A/B testing. Οι χρήστες χωρίστηκαν τυχαία σε ομάδα ελέγχου (psa) και πειραματική ομάδα (ad), και συγκρίνονται τα conversion rates των δύο ομάδων.

Μέσω στατιστικής ανάλυσης (confidence intervals και hypothesis testing), εξετάζεται αν η διαφορά που παρατηρείται είναι στατιστικά και επιχειρηματικά σημαντική.

Μέρος Α - Simulated A/B Test

# --- Παράμετροι πειράματος ---
n_control   <- 8000      # μέγεθος ομάδας ελέγχου
n_treatment <- 8000      # μέγεθος πειραματικής ομάδας
p_control   <- 0.08      # baseline conversion rate
p_treatment <- 0.10      # μετά την αλλαγή (true effect = +2%)

TODO 1 - Δημιουργία δεδομένων

Δημιουργούμε ένα τεχνητό dataset για να προσομοιώσουμε ένα A/B test όπου γνωρίζουμε την “πραγματική” διαφορά. Χρησιμοποιούμε τη rbinom() για να δημιουργήσουμε conversions (0/1) με συγκεκριμένες πιθανότητες για κάθε ομάδα.

experiment <- tibble(
  user_id = 1:(n_control + n_treatment),
  group = rep(c("control", "treatment"), times = c(n_control, 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, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, …

Το simulated dataset δημιουργήθηκε με δύο ισομεγέθεις ομάδες (8000 χρήστες η καθεμία). Οι τιμές της μεταβλητής converted είναι 0/1, όπως αναμένεται για ένα A/B test με binary outcome.

TODO 2 - Summary statistics

Υπολογίζουμε τα βασικά στατιστικά ανά ομάδα, ώστε να συγκρίνουμε την απόδοση των δύο ομάδων. Επίσης κατασκευάζουμε 95% confidence intervals για να εκφράσουμε την αβεβαιότητα των εκτιμήσεων.

Το standard error δείχνει πόσο αξιόπιστη είναι η εκτίμηση του conversion rate, λαμβάνοντας υπόψη το μέγεθος δείγματος.

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,
    .groups = "drop"
  )

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         642          0.0802 0.00304   0.0743   0.0862
## 2 treatment  8000         832          0.104  0.00341   0.0973   0.111

Παρατηρούμε ότι το conversion rate της ομάδας treatment είναι περίπου 10%, ενώ της control περίπου 8%, επιβεβαιώνοντας το αρχικό setup της προσομοίωσης. Τα confidence intervals είναι σχετικά στενά, λόγω του μεγάλου μεγέθους δείγματος.

TODO 3 - Οπτικοποίηση

Οπτικοποιούμε τα conversion rates με bar chart και προσθέτουμε error bars που αντιστοιχούν στα confidence intervals, ώστε να δούμε οπτικά αν υπάρχει διαφορά μεταξύ των ομάδων και πόσο μεγάλη είναι η αβεβαιότητα.

ggplot(summary_stats, aes(x = group, y = conversion_rate, fill = group)) +
  geom_col(width = 0.6, alpha = 0.8) +
  geom_errorbar(
    aes(ymin = ci_lower, ymax = ci_upper),
    width = 0.2,
    linewidth = 0.8
  ) +
  scale_y_continuous(labels = percent, limits = c(0, 0.15)) +
  scale_fill_manual(values = c("control" = "#6c757d", "treatment" = "#28a745")) +
  labs(
    title = "Conversion Rate ανά Ομάδα (Simulated Data)",
    subtitle = "Με 95% Confidence Intervals",
    x = "Ομάδα",
    y = "Conversion Rate"
  ) +
  theme_minimal() +
  theme(legend.position = "none")

Παρατηρούμε ότι η ομάδα treatment (ad) έχει υψηλότερο conversion rate από την control (psa). Τα confidence intervals των δύο ομάδων έχουν μικρή ή καθόλου επικάλυψη, γεγονός που υποδηλώνει ότι η διαφορά είναι πιθανό να είναι στατιστικά σημαντική.

TODO 4 - Έλεγχος υποθέσεων

Εκτελούμε έλεγχο υποθέσεων με prop.test() για να εξετάσουμε αν η διαφορά στα conversion rates είναι στατιστικά σημαντική ή μπορεί να οφείλεται στην τύχη.

Η μηδενική υπόθεση υποθέτει ότι δεν υπάρχει διαφορά μεταξύ των ομάδων.

# Εξαγωγή δεδομένων για το test
control_data <- experiment |> filter(group == "control")
treatment_data <- experiment |> filter(group == "treatment")
test_result <- prop.test(
  x = c(sum(treatment_data$converted), sum(control_data$converted)),
  n = c(nrow(treatment_data), nrow(control_data)),
  correct = FALSE
)
test_result
## 
##  2-sample test for equality of proportions without continuity correction
## 
## data:  c(sum(treatment_data$converted), sum(control_data$converted)) out of c(nrow(treatment_data), nrow(control_data))
## X-squared = 26.976, df = 1, p-value = 2.06e-07
## alternative hypothesis: two.sided
## 95 percent confidence interval:
##  0.01479525 0.03270475
## sample estimates:
##  prop 1  prop 2 
## 0.10400 0.08025

Ο έλεγχος υποθέσεων δίνει πολύ μικρό p-value (p < 0.001), συνεπώς απορρίπτουμε τη μηδενική υπόθεση. Αυτό σημαίνει ότι η διαφορά μεταξύ των δύο ομάδων δεν οφείλεται στην τύχη.

TODO 5 - Χειρωνακτική επαλήθευση

Υπολογίζουμε χειρωνακτικά τη διαφορά των conversion rates και το confidence interval της, ώστε να επιβεβαιώσουμε τα αποτελέσματα του prop.test() και να κατανοήσουμε καλύτερα τη στατιστική διαδικασία.

n1 <- nrow(treatment_data)
n2 <- nrow(control_data)
p1 <- mean(treatment_data$converted)
p2 <- mean(control_data$converted)

Το pooled proportion είναι ένα κοινό ποσοστό που χρησιμοποιείται στον έλεγχο υποθέσεων, υποθέτοντας ότι οι δύο ομάδες είναι ίδιες.

# (α) Pooled estimate
p_pool <- (sum(treatment_data$converted) + sum(control_data$converted)) / (n1 + n2)

# (β) Pooled SE (για έλεγχο υπόθεσης)
se_pooled <- sqrt(p_pool * (1 - p_pool) * (1/n1 + 1/n2))

# (γ) Διαφορά
delta <- p1 - p2

# (δ) 95% CI για τη διαφορά (unpooled SE για CI)
se_diff <- sqrt((p1 * (1 - p1) / n1) + (p2 * (1 - p2) / n2))
ci_diff_lower <- delta - 1.96 * se_diff
ci_diff_upper <- delta + 1.96 * se_diff

cat("Pooled p̂:", round(p_pool, 4), "\n")
## Pooled p̂: 0.0921
cat("Pooled SE:", round(se_pooled, 4), "\n")
## Pooled SE: 0.0046
cat("Διαφορά δ:", round(delta, 4), "\n")
## Διαφορά δ: 0.0237
cat("95% CI για δ: [", round(ci_diff_lower, 4), ",", round(ci_diff_upper, 4), "]\n")
## 95% CI για δ: [ 0.0148 , 0.0327 ]
cat("\nprop.test CI:", round(test_result$conf.int[1], 4), "to", round(test_result$conf.int[2], 4))
## 
## prop.test CI: 0.0148 to 0.0327

Η εκτιμώμενη διαφορά μεταξύ των ομάδων είναι περίπου 2%, ενώ το confidence interval δεν περιλαμβάνει το 0. Τα αποτελέσματα είναι συνεπή με το prop.test(), επιβεβαιώνοντας ότι το effect είναι στατιστικά σημαντικό.

TODO 6 - Power Analysis

Υπολογίζουμε το απαιτούμενο μέγεθος δείγματος για να ανιχνεύσουμε τη συγκεκριμένη διαφορά με 80% πιθανότητα (power), ώστε να αξιολογήσουμε αν το πείραμα είχε επαρκές sample size.

# Cohen's h για effect size
h <- ES.h(p1 = 0.10, p2 = 0.08)
cat("Cohen's h:", round(h, 4), "\n")
## Cohen's h: 0.07
# Απαιτούμενο μέγεθος δείγματος ανά ομάδα
power_result <- pwr.2p.test(
  h = h,
  sig.level = 0.05,
  power = 0.80,
  alternative = "two.sided"
)
cat("Απαιτούμενο n ανά ομάδα:", ceiling(power_result$n), "\n")
## Απαιτούμενο n ανά ομάδα: 3205
cat("Συνολικό απαιτούμενο n:", 2 * ceiling(power_result$n), "\n")
## Συνολικό απαιτούμενο n: 6410
cat("Πραγματικό n που τρέξαμε:", n_control + n_treatment)
## Πραγματικό n που τρέξαμε: 16000

Το απαιτούμενο μέγεθος δείγματος για power 80% είναι 3205 ανά ομάδα, ενώ στο πείραμα χρησιμοποιήθηκαν 8,000. Αυτό σημαίνει ότι το experiment είναι overpowered και τα αποτελέσματα έχουν υψηλή ακρίβεια.

Μέρος Β - Πραγματικά Δεδομένα

TODO 7 - Invariant check

Ελέγχουμε αν η τυχαιοποίηση δούλεψε σωστά, συγκρίνοντας το μέγεθος των ομάδων και την κατανομή τους ανά ημέρα. Αν υπάρχουν μεγάλες ανισορροπίες, τα αποτελέσματα μπορεί να είναι biased.

# (α) Αναλογία ad/psa
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
# (β) Κατανομή ανά ημέρα
ggplot(ads, aes(x = most_ads_day, fill = group)) +
  geom_bar(position = "dodge") +
  labs(
    title = "Κατανομή χρηστών ανά ημέρα εμφάνισης διαφήμισης",
    x = "Ημέρα",
    y = "Αριθμός χρηστών"
  ) +
  theme_minimal() +
  theme(axis.text.x = element_text(angle = 45, hjust = 1))

Η κατανομή των χρηστών μεταξύ των ομάδων είναι σχεδόν ισορροπημένη (~50/50), γεγονός που δείχνει ότι η τυχαιοποίηση λειτούργησε σωστά. Επιπλέον, η κατανομή ανά ημέρα είναι παρόμοια μεταξύ των ομάδων, άρα δεν παρατηρείται εμφανές bias.

TODO 8 - Conversion analysis

Υπολογίζουμε conversion rates και confidence intervals στα πραγματικά δεδομένα και εκτελούμε ξανά prop.test() για να δούμε αν η διαφήμιση έχει στατιστικά σημαντική επίδραση.

real_summary <- ads |>
  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,
    .groups = "drop"
  )

real_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
# Prop test
real_test <- prop.test(
  x = c(
    sum(ads$converted[ads$group == "ad"]),
    sum(ads$converted[ads$group == "psa"])
  ),
  n = c(
    sum(ads$group == "ad"),
    sum(ads$group == "psa")
  ),
  correct = FALSE
)

tidy(real_test)
## # A tibble: 1 × 9
##   estimate1 estimate2 statistic  p.value parameter conf.low conf.high method    
##       <dbl>     <dbl>     <dbl>    <dbl>     <dbl>    <dbl>     <dbl> <chr>     
## 1    0.0255    0.0179      54.3 1.71e-13         1  0.00595   0.00943 2-sample …
## # ℹ 1 more variable: alternative <chr>

Το conversion rate της ομάδας ad είναι υψηλότερο από της psa, ενώ το p-value του ελέγχου είναι πολύ μικρό (p < 0.05). Συνεπώς, η διαφορά είναι στατιστικά σημαντική και υπέρ της νέας διαφήμισης.

TODO 9 - Segmentation

Αναλύουμε τα conversion rates ανά ημέρα της εβδομάδας για να εντοπίσουμε πιθανές διαφορές στην απόδοση της διαφήμισης σε διαφορετικά segments χρηστών.

daily_stats <- ads |>
  group_by(most_ads_day, group) |>
  summarise(
    n = n(),
    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,
    .groups = "drop"
  )

ggplot(daily_stats, aes(x = most_ads_day, y = conversion_rate, 
                         color = group, group = group)) +
  geom_line(linewidth = 1) +
  geom_point(size = 2) +
  geom_ribbon(aes(ymin = ci_lower, ymax = ci_upper, fill = group), 
              alpha = 0.15, color = NA) +
  scale_y_continuous(labels = percent) +
  labs(
    title = "Conversion Rate ανά Ημέρα Εβδομάδας",
    x = "Ημέρα",
    y = "Conversion Rate"
  ) +
  theme_minimal() +
  theme(axis.text.x = element_text(angle = 45, hjust = 1))

Η ανάλυση ανά ημέρα δείχνει ότι το treatment υπερέχει σχεδόν σε όλες τις ημέρες, αν και η διαφορά διαφέρει σε μέγεθος. Αυτό υποδηλώνει ότι η επίδραση της διαφήμισης είναι σχετικά σταθερή αλλά όχι απόλυτα ομοιόμορφη, καθώς ενδέχεται να επηρεάζεται από τη χρονική περίοδο.

TODO 10 - Επιχειρηματική απόφαση

Υπολογίζουμε το absolute και relative lift για να εκτιμήσουμε το μέγεθος της επίδρασης και το συγκρίνουμε με ένα ελάχιστο επιχειρηματικό όριο (δ_min), ώστε να αποφασίσουμε αν η διαφήμιση αξίζει να εφαρμοστεί.

delta_min <- 0.005  # Minimum detectable effect
p_ad <- mean(ads$converted[ads$group == "ad"])
p_psa <- mean(ads$converted[ads$group == "psa"])
absolute_lift <- p_ad - p_psa
relative_lift <- (p_ad - p_psa) / p_psa * 100
cat("Absolute lift:", round(absolute_lift, 4), "\n")
## Absolute lift: 0.0077
cat("Relative lift:", round(relative_lift, 2), "%\n")
## Relative lift: 43.09 %
cat("Minimum threshold (δ_min):", delta_min, "\n")
## Minimum threshold (δ_min): 0.005
# Σύγκριση CI με δ_min
cat("\nΑν το κάτω όριο του CI > δ_min → Στατιστικά και πρακτικά σημαντικό")
## 
## Αν το κάτω όριο του CI > δ_min → Στατιστικά και πρακτικά σημαντικό

Το absolute lift είναι θετικό και μεγαλύτερο από το ελάχιστο αποδεκτό όριο (δ_min = 0.005), ενώ και το confidence interval της διαφοράς βρίσκεται εξολοκλήρου πάνω από το μηδέν και το δ_min. Αυτό υποδηλώνει ότι η επίδραση της καμπάνιας είναι τόσο στατιστικά όσο και επιχειρηματικά σημαντική.

Ερωτήσεις

1.Ποιο είναι το p-value του ελέγχου; Απορρίπτουμε την H₀ σε επίπεδο α = 0.05; Το p-value του ελέγχου είναι πολύ μικρό (p < 0.001). Συνεπώς, απορρίπτουμε τη μηδενική υπόθεση σε επίπεδο σημαντικότητας α = 0.05 και συμπεραίνουμε ότι υπάρχει στατιστικά σημαντική διαφορά μεταξύ των ομάδων.
2.Συμπίπτει το χειρωνακτικό CI με αυτό του prop.test(); Αν όχι, γιατί; Το χειρωνακτικό confidence interval είναι πολύ κοντά σε αυτό του prop.test(), αλλά δεν συμπίπτουν ακριβώς. Αυτό συμβαίνει επειδή το χειρωνακτικό CI βασίζεται σε κανονική προσέγγιση (Wald interval), ενώ το prop.test() χρησιμοποιεί πιο ακριβή μέθοδο (Wilson score interval).
3.Πόσα άτομα χρειαζόντουσαν για power 80%; Πόσα τρέξαμε; Τι συνεπάγεται αυτό; Για power 80% απαιτούνται περίπου 3205 άτομα ανά ομάδα. Στο πείραμα χρησιμοποιήθηκαν 8,000 άτομα ανά ομάδα, δηλαδή σημαντικά περισσότερα από το απαραίτητο. Αυτό σημαίνει ότι το πείραμα είναι overpowered, οδηγώντας σε μεγαλύτερη ακρίβεια αλλά και δυνατότητα ανίχνευσης ακόμη και μικρών διαφορών.

Τελικά συμπεράσματα

Από την ανάλυση του A/B test προκύπτει ότι η νέα διαφημιστική καμπάνια (treatment) παρουσιάζει υψηλότερο conversion rate σε σχέση με την ομάδα ελέγχου (control). Ο στατιστικός έλεγχος (prop.test) έδειξε πολύ μικρό p-value (p < 0.05), γεγονός που μας οδηγεί στην απόρριψη της μηδενικής υπόθεσης και υποδηλώνει ότι η παρατηρούμενη διαφορά δεν οφείλεται στην τύχη.

Τα confidence intervals των δύο ομάδων ενισχύουν το παραπάνω συμπέρασμα, καθώς υποδηλώνουν σαφή διαφορά μεταξύ των conversion rates. Επιπλέον, η ανάλυση ανά ημέρα εβδομάδας δείχνει ότι το θετικό effect της διαφήμισης είναι σχετικά σταθερό, χωρίς ενδείξεις ότι περιορίζεται σε συγκεκριμένα segments χρηστών.

Ο έλεγχος των invariants επιβεβαίωσε ότι η τυχαιοποίηση του πειράματος λειτούργησε σωστά, καθώς οι ομάδες είναι ισορροπημένες. Παράλληλα, το power analysis έδειξε ότι το δείγμα που χρησιμοποιήθηκε ήταν μεγαλύτερο από το απαραίτητο, γεγονός που αυξάνει την αξιοπιστία των αποτελεσμάτων.

Συνολικά, η διαφημιστική καμπάνια φαίνεται να έχει τόσο στατιστικά όσο και πρακτικά σημαντική επίδραση. Επομένως, προτείνεται η εφαρμογή (rollout) της καμπάνιας σε ευρύτερη κλίμακα.