Thống kê 7 — A/B Testing thực chiến

13 thg 7, 2026 3 lượt xem
#bi
#ab-testing
#conversion
#experimentation
#statistics

Vì sao A/B testing là chuẩn vàng của nhân quả?

Sau khi có thống kê mô tả, khoảng tin cậykiểm định giả thuyết, câu hỏi kinh doanh thật sự luôn là nhân quả: "nếu đổi màn hình onboarding, tỷ lệ khách hoàn tất mở tài khoản có tăng không — và tăng vì thiết kế mới, hay vì mùa vụ, vì nhóm khách khác nhau?".

Dữ liệu quan sát (observational) không trả lời được câu này một cách chắc chắn. Nếu ta chỉ so sánh khách đã thấy giao diện mới với khách thấy giao diện cũ theo thời gian, mọi khác biệt đều dính confounder (biến gây nhiễu): nhóm mới có thể là khách trẻ hơn, chiến dịch marketing chạy song song, hay đơn giản là tháng lương. Xem thêm phần bẫy tương quan vs nhân quả trong stat-06.

A/B test (randomized controlled experiment) giải quyết gốc rễ bằng một ý tưởng đơn giản mà mạnh: ngẫu nhiên hoá (randomization). Ta chia người dùng ngẫu nhiên vào hai nhóm — control (A) giữ nguyên, treatment (B) nhận thay đổi — rồi so sánh. Vì việc phân nhóm là ngẫu nhiên, mọi confounder (tuổi, thu nhập, thiết bị, cả những thứ ta không đo được) sẽ phân bố đều giữa hai nhóm theo kỳ vọng. Khác biệt còn lại giữa A và B, sau khi trừ nhiễu thống kê, chính là tác động nhân quả của thay đổi. Đó là lý do A/B test được gọi là gold standard.


1. Vòng đời một A/B test

Một thí nghiệm nghiêm túc không phải "đổi nút rồi xem số nào cao hơn". Nó là một quy trình có kỷ luật:

1.1 Giả thuyết

Phát biểu theo khung kiểm định giả thuyết chuẩn:

  • H0 (null): thay đổi không có tác động. Ví dụ tỷ lệ hoàn tất onboarding của B bằng A: p_B = p_A.
  • H1 (alternative): có tác động. Thường dùng two-sided (p_B ≠ p_A) để không tự lừa mình — one-sided chỉ dùng khi thật sự chỉ quan tâm một chiều và đã cam kết trước.

1.2 Metric chính và guardrail

Đây là bước hay bị làm ẩu nhất.

Loại metricVai tròVí dụ ngân hàng
Primary (OEC)Metric duy nhất quyết định thắng/thua, chọn TRƯỚC khi chạyTỷ lệ hoàn tất mở tài khoản (conversion)
GuardrailKhông được xấu đi, dù primary có tăngTỷ lệ khiếu nại, thời gian tải app, tỷ lệ gọi tổng đài
Secondary/diagnosticGiải thích vì sao primary thay đổiTỷ lệ bỏ ở từng bước form

Quy tắc vàng: một primary metric. Nếu để 5 metric "cùng quan trọng", bạn sẽ luôn tìm được một cái "significant" — đó là bẫy multiple testing (mục 5). OEC (Overall Evaluation Criterion) nên phản ánh giá trị dài hạn, không chỉ click ngắn hạn.

1.3 Đơn vị randomization

Chọn đơn vị ngẫu nhiên hoá phải khớp với đơn vị đo và đơn vị trải nghiệm:

  • User-level: một khách luôn thấy cùng một phiên bản. Đây là mặc định an toàn — tránh chuyện khách thấy giao diện "nhảy" giữa các phiên và tránh vi phạm giả định độc lập.
  • Session-level: chỉ dùng khi thay đổi không cần nhất quán qua các phiên.
  • Account-level / cluster: khi hiệu ứng lan trong nhóm (ví dụ tài khoản doanh nghiệp nhiều người dùng) — lúc này cần cluster randomization và phân tích tính đến tương quan trong cụm, nếu không sẽ đánh giá thấp phương sai.

