Nhân quả 5 — Difference-in-Differences (DiD)

13 thg 7, 2026 2 lượt xem
#bi
#causal-inference
#panel-data
#did
#policy-evaluation

Bài toán: đo tác động khi không A/B được

matching & propensity, ta xử lý dữ liệu quan sát bằng cách so những cá thể giống nhau về confounder đã đo. Nhưng điểm chết ở đó là confounder ẩn: nếu bỏ sót một biến gây nhiễu quan trọng, kết quả sai lệch mà không cách nào phát hiện. Difference-in-Differences (DiD — khác biệt của khác biệt) tiếp cận vấn đề từ một góc khác và mạnh hơn ở nhiều tình huống thực chiến: thay vì cố đo hết confounder, nó khử cả một lớp lớn confounder — mọi thứ cố định theo thời gian — bằng cách quan sát mỗi nhóm ở hai thời điểm và nhìn vào sự thay đổi thay vì mức tuyệt đối.

Bối cảnh điển hình ở ngân hàng: một chính sách / tính năng / chiến dịch áp cho một nhóm (một số chi nhánh, một vùng, một phân khúc khách) tại một thời điểm — nhưng ta không A/B test được vì đã triển khai rồi, hoặc vì không thể chia ngẫu nhiên (ra mắt tính năng app ở vài tỉnh, tung chương trình cho một nhóm khách VIP). Ta có hai nhóm — treated (được can thiệp) và control (không) — và dữ liệu trướcsau thời điểm can thiệp. DiD dùng đúng cấu trúc "hai nhóm × hai kỳ" này.

Ý tưởng cốt lõi chỉ một câu: so sánh thay đổi (trước→sau) của nhóm treated với thay đổi của nhóm control. Hiệu số của hai thay đổi — "khác biệt của các khác biệt" — chính là ước lượng tác động, và nó tự động loại bỏ hai thứ gây nhiễu lớn nhất.


Vì sao lấy "hiệu của hai hiệu"? Hai thứ bị khử

Giả sử ta chỉ có một cách so đơn giản. Cả hai đều sai:

So thẳng treated vs control sau can thiệp (chỉ dùng kỳ sau). Sai vì hai nhóm vốn khác nhau từ đầu: chi nhánh được chọn ra mắt tính năng thường là chi nhánh ở đô thị lớn, khách trẻ, số dư cao. Chênh lệch sau can thiệp lẫn lộn giữa tác động tính năngkhác biệt cố định giữa hai nhóm.

So trước vs sau chỉ trong nhóm treated (chỉ dùng nhóm treated). Sai vì trong khoảng thời gian đó mọi thứ khác cũng thay đổi: mùa vụ, lạm phát, một chiến dịch toàn hàng, xu hướng số hoá chung. Chênh trước→sau lẫn lộn giữa tác động tính năngxu hướng chung theo thời gian.

DiD ghép hai phép so lại để mỗi phép khử một nguồn nhiễu của phép kia:

  • Lấy thay đổi trước→sau của treated đã bao gồm: tác động thật + xu hướng chung.
  • Lấy thay đổi trước→sau của control chỉ gồm: xu hướng chung (control không được can thiệp).
  • Hiệu hai thay đổi = (tác động + xu hướng chung) − (xu hướng chung) = tác động thật.

Đồng thời, vì mỗi nhóm được so với chính nó (trước vs sau), mọi khác biệt cố định giữa hai nhóm (chi nhánh đô thị vốn giao dịch cao hơn) đã bị trừ đi ngay trong từng thay đổi. Kết quả: DiD khử (a) khác biệt cố định giữa hai nhóm (không đổi theo thời gian) (b) xu hướng chung theo thời gian (tác động lên cả hai nhóm như nhau) — kể cả khi những khác biệt/xu hướng đó không đo được. Đây là điểm ăn đứt matching: confounder cố định không cần đo, chỉ cần nó không đổi theo thời gian.


Bảng 2×2 và công thức DiD

Trường hợp gọn nhất: hai nhóm, hai kỳ. Gọi $Y$ là outcome trung bình (ví dụ giá trị giao dịch trung bình / khách):

Trước (pre)Sau (post)Thay đổi (sau − trước)
Treated$T_{trước}$$T_{sau}$$\Delta T = T_{sau} - T_{trước}$
Control$C_{trước}$$C_{sau}$$\Delta C = C_{sau} - C_{trước}$

Ước lượng DiD là hiệu của hai thay đổi:

$$\text{DiD} = (T_{sau} - T_{trước}) - (C_{sau} - C_{trước}) = \Delta T - \Delta C$$

