1 🔬 Μέρος Α — Βασικός A/B Έλεγχος (Simulated Experiment)

Πριν αναλύσουμε τα πραγματικά δεδομένα, δημιουργούμε ένα ελεγχόμενο πείραμα προσομοίωσης (simulation) για να επαληθεύσουμε τις στατιστικές μας μεθόδους.

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

# TODO 1: Simulation
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))
)

# TODO 2: summary_stats
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
  )

knitr::kable(summary_stats, caption = "Συνοπτικά Στατιστικά Προσομοίωσης")
Συνοπτικά Στατιστικά Προσομοίωσης
group n conversions conversion_rate se ci_lower ci_upper
control 8000 643 0.080375 0.0030396 0.0744173 0.0863327
treatment 8000 798 0.099750 0.0033504 0.0931833 0.1063167

1.1 Οπτικοποίηση Προσομοίωσης

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.7, color = "#E5E9F0") +
  scale_y_continuous(labels = percent_format(accuracy = 0.1)) +
  scale_fill_manual(values = c("control" = "#4C566A", "treatment" = "#81A1C1")) +
  labs(title = "Simulated A/B Test Results", x = "Ομάδα", y = "Conversion Rate (%)") +
  theme_minimal()

1.2 Έλεγχος Υποθέσεων & Χειρωνακτική Επαλήθευση

# TODO 4: Έλεγχος Υποθέσεων με prop.test
test_result <- prop.test(x = summary_stats$conversions, n = summary_stats$n, correct = FALSE)
print(test_result)
## 
##  2-sample test for equality of proportions without continuity correction
## 
## data:  summary_stats$conversions out of summary_stats$n
## X-squared = 18.323, df = 1, p-value = 1.865e-05
## alternative hypothesis: two.sided
## 95 percent confidence interval:
##  -0.02824139 -0.01050861
## sample estimates:
##   prop 1   prop 2 
## 0.080375 0.099750
# TODO 5: Χειρωνακτική Επαλήθευση
p_pool  <- sum(summary_stats$conversions) / sum(summary_stats$n)
se_pool <- sqrt(p_pool * (1 - p_pool) * (1/n_control + 1/n_treatment))
delta   <- summary_stats$conversion_rate[2] - summary_stats$conversion_rate[1]
manual_ci <- c(delta - 1.96 * se_pool, delta + 1.96 * se_pool)

cat(sprintf("Χειρωνακτικό Delta (p_treatment - p_control): %.4f\n", delta))
## Χειρωνακτικό Delta (p_treatment - p_control): 0.0194
cat(sprintf("Χειρωνακτικό 95%% CI της διαφοράς: [%.4f, %.4f]\n", manual_ci[1], manual_ci[2]))
## Χειρωνακτικό 95% CI της διαφοράς: [0.0105, 0.0282]

1.3 Power Analysis

h_val <- ES.h(p1 = 0.10, p2 = 0.08)
pwr_calc <- pwr.2p.test(h = h_val, sig.level = 0.05, power = 0.8, alternative = "two.sided")
print(pwr_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

2 📊 Μέρος Β — Πραγματικά Δεδομένα (Kaggle Marketing AB)

2.1 Invariant Check (Έλεγχος Τυχαιοποίησης)

Ελέγχουμε αν η κατανομή των χρηστών έγινε σωστά χωρίς μεροληψία, παρά την εμφανή ανισορροπία μεγέθους (96% vs 4%).

# (α) Αναλογία ad/psa στο δείγμα
ads |> 
  count(group) |> 
  mutate(percentage = n / sum(n)) |>
  knitr::kable(caption = "Κατανομή Δείγματος ανά Ομάδα")
Κατανομή Δείγματος ανά Ομάδα
group n percentage
psa 23524 0.0399999
ad 564577 0.9600001
# (β) Οπτικοποίηση κατανομής ανά ημέρα της εβδομάδας
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", alpha = 0.85) +
  scale_y_continuous(labels = percent) +
  scale_fill_manual(values = c("psa" = "#4C566A", "ad" = "#88C0D0")) +
  labs(title = "Invariant Check: Distribution of Users Across Days", x = "Ημέρα", y = "Ποσοστό (%) Ομάδας") +
  theme_minimal()

Παρόλο που οι ομάδες είναι ανισομεγέθεις (κάτι σύνηθες σε πραγματικά ad campaigns), η ποσοστιαία κατανομή των χρηστών ανά ημέρα της εβδομάδας είναι σχεδόν ταυτόσημη και για τις δύο ομάδες (π.χ. η Παρασκευή παρουσιάζει τη μέγιστη έκθεση και στις δύο). Αυτό αποδεικνύει ότι η τυχαιοποίηση λειτούργησε ορθά.

2.2 Ανάλυση Αποτελεσμάτων

Υπολογίζουμε τα ποσοστά μετατροπής και τρέχουμε τον στατιστικό έλεγχο.

real_summary <- ads |>
  group_by(group) |>
  summarise(
    n = n(),
    conversions = sum(converted),
    conversion_rate = mean(converted),
    se = sqrt(conversion_rate * (1 - conversion_rate) / n)
  )

knitr::kable(real_summary, caption = "Στατιστικά Πραγματικών Δεδομένων")
Στατιστικά Πραγματικών Δεδομένων
group n conversions conversion_rate se
psa 23524 420 0.0178541 0.0008634
ad 564577 14423 0.0255466 0.0002100
# χρησιμοποιούμε τη rev() για να βγει πρώτο το ad και να έχουμε θετικό lift
real_test <- prop.test(x = rev(real_summary$conversions), n = rev(real_summary$n), correct = FALSE)
broom::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>

2.3 Segmentation ανά ημέρα

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),
    ci_lower = conversion_rate - 1.96 * se,
    ci_upper = conversion_rate + 1.96 * se,
    .groups = "drop"
  )