Điểm mấu chốt: nếu đo per-user nhưng randomize per-session, các quan sát không độc lập → sai số chuẩn bị tính sai.


2. Randomization — vì sao là trái tim của mọi thứ

Randomization làm hai việc mà không kỹ thuật thống kê nào thay được:

  1. Cân bằng confounder theo kỳ vọng — kể cả biến không quan sát được. Đây là điều regression trên dữ liệu quan sát không đảm bảo.
  2. Hợp thức hoá suy diễn — p-value và khoảng tin cậy chỉ có ý nghĩa nhân quả khi phân nhóm thực sự ngẫu nhiên.

Kiểm tra thực hành:

  • AA test: chạy hai nhóm giống hệt nhau (cùng trải nghiệm) trước khi chạy A/B. Nếu vẫn thấy khác biệt "significant" thường xuyên hơn mức alpha, hệ thống phân nhóm hoặc logging đang có lỗi.
  • Balance check: so sánh phân bố các biến nền (tuổi, tỉnh, số dư) giữa A và B — phải xấp xỉ bằng nhau.
  • Hash ổn định: gán nhóm bằng hash của user_id với salt cố định, để cùng một khách luôn rơi vào cùng nhóm suốt thí nghiệm.

3. Tính cỡ mẫu TRƯỚC khi chạy — không được bỏ qua

Đây là phần bị bỏ nhiều nhất và gây hại nhất. Chạy test mà không tính cỡ mẫu giống như đi khám mà không biết cần lấy bao nhiêu máu: hoặc bạn underpowered (không đủ để phát hiện hiệu ứng thật, kết luận "không khác" trong khi thực ra có), hoặc bạn chạy quá lâu tốn cơ hội.

Bốn tham số phải quyết định trước:

Tham sốÝ nghĩaGiá trị thường dùng
Baseline rate (p)Tỷ lệ chuyển đổi hiện tại của controlLấy từ dữ liệu lịch sử, ví dụ 12%
MDE (minimum detectable effect)Mức cải thiện nhỏ nhất đáng để quan tâm về mặt kinh doanhVí dụ +1.5 điểm % tuyệt đối, hoặc +10% tương đối
alpha (α)Ngưỡng sai lầm loại I (dương tính giả)0.05
power (1−β)Xác suất phát hiện hiệu ứng nếu nó thật tồn tại0.80 (đôi khi 0.90)

Công thức xấp xỉ cỡ mẫu mỗi nhóm cho so sánh hai tỷ lệ (two-sided):

n mi nhóm ≈ (z_{α/2} + z_{β})² × [ p_A(1−p_A) + p_B(1−p_B) ] / (p_B − p_A)²

Với α = 0.05 → z_{α/2} ≈ 1.96; power = 0.80 → z_{β} ≈ 0.84.

Ví dụ số cụ thể. Baseline p_A = 0.12, MDE tuyệt đối +0.015 nên p_B = 0.135:

  • Tử số: (1.96 + 0.84)² = 2.80² = 7.84
  • Phương sai gộp: 0.12×0.88 + 0.135×0.865 = 0.1056 + 0.1168 = 0.2224
  • Mẫu số: (0.135 − 0.12)² = 0.015² = 0.000225
  • n ≈ 7.84 × 0.2224 / 0.000225 ≈ 7.749

Tức khoảng ~7.750 khách mỗi nhóm, tổng ~15.500. Từ đó, nếu app có ~1.000 khách vào onboarding/ngày và chia 50/50, test cần chạy tối thiểu ~16 ngày. Bài học rút ra ngay:

  • MDE càng nhỏ, cỡ mẫu càng lớn theo bình phương nghịch đảo — muốn phát hiện hiệu ứng nhỏ tốn rất nhiều mẫu.
  • Baseline gần 50% có phương sai lớn nhất → cần nhiều mẫu hơn.
  • Với metric trung bình (không phải tỷ lệ), thay p(1−p) bằng phương sai σ² ước lượng từ lịch sử.

