← All posts

อคติจากตัวแปรกวน → Propensity score → Matching: พิมพ์ตามในเทอร์มินัล

Clinical Epidemiology ResearchUniqcret doctor knowledges THData Analytics or StatisticsMethodology and Research Design TH
อคติจากตัวแปรกวน → Propensity score → Matching: พิมพ์ตามในเทอร์มินัล
On this page

Read in English — อ่านฉบับภาษาอังกฤษ

บทคัดย่อ

Propensity score (Rosenbaum & Rubin, 1983) คือการยุบตัวแปรกวน (confounder) ที่วัดได้ทุกตัวให้เหลือเลขเดียว — ความน่าจะเป็นที่โมเดลทำนายว่าจะได้รับการรักษา — จากนั้นการจับคู่ (matching) จะนำผู้ป่วยกลุ่มรักษากับกลุ่มควบคุมที่มีเลขนี้ใกล้กันมาจับคู่กัน แนวคิดนี้พูดง่ายแต่ เห็นภาพ ยาก บทความนี้ทำให้มันเห็นได้จริง โดยรันการวิเคราะห์ทั้งหมดทีละบรรทัดในเทอร์มินัลที่ฝังอยู่ บนข้อมูล จำลอง 600 คน ที่อายุ เพศ และความรุนแรงของโรคมีผลทั้งต่อ “ใครได้รับการรักษา” และ “ฟื้นตัวเร็วแค่ไหน” เพราะเป็นข้อมูลจำลอง เราจึงรู้คำตอบล่วงหน้า — ยาช่วยลดวันฟื้นตัวลงจริง 3.0 วัน — จึงตรวจได้ว่าค่าประมาณแต่ละแบบตรงกับความจริงแค่ไหน การเทียบแบบไม่ปรับให้ผลกลับทิศ ส่วนการจับคู่ด้วยคะแนนดึงค่ากลับมาใกล้ความจริงในระยะไม่ถึงหนึ่งวัน คุณเป็นคนลงมือรันเทอร์มินัลเอง ทั้งใน R และ Python แล้วดูตาราง ค่า หรือกราฟของแต่ละขั้นปรากฏบนแผง Stage


Visual summary — confounding, propensity score and matching in a terminal
Visual summary · ภาพสรุป

ทำไมการเทียบแบบไม่ปรับถึงชวนเข้าใจผิด

เราอยากรู้ว่า treatment ช่วยให้ฟื้นตัวเร็วขึ้นไหม สิ่งที่ดูจะทำได้ทันที — หาค่าเฉลี่ยวันฟื้นตัวของกลุ่มรักษา หาค่าเฉลี่ยของกลุ่มที่ไม่ได้รักษา แล้วลบกัน — แทบจะทำให้เข้าใจคลาดเคลื่อนเสมอในข้อมูลเชิงสังเกต เพราะไม่มีใครโยนเหรียญเพื่อตัดสินว่าใครจะได้รับการรักษา

ทุกอย่างด้านล่างรันบน ข้อมูลจำลองเพื่อการสอน — ผู้ป่วยสมมติ 600 คน ไม่ใช่คนจริง — สร้างด้วย seed ที่ตายตัวเพื่อให้ทุกคนทำซ้ำได้ เราตั้งใจแจก treatment ให้ผู้ป่วยอายุมากและป่วยหนักบ่อยกว่า ซึ่งลักษณะเดียวกันนี้ก็ทำให้ใช้เวลาฟื้นตัวนานขึ้นด้วย confounding จึงถูกฝังไว้ตั้งแต่ต้น และเราจะได้เห็นการจับคู่ดึงมันกลับออกมา ในรอบของ R สัดส่วนคือ กลุ่มรักษา 352 คน และกลุ่มควบคุม 248 คน ไม่มีการคำนวณในเบราว์เซอร์ของคุณ เทอร์มินัลเพียงเล่นซ้ำการวิเคราะห์จริงที่คำนวณไว้ก่อนแล้ว ทีละบรรทัด

1 · Simulate 600 true effect −3.0 days 2 · Unadjusted diff. +3.88 days · sign flips 3 · Fit PS glm(treated ~ X) 4 · Match 1:1 caliper 0.181 · 198 pairs 5 · Re-check balance every |SMD| under 0.1 6 · Matched estimate −2.93 days ≈ truth
จากกลุ่มที่มีตัวกวน สู่ค่าประมาณที่ซื่อตรง — เส้นเรื่องที่เทอร์มินัลเล่นซ้ำ (ตัวเลขจากแท็บ R)