ggplot(ads_by_day, 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_format(accuracy = 0.1)) +
  scale_color_manual(values = c("psa" = "#4C566A", "ad" = "#88C0D0")) +
  scale_fill_manual(values = c("psa" = "#4C566A", "ad" = "#88C0D0")) +
  labs(title = "Conversion Rate per Day with 95% Confidence Ribbons", x = "Ημέρα", y = "Conversion Rate") +
  theme_minimal()

Η Δευτέρα παρουσιάζει το υψηλότερο conversion rate για την ομάδα “ad”. Τα ribbons των διαστημάτων εμπιστοσύνης δεν επικαλύπτονται σε καμία ημέρα, γεγονός που υποδηλώνει σταθερή και σημαντική υπεροχή της διαφήμισης καθ’ όλη τη διάρκεια της εβδομάδας.

2.4 Επιχειρηματική Απόφαση

p_psa <- real_summary$conversion_rate[1]
p_ad  <- real_summary$conversion_rate[2]

abs_lift <- p_ad - p_psa
rel_lift <- abs_lift / p_psa

cat(sprintf("Absolute Lift: %+.4f (%+.2f ποσοστιαίες μονάδες)\n", abs_lift, abs_lift * 100))
## Absolute Lift: +0.0077 (+0.77 ποσοστιαίες μονάδες)
cat(sprintf("Relative Lift: %+.2f%%\n", rel_lift * 100))
## Relative Lift: +43.09%

2.5 Απαντήσεις στα Ερωτήματα

2.5.1 Ποιο είναι το p-value του ελέγχου;

Το p-value θα είναι \(1.71 \times 10^{-13}\) (ή γραμμένο στην R ως < 2.2e-16, δηλαδή ουσιαστικά μηδενικό). Εφόσον \(p\text{-value} < 0.05\), απορρίπτουμε κατηγορηματικά τη μηδενική υπόθεση (\(H_0\)). Η διαφορά στα ποσοστά μετατροπής (Conversion Rates) είναι στατιστικά ακραία σημαντική.

2.5.2 Συμπίπτει το χειρωνακτικό CI με αυτό του prop.test(); Αν όχι, γιατί;

Οι απόλυτες τιμές και το εύρος των διαστημάτων συμπίπτουν σχεδόν απόλυτα επειδή και οι δύο μέθοδοι βασίζονται στην προσέγγιση της Κανονικής Κατανομής (z = 1.96). Ωστόσο, παρατηρείται διαφορά στα πρόσημα:

  1. Η prop.test() λαμβάνει τα επίπεδα του factor αλφαβητικά, εκτελώντας την αφαίρεση \(p_{control} - p_{treatment}\), με αποτέλεσμα να παράγει αρνητικό διάστημα.

  2. Η χειρωνακτική μέθοδος εκτελεί την αφαίρεση \(p_{treatment} - p_{control}\) για να αποτυπώσει απευθείας το θετικό lift της διαφήμισης, παράγοντας θετικό διάστημα.

2.5.3 Πόσα άτομα χρειαζόντουσαν για power 80%; Πόσα τρέξαμε; Τι συνεπάγεται αυτό;

  • Απαιτούμενο δείγμα: Βάσει του Power Analysis, χρειαζόμασταν περίπου 3.205 άτομα ανά ομάδα (σύνολο ~6.410) για να ανιχνεύσουμε με ασφάλεια μια διαφορά της τάξης του +2%.

  • Πραγματικό δείγμα: Τρέξαμε το πείραμα σε 588.101 άτομα (23.524 στην psa και 564.577 στην ad).

Συνοψίζοντας, το πείραμα είναι ακραία overpowered. Αυτό σημαίνει ότι έχουμε στατιστική ισχύ που αγγίζει το 100%, επιτρέποντάς μας να ανιχνεύσουμε ακόμα και απειροελάχιστες διαφορές με απόλυτη βεβαιότητα. Επιχειρηματικά, η εταιρεία θα μπορούσε να είχε ολοκληρώσει το τεστ πολύ νωρίτερα, εξοικονομώντας πολύτιμο χρόνο και διαφημιστικό budget.

2.6 Τελική Επιχειρηματική Σύσταση

Η έκθεση στη διαφήμιση (“ad”) αύξησε το Conversion Rate από 1.78% σε 2.55%, επιφέροντας ένα εντυπωσιακό Relative Lift +43.1%.

Το διάστημα εμπιστοσύνης της διαφοράς βρίσκεται εξ ολοκλήρου πάνω από το κατώφλι επιχειρηματικής ουσίας (\(\delta_{min} = 0.005\)). Επομένως, ισχύει η Περίπτωση Α (Στατιστικά σημαντικό και επιχειρηματικά ουσιώδες αποτέλεσμα).

Σύσταση: Full Rollout. Η νέα καμπάνια είναι εξαιρετικά αποδοτική και προτείνεται η άμεση και καθολική εφαρμογή της στην πλατφόρμα.