Ví dụ số minh hoạ: sau khi ra mắt tính năng, giao dịch trung bình nhóm treated tăng $T_{sau}-T_{trước} = 12$; cùng kỳ nhóm control cũng tăng $C_{sau}-C_{trước} = 5$ (do xu hướng chung). Tác động ước lượng của tính năng là $12 - 5 = 7$, không phải 12. Nếu chỉ nhìn nhóm treated ta đã thổi phồng tác động gấp gần đôi.

Một cách nhìn tương đương: DiD chính là khoảng cách dọc, tại kỳ sau, giữa outcome thực tế của treated và outcome phản thực (counterfactual) — mức mà treated lẽ ra đạt được nếu không can thiệp, ước lượng bằng cách cho treated đi song song với control.


Ước lượng bằng hồi quy: interaction treated × post

Trong thực hành ta hầu như luôn tính DiD bằng hồi quy tuyến tính thay vì trừ tay bốn ô — vì hồi quy cho ngay sai số chuẩn, khoảng tin cậy, p-value, và cho phép thêm biến kiểm soát. Mô hình chuẩn (xem nền tảng ở tương quan & hồi quy):

$$Y = \beta_0 + \beta_1 \cdot \text{Treated} + \beta_2 \cdot \text{Post} + \beta_3 \cdot (\text{Treated} \times \text{Post}) + \varepsilon$$

trong đó Treated = 1 nếu thuộc nhóm can thiệp, Post = 1 nếu ở kỳ sau. Ý nghĩa từng hệ số:

Hệ sốÝ nghĩaTương ứng bảng 2×2
$\beta_0$Control, kỳ trước$C_{trước}$
$\beta_1$Chênh cố định treated − control (ở kỳ trước)$T_{trước} - C_{trước}$
$\beta_2$Xu hướng chung theo thời gian (đo trên control)$C_{sau} - C_{trước}$
$\beta_3$Ước lượng DiD — tác động can thiệp$\Delta T - \Delta C$

Điểm mấu chốt: hệ số của số hạng tương tác $\text{Treated} \times \text{Post}$ ($\beta_3$) chính là DiD. $\beta_1$ hấp thụ khác biệt cố định giữa hai nhóm, $\beta_2$ hấp thụ xu hướng chung — nên $\beta_3$ đo đúng phần "vượt trội của treated so với đường song song". Với panel nhiều đơn vị và nhiều kỳ, dạng tổng quát là two-way fixed effects (TWFE): thêm fixed effect cho từng đơn vị (khử mọi khác biệt cố định của đơn vị) và cho từng kỳ (khử mọi cú sốc chung theo kỳ), rồi đọc hệ số của biến "đang được treatment". Nhớ cluster sai số chuẩn theo đơn vị (chi nhánh/vùng) vì quan sát trong cùng đơn vị tương quan với nhau.


DiD chỉ đúng khi thoả một giả định — và toàn bộ độ tin cậy của phương pháp nằm ở đây:

Parallel trends (xu hướng song song): nếu không có can thiệp, outcome của nhóm treated và nhóm control sẽ diễn biến song song theo thời gian — chênh lệch giữa hai nhóm giữ nguyên.

Nói cách khác: control là một đại diện hợp lệ cho xu hướng phản thực của treated. Ta không yêu cầu hai nhóm ở cùng mức (chúng được phép khác nhau — đó là $\beta_1$); ta chỉ yêu cầu chúng thay đổi cùng nhịp nếu không có can thiệp. Cả $\Delta C$ mới thay được cho "xu hướng mà treated lẽ ra có".

Trong sơ đồ: control (đường dưới) tăng đều; treated thực tế (đường trên, cao hơn) trước can thiệp chạy song song với control; sau can thiệp nó bật lên khỏi đường counterfactual (nhánh phản thực song song với control). Khoảng tác động chính là khe hở dọc giữa treated thực tế và counterfactual ở các kỳ post — đó là DiD.

Kiểm tra parallel trends thế nào? Bản thân giả định nói về phản thực nên không kiểm chứng trực tiếp được ở kỳ post. Nhưng ta kiểm gián tiếp bằng pre-trends (xu hướng trước can thiệp): nếu ở nhiều kỳ trước can thiệp hai nhóm đã đi song song, ta tin hơn rằng chúng sẽ tiếp tục song song nếu không can thiệp. Công cụ chuẩn là event study: hồi quy outcome theo một loạt biến giả "khoảng cách tới thời điểm can thiệp" (kỳ −3, −2, −1, +1, +2...), chuẩn hoá kỳ −1 = 0. Đọc các hệ số:

  • Các hệ số trước can thiệp ≈ 0 (không có chênh xu hướng trước) → pre-trends phẳng, ủng hộ parallel trends.
  • Các hệ số sau can thiệp khác 0 → tác động, và cho thấy động học (tác động tức thì hay tăng dần).