Nguyên tắc: quyết định thời điểm dừng và cỡ mẫu TRƯỚC khi nhìn kết quả. Đây là điều ngăn bẫy peeking ở mục 5.


4. Phân tích kết quả

4.1 Metric tỷ lệ — two-proportion z-test

Cho conversion, ta so sánh hai tỷ lệ. Gọi x_A, x_B là số chuyển đổi, n_A, n_B là cỡ mẫu:

p̂_A = x_A / n_A,   p̂_B = x_B / n_B
p̂  = (x_A + x_B) / (n_A + n_B)          # tỷ lệ gp cho SE dưới H0
SE  = sqrt( p̂(1p̂) × (1/n_A + 1/n_B) )
z   = (p̂_B − p̂_A) / SE

z lớn (giá trị tuyệt đối) → p-value nhỏ → bằng chứng chống lại H0. Với two-sided α=0.05, |z| > 1.96 là "significant".

Khoảng tin cậy cho chênh lệch (điều thực sự nên báo cáo) dùng SE không gộp:

SE_diff = sqrt( p̂_A(1p̂_A)/n_A + p̂_B(1p̂_B)/n_B )
CI 95%  = (p̂_B − p̂_A) ± 1.96 × SE_diff

Ví dụ: A có 900/7750 (11.6%), B có 1.070/7750 (13.8%). Chênh lệch tuyệt đối +2.2 điểm %, lift tương đối ≈ +19%. Nếu CI 95% của chênh lệch là [+0.9%, +3.5%] (không chứa 0) → có ý nghĩa thống kê. Quan trọng hơn p-value: CI cho biết độ lớn hiệu ứng và độ bất định.

4.2 Metric trung bình — t-test

Với metric liên tục (số giao dịch/tháng, giá trị chi tiêu trung bình, thời gian hoàn tất form), dùng two-sample t-test (Welch, không giả định phương sai bằng nhau):

t = (x̄_B − x̄_A) / sqrt( s_B²/n_B + s_A²/n_A )

Lưu ý phân phối tài chính thường lệch phải mạnh (log-normal, xem stat-02). Nhờ định lý giới hạn trung tâm, trung bình mẫu vẫn xấp xỉ chuẩn khi n lớn, nên t-test trên trung bình vẫn dùng được; nhưng nên cân nhắc winsorize/cap outlier hoặc dùng Mann-Whitney nếu đuôi quá nặng.

4.3 SQL: tỷ lệ chuyển đổi hai nhóm

Trong thực tế bảng thí nghiệm có cột variant và cột cờ converted. Sandbox của chúng ta không có bảng experiment — chỉ có 5 bảng nghiệp vụ — nên đoạn dưới là minh hoạ khái niệm, KHÔNG chạy được trên sandbox:

-- MINH HOẠ (không chạy trên sandbox — cần bảng experiment_assignment)
SELECT variant,
       COUNT(*)                         AS n,
       SUM(converted)                   AS conversions,
       AVG(converted::numeric)          AS conversion_rate
FROM experiment_assignment
WHERE experiment_id = 'onboarding_v2'
GROUP BY variant;

Mẫu hình GROUP BY nhóm → COUNT/SUM/AVG mới là thứ đáng nhớ. Ta có thể chứng minh nó chạy được với đúng schema sandbox bằng một truy vấn tương tự về mặt cấu trúc — gộp và tính tỷ lệ theo nhóm trên bảng thật:

-- ▶ Chạy được
SELECT currency,
       COUNT(*)                                      AS n_accounts,
       AVG(CASE WHEN balance > 0 THEN 1 ELSE 0 END)  AS positive_rate
FROM accounts
GROUP BY currency;

Truy vấn này minh hoạ đúng cơ chế "tỷ lệ theo nhóm": AVG của một biểu thức 0/1 chính là tỷ lệ. Trong A/B test thật, currency được thay bằng variantbalance > 0 được thay bằng cờ converted.


5. Các bẫy — nơi kết luận A/B chết

5.1 Peeking / dừng sớm khi thấy significant