คำตอบแบบไม่ปรับให้เครื่องหมายตรงข้าม

หาผลต่างของค่าเฉลี่ยวันฟื้นตัวสองกลุ่ม แล้วนี่คือสิ่งที่เทอร์มินัลพิมพ์ออกมาในแท็บ R:

ค่า จำนวนวันฟื้นตัว
ค่าเฉลี่ยกลุ่มรักษา 50.30
ค่าเฉลี่ยกลุ่มควบคุม 46.41
ผลต่างแบบไม่ปรับ +3.88  (ค่าจริง = −3.0)

กลุ่มรักษาดูเหมือนฟื้นตัว ช้ากว่า — เป็นบวก +3.88 วัน — ทั้งที่โดยการออกแบบข้อมูล ยาช่วยให้ฟื้นตัว เร็วขึ้น 3.0 วัน ค่าประมาณแบบไม่ปรับไม่ได้แค่มากเกินไป แต่ยัง กลับทิศ ด้วย สาเหตุคือ confounding: กลุ่มรักษาอายุมากและป่วยหนักกว่า และอายุกับความรุนแรงเพิ่มวันฟื้นตัวได้เร็วกว่าที่ยาจะลดลง การเทียบแบบนี้จึงกำลังวัด “ตัวโรค” ไม่ใช่ “ตัวยา”


สองกลุ่มเริ่มต้นห่างกันแค่ไหน

เราวัดช่องว่างพื้นฐานด้วย standardized mean difference (SMD) — ผลต่างค่าเฉลี่ยของตัวแปรระหว่างสองกลุ่ม หารด้วยส่วนเบี่ยงเบนมาตรฐานรวม เพื่อให้อายุ (หน่วยปี) กับความรุนแรง (สเกลของตัวเอง) เทียบกันได้ กฎง่าย ๆ ที่ใช้กันคือ |SMD| > 0.1 บ่งบอกความไม่สมดุลที่มีนัย บน cohort ดิบของ R:

ตัวแปร กลุ่มรักษา กลุ่มควบคุม SMD
อายุ 62.03 58.48 0.36
เพศ 0.57 0.39 0.36
ความรุนแรง 5.45 4.21 0.65

ทั้งสามตัวสูงเกิน 0.1 ชัดเจน โดยความรุนแรงแย่ที่สุดที่ 0.65 กลุ่มรักษาอายุมากและป่วยหนักกว่าจริง ๆ ตั้งแต่ก่อนเราจะทำอะไร — ซึ่งเป็นเหตุผลว่าทำไมผลต่างแบบไม่ปรับถึงออกมากลับทิศ


Propensity score คือคะแนนทำนายการรักษา

การไล่ balance ตัวแปรทีละตัวสามตัวก็ยุ่งพอแล้ว งานวิจัยจริงมียี่สิบตัว เคล็ดของ propensity score คือบีบทุกตัวให้เหลือเลขเดียวต่อคน — ความน่าจะเป็นที่โมเดลทำนายว่าคนนั้นจะได้รับการรักษา:

$$\text{PS}_i = \Pr(\text{treated}=1 \mid X_i)$$

เราได้มันจาก logistic regression ธรรมดาของการรักษาบนตัวกวน คือ glm(treated ~ age + sex + severity, family = binomial) ในรอบของ R สัมประสิทธิ์ที่ประมาณได้ออกมาเป็นบวกทั้งสามตัว — ผู้ป่วยอายุมาก กลุ่มที่โค้ด sex = 1 และป่วยหนักกว่า มีโอกาสได้รับการรักษามากกว่า — ซึ่งก็คือ confounding ที่เขียนออกมาเป็นโมเดลนั่นเอง สังเกตว่าคะแนนนี้ ไม่ใช่ อะไร: มันไม่เคยเห็น outcome เลย มันโมเดล การได้รับการรักษาเท่านั้น — ว่าการรักษาถูกแจกออกไปอย่างไร — ซึ่งเป็นจุดที่คนเข้าใจผิดกันบ่อยที่สุด