Nếu các hệ số trước đã lệch khỏi 0 (pre-trends dốc), parallel trends đáng ngờ → DiD không đáng tin, cần chọn control khác hoặc phương pháp khác.


Các mối đe doạ tới DiD

Ngay cả khi pre-trends đẹp, vẫn phải rà các nguy cơ sau — chúng phá vỡ parallel trends ở kỳ post mà pre-trends không thấy được:

  • Cú sốc riêng nhóm (concurrent shock). Một sự kiện khác chỉ tác động một nhóm trùng thời điểm can thiệp. Ví dụ: vừa ra mắt tính năng app ở vùng A thì vùng A cũng có một chi nhánh đối thủ đóng cửa → tăng giao dịch không phải do tính năng. DiD sẽ quy nhầm cú sốc này thành tác động.
  • Thay đổi thành phần (compositional change). Nhóm treated thay đổi cấu trúc giữa hai kỳ — ví dụ can thiệp thu hút khách mới khác hẳn khách cũ, nên "trước" và "sau" không còn cùng một quần thể. So sánh trên cohort cố định (cùng tập đơn vị hai kỳ) để tránh.
  • Anticipation (dự đoán trước). Đối tượng phản ứng trước khi can thiệp chính thức bắt đầu (nghe tin sắp có ưu đãi → chờ, giảm giao dịch ở kỳ "trước"). Khi đó $T_{trước}$ đã bị nhiễu, làm lệch DiD. Xử lý: chừa một khoảng đệm quanh thời điểm công bố, hoặc kiểm bằng event study (hệ số kỳ −1, −2 khác 0 theo hướng anticipation).
  • Ashenfelter's dip. Nhóm được chọn treatment thường vì vừa sa sút bất thường ngay trước đó (chọn chi nhánh đang yếu để can thiệp) → kỳ trước thấp giả tạo, "phục hồi về trung bình" bị nhầm thành tác động.

Mở rộng: nhiều kỳ, staggered adoption, synthetic control

Bảng 2×2 là ca đơn giản nhất. Thực tế hay gặp phức tạp hơn:

Nhiều kỳ / event study. Như trên: thay vì một "pre" và một "post", dùng nhiều kỳ hai bên để vẽ cả đường động học tác động và kiểm pre-trends. Đây là chuẩn trình bày DiD hiện đại.