Bẫy nguy hiểm nhất. Nếu bạn nhìn p-value liên tục và dừng ngay khi p < 0.05, tỷ lệ dương tính giả thực tế không còn là 5% — nó phình lên 20-30% hoặc hơn, tuỳ số lần nhìn. Lý do: p-value dao động ngẫu nhiên theo thời gian; càng nhìn nhiều lần, càng có lúc nó tình cờ chạm ngưỡng. Cách phòng:

  • Cam kết cỡ mẫu/thời gian trước, chỉ phân tích một lần ở cuối.
  • Nếu cần theo dõi liên tục, dùng sequential testing hợp lệ (mục 6).

5.2 Multiple testing

Test 20 metric cùng lúc ở α=0.05 → kỳ vọng có ~1 metric "significant" hoàn toàn do may rủi. Cách xử lý: hạn chế một primary metric; nếu buộc phải test nhiều, hiệu chỉnh (Bonferroni: chia α cho số test; hoặc kiểm soát FDR bằng Benjamini-Hochberg).

5.3 Novelty & primacy effect

Người dùng cũ có thể click nhiều vào tính năng mới chỉ vì lạ (novelty), hoặc ngược lại chê vì quen cái cũ (primacy). Hiệu ứng này tan dần. Cách phát hiện: xem metric theo thời gian và tách riêng khách mới vs khách cũ; chạy test đủ dài để hiệu ứng ổn định.

5.4 Sample Ratio Mismatch (SRM)

Bạn định chia 50/50 nhưng thực tế nhóm A nhận 52%, B nhận 48% với hàng chục nghìn mẫu — đây là dấu hiệu có lỗi hệ thống (bot lọt vào một nhóm, redirect, logging lệch). Kiểm tra bằng chi-square goodness-of-fit trên số lượng phân nhóm; nếu p rất nhỏ → SRM, và mọi kết quả metric sau đó không đáng tin, phải điều tra trước khi đọc bất cứ con số nào.

5.5 Không đủ power

Kết quả "không significant" không có nghĩa "không có hiệu ứng" — có thể chỉ vì mẫu quá nhỏ. Đây là lý do phải tính power trước. Nếu CI của chênh lệch rộng và bao trùm cả giá trị đáng quan tâm lẫn 0, kết luận đúng là "chưa đủ dữ liệu để kết luận", không phải "hai bên như nhau".


6. Sequential testing & Bayesian A/B (điểm qua)

