# --- Εγκατάσταση & Φόρτωση πακέτων ---
library(tidyverse)
## Warning: package 'tidyverse' was built under R version 4.5.3
## Warning: package 'ggplot2' was built under R version 4.5.3
## Warning: package 'dplyr' was built under R version 4.5.3
## ── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
## ✔ dplyr 1.2.1 ✔ readr 2.2.0
## ✔ forcats 1.0.1 ✔ stringr 1.6.0
## ✔ ggplot2 4.0.3 ✔ tibble 3.3.1
## ✔ lubridate 1.9.5 ✔ tidyr 1.3.2
## ✔ purrr 1.2.1
## ── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
## ✖ dplyr::filter() masks stats::filter()
## ✖ dplyr::lag() masks stats::lag()
## ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors
library(pwr)
## Warning: package 'pwr' was built under R version 4.5.3
library(broom)
## Warning: package 'broom' was built under R version 4.5.3
library(scales)
## Warning: package 'scales' was built under R version 4.5.3
##
## Attaching package: 'scales'
##
## The following object is masked from 'package:purrr':
##
## discard
##
## The following object is masked from 'package:readr':
##
## col_factor
library(janitor)
## Warning: package 'janitor' was built under R version 4.5.3
##
## Attaching package: 'janitor'
##
## The following objects are masked from 'package:stats':
##
## chisq.test, fisher.test
set.seed(42)
# --- Φόρτωση δεδομένων ---
ads <- read_csv("cookie_cats.csv") |>
janitor::clean_names() |>
mutate(
group = factor(version, levels = c("gate_30", "gate_40")),
converted = as.integer(retention_7),
)
## Rows: 90189 Columns: 5
## ── Column specification ────────────────────────────────────────────────────────
## Delimiter: ","
## chr (1): version
## dbl (2): userid, sum_gamerounds
## lgl (2): retention_1, retention_7
##
## ℹ 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.
glimpse(ads)
## Rows: 90,189
## Columns: 7
## $ userid <dbl> 116, 337, 377, 483, 488, 540, 1066, 1444, 1574, 1587, 1…
## $ version <chr> "gate_30", "gate_30", "gate_40", "gate_40", "gate_40", …
## $ sum_gamerounds <dbl> 3, 38, 165, 1, 179, 187, 0, 2, 108, 153, 3, 0, 30, 39, …
## $ retention_1 <lgl> FALSE, TRUE, TRUE, FALSE, TRUE, TRUE, FALSE, FALSE, TRU…
## $ retention_7 <lgl> FALSE, FALSE, FALSE, FALSE, TRUE, TRUE, FALSE, FALSE, T…
## $ group <fct> gate_30, gate_30, gate_40, gate_40, gate_40, gate_40, g…
## $ converted <int> 0, 0, 0, 0, 1, 1, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 1, 1…
Επιλογή μεθόδου: Ποια μέθοδο θα επιλέξετε με βάση τα παραπάνω;
Επιλέγω A/B testing, διότι έχουμε τυχαία κατανομή χρηστών σε δύο διαφορετικά group και επιδιώκουμε να δούμε αν η αλλαγή αυτή επηρεάζει πραγματικά τη διατήρηση των χρηστών.
# Αναλογία στις ομάδες
ads |>
count(group) |>
mutate(pct = n / sum(n))
## # A tibble: 2 × 3
## group n pct
## <fct> <int> <dbl>
## 1 gate_30 44700 0.496
## 2 gate_40 45489 0.504
Η κατανομή των χρηστών μεταξύ των ομάδων είναι σχεδόν ισομερής. Συγκεκριμένα, περίπου το 49% των χρηστών ανήκει στο gate_30, ενώ περίπου το 50% στο gate_40.
Επομένως, το experiment ακολουθεί αναλογία 50/50.
# Διατήρηση 1ης ημέρας
ads |>
count(group, retention_1) |>
group_by(group) |>
mutate(pct = n / sum(n)) |>
ggplot(aes(x = retention_1, y = pct, fill = group)) +
geom_col(position = "dodge") +
scale_y_continuous(labels = percent) +
labs(title = "Invariant check: κατανομή 1η ημέρα",
x = NULL, y = "% της ομάδας") +
theme_minimal()
# Διατήρηση 7ης ημέρας
ads |>
count(group, retention_7
) |>
group_by(group) |>
mutate(pct = n / sum(n)) |>
ggplot(aes(x = retention_7, y = pct, fill = group)) +
geom_col(position = "dodge") +
scale_y_continuous(labels = percent) +
labs(title = "Invariant check: κατανομή 7η ημέρα",
x = NULL, y = "% της ομάδας") +
theme_minimal()
Παρατηρούμε ότι οι δύο ομάδες εμφανίζουν σχετικά παρόμοια κατανομή στις δύο ημέρες, χωρίς ακραίες αποκλίσεις. Αυτό αποτελεί ένδειξη ότι η διαδικασία τυχαιοποίσης λειτούργησε ικανοποιητικά ως προς τη χρονική κατανομή των χρηστών.
# Ανάλυση αποτελέσματος με tidyverse
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 gate_30 44700 8502 0.190 0.00186 0.187 0.194
## 2 gate_40 45489 8279 0.182 0.00181 0.178 0.186
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.190 0.182 10.0 0.00155 0.00312 0.0133
Το dataset δείχνει ότι η ομάδα gate_30 παρουσιάζει υψηλότερο conversion rate σε σχέση με την ομάδα gate_40.
Συγκεκριμένα:
η ομάδα gate_30 έχει conversion rate περίπου 19%
η ομάδα gate_40 έχει conversion rate περίπου 18%
Αυτό σημαίνει ότι οι χρήστες που τοποθετήθηκαν στο νέο group πραγματοποίησαν λιγότερα conversions.
Τα 95% confidence intervals είναι:
gate_30 : περίπου [18.65%, 19.38%]
gate_40: περίπου [17.84%, 18.55%]
Τα διαστήματα εμπιστοσύνης επικαλύπτονται, γεγονός που αποτελεί ισχυρή ένδειξη ότι δεν υπάρχει πραγματική διαφορά μεταξύ των δύο ομάδων.
Ωστόσο, ο έλεγχος αναλογιών (prop.test) έδωσε p-value = 0.0015, το οποίο είναι μικρότερο από το επίπεδο σημαντικότητας α = 0.05 γεγονός που δείχνει ότι η διαφορά μεταξύ των δύο ομάδων είναι στατιστικά σημαντική και δύσκολα οφείλεται σε τυχαία διακύμανση.
Ωστόσο, επειδή η ομάδα gate_40 έχει μικρότερο ποσοστό conversion_rate, δεν θα πρότεινα μετακίνηση της πύλης
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.190 0.182 10.0 0.00155 0.00312 0.0133
# CI για τη διαφορά (lift)
conf_int <- broom::tidy(test)
lift_pct <- (conf_int$estimate1 - conf_int$estimate2) /
conf_int$estimate2 * 100
cat(sprintf("Absolute lift: %.2f ποσοστ. μονάδες\n",
(conf_int$estimate1 - conf_int$estimate2) * 100))
## Absolute lift: 0.82 ποσοστ. μονάδες
cat(sprintf("Relative lift: %+.1f%%\n", lift_pct))
## Relative lift: +4.5%
cat(sprintf("95%% CI for difference: [%.4f, %.4f]\n",
conf_int$conf.low, conf_int$conf.high))
## 95% CI for difference: [0.0031, 0.0133]
Το absolute lift είναι περίπου 0.82 ποσοστιαίες μονάδες και το relative lift 4.5%. Το 95% confidence interval της διαφοράς είναι [0.0031, 0.0133], γεγονός που δείχνει ότι η διαφορά μεταξύ των δύο ομάδων είναι στατιστικά σημαντική. Επιπλέον, το μέγεθος της διαφοράς (Absolute lift) υπερβαίνει το επιχειρηματικό κατώφλι d min=0.005, καθώς ακόμη και το όριο του confidence interval που βρίσκεται πλησιέστερα στο μηδέν έχει απόλυτη τιμή μεγαλύτερη από 0.005. Επομένως, η επίδραση είναι τόσο στατιστικά όσο και επιχειρηματικά σημαντική.
Με βάση τα ευρήματα, θα προτείνατε τη μετακίνηση της πύλης;
Δεν θα πρότεινα μετακίνηση της πύλης, καθώς η πύλη gate_40 έχει μικρότερο ποσοστό conversion_rate από ότι η πύλη gate_30.