Nhân quả 4 — Matching & Propensity Score
Bài toán: so sánh hai nhóm không thể so sánh
Trong RCT, randomization thiết kế ra điều kiện để hai nhóm treatment và control tương đương nhau về mọi đặc điểm. Nhưng như đã bàn ở cuối bài đó, ngân hàng có rất nhiều tình huống không thể randomize: chương trình đã chạy rồi, khách tự chọn tham gia, hoặc chính sách áp cho tất cả. Khi đó ta chỉ có dữ liệu quan sát (observational data) — người tham gia và người không tham gia là do hoàn cảnh và chính họ quyết định, không phải do ta tung đồng xu.
Vấn đề cốt lõi là self-selection: khách chủ động đăng ký gói tiết kiệm mới thường đã là người có kỷ luật tài chính tốt, số dư cao, giao dịch đều. Nếu chỉ so trung bình outcome giữa "người tham gia" và "người không", phần chênh lệch lẫn lộn giữa tác động của chương trình và tác động của việc họ vốn đã tốt hơn từ đầu. Đây chính là confounding, và với dữ liệu quan sát nó gần như luôn có mặt.
Ý tưởng của matching và propensity score rất trực quan: nếu không randomize được để làm hai nhóm tương đương, thì ta làm cho chúng so sánh được sau khi thu dữ liệu — bằng cách chỉ ghép/so những cá thể treated với những cá thể control giống hệt nhau về các biến gây nhiễu. So sánh "táo với táo" thay vì "táo với cam".
Hai giả định phải nắm trước khi làm bất cứ điều gì
Mọi phương pháp trong bài này chỉ đúng khi hai giả định sau thoả — và chúng không kiểm chứng được hoàn toàn từ dữ liệu, nên phải biện luận bằng hiểu biết nghiệp vụ.
1. Unconfoundedness (còn gọi ignorability, conditional independence, selection-on-observables). Giả định rằng ta đã đo được HẾT các confounder. Nói chính xác: sau khi điều kiện hoá (đặt cùng giá trị) trên tập biến quan sát được $X$, việc ai được treatment là độc lập với outcome tiềm năng. Trực giác: trong nhóm khách "giống nhau về $X$", ai tham gia ai không gần như là chuyện ngẫu nhiên. Đây là giả định mạnh và không thể chứng minh — luôn có nguy cơ tồn tại confounder ẩn (unobserved confounder) mà ta không đo được (ví dụ mức độ am hiểu tài chính, biến cố cá nhân).
2. Overlap (common support, positivity). Với mọi tổ hợp giá trị $X$, phải có cả cá thể treated lẫn control — tức xác suất nhận treatment nằm hẳn trong khoảng $(0, 1)$, không phải 0 hay 1. Nếu một loại khách luôn luôn tham gia (không có control tương ứng), ta không có gì để so → không ước lượng được hiệu ứng cho họ.
Hai giả định này là điều kiện để công thức nền tảng đúng:
$$ATE = E_X\big[, E[Y \mid T{=}1, X] - E[Y \mid T{=}0, X] ,\big]$$
Nghĩa là: so sánh outcome treated vs control trong từng lát cắt $X$ giống nhau, rồi lấy trung bình có trọng số theo phân bố của $X$. Matching, stratification, IPW đều là các cách thực thi khác nhau của đúng công thức này.
Matching: ghép cặp giống nhau
Ý tưởng đơn giản nhất: với mỗi cá thể treated, tìm một (hoặc vài) cá thể control giống nhất về các confounder $X$, ghép thành cặp, rồi so outcome trong cặp. Trung bình chênh lệch trong các cặp là ước lượng hiệu ứng (thường là ATT — Average Treatment effect on the Treated, hiệu ứng trên chính nhóm được treatment).
Các biến thể:
| Kiểu matching | Cách ghép | Ưu / nhược |
|---|---|---|
| Exact matching | Chỉ ghép khi $X$ trùng khớp hoàn toàn | Sạch nhất; nhưng bung theo số chiều — nhiều treated không tìm được cặp |
| Nearest neighbor (NN) | Ghép với control gần nhất theo một khoảng cách (Euclid, Mahalanobis) | Luôn tìm được cặp; nhưng cặp có thể tệ nếu control gần nhất vẫn xa |
| Caliper matching | Chỉ chấp nhận cặp trong bán kính (caliper) tối đa | Loại các cặp tệ, tăng chất lượng; nhưng bỏ một số treated → giảm mẫu, đổi quần thể diễn giải |
| k:1 / with replacement | 1 treated ghép k control, hoặc control được tái sử dụng | Giảm phương sai / tận dụng control hiếm; đổi lại bias–variance |
Curse of dimensionality (lời nguyền số chiều). Đây là điểm chết của matching trực tiếp. Với 2–3 biến rời rạc, exact matching còn khả thi. Nhưng ngân hàng thường có hàng chục confounder (tuổi, thu nhập, số dư, số sản phẩm, thâm niên, vùng, kênh...). Khi số chiều tăng, không gian $X$ "loãng" ra: hầu như không có hai khách nào trùng khớp hoàn toàn, và ngay cả "gần nhất" cũng xa. Matching trở nên bất khả thi hoặc cho cặp kém chất lượng.
Propensity score: gom nhiều chiều về một điểm
Lời giải kinh điển (Rosenbaum & Rubin, 1983) cho lời nguyền số chiều là propensity score (điểm xu hướng) — ký hiệu $e(X)$ — định nghĩa là xác suất được nhận treatment cho trước các biến $X$:
$$e(X) = P(T = 1 \mid X)$$
Kết quả nền tảng: nếu unconfoundedness đúng theo $X$ thì nó cũng đúng khi chỉ điều kiện hoá trên propensity score $e(X)$. Nói cách khác, cân bằng trên một con số $e(X)$ là đủ để cân bằng trên cả vector $X$. Ta đã nén cả chục chiều confounder về một điểm vô hướng trong $[0,1]$ — thoát khỏi curse of dimensionality.
Ước lượng $e(X)$ thế nào? Cách phổ biến nhất là logistic regression (xem ml-03 linear & logistic): hồi quy biến nhị phân $T$ (tham gia / không) theo các confounder $X$, đầu ra là xác suất tham gia dự đoán — chính là propensity score. Có thể dùng mô hình mạnh hơn (gradient boosting) nếu quan hệ phi tuyến, nhưng logistic vẫn là mặc định vì đơn giản và dễ chẩn đoán.
Lưu ý quan trọng: propensity score model dự đoán $T$, KHÔNG dự đoán outcome $Y$. Ta không quan tâm nó dự đoán "tham gia" chính xác đến đâu — chỉ quan tâm nó có cân bằng được các confounder giữa hai nhóm hay không (kiểm tra balance ở dưới).
Bốn cách dùng propensity score
1. PSM — Propensity Score Matching
Ghép mỗi treated với control có $e(X)$ gần nhất (thường kèm caliper, ví dụ $\le 0.2$ độ lệch chuẩn của logit propensity). Sau ghép, tập cặp trở thành "gần như RCT" trên các biến đã đo → so outcome trong cặp. Trực quan, dễ giải thích cho nghiệp vụ. Nhược: bỏ các treated không có cặp tốt (giảm mẫu), và ước lượng ATT chứ không mặc nhiên là ATE.
2. Stratification (phân tầng / subclassification)
Chia toàn bộ khách thành các tầng theo propensity score (kinh điển là 5 tầng ngũ phân vị — quintile). Trong mỗi tầng, khách treated và control có $e(X)$ tương đương → confounder xấp xỉ cân bằng. Ta so chênh outcome treated–control trong từng tầng, rồi lấy trung bình có trọng số theo cỡ tầng để ra ATE. Đây là cách trực quan nhất và cũng là cái ta minh hoạ được bằng SQL ở dưới: chia tầng → so sánh trong tầng → gộp lại.
3. IPW — Inverse Probability Weighting
Thay vì ghép hay chia tầng, ta gán trọng số cho từng cá thể nghịch với xác suất họ được ở nhóm hiện tại:
- Treated: trọng số $= 1 / e(X)$
- Control: trọng số $= 1 / (1 - e(X))$
Trực giác: một khách treated hiếm (ít khả năng được treatment, $e$ nhỏ) đại diện cho nhiều khách giống họ chưa được treatment → nhân trọng số lớn lên để "bù". Kết quả là một pseudo-population (quần thể giả) trong đó treatment độc lập với $X$ — như thể đã randomize. So trung bình outcome có trọng số giữa hai nhóm cho ra ATE. Cảnh báo: khi $e(X)$ tiến gần 0 hoặc 1 (overlap kém), trọng số bùng nổ, một vài cá thể chi phối kết quả → phương sai lớn, cần stabilized weights hoặc trimming/clipping (cắt các điểm $e$ cực đoan).
4. Doubly robust (kết hợp)
Kết hợp hai mô hình: một outcome model ($E[Y \mid T, X]$) và một propensity model ($e(X)$). Tính chất "doubly robust" (nhị trọng vững): ước lượng vẫn không thiên lệch nếu ÍT NHẤT MỘT trong hai mô hình đúng. Ta có "hai lần cơ hội" — an toàn hơn hẳn khi chỉ dựa vào một mô hình. Các phương pháp hiện đại (AIPW, TMLE) đều thuộc họ này và là lựa chọn mặc định trong thực hành nghiêm túc.
| Phương pháp | Cơ chế | Cho ra | Rủi ro chính |
|---|---|---|---|
| PSM | Ghép cặp theo điểm | ATT | Bỏ mẫu, chất lượng cặp |
| Stratification | Chia tầng, so trong tầng | ATE (gộp có trọng số) | Tầng thô → còn confounding dư |
| IPW | Trọng số nghịch xác suất | ATE | Trọng số bùng nổ khi overlap kém |
| Doubly robust | Outcome + propensity | ATE | Phức tạp hơn; vẫn cần overlap |
Chẩn đoán: balance và overlap
Propensity score không phải nút bấm ma thuật — phải kiểm tra hai thứ sau khi áp dụng, nếu không kết quả vô nghĩa.
Balance (cân bằng). Sau matching/weighting, các confounder $X$ phải trở nên tương đương giữa treated và control. Thước đo chuẩn là Standardized Mean Difference (SMD — chênh trung bình chuẩn hoá): chênh trung bình một biến giữa hai nhóm, chia cho độ lệch chuẩn gộp. Quy ước thực hành: |SMD| < 0.1 là cân bằng tốt. Luôn báo cáo bảng SMD trước và sau điều chỉnh (love plot) — nếu sau matching mà SMD vẫn lớn thì mô hình propensity chưa đạt, phải sửa (thêm biến, thêm số hạng tương tác/bậc hai).
Overlap (common support). Vẽ phân bố propensity score của hai nhóm chồng lên nhau. Vùng $e(X)$ mà chỉ có treated hoặc chỉ có control là vùng không có common support — ở đó không so sánh được, thường bị trim (cắt bỏ) khỏi phân tích. Trim làm sạch ước lượng nhưng đổi quần thể diễn giải: hiệu ứng giờ chỉ đúng cho vùng có overlap, không phải toàn bộ khách.
Hạn chế cốt lõi: đừng nhầm với RCT
Đây là mục quan trọng nhất — dễ bị lạm dụng nhất trong thực tế.
Chỉ điều chỉnh được confounder ĐÃ ĐO. Toàn bộ sức mạnh của matching/propensity đứng trên giả định unconfoundedness: ta đã đo hết confounder. Nhưng propensity score chỉ cân bằng những biến ta đưa vào mô hình. Với confounder ẩn (unobserved — không có trong dữ liệu), phương pháp bó tay hoàn toàn. Đây là khác biệt sinh tử với RCT: randomization cân bằng cả biến ẩn; matching thì không. Một mô hình propensity đẹp, balance SMD hoàn hảo vẫn có thể sai lệch nặng nếu bỏ sót một confounder quan trọng.
Phụ thuộc giả định mạnh, không kiểm chứng được. Unconfoundedness không test được từ dữ liệu. Vì thế mọi kết luận nhân quả từ observational data đều có điều kiện: "đúng nếu đã đo hết confounder". Phải nói rõ điều kiện này khi báo cáo, không tuyên bố như thể là RCT.
Sensitivity analysis (phân tích độ nhạy). Vì không loại trừ được confounder ẩn, thực hành đúng là đánh giá kết quả nhạy đến đâu với nó. Kỹ thuật như Rosenbaum bounds hay E-value trả lời: "một confounder ẩn phải mạnh cỡ nào mới lật ngược được kết luận?". Nếu chỉ cần một confounder yếu là đảo kết quả → kết luận mong manh; nếu phải cần một confounder cực mạnh (mạnh hơn mọi biến đã đo) → kết luận vững hơn. Đây là chuẩn mực khi trình kết quả observational cho hội đồng ra quyết định.
Use case thực tế
Bối cảnh. NCB triển khai chương trình "Ưu đãi khách hàng thân thiết" cho phép khách đăng ký nhận hoàn phí giao dịch. Chương trình không randomize — khách tự đăng ký (self-selection). Câu hỏi: chương trình có thực sự làm tăng giá trị giao dịch trung bình của khách, hay chỉ là những khách vốn đã giao dịch nhiều mới đăng ký?
Vì sao không so trực tiếp được. Khách đăng ký (treated) trung bình có số dư và tần suất giao dịch cao hơn hẳn nhóm không đăng ký (control) từ trước khi chương trình chạy. So thẳng "giao dịch của người đăng ký vs không đăng ký" sẽ thổi phồng hiệu ứng — đó là confounding do self-selection.
Quy trình xử lý.
- Xác định confounder đã đo: số dư, số sản phẩm, thâm niên, độ tuổi, vùng, tần suất giao dịch kỳ trước. (Ghi nhận: các yếu tố như "ý thức chi tiêu" không đo được → là confounder ẩn tiềm tàng.)
- Ước lượng propensity score: logistic regression
đăng_ký ~ số_dư + số_sản_phẩm + thâm_niên + tuổi + vùng. - Kiểm tra overlap: cắt bỏ vùng propensity không có common support.
- Áp dụng stratification (5 tầng theo điểm) hoặc PSM (caliper), so outcome trong tầng/cặp.
- Kiểm tra balance: SMD mọi confounder < 0.1 sau điều chỉnh.
- Kết quả (minh hoạ): so thẳng cho "chênh +38% giá trị giao dịch" — nhưng sau khi điều chỉnh confounder chỉ còn +9%, CI 95% [+4%, +14%]. Phần lớn "hiệu ứng" ban đầu là self-selection, không phải tác dụng thật của chương trình.
- Sensitivity analysis: E-value cho thấy cần một confounder ẩn mạnh vừa phải mới xoá được +9% → kết luận thận trọng dương, đề xuất kiểm chứng bằng một RCT nhỏ ở nhóm mới.
Minh hoạ SQL — kỹ thuật stratification trên sandbox. Sandbox không có bảng chương trình thật. Để minh hoạ cơ chế "chia tầng theo confounder rồi so sánh outcome giữa hai nhóm trong từng tầng", ta:
- coi currency (
VNDvs khác) là nhóm cần so sánh (đóng vai treatment/control giả lập), - coi số dư là confounder cần điều chỉnh → chia khách thành các tầng số dư (
ntile), - so trung bình giá trị giao dịch giữa hai nhóm currency trong từng tầng — đúng tinh thần "so trong lát cắt confounder giống nhau".
-- ▶ Chạy được
WITH base AS (
SELECT
a.id AS account_id,
CASE WHEN a.currency = 'VND' THEN 'grp_vnd' ELSE 'grp_other' END AS grp,
NTILE(5) OVER (ORDER BY a.balance) AS balance_stratum
FROM accounts a
),
txn AS (
SELECT b.balance_stratum, b.grp, t.amount
FROM base b
JOIN transactions t ON t.account_id = b.account_id
)
SELECT
balance_stratum,
grp,
COUNT(*) AS n_txn,
ROUND(AVG(amount)::numeric, 2) AS avg_amount
FROM txn
GROUP BY balance_stratum, grp
ORDER BY balance_stratum, grp;
Kết quả cho từng tầng số dư, trung bình giao dịch của mỗi nhóm currency. So sánh trong cùng một tầng đã loại phần chênh do khác biệt số dư — mô phỏng việc "điều chỉnh confounder". Bước tiếp theo (ngoài SQL demo) là gộp chênh của các tầng bằng trung bình có trọng số theo cỡ tầng → ước lượng "đã điều chỉnh", đúng công thức stratification.
Có thể gộp thẳng trong một truy vấn để thấy chênh trong tầng và chênh gộp có trọng số — đúng bước cuối của stratification:
-- ▶ Chạy được
WITH base AS (
SELECT
a.id AS account_id,
CASE WHEN a.currency = 'VND' THEN 'grp_vnd' ELSE 'grp_other' END AS grp,
NTILE(5) OVER (ORDER BY a.balance) AS stratum
FROM accounts a
),
per_stratum AS (
SELECT
b.stratum,
COUNT(*) AS n_txn,
AVG(CASE WHEN b.grp = 'grp_vnd' THEN t.amount END) AS avg_vnd,
AVG(CASE WHEN b.grp = 'grp_other' THEN t.amount END) AS avg_other
FROM base b
JOIN transactions t ON t.account_id = b.account_id
GROUP BY b.stratum
)
SELECT
stratum,
n_txn,
ROUND(avg_vnd::numeric, 2) AS avg_vnd,
ROUND(avg_other::numeric, 2) AS avg_other,
ROUND((avg_vnd - avg_other)::numeric, 2) AS diff_in_stratum,
ROUND(
(SUM((avg_vnd - avg_other) * n_txn) OVER () )
/ NULLIF(SUM(n_txn) OVER (), 0)::numeric, 2) AS weighted_overall_diff
FROM per_stratum
ORDER BY stratum;
Cột diff_in_stratum là chênh đã điều chỉnh trong tầng; weighted_overall_diff là ước lượng gộp có trọng số theo cỡ tầng — chính là logic stratification estimator. NULLIF chặn chia 0, ::numeric bảo đảm ROUND chạy đúng trên PostgreSQL. (Nhắc lại: đây là minh hoạ cú pháp stratification; currency/số dư chỉ đóng vai treatment/confounder giả lập, không phải một chương trình thật.)
Ghi nhớ
- Khi không randomize được, matching/propensity làm hai nhóm so sánh được sau khi thu dữ liệu bằng cách chỉ so những cá thể giống nhau về confounder đã đo — táo với táo.
- Mọi thứ đứng trên hai giả định: unconfoundedness (đã đo hết confounder) và overlap (mọi $X$ đều có cả treated lẫn control). Cả hai không kiểm chứng đầy đủ được từ dữ liệu.
- Matching (exact / nearest neighbor / caliper) trực quan nhưng chết vì curse of dimensionality khi nhiều confounder.
- Propensity score $e(X)=P(T{=}1\mid X)$ — thường ước lượng bằng logistic regression — nén nhiều chiều về một điểm; cân bằng trên $e(X)$ là đủ.
- Bốn cách dùng: PSM (ghép theo điểm → ATT), stratification (chia tầng, so trong tầng, gộp có trọng số), IPW (trọng số nghịch xác suất → pseudo-population; coi chừng trọng số bùng nổ), doubly robust (kết hợp outcome + propensity, đúng nếu một trong hai mô hình đúng).
- Luôn chẩn đoán: balance bằng SMD (< 0.1) trước/sau, và overlap (trim vùng không common support).
- Hạn chế sinh tử: chỉ điều chỉnh confounder ĐÃ ĐO — bó tay với confounder ẩn; khác hẳn RCT vốn cân bằng cả biến ẩn. Luôn kèm sensitivity analysis (Rosenbaum bounds / E-value).
- Ngân hàng: đo tác động chương trình khi khách tự chọn tham gia (self-selection) → ghép khách tham gia với khách tương tự không tham gia; báo cáo con số đã điều chỉnh kèm giả định, tốt nhất kiểm chứng lại bằng một RCT nhỏ.
Bài viết liên quan
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.
Kimball dimensional modeling: bảng fact/dimension, star vs snowflake, grain, và Slowly Changing Dimension.
Khối OLAP, các thao tác drill-down/roll-up/slice & dice/pivot, OLAP vs OLTP và ROLAP/MOLAP/HOLAP.
Từ nguồn dữ liệu qua ETL/ELT vào Data Warehouse, Data Mart đến dashboard; staging, ODS, batch vs streaming.