ด้านล่างนี้คุณรันเองได้ทั้งกระบวนการ เทอร์มินัลเล่นซ้ำการวิเคราะห์จริงทีละบรรทัด ตั้งแต่ cohort ดิบไปจนถึงค่าประมาณหลังจับคู่ พิมพ์ตามบรรทัดที่ไฮไลต์ (หรือกด Enter เพื่อเติมและรัน) กด Tab เพื่อรับคำที่ระบบเดาให้ แล้วแผง Stage ด้านขวาจะแสดงตาราง ค่า หรือกราฟที่แต่ละขั้นสร้างขึ้น สลับแท็บระหว่าง R กับ Python เพื่อดูการวิเคราะห์เดียวกันในทั้งสองภาษา อย่าลืม: ไม่มีการคำนวณในเบราว์เซอร์ ทุกค่าและกราฟถูกคำนวณไว้ก่อนแล้ว

แนวคิด propensity score นิยามได้ง่ายแต่ เห็นภาพ ยาก เทอร์มินัลนี้รันการวิเคราะห์จริงทีละบรรทัดบนข้อมูล จำลอง 600 คน ที่อายุ เพศ และความรุนแรงของโรคมีผลทั้งต่อโอกาสได้รับการรักษา และ จำนวนวันที่ฟื้นตัว การเปรียบเทียบแบบไม่ปรับจึงมีตัวกวนปนหนัก ลองดูเส้นเรื่อง: ผลต่างวันฟื้นตัวแบบไม่ปรับออกมาลำเอียง (กลับทิศและห่างจากค่าจริง) จากนั้นการจับคู่ด้วยคะแนนจะปิดช่องว่างลง และค่าที่ได้จากการจับคู่จะเข้าใกล้ค่าที่เราตั้งไว้ คือ ผลการรักษาจริง −3.0 วัน คุณเป็นคนลงมือเอง — พิมพ์ตามบรรทัดที่ไฮไลต์ (หรือกด Enter เพื่อเติมและรัน) กด Tab เพื่อรับคำที่ระบบเดาให้ แล้วแผง Stage ด้านขวาจะแสดงตาราง ค่า หรือกราฟที่โค้ดแต่ละขั้นสร้างขึ้น สลับแท็บระหว่าง R กับ Python เพื่อดูการวิเคราะห์เดียวกันในทั้งสองภาษา ไม่มีการคำนวณในเบราว์เซอร์ของคุณ ทุกค่าและกราฟถูกคำนวณด้วย R และ Python ไว้ก่อนแล้ว

A propensity score is easy to define and hard to see. This terminal runs the real analysis one line at a time on a simulated cohort of 600 patients, where age, sex and severity push people toward treatment and shape how fast they recover — so an unadjusted comparison is badly confounded. Watch the arc: the unadjusted difference in recovery days comes out biased (opposite sign, far from the truth), then matching on the score closes the gap and the matched estimate lands near the value baked into the simulation, a true effect of −3.0 days. You drive it — type the highlighted line (or press Enter to autofill and run), press Tab to accept the autocomplete ghost, and the Stage on the right shows the table, number, or plot each step produces. Switch between the R and Python tabs to see the same analysis in either language. Nothing runs in your browser; every value and plot was computed in R and Python beforehand.

การจับคู่ดึงอะไรกลับมาได้บ้าง

การจับคู่เป็นข้อเสนอ ไม่ใช่ข้อพิสูจน์ เราจึงคำนวณ SMD ใหม่บนกลุ่มที่จับคู่แล้วและดูผล เมื่อใช้ caliper เท่ากับ 0.181 บน logit ของ propensity score ลูปจับคู่ 1:1 แบบเพื่อนบ้านใกล้สุด (greedy) เก็บกลุ่มรักษาไว้ 198 คนจาก 352 คน — เฉพาะคนที่มี control ใกล้พอ การจับคู่แบบเพื่อนบ้านใกล้สุดก็คือการจับผู้ป่วยกลุ่มรักษาแต่ละคนเข้ากับกลุ่มควบคุมที่คะแนนใกล้ที่สุด ซึ่งเป็นกฎเดียวกับที่ MatchIt ใช้อยู่ภายใน การเขียนลูปเองกับการเรียก MatchIt จึงเป็นสองเส้นทางสู่การจับคู่เดียวกัน ในกลุ่มที่จับคู่แล้ว ความไม่สมดุลพื้นฐานแทบหายไป:

ตัวแปร SMD ก่อน SMD หลัง |SMD| ต่ำกว่า 0.1?
อายุ 0.36 0.04 ใช่
เพศ 0.36 0.00 ใช่
ความรุนแรง 0.65 −0.02 ใช่