Staggered adoption (áp dụng lệch thời điểm). Rất phổ biến ở ngân hàng: các chi nhánh nhận tính năng ở các thời điểm khác nhau (rollout theo đợt) chứ không cùng lúc. Tưởng chừng chỉ cần chạy TWFE như thường, nhưng đây là cảnh báo quan trọng của kinh tế lượng hiện đại: khi thời điểm treatment lệch nhau tác động thay đổi theo thời gian, ước lượng TWFE truyền thống có thể thiên lệch nặng — vì nó ngầm dùng các đơn vị đã được treatment trước đó làm control cho đơn vị được treatment sau ("forbidden comparison / bad controls"), thậm chí gán trọng số âm cho một số so sánh, khiến hệ số tổng hợp có thể ngược dấu với mọi tác động cá thể (Goodman-Bacon; de Chaisemartin & D'Haultfœuille; Callaway & Sant'Anna). Cách xử lý là dùng các estimator hiện đại cho staggered DiD (Callaway–Sant'Anna, Sun–Abraham, Borusyak et al.) chỉ dùng nhóm chưa-từng/chưa-được treatment làm control sạch. Ghi nhớ: rollout theo đợt → đừng ngây thơ dùng TWFE một hệ số.

Synthetic control (đối chứng tổng hợp). Khi chỉ có một đơn vị treated (một tỉnh, một chi nhánh lớn) và không có control nào đơn lẻ đủ giống, synthetic control dựng một "control tổng hợp" bằng tổ hợp có trọng số của nhiều đơn vị control sao cho khớp với đường tiền-can-thiệp của treated. Có thể xem là khái quát/anh em của DiD cho ca "một treated, chuỗi thời gian dài" — thường dùng cho đánh giá chính sách cấp vùng/quốc gia.


DiD vs matching — và kết hợp

DiD và matching/propensity giải quyết hai loại confounder khác nhau, nên bổ sung cho nhau:

Khử đượcKhông khử được
Matching / propensityConfounder đã đo (kể cả thay đổi theo thời gian)Confounder ẩn (chưa đo)
DiDConfounder cố định theo thời gian (kể cả ẩn)Confounder thay đổi theo thời gian phá parallel trends

Vì thế thực hành mạnh là kết hợp: dùng matching/propensity để chọn nhóm control giống treated về các đặc điểm quan sát được (làm parallel trends đáng tin hơn), rồi chạy DiD trên cặp/nhóm đã cân bằng đó ("matched DiD" / "conditional DiD"). Matching lo confounder đo được; DiD lo confounder cố định không đo được. Điểm yếu còn lại của DiD — confounder biến thiên theo thời gian riêng cho một nhóm — là thứ không phương pháp nào ở đây xử lý trọn vẹn, phải dựa vào thiết kế và IV/RDD khi có công cụ phù hợp.


Use case thực tế

Bối cảnh. NCB ra mắt tính năng chuyển tiền nhanh trong app nhưng chỉ bật cho khách ở một số tỉnh (nhóm treated) trong đợt thí điểm quý 1; các tỉnh còn lại (control) chưa có. Không A/B được vì rollout theo địa lý, không ngẫu nhiên. Câu hỏi: tính năng có làm tăng số lượng / giá trị giao dịch số của khách không?

Vì sao không so thẳng. Các tỉnh thí điểm là đô thị lớn, khách trẻ, vốn giao dịch nhiều hơn (khác biệt cố định) toàn hàng đang có xu hướng số hoá tăng đều (xu hướng chung). So thẳng treated vs control sau đợt, hoặc so trước→sau chỉ trong treated, đều thổi phồng tác động.

Quy trình DiD.

  1. Xác định treated / controlmốc thời gian can thiệp (ngày bật tính năng). Chốt outcome: giá trị giao dịch trung bình / khách / tháng.
  2. Kiểm pre-trends bằng event study: dựng chuỗi 4–6 tháng trước mốc cho cả hai nhóm; ước lượng hệ số từng kỳ. Nếu các kỳ trước ≈ 0 (song song) → parallel trends đáng tin.
  3. Chạy hồi quy DiD với TWFE (fixed effect tỉnh + tháng), đọc hệ số $\text{Treated} \times \text{Post}$, cluster sai số chuẩn theo tỉnh.
  4. Rà mối đe doạ: có cú sốc riêng nào ở tỉnh thí điểm cùng kỳ? Có anticipation (khách nghe tin trước)? Thành phần khách có đổi (tính năng hút khách mới)?
  5. Nếu rollout theo đợt (các tỉnh bật lệch tháng) → không dùng TWFE một hệ số; chuyển sang estimator staggered (Callaway–Sant'Anna) để tránh bias trọng số âm.
  6. Kết quả (minh hoạ): nhóm treated tăng +18% giao dịch số sau đợt; nhưng control cũng tăng +11% (số hoá chung). DiD = +18% − 11% = +7 điểm phần trăm là tác động thật của tính năng, CI 95% [+3, +11]. Kết luận: tính năng có tác động dương thật nhưng nhỏ hơn nhiều con số +18% ngây thơ.

Minh hoạ SQL — tính DiD 2×2 trên sandbox. Sandbox không có bảng chương trình thật. Để minh hoạ cơ chế DiD 2×2 ("chia trước/sau bằng một mốc thời gian, chia treated/control bằng một thuộc tính, tính trung bình mỗi ô, rồi lấy hiệu của hai hiệu"), ta quy ước chỉ để minh hoạ:

  • Mốc thời gian chia trước/sau: một ngày cắt trên transactions.created_at (ví dụ 2024-01-01).
  • Nhóm treated/control: dùng thuộc tính currency của tài khoản (VND đóng vai treated, khác VND đóng vai control) — chỉ để có hai nhóm, không phải một can thiệp thật.
  • Tính trung bình amount mỗi ô của bảng 2×2, rồi ghép lại thành công thức DiD.
-- ▶ Chạy được
WITH cell AS (
  SELECT
    CASE WHEN a.currency = 'VND' THEN 'treated' ELSE 'control' END AS grp,
    CASE WHEN t.created_at >= TIMESTAMP '2024-01-01' THEN 'post' ELSE 'pre' END AS period,
    t.amount
  FROM transactions t
  JOIN accounts a ON a.id = t.account_id
)
SELECT
  grp,
  period,
  COUNT(*)                        AS n_txn,
  ROUND(AVG(amount)::numeric, 2)  AS avg_amount
FROM cell
GROUP BY grp, period
ORDER BY grp, period;

Truy vấn trên cho bốn ô $T_{trước}, T_{sau}, C_{trước}, C_{sau}$. Bước tiếp theo — lấy hiệu của hai hiệu — có thể gộp thẳng trong một truy vấn để ra luôn con số DiD:

-- ▶ Chạy được
WITH cell AS (
  SELECT
    CASE WHEN a.currency = 'VND' THEN 'treated' ELSE 'control' END AS grp,
    CASE WHEN t.created_at >= TIMESTAMP '2024-01-01' THEN 'post' ELSE 'pre' END AS period,
    t.amount
  FROM transactions t
  JOIN accounts a ON a.id = t.account_id
),
avg_cell AS (
  SELECT grp, period, AVG(amount) AS avg_amount
  FROM cell
  GROUP BY grp, period
),
wide AS (
  SELECT
    MAX(avg_amount) FILTER (WHERE grp = 'treated' AND period = 'post') AS t_post,
    MAX(avg_amount) FILTER (WHERE grp = 'treated' AND period = 'pre')  AS t_pre,
    MAX(avg_amount) FILTER (WHERE grp = 'control' AND period = 'post') AS c_post,
    MAX(avg_amount) FILTER (WHERE grp = 'control' AND period = 'pre')  AS c_pre
  FROM avg_cell
)
SELECT
  ROUND(t_pre::numeric,  2)                                   AS treated_pre,
  ROUND(t_post::numeric, 2)                                   AS treated_post,
  ROUND(c_pre::numeric,  2)                                   AS control_pre,
  ROUND(c_post::numeric, 2)                                   AS control_post,
  ROUND((t_post - t_pre)::numeric, 2)                         AS delta_treated,
  ROUND((c_post - c_pre)::numeric, 2)                         AS delta_control,
  ROUND(((t_post - t_pre) - (c_post - c_pre))::numeric, 2)    AS did_estimate
FROM wide;

Cột delta_treated = $\Delta T$, delta_control = $\Delta C$, và did_estimate = $\Delta T - \Delta C$ — đúng công thức DiD 2×2. FILTER (WHERE ...) pivot bốn ô thành bốn cột, ::numeric bảo đảm ROUND chạy đúng trên PostgreSQL. (Nhắc lại: đây thuần tuý minh hoạ cơ chế DiD — currency và mốc 2024-01-01 chỉ đóng vai nhóm/mốc giả lập, không phải một can thiệp có thật; con số ra không có ý nghĩa nghiệp vụ.)


Ghi nhớ

  • DiD so THAY ĐỔI (trước→sau) của treated với thay đổi của control; hiệu của hai thay đổi $\Delta T - \Delta C$ là ước lượng tác động — khử (a) khác biệt cố định giữa hai nhóm và (b) xu hướng chung theo thời gian, kể cả khi chúng không đo được.
  • Bảng 2×2 (treated/control × trước/sau) → $\text{DiD} = (T_{sau}-T_{trước}) - (C_{sau}-C_{trước})$; tương đương khe hở dọc giữa treated thực tế và counterfactual song song với control.
  • Ước lượng chuẩn bằng hồi quy: hệ số của interaction $\text{Treated}\times\text{Post}$ chính là DiD (xem hồi quy); panel nhiều kỳ dùng TWFE, nhớ cluster sai số theo đơn vị.
  • Giả định sống còn là PARALLEL TRENDS: nếu không can thiệp, hai nhóm sẽ đi song song (không cần cùng mức). Kiểm gián tiếp bằng pre-trends / event study — hệ số các kỳ trước ≈ 0.
  • Mối đe doạ: cú sốc riêng nhóm, thay đổi thành phần, anticipation, Ashenfelter's dip — phá parallel trends ở kỳ post.
  • Mở rộng: event study (động học + kiểm pre-trends); staggered adoptionđừng ngây thơ dùng TWFE một hệ số (bias trọng số âm), dùng estimator hiện đại (Callaway–Sant'Anna...); synthetic control cho ca một treated.
  • DiD vs matching bổ sung nhau: matching lo confounder đã đo, DiD lo confounder cố định không đo được → kết hợp "matched DiD".
  • Ngân hàng: đo tác động một chính sách/tính năng/chiến dịch áp cho một nhóm chi nhánh/phân khúc tại một thời điểm khi không A/B được — luôn báo con số đã trừ xu hướng chung kèm kiểm pre-trends.

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.

13 thg 7, 2026 4

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

13 thg 7, 2026 3

Khối OLAP, các thao tác drill-down/roll-up/slice & dice/pivot, OLAP vs OLTP và ROLAP/MOLAP/HOLAP.

13 thg 7, 2026 3

Từ nguồn dữ liệu qua ETL/ELT vào Data Warehouse, Data Mart đến dashboard; staging, ODS, batch vs streaming.

13 thg 7, 2026 3