Khung cổ điển (fixed-horizon) đòi phải cố định cỡ mẫu. Hai hướng thay thế cho phép ra quyết định linh hoạt hơn:

  • Sequential testing: các phương pháp như group sequential (ranh giới O'Brien-Fleming) hay always-valid p-values / mSPRT cho phép nhìn kết quả liên tục mà vẫn kiểm soát Type I error, nhờ điều chỉnh ngưỡng theo số lần nhìn. Đổi lại: phức tạp hơn và thường cần cỡ mẫu tối đa lớn hơn cho cùng power.
  • Bayesian A/B: thay vì p-value, báo cáo P(B tốt hơn A) và phân phối hậu nghiệm của lift (dùng prior Beta cho tỷ lệ → hậu nghiệm Beta, xem Bayes trong stat-02). Diễn giải trực quan cho stakeholder ("87% khả năng B thắng, và nếu B thua thì thiệt hại kỳ vọng nhỏ"). Cần lưu ý prior và cũng không miễn nhiễm với việc dừng tuỳ tiện nếu diễn giải sai.

Không có phương pháp nào "miễn phí": mọi cách nhìn dữ liệu liên tục đều phải trả giá bằng ngưỡng chặt hơn hoặc mẫu nhiều hơn. Chọn theo văn hoá tổ chức và mức độ cần ra quyết định nhanh.


Use case thực tế

Bối cảnh (NCB). Sản phẩm muốn tăng tỷ lệ khách hoàn tất mở thẻ tín dụng ngay trong app. Đề xuất: rút form 5 bước xuống 3 bước và thêm thanh tiến độ.

  1. Giả thuyết: form ngắn hơn tăng tỷ lệ hoàn tất. H0: p_B = p_A.
  2. Metric: primary = tỷ lệ submit hồ sơ thành công. Guardrail = tỷ lệ hồ sơ bị từ chối do thiếu thông tin (không được tăng), và tỷ lệ khiếu nại. Diagnostic = drop-off từng bước.
  3. Randomization: user-level, hash customer_id với salt cố định, 50/50.
  4. Cỡ mẫu: baseline 12%, MDE +1.5 điểm %, α=0.05, power=0.80 → ~7.750/nhóm (tính ở mục 3), dự kiến ~16 ngày.
  5. Chạy & kiểm tra: AA test một tuần trước sạch; trong khi chạy kiểm tra SRM hàng ngày (52/48 với 15k mẫu → chi-square p<0.001 → phải dừng điều tra; hoá ra một phiên bản app cũ luôn rơi vào control).
  6. Phân tích cuối: A = 900/7750 (11.6%), B = 1.070/7750 (13.8%). z-test cho p < 0.001; CI 95% chênh lệch [+0.9%, +3.5%], lift ~+19%. Guardrail: tỷ lệ từ chối không đổi, khiếu nại giảm nhẹ.
  7. Quyết định: ship cho 100%, theo dõi 4 tuần sau để loại trừ novelty effect.

Các thí nghiệm anh em cùng khung: thử giao diện onboarding (đo tỷ lệ hoàn tất eKYC), và email nhắc thanh toán thẻ (đo tỷ lệ trả nợ đúng hạn — cẩn thận đơn vị randomization là khách, không phải email, để tránh gửi lẫn lộn). Việc đánh giá chất lượng mô hình chấm điểm đề xuất offer nên xem ml-05.


Ghi nhớ

  • Randomization là thứ biến so sánh thành nhân quả — nó cân bằng cả confounder không quan sát được. Không có nó, chỉ là tương quan (stat-06).
  • Chọn một primary metric + guardrail trước khi chạy. Nhiều primary = multiple testing trá hình.
  • Tính cỡ mẫu trước từ baseline, MDE, α, power. Underpowered → "không significant" vô nghĩa. Cỡ mẫu tỷ lệ nghịch với bình phương MDE.
  • Tỷ lệ chuyển đổi → two-proportion z-test; trung bình → t-test (Welch). Luôn báo cáo CI của chênh lệch và lift, không chỉ p-value.
  • Bẫy chết người: peeking/dừng sớm (thổi phồng Type I), multiple testing, novelty effect, SRM (kiểm tra bằng chi-square trước khi tin bất kỳ số nào), thiếu power.
  • Muốn nhìn kết quả liên tục → dùng sequential testing hợp lệ hoặc Bayesian A/B, đừng peeking kiểu naïve.
  • Mẫu SQL cốt lõi: GROUP BY nhóm rồi AVG(cờ 0/1) cho ra tỷ lệ theo nhóm — cùng cấu trúc dù là variant hay currency.

Bài viết liên quan

Bài mở đầu series thống kê: đo xu hướng trung tâm và độ phân tán, phân vị và five-number summary, hình dạng phân phối, và cách xử lý dữ liệu lệch phải điển hình trong ngân hàng. Kèm SQL chạy được trên PostgreSQL sandbox.

13 thg 7, 2026 3

Phân biệt ước lượng điểm và ước lượng khoảng, cách xây khoảng tin cậy (CI) bằng margin of error z*·SE / t*·SE, đánh đổi mức 90/95/99%, và cách DIỄN GIẢI ĐÚNG khoảng tin cậy (95% CI không phải xác suất tham số nằm trong khoảng). Có CI cho trung bình và cho tỷ lệ, ảnh hưởng của cỡ mẫu, cùng ví dụ ngân hàng về tỷ lệ nợ xấu và số dư trung bình.

13 thg 7, 2026 3

Chọn biểu đồ đúng cho từng loại dữ liệu, nguyên tắc thiết kế dashboard, và kể chuyện bằng dữ liệu.

13 thg 7, 2026 3

Kimball dimensional modeling: bảng fact/dimension, star vs snowflake, grain, và Slowly Changing Dimension.

13 thg 7, 2026 2