ทุกช่องว่างยุบลงต่ำกว่าเส้น 0.1 — ความรุนแรงที่แย่ที่สุดลดจาก 0.65 เหลือ −0.02 ทีนี้ถึงบทสรุป ประเมินผลการรักษาใหม่ ภายใน กลุ่มที่จับคู่แล้ว แล้วนำมาเรียงเทียบกับคำตอบแบบไม่ปรับและค่าจริง:

ค่าประมาณ จำนวนวันฟื้นตัว
ผลต่างแบบไม่ปรับ +3.88
ผลต่างหลังจับคู่ −2.93
ผลจริง −3.00

ค่าประมาณแบบไม่ปรับมีเครื่องหมายตรงข้าม ส่วนค่าประมาณหลังจับคู่มาอยู่ที่ −2.93 วัน ห่างจากค่าจริง −3.0 ไม่ถึงหนึ่งในสิบวัน ข้อมูลชุดเดิม คอลัมน์ outcome ตัวเดิม สิ่งเดียวที่เปลี่ยนคือ เรานำผู้ป่วยกลุ่มรักษาแต่ละคนไปเทียบกับใคร


R กับ Python: เรื่องเดียวกัน คนละการสุ่ม

สองแท็บนี้ไม่ใช่การมองข้อมูลชุดเดียวกันสองมุม แต่ละแท็บสุ่ม cohort ของตัวเองด้วยตัวสร้างเลขสุ่มและ seed ของตัวเอง ตัวเลขเป๊ะ ๆ จึงต่างกัน — และนั่นแหละคือประเด็น ในแท็บ Python ผลต่างแบบไม่ปรับคือ +2.75 วัน (เครื่องหมายตรงข้ามอีกเช่นกัน) caliper เก็บคู่ที่จับได้ 116 คู่ และค่าประมาณหลังจับคู่คือ −3.10 วัน เทียบกับค่าจริง −3.0 เท่ากัน การสุ่มอิสระสองครั้ง ให้คำตอบแบบไม่ปรับที่ลำเอียงสองครั้ง และค่าประมาณหลังจับคู่ที่ห่างจากความจริงไม่ถึงหนึ่งวันทั้งสองครั้ง เส้นเรื่องนี้เป็นของวิธีการ ไม่ใช่ของตัวอย่างที่บังเอิญโชคดีเพียงชุดเดียว


ข้อมูลจำลองนี้พิสูจน์อะไร — และไม่พิสูจน์อะไร

เดโมนี้น่าเชื่อถือเพราะมันถูก “จัดฉาก” อย่างซื่อสัตย์ และควรพูดให้ชัดถึงข้อจำกัดของมัน


บทสรุป

Propensity score matching คือแนวคิดเล็ก ๆ ที่ซื่อสัตย์อันเดียว ทำซ้ำ ๆ: เปลี่ยน confounder ที่วัดได้ทุกตัวให้เป็นความน่าจะเป็นที่ทำนายไว้ค่าเดียว จับคู่คนที่มีเลขนี้ตรงกัน แล้ว ตรวจ ว่าการจับคู่ทำให้สองกลุ่มเทียบกันได้จริงหรือไม่ ดูมันสักครั้งในเทอร์มินัลด้านบน แล้วกล่องดำก็กลายเป็นลำดับสั้น ๆ ที่อ่านออก — จาก +3.88 ที่ลำเอียง กลายเป็น −2.93 ที่ซื่อตรง สำหรับ confounder ที่วัดได้ และเฉพาะตัวเหล่านั้นเท่านั้น

สำหรับวิธีเดียวกันที่สอนทีละขั้น — ครอบคลุมทั้งแบบกลุ่มรักษาเทียบกลุ่มควบคุมและแบบ case–control และจับคู่ทั้งแบบทำเองและด้วย MatchIt — ดูได้ที่ Propensity Score for Matching, Step by Step

Key takeaways

0
ถึงนักอ่านชาวไทยและต่างชาติทำความเข้าใจบริบททางการแพทย์ของผมอ่านต่อ →ถึงนักอ่านชาวไทยและต่างชาติทำความเข้าใจเนื้อหาของผมที่นอกเหนือจากการแพทย์อ่านต่อ →

ความคิดเห็น

ยังไม่มีความคิดเห็น มาเป็นคนแรกกันเลย

เข้าสู่ระบบเพื่อแสดงความคิดเห็น