Marginal Structural Models: เมื่อตัวกวนวันนี้คือผลของการรักษาเมื่อวาน

On this page
Read the English version
บทคัดย่อ
ในหอผู้ป่วยวิกฤต ภาวะอวัยวะบกพร่องรุนแรงทำให้มีแนวโน้มให้ยามากขึ้น และยาแต่ละครั้งลดโอกาสเกิดภาวะอวัยวะบกพร่องในการประเมินครั้งถัดไป ภาวะอวัยวะบกพร่องจึงเป็นตัวกวนที่เปลี่ยนตามเวลา (time-varying confounder) ซึ่งการรักษาก่อนหน้าส่งผลต่อมัน ถ้าตัดตัวแปรนี้ออกจากการถดถอยแบบธรรมดา ยาทุกครั้งยังถูกกวน ถ้าใส่เข้าไป จะปิดกั้นผลที่ผ่านภาวะอวัยวะบกพร่อง และเปิดเส้นทางลวง (เส้นทางผ่านตัวแปรชนกัน หรือ collider ซึ่งเป็นจุดที่ลูกศรสองเส้นชี้มาบรรจบกัน) ผ่านความเปราะบางที่ไม่ได้วัด แบบจำลองโครงสร้างระดับประชากร (marginal structural model, MSM) หลีกเลี่ยงทั้งสองปัญหาด้วยการถ่วงน้ำหนักผู้ป่วยแต่ละคนด้วยส่วนกลับของความน่าจะเป็นของประวัติการรักษาที่ได้รับ ตัวอย่างคำนวณด้วยมือที่มีผู้ป่วย 800 คนและข้อมูลจำลองของผู้ป่วย 3,000 คนแสดงความผิดพลาดทั้งสองแบบ ในข้อมูลจำลองนั้น การถดถอยแบบธรรมดาให้ผลต่อหนึ่งวันที่ได้รับยาเป็น +0.086 หรือ -0.013 ส่วน MSM ให้ -0.057 เทียบกับค่าจริง -0.050 และให้ -0.172 สำหรับการได้รับยาตลอดเทียบกับไม่ได้รับเลย เทียบกับค่าจริง -0.150 บทความนี้แสดงวิธีสร้างน้ำหนักแบบปรับเสถียรสะสม (cumulative stabilised weight: น้ำหนักที่คูณกันข้ามหลายวันและปรับขนาดให้มีค่าเฉลี่ยราว 1) ประมาณและตรวจ MSM และรายงานข้อจำกัดของมัน
การถดถอยที่แพทย์เจ้าของไข้ไม่เชื่อ
ในหอผู้ป่วยวิกฤต (intensive care unit, ICU) ทีมพิจารณาให้ยา X ซึ่งเป็นยาสมมติที่ปกป้องอวัยวะซ้ำทุกสองวัน คือวันที่ 0 วันที่ 2 และวันที่ 4 ภาวะอวัยวะบกพร่องรุนแรง (severe organ dysfunction) ณ การประเมินแต่ละครั้งทำให้ทีมมีแนวโน้มให้ยามากขึ้น ยาที่ให้ในการประเมินครั้งหนึ่งลดโอกาสเกิดภาวะอวัยวะบกพร่องรุนแรงในการประเมินครั้งถัดไป ผลลัพธ์ที่สำคัญคือการเสียชีวิตภายในวันที่ 28
แพทย์ประจำบ้านต่อยอดสาขาเวชบำบัดวิกฤต (critical care fellow) รวบรวมบันทึกของหอผู้ป่วยเป็นกลุ่มผู้ป่วย 3,000 คน แล้วถดถอยการเสียชีวิตภายในวันที่ 28 บนจำนวนวันที่ผู้ป่วยแต่ละคนได้รับยา X เมื่อไม่ปรับ แต่ละวันที่ได้รับยาเพิ่มความเสี่ยงเสียชีวิต 0.068 ราวกับว่ายาเป็นอันตราย เมื่อปรับด้วยอายุและภาวะอวัยวะบกพร่องรุนแรงในแต่ละวันที่ตัดสินใจ ค่าประมาณลดลงเหลือ 0.011 ต่อหนึ่งวันที่ได้รับยา (ช่วงเชื่อมั่น 95% คือ -0.006 ถึง 0.027)
แพทย์เจ้าของไข้ (attending physician) ไม่เชื่อตัวเลขทั้งสอง เชื่อกันว่ายา X ออกฤทธิ์เป็นหลักด้วยการป้องกันภาวะอวัยวะบกพร่องในการประเมินครั้งถัดไป การปรับด้วยภาวะอวัยวะบกพร่องจึงปรับเอาผลที่ทีมต้องการวัดทิ้งไปเอง แต่ถ้าไม่ใส่ ผู้ป่วยที่อาการหนักที่สุดจะกระจุกอยู่ในกลุ่มที่ได้รับยา เพราะภาวะอวัยวะบกพร่องคือเหตุผลที่ให้ยา
ข้อโต้แย้งทั้งสองถูกต้อง และการเลือกตัวแปรร่วมแบบใดในการถดถอยแบบธรรมดาก็ตอบไม่ได้ทั้งสองเรื่อง แบบจำลองโครงสร้างระดับประชากร (marginal structural models, MSMs) สร้างขึ้นเพื่อปัญหานี้ คือแบบจำลองของความเสี่ยงที่แผนการรักษาแต่ละแผนจะให้ โดยประมาณด้วยน้ำหนักแทนการปรับตัวแปรร่วม กลุ่มผู้ป่วยในบทความนี้เป็นข้อมูลจำลอง จึงทราบคำตอบที่แท้จริง แต่ละวันที่ได้รับยาลดความเสี่ยงเสียชีวิตภายในวันที่ 28 ลง 0.050
คำถาม: การเปรียบเทียบแผนการรักษา
ก่อนสร้างแบบจำลองใด คำถามต้องอยู่ในรูปที่การทดลองตอบได้ แผนการรักษา (treatment regime) หรือกลยุทธ์การรักษา (treatment strategy) คือกฎที่กำหนดการรักษาในทุกวันที่ตัดสินใจ สองแผนที่สนใจเป็นหลักคือได้รับยาตลอด (ยา X ในวันที่ 0, 2 และ 4) และไม่ได้รับยาเลย (ไม่มียาในทั้งสามวัน) แผนอาจขึ้นกับสภาพผู้ป่วยก็ได้ เช่น "ให้ยา X เมื่อมีภาวะอวัยวะบกพร่องรุนแรง" แต่บทความนี้ใช้แผนแบบตายตัว
ให้ $A_t$ แทนการรักษาในวันที่ตัดสินใจ $t$ (1 = ได้รับยา X, 0 = ไม่ได้รับ) โดย $t$ = 0, 1 และ 2 แทนวันที่ 0, 2 และ 4 แผนคือลำดับ $\bar a = (a_0, a_1, a_2)$ และขีดบนตัวอักษรหมายถึงประวัติ ได้รับยาตลอดคือ $\bar a = (1, 1, 1)$ และไม่ได้รับยาเลยคือ $\bar a = (0, 0, 0)$ ให้ $Y$ แทนการเสียชีวิตภายในวันที่ 28 (1 = เสียชีวิต)
ปริมาณเป้าหมายของการประมาณ (estimand) คือปริมาณที่การศึกษาตั้งใจประมาณ ในที่นี้คือความเสี่ยงเสียชีวิตภายในวันที่ 28 หากผู้ป่วยทุกคนได้รับยาตลอด ลบด้วยความเสี่ยงหากผู้ป่วยทุกคนไม่ได้รับยาเลย นั่นคือการเปรียบเทียบที่การทดลองแบบสุ่มสองกลุ่มของสองแผนนี้จะทำ โดยติดตามผู้ป่วยทุกคนตั้งแต่การประเมินในวันที่ 0
ตามศัพท์ที่ใช้กัน นี่คือ ผลเฉลี่ยของการรักษาในประชากรทั้งหมด (average treatment effect, ATE) คือผลที่เฉลี่ยทั้งประชากร ในที่นี้คือผู้ป่วยทุกคนในกลุ่มผู้ป่วยไอซียูตั้งแต่วันที่ 0 ATE ในที่นี้ถามว่าจะเกิดอะไรขึ้นหากผู้ป่วยทุกคนทำตามแต่ละแผนตลอด มันไม่ใช่ผลของยาหนึ่งครั้ง และไม่ใช่ผลต่างระหว่างผู้ป่วยที่บังเอิญทำตามแต่ละแผน
ในกลุ่มผู้ป่วยจำลอง (ข้อมูลจำลอง) ผู้ป่วย 471 คนได้รับยา X ครบสามวัน และ 588 คนไม่ได้รับเลย ที่เหลือได้รับในหนึ่งวัน (1,009 คน) หรือสองวัน (932 คน) การเปรียบเทียบ 471 คนกับ 588 คนให้คำตอบที่ถูกกวน เพราะสองกลุ่มต่างกันในภาวะอวัยวะบกพร่องทุกวันที่ตัดสินใจ
วาดปัญหาให้เห็นตามเวลา
ให้ $L_t$ แทนภาวะอวัยวะบกพร่องรุนแรงในการประเมินของวันที่ตัดสินใจ $t$ (1 = มี) ซึ่งบันทึกก่อนการตัดสินใจของวันนั้น จากนี้ไป ภาวะอวัยวะบกพร่องหมายถึงแบบรุนแรงนี้ ให้ $V$ แทนตัวแปรร่วม ณ จุดเริ่มต้น ในที่นี้คือกลุ่มอายุ (1 = อายุ 65 ปีขึ้นไป, 0 = อายุน้อยกว่านั้น) และ $U$ แทนความเปราะบาง (frailty) ซึ่งเป็นลักษณะของผู้ป่วยที่ไม่มีใครในหอผู้ป่วยบันทึก แผนภาพแสดงว่าตัวแปรเหล่านี้เป็นสาเหตุของกันและกันอย่างไรในสองวันแรกที่ตัดสินใจ
ตัวแปรหนึ่งตัว สองบทบาท
ดู $L_1$ คือภาวะอวัยวะบกพร่องรุนแรงในวันที่ 2 ลูกศร $A_0 \to L_1$ บอกว่ายาวันที่ 0 เปลี่ยนมัน ลูกศร $L_1 \to A_1$ และ $L_1 \to Y$ บอกว่ามันขับเคลื่อนการตัดสินใจวันที่ 2 และความเสี่ยงเสียชีวิต
สำหรับยาวันที่ 0 $L_1$ เป็น ตัวแปรสื่อกลาง (mediator) คือตัวแปรบนเส้นทางเหตุผลจากการรักษาไปสู่ผลลัพธ์ ในที่นี้คือ $A_0 \to L_1 \to Y$ สำหรับยาวันที่ 2 $L_1$ เป็นตัวกวน คือสาเหตุร่วมของยานั้นและการเสียชีวิตผ่าน $A_1 \leftarrow L_1 \to Y$ มันไม่เคยเป็นทั้งสองอย่างสำหรับยาครั้งเดียวกัน มันเป็นผลของการรักษาก่อนหน้าและเป็นเหตุผลของการรักษาครั้งถัดไป
ตัวแปรร่วมที่วัดระหว่างติดตามผล ซึ่งมีผลต่อการรักษาครั้งหลังและต่อผลลัพธ์ เรียกว่า ตัวกวนที่เปลี่ยนตามเวลา (time-varying confounder) เมื่อการรักษาก่อนหน้าเปลี่ยนมันด้วยเช่นในที่นี้ รูปแบบนี้เรียกว่า วงจรป้อนกลับระหว่างการรักษากับตัวกวน (treatment-confounder feedback) [1] ผู้ป่วยที่ได้รับยาในการตัดสินใจหนึ่งมักได้รับยาในครั้งถัดไปด้วย ($A_{t-1} \to A_t$ เมื่อ $A_{t-1}$ คือการรักษาในการตัดสินใจครั้งก่อน) ความเปราะบางเพิ่มทั้งโอกาสเกิดภาวะอวัยวะบกพร่องและความเสี่ยงเสียชีวิต ($U \to L_t$ และ $U \to Y$)
ความเปราะบางไม่เคยมีผลต่อการรักษาเลย ยกเว้นผ่านภาวะอวัยวะบกพร่อง รายละเอียดนี้ตัดสินว่าปัญหาแก้ได้ด้วยข้อมูลที่วัดไว้หรือไม่ และจะกลับมาอีกในหัวข้อเรื่องข้อสมมติ
ทำไมการถดถอยแบบธรรมดาจึงผิดพลาดทั้งสองทาง
สมมติว่าการวิเคราะห์คือการถดถอยการเสียชีวิตบนการรักษา และการตัดสินใจเดียวคือจะใส่ตัวแปรร่วมใดบ้าง ภาวะอวัยวะบกพร่องจะตัดออกหรือใส่เข้าไปก็ได้ และแต่ละทางทำให้ส่วนต่างกันของแผนภาพเสียหาย
ตัดภาวะอวัยวะบกพร่องออก: ยาทุกครั้งยังถูกกวน
เมื่อไม่มี $L_t$ ในแบบจำลอง เส้นทาง $A_t \leftarrow L_t \to Y$ ยังเปิดอยู่ในทุกวันที่ตัดสินใจ รวมถึง $A_0 \leftarrow L_0 \to Y$ ในวันที่ 0 ผู้ป่วยที่ได้รับยาในวันที่ตัดสินใจหนึ่งได้รับเพราะมีภาวะอวัยวะบกพร่องรุนแรง ซึ่งเพิ่มความเสี่ยงเสียชีวิตไม่ว่ายาจะทำอะไร ยาจึงรับความเสี่ยงของผู้ป่วยที่ได้รับมันไป และดูเหมือนเป็นอันตราย $L_0$ ซึ่งบันทึกก่อนการรักษาใด ๆ เป็นตัวกวน ณ จุดเริ่มต้นตามปกติที่ปรับได้อย่างปลอดภัย ปัญหาเริ่มที่ $L_1$ และ $L_2$ ซึ่งยาครั้งก่อนเปลี่ยนไปแล้ว
ปรับด้วยภาวะอวัยวะบกพร่อง: ผลถูกปิดกั้นและเส้นทางตัวแปรชนกันเปิดขึ้น
เมื่อมี $L_t$ ในแบบจำลอง จะผิดพลาดสองอย่างที่ต่างกัน อย่างแรก $L_1$ อยู่บนเส้นทาง $A_0 \to L_1 \to Y$ การตรึงมันไว้จึงตัดส่วนของผลของยาวันที่ 0 ที่ออกฤทธิ์ด้วยการป้องกันภาวะอวัยวะบกพร่องทิ้งไป ในกลุ่มผู้ป่วยจำลอง นั่นคือผลส่วนใหญ่ จากค่าจริง -0.050 ต่อหนึ่งวันที่ได้รับยา มี -0.045 ที่ผ่านภาวะอวัยวะบกพร่อง และเป็นผลโดยตรงเพียง -0.005
อย่างที่สอง $L_1$ เป็น ตัวแปรชนกัน (collider) บนอีกเส้นทางหนึ่ง ตัวแปรชนกันคือตัวแปรที่ลูกศรสองเส้นบนเส้นทางชี้เข้าหา ในที่นี้คือ $A_0 \to L_1 \leftarrow U$ ถ้าปล่อยไว้ ตัวแปรชนกันจะปิดกั้นเส้นทางที่ผ่านมัน แต่การปรับด้วยมันจะเปิดเส้นทาง $A_0 \to L_1 \leftarrow U \to Y$ ซึ่งเชื่อมยาวันที่ 0 กับการเสียชีวิตผ่านความเปราะบาง
เส้นทางที่เปิดขึ้นมีทิศทางในที่นี้ ในกลุ่มผู้ป่วยที่มีภาวะอวัยวะบกพร่องรุนแรงในวันที่ 2 ผู้ที่ได้รับยาในวันที่ 0 เกิดภาวะนี้ทั้งที่ได้ยา จึงมีผู้เปราะบางมากกว่า ผู้ป่วยเปราะบางเสียชีวิตบ่อยกว่า แบบจำลองที่ปรับจึงทำให้ยาครั้งก่อนดูแย่กว่าความจริง ผลที่ถูกปิดกั้นและเส้นทางที่เปิดขึ้นรวมกันทำให้ค่าประมาณที่ปรับแล้วใกล้ศูนย์กว่าค่าจริงมาก
ผู้วิเคราะห์ติดกับ ถ้าตัด $L_t$ ออก ยาทุกครั้งถูกกวน ถ้าใส่เข้าไป ยาครั้งก่อนสูญเสียผลจริงและได้ผลลวงมาแทน ไม่มีชุดตัวแปรร่วมใดในการถดถอยผลลัพธ์ครั้งเดียวที่แก้ปัญหาทั้งสองได้ [1, 2]
ทางสองแพร่งในตารางเดียว
| ทางเลือก | สิ่งที่แก้ได้ | สิ่งที่เสียหาย | ทิศทางในกลุ่มผู้ป่วยจำลอง |
|---|---|---|---|
| ตัด $L_t$ ออก | คงผลที่ผ่านภาวะอวัยวะบกพร่องไว้ | ทิ้ง $A_t \leftarrow L_t \to Y$ ไว้เปิด ยาแต่ละครั้ง รวมถึงครั้งแรก จึงถูกกวนด้วยภาวะอวัยวะบกพร่องของวันนั้น | ยาดูเหมือนเป็นอันตราย |
| ปรับด้วย $L_t$ | ขจัดการกวนของยาแต่ละครั้งโดย $L_t$ | ปิดกั้นผลที่ผ่าน $L_t$ และเปิด $A_{t-1} \to L_t \leftarrow U \to Y$ | ค่าประมาณถูกดึงเข้าหาศูนย์ |
| ถ่วงน้ำหนักด้วยส่วนกลับของความน่าจะเป็นของการรักษาที่ได้รับ (MSM) | ขจัดการกวนโดย $L_t$ โดยไม่ต้องปรับด้วย $L_t$ | ต้องไม่มีตัวกวนที่ไม่ได้วัดเมื่อกำหนดประวัติที่วัดไว้ แบบจำลองการรักษาต้องถูกต้อง และทุกประวัติต้องมีโอกาสมากกว่าศูนย์ที่จะได้รับแต่ละการรักษา | ใกล้ค่าจริง |
ผลลัพธ์ที่อาจเกิดขึ้นภายใต้แผนการรักษา และแบบจำลองของมัน
ผลลัพธ์ที่อาจเกิดขึ้น (potential outcome) คือผลลัพธ์ที่ผู้ป่วยจะมีหากได้รับการรักษาแบบที่กำหนด และสังเกตได้เพียงค่าเดียวคือของการรักษาที่ได้รับจริง ภายใต้แผน $\bar a$ เขียนเป็น $Y^{\bar a}$ คือผู้ป่วยจะเสียชีวิตภายในวันที่ 28 หรือไม่ หากทำตามแผน $\bar a$ เมื่อมีสามวันที่ตัดสินใจ จะมีแผนที่เป็นไปได้แปดแผน ผู้ป่วยแต่ละคนจึงมีผลลัพธ์ที่อาจเกิดขึ้นแปดค่า และในที่นี้สังเกตได้เพียงค่าเดียว คือค่าของแผนที่ผู้ป่วยทำตามจริง
แบบจำลองโครงสร้างระดับประชากร (marginal structural model, MSM) คือแบบจำลองของค่าเฉลี่ยของผลลัพธ์ที่อาจเกิดขึ้นข้ามแผนต่าง ๆ [2] ที่เรียกว่าระดับประชากร (marginal) เพราะมันเฉลี่ยข้ามตัวกวนที่เปลี่ยนตามเวลาแทนที่จะปรับด้วยตัวกวนเหล่านั้น และยังใส่ตัวแปรร่วม ณ จุดเริ่มต้นอย่าง $V$ ได้ ที่เรียกว่าโครงสร้าง (structural) เพราะมันเป็นแบบจำลองของผลลัพธ์ที่อาจเกิดขึ้นซึ่งเป็นเชิงเหตุผล ไม่ใช่ความสัมพันธ์ที่สังเกตได้ MSM แบบง่ายสำหรับกลุ่มผู้ป่วยไอซียูคือ
$$E[Y^{\bar a}] = g_0 + g_1\,\mathrm{cum}(\bar a)$$ในสมการนี้ $\mathrm{cum}(\bar a) = a_0 + a_1 + a_2$ คือจำนวนวันที่ได้รับยาในแผนนั้น โดยวันที่ได้รับยาคือวันที่ตัดสินใจที่ให้ยา X ในแบบจำลอง $g_0$ คือความเสี่ยงเสียชีวิตหากไม่ได้รับยาเลย และ $g_1$ คือการเปลี่ยนแปลงของความเสี่ยงต่อหนึ่งวันที่ได้รับยา ผลต่างความเสี่ยงของการได้รับยาตลอดเทียบกับไม่ได้รับเลยจึงเท่ากับ $3 g_1$ เพราะแบบจำลองเป็นเชิงเส้นในความเสี่ยง $g_1$ จึงเป็นผลต่างความเสี่ยงโดยตรง
แบบจำลองนี้เป็นข้อสมมติ คือมีเพียงจำนวนวันที่ได้รับยาที่สำคัญ ไม่ใช่ลำดับของวัน กลุ่มผู้ป่วยจำลองสร้างให้เป็นเช่นนั้น โดยมี $g_1$ = -0.050 กับข้อมูลจริง มักเลือกรูปแบบไว้ล่วงหน้าและตรวจเทียบกับรูปแบบที่ยืดหยุ่นกว่า เช่นหนึ่งพจน์ต่อหนึ่งวันที่ตัดสินใจ
MSM ถูกนำเข้าสู่วิชาระบาดวิทยาเพื่อปัญหาแบบนี้พอดี [2] และงานแรก ๆ ที่ใช้ประมาณผลของซิโดวูดีน (zidovudine) ต่อการรอดชีพของชายที่ติดเชื้อเอชไอวี [3] MSM ประมาณหลังถ่วงน้ำหนักใหม่ เพื่อให้ในกลุ่มที่ถ่วงน้ำหนักแล้ว การรักษาในแต่ละวันไม่ขึ้นกับประวัติภาวะอวัยวะบกพร่องอีกต่อไป เมื่อกำหนดยาครั้งก่อนและอายุ ตัวอย่างคำนวณด้วยมือด้านล่างแสดงการถ่วงน้ำหนักใหม่ด้วยตัวเลขเล็กพอจะตรวจเองได้
ตัวอย่างคำนวณด้วยมือ: ยาที่ออกฤทธิ์เพียงด้วยการป้องกันภาวะอวัยวะบกพร่อง
สมมติผู้ป่วย 800 คนและสองวันที่ตัดสินใจ โดยไม่มีความเปราะบางและไม่มีกลุ่มอายุ ครึ่งหนึ่งที่สุ่มมาได้รับยา X ในวันแรก ($A_0 = 1$) ภาวะอวัยวะบกพร่องรุนแรงในวันที่สอง ($L_1 = 1$) เกิดใน 50% ของผู้ที่ไม่ได้รับยา และ 25% ของผู้ที่ได้รับยา ในวันที่สอง ยาไปถึง 75% ของผู้ป่วยที่มีภาวะอวัยวะบกพร่อง และ 25% ของผู้ป่วยที่เหลือ ($A_1 = 1$)
ความเสี่ยงเสียชีวิตคือ 0.30 เมื่อมีภาวะอวัยวะบกพร่องในวันที่สอง และ 0.10 เมื่อไม่มี ไม่ว่าจะได้รับการรักษาแบบใด ยาจึงออกฤทธิ์เพียงด้วยการป้องกันภาวะอวัยวะบกพร่อง จำนวนด้านล่างเป็นจำนวนที่คาดไว้ จึงมีเศษของการเสียชีวิตปรากฏ
-
ความเสี่ยงจริงหากได้รับยาตลอด
\[ \tfrac{1}{4} \times 0.30 + \tfrac{3}{4} \times 0.10 = 0.15 \]
หากทุกคนได้รับยาในวันแรก หนึ่งในสี่จะมีภาวะอวัยวะบกพร่องในวันที่สอง
-
ความเสี่ยงจริงหากไม่ได้รับยาเลย
\[ \tfrac{1}{2} \times 0.30 + \tfrac{1}{2} \times 0.10 = 0.20 \]
หากไม่มีใครได้รับยา ครึ่งหนึ่งจะมี ผลต่างความเสี่ยงจริงคือ 0.15 ลบ 0.20 หรือ -0.05 ยาช่วยชีวิตได้
-
ใครทำตามแผนได้รับยาตลอด
\[ 100 \times \tfrac{3}{4} + 300 \times \tfrac{1}{4} = 75 + 75 = 150 \]
ในผู้ป่วย 400 คนที่ได้รับยาในวันแรก 100 คนมีภาวะอวัยวะบกพร่องในวันที่สอง และ 300 คนไม่มี ภาวะอวัยวะบกพร่องทำให้มีแนวโน้มได้ยาครั้งที่สอง กลุ่มได้รับยาตลอดจึงมีผู้ป่วยที่มีภาวะอวัยวะบกพร่อง 75 คนและไม่มี 75 คน
-
ใครทำตามแผนไม่ได้รับยาเลย
\[ 200 \times \tfrac{1}{4} + 200 \times \tfrac{3}{4} = 50 + 150 = 200 \]
ในผู้ที่ไม่ได้รับยา 200 คนมีภาวะอวัยวะบกพร่องและ 200 คนไม่มี กลุ่มไม่ได้รับยาเลยมีผู้ป่วยที่มีภาวะอวัยวะบกพร่อง 50 คนและไม่มี 150 คน
-
การเปรียบเทียบแบบดิบ
\[ \frac{22.5 + 7.5}{150} = 0.20, \; \frac{15 + 15}{200} = 0.15 \]
การเสียชีวิตคือ 30 จาก 150 ในกลุ่มได้รับยาตลอด และ 30 จาก 200 ในกลุ่มไม่ได้รับยาเลย ผลต่างดิบคือ +0.05 ยาจึงดูเหมือนเป็นอันตราย เพราะกลุ่มได้รับยาตลอดมีสัดส่วนผู้ที่มีภาวะอวัยวะบกพร่องมากกว่า
-
การปรับด้วยภาวะอวัยวะบกพร่อง
ภายในแต่ละระดับของ $L_1$ ความเสี่ยงคือ 0.30 หรือ 0.10 ไม่ว่าจะได้รับการรักษาแบบใด ผลต่างที่ปรับแล้วจึงเป็น 0 ประโยชน์ทั้งหมดผ่านภาวะอวัยวะบกพร่อง และการปรับลบมันทิ้ง
-
น้ำหนัก ได้รับยาตลอดและมีภาวะอวัยวะบกพร่อง
\[ w = \frac{1}{0.5 \times 0.75} = \tfrac{8}{3} = 2.67 \]
น้ำหนักคือหนึ่งส่วนความน่าจะเป็นของประวัติการรักษาที่ได้รับ เมื่อกำหนดประวัติที่วัดไว้ ในที่นี้วันแรกให้ 1/0.5 = 2 และวันที่สองให้ 1/0.75 = 1.33 ประวัติที่มีความน่าจะเป็น 3/8
-
น้ำหนัก ได้รับยาตลอดและไม่มีภาวะอวัยวะบกพร่อง
\[ w = \frac{1}{0.5 \times 0.25} = 8 \]
เมื่อไม่มีภาวะอวัยวะบกพร่อง วันที่สองให้ 1/0.25 = 4 ประวัติที่มีความน่าจะเป็น 1/8
-
กลุ่มได้รับยาตลอดที่ถ่วงน้ำหนักแล้ว
\[ \frac{60 + 60}{200 + 600} = \frac{120}{800} = 0.15 \]
ผู้ป่วยที่ได้รับยาตลอดและมีภาวะอวัยวะบกพร่อง 75 คนนับเป็น 200 และผู้ที่ไม่มี 75 คนนับเป็น 600 กลุ่มที่ถ่วงน้ำหนักแล้วมี 800 คน หนึ่งในสี่มีภาวะอวัยวะบกพร่อง ราวกับว่าทุกคนได้รับยาตลอด และความเสี่ยงของกลุ่มคือค่าจริง 0.15
-
กลุ่มไม่ได้รับยาเลยที่ถ่วงน้ำหนักแล้ว
\[ \frac{120 + 40}{400 + 400} = \frac{160}{800} = 0.20 \]
ผู้ป่วยที่ไม่ได้รับยาเลยและมีภาวะอวัยวะบกพร่องทำตามประวัติที่มีความน่าจะเป็น 1/8 จึงได้น้ำหนัก 8 ส่วนคนอื่นได้ 2.67 ผู้ป่วย 50 และ 150 คนนับเป็น 400 เท่ากัน กลุ่มนี้มีภาวะอวัยวะบกพร่องครึ่งหนึ่ง และความเสี่ยงคือค่าจริง 0.20 ผลต่างที่ถ่วงน้ำหนักแล้วคือ -0.05
-
น้ำหนักแบบปรับเสถียร ได้รับยาตลอด
\[ sw = 0.1875 \times \tfrac{8}{3} = 0.5, \; sw = 0.1875 \times 8 = 1.5 \]
น้ำหนักแบบปรับเสถียรคูณด้วยความน่าจะเป็นของประวัติเดียวกันโดยไม่สนใจภาวะอวัยวะบกพร่อง นั่นคือ 0.5 สำหรับยาครั้งแรกคูณ 0.375 สำหรับยาครั้งที่สอง เพราะ 150 จาก 400 คนที่ได้รับยาครั้งแรกได้รับยาอีก ตัวเศษ 0.1875 ย่อน้ำหนักเหลือ 0.5 และ 1.5
-
ความเสี่ยงที่ถ่วงน้ำหนักแบบปรับเสถียร
\[ \frac{11.25 + 11.25}{37.5 + 112.5} = \frac{22.5}{150} = 0.15 \]
กลุ่มได้รับยาตลอดที่ปรับเสถียรคงขนาดจริงไว้ที่ 150 และความเสี่ยง 0.15 สำหรับกลุ่มไม่ได้รับยาเลย ตัวเศษคือ 0.5 สำหรับไม่ได้ยาครั้งแรก คูณ 0.5 สำหรับไม่ได้ยาครั้งที่สอง เพราะครึ่งหนึ่งของผู้ที่ไม่ได้รับยาในวันแรก รวม 200 คน ยังคงไม่ได้รับยา จึงได้ 0.25 น้ำหนักกลายเป็น 2 และ 0.67 กลุ่มคงขนาด 200 ไว้ และความเสี่ยงคือ 40 จาก 200 หรือ 0.20
ผลลัพธ์: การถ่วงน้ำหนักคืนความเสี่ยงจริง 0.15 และ 0.20 และผลต่างจริง -0.05 ในขณะที่การเปรียบเทียบแบบดิบให้ +0.05 และการปรับให้ 0 การปรับเสถียรไม่เปลี่ยนคำตอบ และดึงน้ำหนักเข้าหา 1 จาก 2.67 และ 8 เหลือระหว่าง 0.5 ถึง 2
สองขั้นแรกคือการคำนวณ g-formula ขนาดเล็ก คือความเสี่ยงภายใต้แต่ละแผน เฉลี่ยข้ามภาวะอวัยวะบกพร่องที่แผนนั้นจะก่อให้เกิด g-formula จะกลับมาอีกใกล้จบบทความ
ตัวอย่างคำนวณด้วยมือ: ค่าจริงและสี่วิธีประมาณ
| วิธี | ความเสี่ยง ได้รับยาตลอด | ความเสี่ยง ไม่ได้รับยาเลย | ผลต่าง |
|---|---|---|---|
| ค่าจริง | 0.15 | 0.20 | -0.05 |
| การเปรียบเทียบแบบดิบ | 0.20 | 0.15 | +0.05 |
| ปรับด้วย $L_1$ | 0.30 หรือ 0.10 ตามระดับของ $L_1$ | 0.30 หรือ 0.10 ตามระดับของ $L_1$ | 0 |
| น้ำหนักส่วนกลับของความน่าจะเป็น | 0.15 | 0.20 | -0.05 |
| น้ำหนักแบบปรับเสถียร | 0.15 | 0.20 | -0.05 |
น้ำหนักแบบปรับเสถียรสะสม
เมื่อมีสามวันที่ตัดสินใจ น้ำหนักแต่ละค่าเป็นผลคูณของหนึ่งตัวประกอบต่อหนึ่งวัน สำหรับผู้ป่วย $i$ ที่ได้รับการรักษา $a_{i0}$, $a_{i1}$ และ $a_{i2}$ น้ำหนักแบบปรับเสถียรสะสม (cumulative stabilised weight) คือ
$$sw_i = \prod_{t=0}^{2} \frac{P(A_t = a_{it} \mid \bar A_{t-1}, V)}{P(A_t = a_{it} \mid \bar A_{t-1}, \bar L_t, V)}$$ตัวส่วนคือความน่าจะเป็นของการรักษาที่ได้รับจริงในวัน $t$ เมื่อกำหนดทุกอย่างที่วัดไว้ก่อนการตัดสินใจนั้น ได้แก่การรักษาก่อนหน้า $\bar A_{t-1}$ (ไม่มีอะไรก่อนวันที่ 0) ประวัติภาวะอวัยวะบกพร่อง $\bar L_t$ และกลุ่มอายุ $V$ ตัวเศษคือความน่าจะเป็นเดียวกันโดยไม่มีภาวะอวัยวะบกพร่อง หากตัดตัวเศษออกจะได้ น้ำหนักแบบไม่ปรับเสถียร (unstabilised weight) $w_i$ คือหนึ่งส่วนผลคูณของตัวส่วน
ในกลุ่มที่ถ่วงน้ำหนักแล้ว การรักษาในแต่ละวันไม่ขึ้นกับภาวะอวัยวะบกพร่องอีกต่อไป เมื่อกำหนดยาครั้งก่อนและอายุ ราวกับว่าถูกกำหนดโดยไม่มองมัน ภาวะอวัยวะบกพร่องยังตอบสนองต่อยาครั้งก่อนเหมือนเดิม ผลที่ผ่านมันจึงคงอยู่ การกวนโดย $L_t$ ถูกขจัดโดยไม่ต้องปรับด้วย $L_t$ ซึ่งเป็นสิ่งที่การถดถอยทำไม่ได้ กลุ่มที่ถ่วงน้ำหนักนี้มักเรียกว่า ประชากรเสมือน (pseudo-population)
น้ำหนักที่ใหญ่มากมีวิธีแก้ทั่วไปอีกสองวิธีนอกจากการปรับเสถียร การตัดปลายน้ำหนัก (truncation) จำกัดน้ำหนักที่ใหญ่ที่สุดไว้ที่ค่าที่เลือก เช่นเปอร์เซนไทล์สูง การตัดผู้ป่วยออกจากการวิเคราะห์ (trimming) คือการตัดผู้ป่วยที่ความน่าจะเป็นของการรักษาสุดโต่ง คือใกล้ 0 หรือ 1 ออก ทั้งสามวิธีอธิบายที่จุดเวลาเดียวใน น้ำหนักที่สูงผิดปกติ: stabilization, truncation และ trimming เปลี่ยนอะไรไปจริง ๆ
การวิเคราะห์การรักษา ณ จุดเวลาเดียว (point-treatment analysis) มีการตัดสินใจรักษาหนึ่งครั้งต่อผู้ป่วย ในการวิเคราะห์การรักษา ณ จุดเวลาเดียว การทำ stabilization คือการคูณน้ำหนักทุกตัวในกลุ่มการรักษาเดียวกันด้วยค่าคงที่ค่าเดียว ผู้ป่วยที่มีน้ำหนักสูงผิดปกติจึงยังคงสูงผิดปกติเมื่อเทียบกับคนอื่นในกลุ่มเดียวกัน stabilization มีความสำคัญมากที่สุดใน marginal structural model ซึ่งน้ำหนักถูกคูณสะสมผ่านหลายจุดเวลา การทำ truncation แลกความลำเอียงกับความแปรปรวน ส่วนการทำ trimming เปลี่ยนประชากรที่ค่าประมาณนั้นอธิบาย
ใน MSM ตัวเศษต่างกันระหว่างประวัติการรักษา การปรับเสถียรจึงปรับขนาดของทั้งประวัติเทียบกัน ไม่ใช่คูณน้ำหนักทุกตัวด้วยค่าคงที่ค่าเดียว ภายในประวัติการรักษาเดียวและกลุ่มอายุเดียว ตัวประกอบนี้เท่ากัน เช่นในตัวอย่างคำนวณด้วยมือที่น้ำหนัก 2.67 และ 8 ของกลุ่มได้รับยาตลอดกลายเป็น 0.5 และ 1.5 น้ำหนักแบบไม่ปรับเสถียรให้ทุกประวัติการรักษามีส่วนแบ่งเท่ากันในกลุ่มที่ถ่วงน้ำหนัก ส่วนน้ำหนักแบบปรับเสถียรคงสัดส่วนที่สังเกตได้ไว้ เมื่อรูปแบบของ MSM ผิด ทั้งสองจึงให้คำตอบต่างกันได้ ในสามวัน ประโยชน์ก็เห็นได้แล้วในกลุ่มผู้ป่วยจำลองด้านล่าง ซึ่งน้ำหนักแบบปรับเสถียรให้ข้อมูลเท่ากับผู้ป่วยที่ถ่วงน้ำหนักเท่ากัน 1,694 คน และแบบไม่ปรับเสถียรเท่ากับ 1,456 คน
อะไรใส่ในตัวเศษได้
ตัวเศษใส่การรักษาก่อนหน้าและตัวแปรร่วม ณ จุดเริ่มต้นได้ แต่ห้ามใส่ $L_t$ หรือตัวแปรใดที่วัดหลังการรักษาเริ่ม การใส่ $L_t$ ในตัวเศษจะหักล้างมันออกจากน้ำหนัก และนำการกวนกลับมา ตัวแปรร่วม ณ จุดเริ่มต้นใด ๆ ในตัวเศษ ในที่นี้คือ $V$ ต้องปรากฏใน MSM ด้วย [4] แบบจำลองจึงกลายเป็น
$$E[Y^{\bar a} \mid V] = g_0 + g_1\,\mathrm{cum}(\bar a) + g_2 V$$ในสมการนี้ $g_0$ กลายเป็นความเสี่ยงหากไม่ได้รับยาเลยสำหรับผู้ป่วยอายุน้อยกว่า 65 ปี ($V = 0$) และ $g_2$ คือความต่างของความเสี่ยงระหว่างกลุ่มอายุภายใต้แผนใดก็ตาม ในกลุ่มผู้ป่วยจำลอง หนึ่งวันที่ได้รับยามีผลเท่ากันในทั้งสองกลุ่มอายุ $g_1$ จึงมีความหมายเดียวกันไม่ว่าจะมี $V$ หรือไม่ เพื่ออ่านความเสี่ยงภายใต้แต่ละแผนของทั้งกลุ่ม โค้ด Stata และ R ด้านล่างยังประมาณรุ่นระดับประชากร ซึ่งตัวเศษตัด $V$ ออก และ MSM มีเพียง $\mathrm{cum}(\bar a)$
ประมาณ MSM ทีละขั้น
- จัดข้อมูลให้มีหนึ่งแถวต่อผู้ป่วยหนึ่งคนต่อวันที่ตัดสินใจหนึ่งวัน โดยมี $A_t$, $L_t$, $V$ และการรักษาครั้งก่อน $A_{t-1}$ (0 ในวันที่ 0)
- ประมาณแบบจำลองตัวส่วน คือการถดถอยโลจิสติกของ $A_t$ บน $L_t$, $A_{t-1}$, $V$ และวัน โดยรวมทุกแถวผู้ป่วย-วัน รวมทุกวัน (pooled) หมายถึงใช้แบบจำลองเดียวสำหรับทุกวัน พร้อมพจน์ของวัน
- ประมาณแบบจำลองตัวเศษแบบเดียวกัน โดยไม่มี $L_t$
- ในแต่ละแถว ใช้ความน่าจะเป็นของการรักษาที่ทำนายได้ $p$ ความน่าจะเป็นของการรักษาที่ได้รับจริงคือ $p$ ถ้าได้รับยา และ $1 - p$ ถ้าไม่ได้รับ
- ภายในผู้ป่วยแต่ละคน คูณอัตราส่วนตัวเศษต่อตัวส่วนข้ามสามวัน
- ประมาณแบบจำลองผลลัพธ์ด้วยหนึ่งแถวต่อผู้ป่วย ถ่วงน้ำหนักด้วยน้ำหนักสะสม และใช้ค่าคลาดเคลื่อนมาตรฐานที่คำนึงถึงน้ำหนัก
ใน Stata แบบจำลองการรักษาคือ logit A L A_prev V i.t และ logit A A_prev V i.t ผลคูณสะสมคือผลรวมสะสมของลอการิทึม: by id (t): generate double sw = exp(sum(ln(fnum / fden)))
ใน R ตัวส่วนคือ den <- glm(A ~ L + A_prev + V + factor(t), data = d, family = binomial) ตัวเศษตัด L ออก และ ave() กับ FUN = cumprod หาผลคูณภายในผู้ป่วยแต่ละคน มีโค้ด Stata สำหรับวิธีนี้ที่ตีพิมพ์แล้ว [5] และแพ็กเกจ ipw ใน R สร้างน้ำหนักแบบเดียวกัน [6]
แบบจำลองผลลัพธ์ในที่นี้คือการถดถอยเชิงเส้นถ่วงน้ำหนักของการเสียชีวิตบน $\mathrm{cum}(\bar a)$ และ $V$ เป็น แบบจำลองความเสี่ยงเชิงเส้น (linear risk model) ซึ่งสัมประสิทธิ์คือผลต่างความเสี่ยง ใน Stata คือ glm Y cumA V if t == 2 [pweight = sw], family(gaussian) link(identity) vce(cluster id) โดย t == 2 เก็บแถววันที่ 4 ของผู้ป่วยแต่ละคนไว้ MSM แบบโลจิสติกก็ใช้ได้เช่นกันหากกำหนดถูกต้อง และจะให้ odds ratio ต่อหนึ่งวันที่ได้รับยา รูปแบบเชิงเส้นเหมาะกับกลุ่มนี้เพราะความเสี่ยงจริงเป็นเส้นตรงตามการออกแบบ
ค่าคลาดเคลื่อนมาตรฐานแบบแซนด์วิช (robust standard error) ประมาณความแปรปรวนจากการกระจายของส่วนร่วมของผู้ป่วยแต่ละคนแทนสูตรของแบบจำลอง จึงคำนึงถึงน้ำหนักที่ไม่เท่ากัน มันปฏิบัติต่อน้ำหนักที่ประมาณมาเหมือนเป็นค่าที่ทราบ ซึ่งมักทำให้ช่วงเชื่อมั่นค่อนข้างอนุรักษ์ [2]
เมื่อมีหนึ่งแถวต่อผู้ป่วยอย่างในที่นี้ ค่าคลาดเคลื่อนมาตรฐานแบบจัดกลุ่มกับแบบ robust ธรรมดาเท่ากัน การจัดกลุ่มมีความหมายเมื่อแบบจำลองผลลัพธ์มีหนึ่งแถวต่อผู้ป่วย-วัน บูตสแตรป (bootstrap) ซึ่งสุ่มตัวอย่างผู้ป่วยซ้ำและทำการวิเคราะห์ทั้งหมดซ้ำ รวมทั้งแบบจำลองน้ำหนักทั้งสอง เป็นทางเลือกเมื่อช่วงเชื่อมั่นสำคัญ
ในกลุ่มผู้ป่วยจำลอง การตัดสินใจในแต่ละวันขึ้นกับภาวะอวัยวะบกพร่องของวันนั้น ยาครั้งก่อน และอายุเท่านั้น พจน์เหล่านี้จึงเพียงพอ กับข้อมูลจริง ตัวส่วนมักรวมประวัติก่อนหน้าที่น่าจะขับเคลื่อนการรักษา เช่นภาวะอวัยวะบกพร่องในการประเมินครั้งก่อน ๆ
กลุ่มผู้ป่วยไอซียูจำลองที่ทราบคำตอบ
กลุ่มนี้มีผู้ป่วยจำลอง 3,000 คนที่ประเมินในวันที่ 0, 2 และ 4 รวมเป็น 9,000 แถวผู้ป่วย-วัน ผู้ป่วยทุกคนรอดถึงวันที่ 4 จึงมีการตัดสินใจครบสามครั้ง และบันทึกการเสียชีวิตภายในวันที่ 28 ของทุกคน ความน่าจะเป็นทุกค่าที่สร้างข้อมูลถูกกำหนดไว้ล่วงหน้า จึงทราบผลจริง ตารางด้านล่างแสดงการออกแบบ และสองย่อหน้าถัดไปอ่านตารางเทียบกับแผนภาพ
ยาแต่ละครั้งลดโอกาสเกิดภาวะอวัยวะบกพร่องรุนแรงในการตัดสินใจครั้งถัดไปลง 0.30 และแต่ละวันที่มีภาวะอวัยวะบกพร่องเพิ่มความเสี่ยงเสียชีวิต 0.15 หนึ่งวันที่ได้รับยาจึงช่วยไว้ $0.30 \times 0.15 = 0.045$ ผ่านภาวะอวัยวะบกพร่อง ยาครั้งสุดท้ายออกฤทธิ์ผ่านภาวะอวัยวะบกพร่องในวันที่ 6 ซึ่งไม่เคยถูกบันทึก เมื่อบวกส่วนที่เป็นผลโดยตรง -0.005 จะได้ผลจริง คือ $g_1$ = -0.050 ต่อหนึ่งวันที่ได้รับยา
เนื่องจากความเสี่ยงเสียชีวิตเป็นฟังก์ชันเส้นตรงของสาเหตุ MSM เชิงเส้นจึงถูกต้องพอดี ความเสี่ยงเสียชีวิตจริงภายในวันที่ 28 คือ 0.4725 หากไม่ได้รับยาเลย และ 0.3225 หากได้รับยาตลอด ผลต่าง -0.150 ภาวะอวัยวะบกพร่องรุนแรงขับเคลื่อนการให้ยาอย่างแรง โดยมีสัมประสิทธิ์ 2.0 บนสเกลล็อกออดส์ ผู้ที่ได้รับยาจึงป่วยหนักกว่ามากในทุกวันที่ตัดสินใจ ในผู้ป่วย 3,000 คน เสียชีวิต 1,232 คน (41.1%) และ 4,286 จาก 9,000 แถวผู้ป่วย-วันได้รับยา
กลุ่มผู้ป่วยจำลองถูกสร้างอย่างไร
| ตัวแปร | ความหมาย | แบบจำลองจริง |
|---|---|---|
| $U$ | ความเปราะบางที่ไม่ได้วัด ไม่เคยบันทึก | เป็น 1 ด้วยความน่าจะเป็น 0.40 |
| $V$ | อายุ 65 ปีขึ้นไป วัด ณ จุดเริ่มต้น | เป็น 1 ด้วยความน่าจะเป็น 0.45 |
| $L_t$ | ภาวะอวัยวะบกพร่องรุนแรงในวันที่ตัดสินใจ $t$ | $P(L_t = 1) = 0.32 + 0.45\,U - 0.30\,A_{t-1}$ |
| $A_t$ | ยา X ในวันที่ตัดสินใจ $t$ | $\operatorname{logit} P(A_t = 1) = -1.2 + 2.0\,L_t + 1.5\,A_{t-1} - 0.5\,V$ |
| $Y$ | เสียชีวิตภายในวันที่ 28 | $P(Y = 1) = 0.05 + 0.25\,U + 0.05\,V + 0.15\,(L_0 + L_1 + L_2 + L_6) - 0.005\,(A_0 + A_1 + A_2)$ |
แต่ละการวิเคราะห์ประมาณอะไรได้
ทำการวิเคราะห์หกแบบทั้งใน Stata และ R และแบบที่เจ็ดใน R เท่านั้น สี่แบบเป็นการถดถอยแบบธรรมดาที่ทีมอาจลองก่อน และสามแบบเป็น MSM ที่ประมาณด้วยน้ำหนักแบบปรับเสถียรสะสม ตารางแสดงค่าประมาณผลของหนึ่งวันที่ได้รับยาเทียบกับค่าที่ได้เมื่อกลุ่มใหญ่มาก (large-sample value) คือค่าที่การวิเคราะห์เดียวกันจะไปถึงในกลุ่มขนาดไม่จำกัด
| การวิเคราะห์ | ค่าประมาณ (ผลต่างความเสี่ยง) | ช่วงเชื่อมั่น 95% | ค่าเมื่อกลุ่มใหญ่มาก |
|---|---|---|---|
| รวมแถวผู้ป่วย-วัน ไม่ปรับ | 0.086 | 0.063 ถึง 0.109 | 0.086 |
| รวมแถวผู้ป่วย-วัน ปรับด้วย $L_t$ และ $V$ | -0.013 | -0.036 ถึง 0.011 | -0.012 |
| จำนวนวันที่ได้รับยา ไม่ปรับ | 0.068 | 0.050 ถึง 0.085 | 0.064 |
| จำนวนวันที่ได้รับยา ปรับด้วย $V$, $L_0$, $L_1$ และ $L_2$ | 0.011 | -0.006 ถึง 0.027 | 0.0005 |
| MSM น้ำหนักแบบปรับเสถียร ใส่ $V$ | -0.057 | -0.081 ถึง -0.034 | -0.050 (ค่าจริง) |
| MSM ระดับประชากร น้ำหนักแบบปรับเสถียร | -0.057 | -0.080 ถึง -0.033 | -0.050 (ค่าจริง) |
| MSM แบบจำลองการรักษาหนึ่งแบบต่อวัน (R, WeightIt) | -0.055 | -0.079 ถึง -0.031 | -0.050 (ค่าจริง) |
อ่านตาราง
การรวมแถวผู้ป่วย-วันโดยไม่ปรับไปไกลที่สุดจากค่าจริง แต่ละวันที่ได้รับยาดูเหมือนเพิ่มความเสี่ยงเสียชีวิต +0.086 การปรับแบบจำลองรวมด้วย $L_t$ และ $V$ ทำให้ค่าประมาณแกว่งไปที่ -0.013 และค่าเมื่อกลุ่มใหญ่มากของมัน -0.012 เป็นเพียงส่วนเล็กของประโยชน์จริง แบบจำลองที่นับวันที่ได้รับยาก็ไม่ดีกว่า เมื่อไม่ปรับ ยาดูเหมือนเป็นอันตรายที่ 0.068 ต่อวัน เมื่อปรับด้วยภาวะอวัยวะบกพร่องในวันที่ 0, 2 และ 4 ค่าเมื่อกลุ่มใหญ่มากคือ 0.0005 ซึ่งแทบเป็นศูนย์
การถดถอยสองแบบของแพทย์ประจำบ้านต่อยอดคือแถวที่สามและสี่ ค่าเมื่อกลุ่มใหญ่มากของทั้งสองแสดงว่าผู้ป่วยมากขึ้นก็ไม่ช่วย เพราะความลำเอียงฝังอยู่ในคำถามที่การถดถอยแต่ละแบบตอบ
MSM ที่ใช้น้ำหนักแบบปรับเสถียรให้ -0.057 ต่อหนึ่งวันที่ได้รับยา (ช่วงเชื่อมั่น 95% คือ -0.081 ถึง -0.034) และช่วงนี้ครอบคลุมค่าจริง -0.050 สามวันที่ได้รับยาให้ผลต่างความเสี่ยงของการได้รับยาตลอดเทียบกับไม่ได้รับเลยที่ -0.172 (ช่วงเชื่อมั่น 95% คือ -0.242 ถึง -0.102) เทียบกับค่าจริง -0.150 MSM ระดับประชากรให้ -0.057 และน้ำหนักจากแบบจำลองการรักษาหนึ่งแบบต่อวัน ซึ่งสร้างด้วยแพ็กเกจ WeightIt ใน R ให้ -0.055
MSM ระดับประชากรยังให้ความเสี่ยงภายใต้แต่ละแผน คือ 0.489 หากไม่ได้รับยาเลย และ 0.319 หากได้รับยาตลอด เทียบกับความเสี่ยงจริง 0.4725 และ 0.3225 ภายใต้แบบจำลองจริง หนึ่งวันที่ได้รับยาให้ 0.4225 และสองวันให้ 0.3725 ลดลง 0.050 ต่อหนึ่งวัน ด้วยเส้นตรง MSM ใช้ผู้ป่วยทั้ง 3,000 คน ไม่ใช่เพียง 471 และ 588 คนที่ทำตามสองแผน
ความเสี่ยงเสียชีวิตภายในวันที่ 28 ภายใต้แต่ละแผน
| แผน | ความเสี่ยงจริง | ค่าประมาณจาก MSM ระดับประชากร |
|---|---|---|
| ไม่ได้รับยาเลย, $\bar a = (0, 0, 0)$ | 0.4725 | 0.489 |
| ได้รับยาตลอด, $\bar a = (1, 1, 1)$ | 0.3225 | 0.319 |
น้ำหนักหน้าตาเป็นอย่างไร
ควรดูน้ำหนักก่อนอ่านแบบจำลองผลลัพธ์ ตารางด้านล่างเปรียบเทียบน้ำหนักสะสมในวันที่ 4 หลังการตัดสินใจทั้งสามครั้ง ขนาดตัวอย่างประสิทธิผล (effective sample size, ESS) คือจำนวนผู้ป่วยที่ถ่วงน้ำหนักเท่ากันซึ่งให้ข้อมูลเท่ากัน
| น้ำหนัก | ค่าเฉลี่ย | ค่าสูงสุด | ขนาดตัวอย่างประสิทธิผล |
|---|---|---|---|
| แบบปรับเสถียร, $sw$ | 0.999 | 10.30 | 1,694 |
| แบบไม่ปรับเสถียร, $w$ | 7.96 | 94.94 | 1,456 |
อ่านค่าน้ำหนัก
น้ำหนักแบบปรับเสถียรมีค่าเฉลี่ย 0.999 ตามที่ควรเป็น น้ำหนักแบบปรับเสถียรที่แบบจำลองถูกต้องมีค่าคาดหวังเท่ากับ 1 [4] ค่าน้อยที่สุดคือ 0.27 99% ไม่เกิน 4.03 และค่ามากที่สุดคือ 10.30 น้ำหนักแบบปรับเสถียรของ MSM ระดับประชากรมีพฤติกรรมเดียวกัน โดยมีค่าเฉลี่ย 0.995 และค่าสูงสุด 9.52
น้ำหนักแบบไม่ปรับเสถียรมีค่าเฉลี่ย 7.96 ใกล้แปด ซึ่งเป็นจำนวนประวัติการรักษาที่เป็นไปได้ในสามวัน และน้ำหนักของผู้ป่วยหนึ่งรายสูงถึง 94.94 เมื่อเทียบกับค่าเฉลี่ยของตัวเอง น้ำหนักที่ใหญ่ที่สุดนั้นสุดโต่งพอ ๆ กับน้ำหนักแบบปรับเสถียรที่ใหญ่ที่สุดเทียบกับ 1 การเทียบค่าสูงสุดสองค่าจึงทำให้ประโยชน์ของการปรับเสถียรดูเกินจริง
ESS คำนวณจาก $(\sum w)^2 / \sum w^2$ และไม่เปลี่ยนเมื่อคูณน้ำหนักทุกค่าด้วยค่าคงที่เดียวกัน ค่านี้คือ 1,456 สำหรับน้ำหนักแบบไม่ปรับเสถียร เทียบกับ 1,694 สำหรับแบบปรับเสถียร ดังนั้นกับน้ำหนักแบบไม่ปรับเสถียร กลุ่มเดิมให้ข้อมูลน้อยลง
เมื่อมีการตัดสินใจรายวันตลอดหลายสัปดาห์ ผลคูณยาวขึ้นและน้ำหนักแบบไม่ปรับเสถียรผันผวนมากขึ้น จึงเป็นเหตุผลที่น้ำหนักแบบปรับเสถียรเป็นทางเลือกปกติของ MSM [4]
Stata: แบบจำลองน้ำหนัก น้ำหนักสะสม และ MSM
* Simulated ICU cohort, three treatment days: treatment-confounder feedback.
* Simulated data: not evidence about any real drug or patient.
* Question: what does each treated day of drug X do to the risk of death by day 28?
* Methods: naive regressions (with and without L_t) versus a marginal structural model (MSM)
* fitted with cumulative stabilised inverse probability of treatment weights.
* Variables: L_t = 1 when severe organ dysfunction is present on day t, A_t = 1 when the drug is given on day t,
* V = 1 for age 65 or older, Y = 1 for death by day 28.
version 18
clear all
set more off
* Check the built-in commands this file uses.
capture which logit
display "VERIFY logit " cond(_rc == 0, "available", "missing")
capture which glm
display "VERIFY glm " cond(_rc == 0, "available", "missing")
* Read the simulated cohort file (not published): one row per patient-day (days 0, 2, 4).
import delimited using ../../datasets/W2/W2.csv, clear case(preserve) varnames(1)
sort id t
count if t == 0
display "CANON w2.n " strtrim(string(r(N), "%12.0f"))
count
display "CANON w2.n_rows " strtrim(string(r(N), "%12.0f"))
count if t == 2 & Y == 1
display "CANON w2.deaths " strtrim(string(r(N), "%12.0f"))
count if A == 1
display "CANON w2.treated_days " strtrim(string(r(N), "%12.0f"))
* Naive 1: pool all patient-days and regress death on that day's treatment (linear risk model), CI clustered by patient.
glm Y A, family(gaussian) link(identity) vce(cluster id)
display "CANON w2.naive.unadj.b " strtrim(string(_b[A], "%12.4f"))
display "CANON w2.naive.unadj.se " strtrim(string(_se[A], "%12.4f"))
display "CANON w2.naive.unadj.lo " strtrim(string(_b[A] - invnormal(0.975) * _se[A], "%12.4f"))
display "CANON w2.naive.unadj.hi " strtrim(string(_b[A] + invnormal(0.975) * _se[A], "%12.4f"))
* Naive 2: the same pooled regression adjusted for that day's organ dysfunction L_t and age V.
glm Y A L V, family(gaussian) link(identity) vce(cluster id)
display "CANON w2.naive.adj.b " strtrim(string(_b[A], "%12.4f"))
display "CANON w2.naive.adj.se " strtrim(string(_se[A], "%12.4f"))
display "CANON w2.naive.adj.lo " strtrim(string(_b[A] - invnormal(0.975) * _se[A], "%12.4f"))
display "CANON w2.naive.adj.hi " strtrim(string(_b[A] + invnormal(0.975) * _se[A], "%12.4f"))
* Cumulative treatment through each day, and organ dysfunction on days 0, 2 and 4 copied to every row.
by id (t): generate cumA = sum(A)
by id (t): generate L0 = L[1]
by id (t): generate L1 = L[2]
by id (t): generate L2 = L[3]
count if t == 2 & cumA == 3
display "CANON w2.always_treated " strtrim(string(r(N), "%12.0f"))
count if t == 2 & cumA == 0
display "CANON w2.never_treated " strtrim(string(r(N), "%12.0f"))
* Naive 3: one row per patient (day 4), death on the number of treated days (no adjustment).
glm Y cumA if t == 2, family(gaussian) link(identity) vce(cluster id)
display "CANON w2.cum.crude.b " strtrim(string(_b[cumA], "%12.4f"))
display "CANON w2.cum.crude.se " strtrim(string(_se[cumA], "%12.4f"))
display "CANON w2.cum.crude.lo " strtrim(string(_b[cumA] - invnormal(0.975) * _se[cumA], "%12.4f"))
display "CANON w2.cum.crude.hi " strtrim(string(_b[cumA] + invnormal(0.975) * _se[cumA], "%12.4f"))
* Naive 4: the same, adjusted for age and organ dysfunction on days 0, 2 and 4.
glm Y cumA V L0 L1 L2 if t == 2, family(gaussian) link(identity) vce(cluster id)
display "CANON w2.cum.adj.b " strtrim(string(_b[cumA], "%12.4f"))
display "CANON w2.cum.adj.se " strtrim(string(_se[cumA], "%12.4f"))
display "CANON w2.cum.adj.lo " strtrim(string(_b[cumA] - invnormal(0.975) * _se[cumA], "%12.4f"))
display "CANON w2.cum.adj.hi " strtrim(string(_b[cumA] + invnormal(0.975) * _se[cumA], "%12.4f"))
* Treatment models, pooled over days: the denominator uses history (L_t, A_{t-1}, V).
logit A L A_prev V i.t
predict double pden, pr
* Numerator for the stabilised weight: same model without L_t.
logit A A_prev V i.t
predict double pnum, pr
* Numerator for the marginal MSM: without L_t and without V.
logit A A_prev i.t
predict double pnumm, pr
* Probability of the treatment each patient actually received on each day.
generate double fden = cond(A == 1, pden, 1 - pden)
generate double fnum = cond(A == 1, pnum, 1 - pnum)
generate double fnumm = cond(A == 1, pnumm, 1 - pnumm)
* Cumulative product over days within each patient (as a running sum of logs) gives the weight at day 4.
by id (t): generate double w = exp(sum(ln(1 / fden)))
by id (t): generate double sw = exp(sum(ln(fnum / fden)))
by id (t): generate double swm = exp(sum(ln(fnumm / fden)))
* Weight summaries at day 4; the effective sample size is (sum of weights)^2 / sum of squared weights.
summarize sw if t == 2, detail
display "CANON w2.sw.mean " strtrim(string(r(mean), "%12.4f"))
display "CANON w2.sw.sd " strtrim(string(r(sd), "%12.4f"))
display "CANON w2.sw.min " strtrim(string(r(min), "%12.4f"))
display "CANON w2.sw.p1 " strtrim(string(r(p1), "%12.4f"))
display "CANON w2.sw.p99 " strtrim(string(r(p99), "%12.4f"))
display "CANON w2.sw.max " strtrim(string(r(max), "%12.4f"))
generate double sw_sq = sw^2
summarize sw if t == 2
scalar sum_sw = r(sum)
summarize sw_sq if t == 2
display "CANON w2.sw.ess " strtrim(string(sum_sw^2 / r(sum), "%12.4f"))
generate double w_sq = w^2
summarize w if t == 2
display "CANON w2.w.mean " strtrim(string(r(mean), "%12.4f"))
display "CANON w2.w.max " strtrim(string(r(max), "%12.4f"))
scalar sum_w = r(sum)
summarize w_sq if t == 2
display "CANON w2.w.ess " strtrim(string(sum_w^2 / r(sum), "%12.4f"))
summarize swm if t == 2
display "CANON w2.swm.mean " strtrim(string(r(mean), "%12.4f"))
display "CANON w2.swm.max " strtrim(string(r(max), "%12.4f"))
* MSM: weighted regression of death on cumulative treatment and age, CI clustered by patient.
glm Y cumA V if t == 2 [pweight = sw], family(gaussian) link(identity) vce(cluster id)
display "CANON w2.msm.b " strtrim(string(_b[cumA], "%12.4f"))
display "CANON w2.msm.se " strtrim(string(_se[cumA], "%12.4f"))
display "CANON w2.msm.lo " strtrim(string(_b[cumA] - invnormal(0.975) * _se[cumA], "%12.4f"))
display "CANON w2.msm.hi " strtrim(string(_b[cumA] + invnormal(0.975) * _se[cumA], "%12.4f"))
* Always treated versus never treated is three treated days: three times the per-day effect.
display "CANON w2.msm.rd_always_never " strtrim(string(3 * _b[cumA], "%12.4f"))
display "CANON w2.msm.rd_always_never.lo " strtrim(string(3 * (_b[cumA] - invnormal(0.975) * _se[cumA]), "%12.4f"))
display "CANON w2.msm.rd_always_never.hi " strtrim(string(3 * (_b[cumA] + invnormal(0.975) * _se[cumA]), "%12.4f"))
* Marginal MSM (numerator without V, no V in the model): gives the risks under never and always treated.
glm Y cumA if t == 2 [pweight = swm], family(gaussian) link(identity) vce(cluster id)
display "CANON w2.msm_marg.b " strtrim(string(_b[cumA], "%12.4f"))
display "CANON w2.msm_marg.se " strtrim(string(_se[cumA], "%12.4f"))
display "CANON w2.msm_marg.lo " strtrim(string(_b[cumA] - invnormal(0.975) * _se[cumA], "%12.4f"))
display "CANON w2.msm_marg.hi " strtrim(string(_b[cumA] + invnormal(0.975) * _se[cumA], "%12.4f"))
display "CANON w2.msm_marg.risk_never " strtrim(string(_b[_cons], "%12.4f"))
display "CANON w2.msm_marg.risk_always " strtrim(string(_b[_cons] + 3 * _b[cumA], "%12.4f"))
display "Simulated data (ข้อมูลจำลอง): no number above is evidence about any real drug."
* ---- The true values built into the simulated cohort (exact: computed from the design, no sampling) ----
* The cohort was simulated from these probabilities. U is an unmeasured patient trait; L_6 is the day-6
* value of L (after the last treatment day, never recorded).
* P(U = 1) = pU and P(V = 1) = pV
* P(L_t = 1) = lBase + lU*U + lA*A_{t-1}, with A_{t-1} = 0 on day 0
* logit P(A_t = 1) = aBase + aL*L_t + aA*A_{t-1} + aV*V
* P(death by day 28) = yBase + yU*U + yV*V + yL*(L_0 + L_1 + L_2 + L_6) + yA*(A_0 + A_1 + A_2)
* The true values need no patient data, so start from an empty dataset.
clear
scalar pU = 0.40
scalar pV = 0.45
scalar lBase = 0.32
scalar lU = 0.45
scalar lA = -0.30
scalar aBase = -1.2
scalar aL = 2.0
scalar aA = 1.5
scalar aV = -0.5
scalar yBase = 0.05
scalar yU = 0.25
scalar yV = 0.05
scalar yL = 0.15
scalar yA = -0.005
display "CANON w2.truth.design.p_u " strtrim(string(pU, "%12.4f"))
display "CANON w2.truth.design.p_v " strtrim(string(pV, "%12.4f"))
display "CANON w2.truth.design.l_base " strtrim(string(lBase, "%12.4f"))
display "CANON w2.truth.design.l_u " strtrim(string(lU, "%12.4f"))
display "CANON w2.truth.design.l_a " strtrim(string(lA, "%12.4f"))
display "CANON w2.truth.design.a_base " strtrim(string(aBase, "%12.4f"))
display "CANON w2.truth.design.a_l " strtrim(string(aL, "%12.4f"))
display "CANON w2.truth.design.a_a " strtrim(string(aA, "%12.4f"))
display "CANON w2.truth.design.a_v " strtrim(string(aV, "%12.4f"))
display "CANON w2.truth.design.y_base " strtrim(string(yBase, "%12.4f"))
display "CANON w2.truth.design.y_u " strtrim(string(yU, "%12.4f"))
display "CANON w2.truth.design.y_v " strtrim(string(yV, "%12.4f"))
display "CANON w2.truth.design.y_l " strtrim(string(yL, "%12.4f"))
display "CANON w2.truth.design.y_a " strtrim(string(yA, "%12.4f"))
* Each treated day lowers the risk of death directly (yA) and by lowering the next day's L (yL x lA).
scalar g1 = yA + yL * lA
display "CANON w2.truth.msm.g1_per_treated_day " strtrim(string(g1, "%12.4f"))
display "CANON w2.truth.msm.g1_direct_part " strtrim(string(yA, "%12.4f"))
display "CANON w2.truth.msm.g1_through_l_part " strtrim(string(yL * lA, "%12.4f"))
* Risk of death by day 28 if every patient were treated on k of the three days (k = 0 never, k = 3 always).
* EL is P(L_t = 1) after an untreated day; L_0, L_1, L_2 and L_6 each add yL x EL, and each treated day adds g1.
scalar EL = lBase + lU * pU
scalar rNever = yBase + yU * pU + yV * pV + yL * 4 * EL
display "CANON w2.truth.msm.risk_never_treated " strtrim(string(rNever, "%12.4f"))
display "CANON w2.truth.msm.risk_cum1 " strtrim(string(rNever + g1, "%12.4f"))
display "CANON w2.truth.msm.risk_cum2 " strtrim(string(rNever + 2 * g1, "%12.4f"))
display "CANON w2.truth.msm.risk_always_treated " strtrim(string(rNever + 3 * g1, "%12.4f"))
display "CANON w2.truth.msm.rd_always_vs_never " strtrim(string(3 * g1, "%12.4f"))
* The model with age V (the one fitted above): intercept at V = 0 and the coefficient of V.
display "CANON w2.truth.msm.g0_conditional " strtrim(string(yBase + yU * pU + yL * 4 * EL, "%12.4f"))
display "CANON w2.truth.msm.g2_v " strtrim(string(yV, "%12.4f"))
* Where each naive regression lands in an infinitely large cohort: list all 512 combinations of
* U, V, L_0, A_0, L_1, A_1, L_2, A_2 and L_6 with their probabilities under the observed treatment
* process, then fit the same regressions to the expected risk, weighting each combination by its probability.
set obs 512
generate int cell = _n - 1
generate byte U = mod(cell, 2)
generate byte V = mod(floor(cell / 2), 2)
generate byte L0 = mod(floor(cell / 4), 2)
generate byte A0 = mod(floor(cell / 8), 2)
generate byte L1 = mod(floor(cell / 16), 2)
generate byte A1 = mod(floor(cell / 32), 2)
generate byte L2 = mod(floor(cell / 64), 2)
generate byte A2 = mod(floor(cell / 128), 2)
generate byte L6 = mod(floor(cell / 256), 2)
generate double pL0 = lBase + lU * U
generate double pA0 = invlogit(aBase + aL * L0 + aV * V)
generate double pL1 = lBase + lU * U + lA * A0
generate double pA1 = invlogit(aBase + aL * L1 + aA * A0 + aV * V)
generate double pL2 = lBase + lU * U + lA * A1
generate double pA2 = invlogit(aBase + aL * L2 + aA * A1 + aV * V)
generate double pL6 = lBase + lU * U + lA * A2
generate double p = cond(U == 1, pU, 1 - pU) * cond(V == 1, pV, 1 - pV)
replace p = p * cond(L0 == 1, pL0, 1 - pL0) * cond(A0 == 1, pA0, 1 - pA0)
replace p = p * cond(L1 == 1, pL1, 1 - pL1) * cond(A1 == 1, pA1, 1 - pA1)
replace p = p * cond(L2 == 1, pL2, 1 - pL2) * cond(A2 == 1, pA2, 1 - pA2)
replace p = p * cond(L6 == 1, pL6, 1 - pL6)
generate double risk = yBase + yU * U + yV * V + yL * (L0 + L1 + L2 + L6) + yA * (A0 + A1 + A2)
generate cumA = A0 + A1 + A2
* Naive 3 and 4 (one row per patient).
quietly regress risk cumA [aweight = p]
display "CANON w2.truth.naive_limit.cum_crude " strtrim(string(_b[cumA], "%12.4f"))
quietly regress risk cumA V L0 L1 L2 [aweight = p]
display "CANON w2.truth.naive_limit.cum_adj " strtrim(string(_b[cumA], "%12.4f"))
* Naive 1 and 2 (one row per patient-day): three rows per combination, one per treatment day.
expand 3
bysort cell: generate t = _n - 1
generate A = cond(t == 0, A0, cond(t == 1, A1, A2))
generate L = cond(t == 0, L0, cond(t == 1, L1, L2))
quietly regress risk A [aweight = p]
display "CANON w2.truth.naive_limit.unadj " strtrim(string(_b[A], "%12.4f"))
quietly regress risk A L V [aweight = p]
display "CANON w2.truth.naive_limit.adj " strtrim(string(_b[A], "%12.4f"))
display "These true values describe the simulation design (simulated data), not any real drug."
* ---- Follow-up length, the marginal MSM intercept, and how many days each patient was treated ----
* Death is counted up to day 28.
scalar deathDay = 28
display "CANON w2.truth.design.death_day " strtrim(string(deathDay, "%12.0f"))
* The intercept of the marginal MSM is the risk of death by day 28 if no patient were ever treated.
display "CANON w2.truth.msm.g0_marginal " strtrim(string(rNever, "%12.4f"))
* Read the simulated cohort file (not published) again: share who died, patients treated on one or two days.
import delimited using ../../datasets/W2/W2.csv, clear case(preserve) varnames(1)
sort id t
by id (t): generate cumA = sum(A)
summarize Y if t == 2
display "CANON w2.death_risk " strtrim(string(r(mean), "%12.4f"))
count if t == 2 & cumA == 1
display "CANON w2.treated_one_day " strtrim(string(r(N), "%12.0f"))
count if t == 2 & cumA == 2
display "CANON w2.treated_two_days " strtrim(string(r(N), "%12.0f"))
tabulate cumA if t == 2
display "Simulated data (ข้อมูลจำลอง): not evidence about any real drug or patient."
. * MSM: weighted regression of death on cumulative treatment and age, CI clust
> ered by patient.
. glm Y cumA V if t == 2 [pweight = sw], family(gaussian) link(identity) vce(cl
> uster id)
Iteration 0: Log pseudolikelihood = -2096.0075
Generalized linear models Number of obs = 3,000
Optimization : ML Residual df = 2,997
Scale parameter = .2370902
Deviance = 710.5593289 (1/df) Deviance = .2370902
Pearson = 710.5593289 (1/df) Pearson = .2370902
Variance function: V(u) = 1 [Gaussian]
Link function : g(u) = u [Identity]
AIC = 1.399338
Log pseudolikelihood = -2096.007537 BIC = -23284.52
(Std. err. adjusted for 3,000 clusters in id)
------------------------------------------------------------------------------
| Robust
Y | Coefficient std. err. z P>|z| [95% conf. interval]
-------------+----------------------------------------------------------------
cumA | -.0572908 .0118918 -4.82 0.000 -.0805982 -.0339834
V | .0477372 .0242604 1.97 0.049 .0001876 .0952868
_cons | .4654255 .0266901 17.44 0.000 .4131138 .5177371
------------------------------------------------------------------------------
R: การวิเคราะห์ชุดเดียวกันและตารางสรุป
# Simulated ICU cohort, three treatment days: treatment-confounder feedback.
# Simulated data: not evidence about any real drug or patient.
# Question: what does each treated day of drug X do to the risk of death by day 28?
# Methods: naive regressions (with and without L_t) versus a marginal structural model (MSM)
# fitted with cumulative stabilised inverse probability of treatment weights.
# Variables: L_t = 1 when severe organ dysfunction is present on day t, A_t = 1 when the drug is given on day t,
# V = 1 for age 65 or older, Y = 1 for death by day 28.
# Check the packages this script uses; install into the user library when one is missing.
need <- c("sandwich", "ipw", "WeightIt")
for (p in need) {
if (!requireNamespace(p, quietly = TRUE)) {
try(install.packages(p, repos = "https://cloud.r-project.org"), silent = TRUE)
}
cat(sprintf("VERIFY %s %s\n", p, if (requireNamespace(p, quietly = TRUE)) "available" else "missing"))
}
# Print each result on its own labelled line: 4 decimals for estimates, integers for counts.
canon <- function(key, value, digits = 4) {
cat(sprintf("CANON w2.%s %s\n", key, formatC(value, format = "f", digits = digits)))
}
# Read the simulated cohort file (not published): one row per patient-day (days 0, 2, 4).
d <- read.csv(file.path("..", "..", "datasets", "W2", "W2.csv"))
d <- d[order(d$id, d$t), ]
canon("n", length(unique(d$id)), 0)
canon("n_rows", nrow(d), 0)
canon("deaths", sum(d$Y[d$t == 2]), 0)
canon("treated_days", sum(d$A), 0)
# Cluster-robust (sandwich) 95% CI by patient for one coefficient; HC0 with the G/(G-1) factor, as Stata glm does.
ci_cl <- function(fit, term, cluster) {
V <- sandwich::vcovCL(fit, cluster = cluster, type = "HC0", cadjust = TRUE)
b <- unname(coef(fit)[term]); se <- sqrt(V[term, term])
c(b = b, se = se, lo = b - qnorm(0.975) * se, hi = b + qnorm(0.975) * se)
}
print_est <- function(key, est) {
canon(paste0(key, ".b"), est[["b"]]); canon(paste0(key, ".se"), est[["se"]])
canon(paste0(key, ".lo"), est[["lo"]]); canon(paste0(key, ".hi"), est[["hi"]])
}
# Naive 1: pool all patient-days and regress death on that day's treatment (linear risk model).
f_unadj <- glm(Y ~ A, data = d, family = gaussian)
print_est("naive.unadj", ci_cl(f_unadj, "A", ~id))
# Naive 2: the same pooled regression adjusted for that day's organ dysfunction L_t and age V.
f_adj <- glm(Y ~ A + L + V, data = d, family = gaussian)
print_est("naive.adj", ci_cl(f_adj, "A", ~id))
# Cumulative treatment through each day, and organ dysfunction on each day, in wide form.
d$cumA <- ave(d$A, d$id, FUN = cumsum)
wide <- d[d$t == 2, c("id", "V", "Y", "cumA")]
Lw <- reshape(d[, c("id", "t", "L")], idvar = "id", timevar = "t", direction = "wide")
wide <- merge(wide, Lw, by = "id")
wide <- wide[order(wide$id), ]
canon("always_treated", sum(wide$cumA == 3), 0)
canon("never_treated", sum(wide$cumA == 0), 0)
# Naive 3: one row per patient, death on the number of treated days (no adjustment).
f_cc <- glm(Y ~ cumA, data = wide, family = gaussian)
print_est("cum.crude", ci_cl(f_cc, "cumA", ~id))
# Naive 4: the same, adjusted for age and organ dysfunction on days 0, 2 and 4.
f_ca <- glm(Y ~ cumA + V + L.0 + L.1 + L.2, data = wide, family = gaussian)
print_est("cum.adj", ci_cl(f_ca, "cumA", ~id))
# Treatment models, pooled over days: denominator uses history (L_t, A_{t-1}, V); numerators drop L_t.
den <- glm(A ~ L + A_prev + V + factor(t), data = d, family = binomial)
num <- glm(A ~ A_prev + V + factor(t), data = d, family = binomial)
numm <- glm(A ~ A_prev + factor(t), data = d, family = binomial)
pa <- function(fit) { p <- fitted(fit); ifelse(d$A == 1, p, 1 - p) }
# Cumulative product over days within each patient gives the weight at day 4.
d$w <- ave(1 / pa(den), d$id, FUN = cumprod)
d$sw <- ave(pa(num) / pa(den), d$id, FUN = cumprod)
d$swm <- ave(pa(numm) / pa(den), d$id, FUN = cumprod)
last <- d[d$t == 2, ]
last <- last[order(last$id), ]
# Weight summaries at day 4 (percentiles use the same averaging rule as Stata summarize, detail).
ess <- function(x) sum(x)^2 / sum(x^2)
q <- function(x, p) unname(quantile(x, p, type = 2))
canon("sw.mean", mean(last$sw)); canon("sw.sd", sd(last$sw))
canon("sw.min", min(last$sw)); canon("sw.p1", q(last$sw, 0.01))
canon("sw.p99", q(last$sw, 0.99)); canon("sw.max", max(last$sw))
canon("sw.ess", ess(last$sw))
canon("w.mean", mean(last$w)); canon("w.max", max(last$w)); canon("w.ess", ess(last$w))
canon("swm.mean", mean(last$swm)); canon("swm.max", max(last$swm))
# MSM: weighted regression of death on cumulative treatment and age, cluster-robust CI by patient.
msm <- glm(Y ~ cumA + V, data = last, weights = sw, family = gaussian)
e_msm <- ci_cl(msm, "cumA", ~id)
print_est("msm", e_msm)
canon("msm.rd_always_never", 3 * e_msm[["b"]])
canon("msm.rd_always_never.lo", 3 * e_msm[["lo"]])
canon("msm.rd_always_never.hi", 3 * e_msm[["hi"]])
# Marginal MSM (numerator without V, no V in the model): gives the risks under never and always treated.
msmm <- glm(Y ~ cumA, data = last, weights = swm, family = gaussian)
e_m <- ci_cl(msmm, "cumA", ~id)
print_est("msm_marg", e_m)
canon("msm_marg.risk_never", unname(coef(msmm)[1]))
canon("msm_marg.risk_always", unname(coef(msmm)[1] + 3 * coef(msmm)[2]))
# Cross-check: ipw::ipwtm builds the same pooled stabilised weights from its own code.
if (requireNamespace("ipw", quietly = TRUE)) {
dd <- d
dd$tf <- factor(dd$t)
iw <- ipw::ipwtm(exposure = A, family = "binomial", link = "logit",
numerator = ~ A_prev + V + tf, denominator = ~ L + A_prev + V + tf,
id = id, timevar = t, type = "all", data = dd)
cat(sprintf("CHECK ipwtm versus hand-built weights, largest absolute difference %.2e\n",
max(abs(iw$ipw.weights - dd$sw))))
}
# Cross-check: WeightIt::weightitMSM fits one treatment model per day (not pooled), so it differs slightly.
if (requireNamespace("WeightIt", quietly = TRUE)) {
wd <- reshape(d[, c("id", "t", "V", "L", "A", "Y")], idvar = c("id", "V", "Y"),
timevar = "t", direction = "wide")
wd <- wd[order(wd$id), ]
wm <- WeightIt::weightitMSM(list(A.0 ~ L.0 + V, A.1 ~ A.0 + L.1 + V, A.2 ~ A.1 + L.2 + V),
data = wd, method = "glm", stabilize = TRUE)
wd$cumA <- wd$A.0 + wd$A.1 + wd$A.2
fw <- glm(Y ~ cumA + V, data = wd, weights = wm$weights, family = gaussian)
ew <- ci_cl(fw, "cumA", ~id)
canon("msm_weightit.b.r", ew[["b"]]); canon("msm_weightit.lo.r", ew[["lo"]]); canon("msm_weightit.hi.r", ew[["hi"]])
}
cat("Simulated data (ข้อมูลจำลอง): no number above is evidence about any real drug.\n")
# ---- The true values built into the simulated cohort (exact: computed from the design, no sampling) ----
# The cohort was simulated from these probabilities. U is an unmeasured patient trait; L_6 is the day-6
# value of L (after the last treatment day, never recorded).
# P(U = 1) = pU and P(V = 1) = pV
# P(L_t = 1) = lBase + lU*U + lA*A_{t-1}, with A_{t-1} = 0 on day 0
# logit P(A_t = 1) = aBase + aL*L_t + aA*A_{t-1} + aV*V
# P(death by day 28) = yBase + yU*U + yV*V + yL*(L_0 + L_1 + L_2 + L_6) + yA*(A_0 + A_1 + A_2)
pU <- 0.40; pV <- 0.45
lBase <- 0.32; lU <- 0.45; lA <- -0.30
aBase <- -1.2; aL <- 2.0; aA <- 1.5; aV <- -0.5
yBase <- 0.05; yU <- 0.25; yV <- 0.05; yL <- 0.15; yA <- -0.005
design <- c(p_u = pU, p_v = pV, l_base = lBase, l_u = lU, l_a = lA, a_base = aBase, a_l = aL, a_a = aA,
a_v = aV, y_base = yBase, y_u = yU, y_v = yV, y_l = yL, y_a = yA)
for (k in names(design)) canon(paste0("truth.design.", k), design[[k]])
# Each treated day lowers the risk of death directly (yA) and by lowering the next day's L (yL x lA).
g1 <- yA + yL * lA
canon("truth.msm.g1_per_treated_day", g1)
canon("truth.msm.g1_direct_part", yA)
canon("truth.msm.g1_through_l_part", yL * lA)
# Risk of death by day 28 if every patient were treated on k of the three days (k = 0 never, k = 3 always).
# EL is P(L_t = 1) after an untreated day; L_0, L_1, L_2 and L_6 each add yL x EL, and each treated day adds g1.
EL <- lBase + lU * pU
r_never <- yBase + yU * pU + yV * pV + yL * 4 * EL
canon("truth.msm.risk_never_treated", r_never)
canon("truth.msm.risk_cum1", r_never + g1)
canon("truth.msm.risk_cum2", r_never + 2 * g1)
canon("truth.msm.risk_always_treated", r_never + 3 * g1)
canon("truth.msm.rd_always_vs_never", 3 * g1)
# The model with age V (the one fitted above): intercept at V = 0 and the coefficient of V.
canon("truth.msm.g0_conditional", yBase + yU * pU + yL * 4 * EL)
canon("truth.msm.g2_v", yV)
# Where each naive regression lands in an infinitely large cohort: list all 512 combinations of
# U, V, L_0, A_0, L_1, A_1, L_2, A_2 and L_6 with their probabilities under the observed treatment
# process, then fit the same regressions to the expected risk, weighting each combination by its probability.
g <- expand.grid(U = 0:1, V = 0:1, L0 = 0:1, A0 = 0:1, L1 = 0:1, A1 = 0:1, L2 = 0:1, A2 = 0:1, L6 = 0:1)
probL <- function(U, Aprev) lBase + lU * U + lA * Aprev
probA <- function(L, Aprev, V) plogis(aBase + aL * L + aA * Aprev + aV * V)
pick <- function(x, p) ifelse(x == 1, p, 1 - p)
g$p <- with(g, pick(U, pU) * pick(V, pV) * pick(L0, probL(U, 0)) * pick(A0, probA(L0, 0, V)) *
pick(L1, probL(U, A0)) * pick(A1, probA(L1, A0, V)) *
pick(L2, probL(U, A1)) * pick(A2, probA(L2, A1, V)) * pick(L6, probL(U, A2)))
g$risk <- with(g, yBase + yU * U + yV * V + yL * (L0 + L1 + L2 + L6) + yA * (A0 + A1 + A2))
g$cumA <- g$A0 + g$A1 + g$A2
# Naive 3 and 4 (one row per patient).
lim_cum <- coef(lm(risk ~ cumA, data = g, weights = p))[["cumA"]]
lim_cum_adj <- coef(lm(risk ~ cumA + V + L0 + L1 + L2, data = g, weights = p))[["cumA"]]
canon("truth.naive_limit.cum_crude", lim_cum)
canon("truth.naive_limit.cum_adj", lim_cum_adj)
# Naive 1 and 2 (one row per patient-day): three rows per combination, one per treatment day.
gd <- rbind(transform(g, A = A0, L = L0), transform(g, A = A1, L = L1), transform(g, A = A2, L = L2))
lim_unadj <- coef(lm(risk ~ A, data = gd, weights = p))[["A"]]
lim_adj <- coef(lm(risk ~ A + L + V, data = gd, weights = p))[["A"]]
canon("truth.naive_limit.unadj", lim_unadj)
canon("truth.naive_limit.adj", lim_adj)
# ---- summary tables (simulated data) ----
# Effect of one treated day on the risk of death by day 28: each estimate with its 95% CI (clustered by
# patient) beside the value it converges to in an infinitely large cohort (for the MSM: the true effect).
est3 <- function(e) unname(e[c("b", "lo", "hi")])
t_eff <- rbind(
naive_pooled = c(est3(ci_cl(f_unadj, "A", ~id)), lim_unadj),
naive_pooled_adj_L_V = c(est3(ci_cl(f_adj, "A", ~id)), lim_adj),
naive_cumulative = c(est3(ci_cl(f_cc, "cumA", ~id)), lim_cum),
naive_cumulative_adj = c(est3(ci_cl(f_ca, "cumA", ~id)), lim_cum_adj),
msm_stabilised_weights = c(est3(e_msm), g1),
msm_marginal = c(est3(e_m), g1))
colnames(t_eff) <- c("per_day", "lo", "hi", "large_sample_value")
cat("Effect of one treated day on death by day 28, simulated data\n")
print(round(t_eff, 4))
# Cumulative weights at day 4: stabilised versus unstabilised.
t_wt <- rbind(stabilised = c(mean(last$sw), max(last$sw), ess(last$sw)),
unstabilised = c(mean(last$w), max(last$w), ess(last$w)))
colnames(t_wt) <- c("mean", "max", "ess")
cat("Cumulative weights at day 4 (3,000 patients), simulated data\n")
print(round(t_wt, 4))
# Risk of death by day 28 under each regime, from the marginal MSM, beside the true risk.
t_risk <- rbind(never_treated = c(coef(msmm)[[1]], r_never),
always_treated = c(coef(msmm)[[1]] + 3 * coef(msmm)[[2]], r_never + 3 * g1))
colnames(t_risk) <- c("msm_estimate", "true_value")
cat("Risk of death by day 28 by treatment regime, simulated data\n")
print(round(t_risk, 4))
cat("These true values describe the simulation design (simulated data), not any real drug.\n")
# ---- Follow-up length, the marginal MSM intercept, and how many days each patient was treated ----
# Death is counted up to day 28.
canon("truth.design.death_day", 28, 0)
# The intercept of the marginal MSM is the risk of death by day 28 if no patient were ever treated.
canon("truth.msm.g0_marginal", r_never)
# Share who died, and patients treated on one or two of the three days.
canon("death_risk", mean(wide$Y))
canon("treated_one_day", sum(wide$cumA == 1), 0)
canon("treated_two_days", sum(wide$cumA == 2), 0)
print(table(treated_days = wide$cumA))
cat("Simulated data (ข้อมูลจำลอง): not evidence about any real drug or patient.\n")
> cat("Effect of one treated day on death by day 28, simulated data\n")
Effect of one treated day on death by day 28, simulated data
> print(round(t_eff, 4))
per_day lo hi large_sample_value
naive_pooled 0.0859 0.0631 0.1087 0.0860
naive_pooled_adj_L_V -0.0128 -0.0363 0.0107 -0.0119
naive_cumulative 0.0676 0.0498 0.0854 0.0641
naive_cumulative_adj 0.0108 -0.0057 0.0273 0.0005
msm_stabilised_weights -0.0573 -0.0806 -0.0340 -0.0500
msm_marginal -0.0566 -0.0803 -0.0329 -0.0500
> t_wt <- rbind(stabilised = c(mean(last$sw), max(last$sw),
+ ess(last$sw)), unstabilised = c(mean(last$w), max(last$w),
+ ess(last$w)))
> colnames(t_wt) <- c("mean", "max", "ess")
> cat("Cumulative weights at day 4 (3,000 patients), simulated data\n")
Cumulative weights at day 4 (3,000 patients), simulated data
> print(round(t_wt, 4))
mean max ess
stabilised 0.9994 10.3018 1694.320
unstabilised 7.9648 94.9382 1456.026
> t_risk <- rbind(never_treated = c(coef(msmm)[[1]],
+ r_never), always_treated = c(coef(msmm)[[1]] + 3 * coef(msmm)[[2]],
+ r_never + 3 * g1))
> colnames(t_risk) <- c("msm_estimate", "true_value")
> cat("Risk of death by day 28 by treatment regime, simulated data\n")
Risk of death by day 28 by treatment regime, simulated data
> print(round(t_risk, 4))
msm_estimate true_value
never_treated 0.4888 0.4725
always_treated 0.3189 0.3225
ลองขยับลูกศรด้วยตัวคุณเอง
ความลำเอียงแต่ละแบบขึ้นกับความแรงของลูกศรบางเส้น ถ้าภาวะอวัยวะบกพร่องแทบไม่มีอิทธิพลต่อการรักษา การตัดมันออกก็เสียหายน้อย ถ้ายาไม่เปลี่ยนภาวะอวัยวะบกพร่อง การปรับด้วยมันก็ไม่ปิดกั้นผลและไม่เปิดเส้นทางตัวแปรชนกัน วิดเจ็ตให้คุณปรับลูกศรสามเส้นที่สำคัญและเปรียบเทียบแต่ละค่าประมาณกับค่าจริง
เมื่อผู้ป่วยออกจากการติดตามก่อนกำหนด: น้ำหนักสำหรับการเซ็นเซอร์ใน MSM
ในกลุ่มผู้ป่วยจำลอง ไม่มีใครหายไปก่อนทราบการเสียชีวิตภายในวันที่ 28 จึงไม่ต้องใช้น้ำหนักการเซ็นเซอร์ แต่กลุ่มจริงสูญเสียผู้ป่วย เช่นจากการส่งต่อไปโรงพยาบาลอื่น เมื่อการออกขึ้นกับภาวะอวัยวะบกพร่อง ผู้ที่ยังอยู่ก็ไม่เป็นตัวแทนของผู้ที่เริ่มต้นอีกต่อไป การถ่วงน้ำหนักด้วยส่วนกลับของความน่าจะเป็นที่ไม่ถูกเซ็นเซอร์ (inverse probability of censoring weighting, IPCW) จัดการเรื่องนี้แบบเดียวกับการรักษา คือสร้างแบบจำลองความน่าจะเป็นที่จะยังอยู่ในการติดตามในแต่ละวัน เมื่อกำหนดประวัติที่วัดไว้ ถ่วงน้ำหนักผู้ป่วยที่เหลือแต่ละคนด้วยหนึ่งส่วนความน่าจะเป็นสะสม และคูณน้ำหนักนั้นกับน้ำหนักการรักษา [4] การถ่วงน้ำหนักสำหรับการรักษา การเซ็นเซอร์ และการคัดเลือกถูกเปรียบเทียบเคียงข้างกันใน ตระกูล IPW: แนวคิดเดียว กับคนที่หายไปสามแบบ
ข้อสมมติ และการตรวจที่ไปด้วยกัน
MSM ประมาณผลต่างระหว่างแผนได้ก็ต่อเมื่อเป็นไปตามเงื่อนไขสี่ประการ [7] ไม่มีข้อใดพิสูจน์ได้จากข้อมูล แต่ทุกข้อมีการตรวจที่เปิดเผยปัญหาได้
ความแลกเปลี่ยนกันได้แบบลำดับ (sequential exchangeability)
ความแลกเปลี่ยนกันได้แบบลำดับ (sequential exchangeability) หมายความว่าในทุกวันที่ตัดสินใจ ในกลุ่มผู้ป่วยที่มีประวัติที่วัดไว้เหมือนกัน ผู้ที่ได้รับยากับผู้ที่ไม่ได้รับมีการแจกแจงของผลลัพธ์ที่อาจเกิดขึ้นเหมือนกัน พูดให้ง่าย คือไม่มีสิ่งที่ไม่ได้วัดมาผลักให้การรักษาและการเสียชีวิตไปด้วยกัน เมื่อทราบ $\bar L_t$, $\bar A_{t-1}$ และ $V$ แล้ว ในกลุ่มผู้ป่วยจำลอง ความเปราะบางไม่ได้วัด แต่ส่งผลต่อการรักษาผ่านภาวะอวัยวะบกพร่องเท่านั้น เงื่อนไขจึงเป็นจริง หากทีมประเมินความเปราะบางที่ข้างเตียงและใช้มันตัดสินใจรักษาด้วย การถ่วงน้ำหนักด้วยตัวแปรที่วัดไว้จะไม่ขจัดความลำเอียง
Positivity ในทุกวัน
ข้อสมมติ positivity ต้องการโอกาสมากกว่าศูนย์ของแต่ละทางเลือกการรักษาในทุกวันที่ตัดสินใจ สำหรับทุกประวัติที่วัดไว้ซึ่งเกิดขึ้นจริง หากแนวทางของหน่วยกำหนดให้ยา X ทุกครั้งเมื่อมีภาวะอวัยวะบกพร่องรุนแรง ผู้ป่วยที่มีภาวะอวัยวะบกพร่องในวันที่ตัดสินใจก็ไม่อาจเป็นตัวแทนของแผนไม่ได้รับยาเลย พูดอย่างเคร่งครัด ความเสี่ยงภายใต้แผนหนึ่งต้องการ positivity เฉพาะตามประวัติที่แผนนั้นผ่าน ตรวจความน่าจะเป็นที่ทำนายได้รายวัน ความน่าจะเป็นใกล้ 0 หรือ 1 ปรากฏเป็นน้ำหนักที่ใหญ่มาก
Consistency
ข้อสมมติ consistency หมายความว่าผลลัพธ์ที่สังเกตได้ของผู้ป่วยเท่ากับผลลัพธ์ที่อาจเกิดขึ้นภายใต้แผนที่ผู้ป่วยทำตามจริง ซึ่งต้องการแผนที่นิยามชัดเจน "ยา X ในวันที่ 2" ควรหมายถึงช่วงขนาดยาเดียวและทางให้ยาเดียว ไม่ใช่สิ่งที่บันทึกไว้ในแผนภูมิ
แบบจำลองที่ถูกต้อง
น้ำหนักดีได้เพียงเท่าที่แบบจำลองการรักษาดี และ MSM ต้องมีรูปแบบที่ถูกต้อง น้ำหนักแบบปรับเสถียรควรมีค่าเฉลี่ยใกล้ 1 ในแต่ละวัน (0.999 ในที่นี้ที่วันที่ 4) และการกระจายตามวันไม่ควรมีหางสุดโต่ง ค่าเฉลี่ยใกล้ 1 เป็นเงื่อนไขที่จำเป็นแต่ไม่แสดงว่าแบบจำลองถูกต้อง
การตรวจเพิ่มเติมคือความสมดุลของ $L_t$ ระหว่างผู้ป่วยที่ได้รับและไม่ได้รับยาในข้อมูลที่ถ่วงน้ำหนักแล้ว ภายในแต่ละระดับของยาครั้งก่อนและกลุ่มอายุ ทีละวัน หากไม่แบ่งแบบนี้ น้ำหนักแบบปรับเสถียรที่สร้างถูกต้องก็ยังอาจทิ้งให้ผู้ที่ได้รับยามีภาวะอวัยวะบกพร่องน้อยกว่า เพราะพวกเขาได้รับยาในการตัดสินใจครั้งก่อนบ่อยกว่า และยานั้นลดภาวะนี้ สำหรับน้ำหนักแบบไม่ปรับเสถียร คาดว่าจะสมดุลได้แม้ไม่แบ่ง การประมาณซ้ำด้วยน้ำหนักที่ตัดปลายหรือ MSM ที่ยืดหยุ่นกว่าแสดงว่าค่าประมาณขึ้นกับการเลือกเหล่านี้หรือไม่ [4]
MSM กับการจำลองการทดลองเป้าหมาย: วิธีวิเคราะห์ภายในแบบแผนการศึกษา
การจำลองการทดลองเป้าหมาย (target trial emulation) เป็นกรอบแบบแผนการศึกษา ก่อนวิเคราะห์ข้อมูลเชิงสังเกต ทีมเขียนระบุการทดลองแบบสุ่มที่จะทำ ได้แก่เกณฑ์คัดเลือก กลยุทธ์การรักษา การจัดสรร เวลาเริ่มต้น (time zero คือจุดที่เริ่มนับการติดตามผู้ป่วย) การติดตาม ผลลัพธ์ และการเปรียบเทียบเชิงเหตุผล จากนั้นสร้างการวิเคราะห์ให้เลียนแบบการทดลองนั้น [8] MSM เป็นวิธีวิเคราะห์ ทั้งสองจึงไม่ใช่ทางเลือกที่แทนกัน
ทั้งสองรวมกันได้อย่างเป็นธรรมชาติ การทดลองจำลองของ "ให้ยา X ตลอด" เทียบกับ "ไม่ให้เลย" เริ่มผู้ป่วยทุกคนที่เวลาเริ่มต้นเดียวกัน ผู้ป่วยที่เบี่ยงจากกลยุทธ์อาจถูก เซ็นเซอร์เทียม (artificially censored) ณ จุดที่เบี่ยง ซึ่งนำผู้ป่วยออกจากการเปรียบเทียบของกลยุทธ์นั้นนับจากนั้น
การเบี่ยงมักขึ้นกับปัจจัยที่เปลี่ยนตามเวลาชุดเดียวกัน เช่นภาวะอวัยวะบกพร่อง การเซ็นเซอร์นี้จึงให้ข้อมูลเอนเอียง คือการออกขึ้นกับปัจจัยที่ส่งผลต่อการเสียชีวิตด้วย น้ำหนักสำหรับการคงอยู่โดยไม่ถูกเซ็นเซอร์แก้ปัญหานี้ ควบคู่กับน้ำหนักการรักษาในที่ที่การรักษายังถูกกวน การจำลองการทดลองเป้าหมายกำหนดคำถามและจุดเริ่มต้นของการติดตาม ส่วน MSM และน้ำหนักจัดการกับการกวนที่เปลี่ยนตามเวลา การทดลองจำลองบางอย่างไม่ต้องใช้ MSM เช่นเมื่อกลยุทธ์ถูกกำหนดครั้งเดียวที่เวลาเริ่มต้น
g-formula แบบพาราเมตริกโดยสรุป
g-formula แบบพาราเมตริก (parametric g-formula) ไปถึงเป้าหมายเดียวกันจากอีกด้านหนึ่ง [1, 7] แทนที่จะสร้างแบบจำลองการรักษา มันสร้างแบบจำลองของผลลัพธ์และตัวกวนที่เปลี่ยนตามเวลา เมื่อกำหนดประวัติ แล้วจำลองว่าผู้ป่วยแต่ละคนจะเป็นอย่างไรภายใต้แผน โดยสุ่ม $L_1$ จากแบบจำลองของมันเมื่อกำหนด $A_0$ ที่กำหนดให้ และต่อไปเรื่อย ๆ แล้วเฉลี่ยความเสี่ยงที่ทำนายได้
การคำนวณค่าจริงในตัวอย่างคำนวณด้วยมือคือ g-formula ที่ทำด้วยมือ วิธีนี้พึ่งเงื่อนไขเชิงเหตุผลชุดเดียวกับ MSM แต่ต้องถูกต้องคนละแบบจำลอง คือแบบจำลองผลลัพธ์และตัวกวนแทนแบบจำลองการรักษา ผลที่ตรงกันของสองแนวทางให้ความมั่นใจ เพราะทั้งสองอาจผิดพลาดต่างกัน
สิ่งที่ MSM ไม่อนุญาตให้สรุป
รายงานใดที่ใช้ MSM ควรกล่าวถึงข้อจำกัดสามประการอย่างน้อยหนึ่งประโยค
- มันไม่แก้การกวนของ $A_t$ ที่ไม่ได้วัด เพราะการถ่วงน้ำหนักทำให้สมดุลเฉพาะสิ่งที่อยู่ในแบบจำลองการรักษา
- มันประมาณผลต่างระดับประชากรของแผน คือผลต่างความเสี่ยงหากทุกคนทำตามแผนหนึ่งแทนอีกแผน มันไม่บอกว่าผู้ป่วยรายใดรายหนึ่งจะตอบสนองอย่างไร
- แผนที่เปรียบเทียบต้องมีข้อมูลรองรับ ในที่นี้ผู้ป่วย 471 คนได้รับยาตลอดและ 588 คนไม่ได้รับเลย แผนที่แทบไม่มีใครทำตามจะอิงรูปแบบของแบบจำลองมากกว่าข้อมูล
รูปแบบนี้มีข้อควรระวังของตัวเองด้วย เส้นตรงใน $\mathrm{cum}(\bar a)$ ปฏิบัติต่อทุกแผนที่มีจำนวนวันที่ได้รับยาเท่ากันว่าเท่ากัน จึงจะรายงานความเสี่ยงของแผนผิดหากจังหวะเวลาของยามีความสำคัญ
ความเข้าใจผิดที่พบบ่อยและวิธีแก้
-
"MSM แก้การกวนที่เปลี่ยนตามเวลาได้"
การถ่วงน้ำหนักขจัดการกวนได้เฉพาะโดยตัวแปรที่อยู่ในแบบจำลองการรักษา และเฉพาะเมื่อแบบจำลองเหล่านั้นถูกต้องและทุกประวัติมีโอกาสได้รับแต่ละการรักษา ความเปราะบางไม่ก่อปัญหาในกลุ่มผู้ป่วยจำลอง เพราะมันส่งผลต่อการรักษาผ่านภาวะอวัยวะบกพร่องเท่านั้น
วิธีแก้: MSM ขจัดความลำเอียงจากตัวกวนที่เปลี่ยนตามเวลาที่วัดไว้ ซึ่งการรักษาก่อนหน้าส่งผลต่อมัน โดยมีเงื่อนไขว่าแบบจำลองน้ำหนักถูกต้องและ positivity เป็นจริง มันไม่แก้การกวนที่ไม่ได้วัด
-
"การปรับด้วยภาวะอวัยวะบกพร่องในแต่ละวันก็เพียงพอ"
การปรับขจัดการกวนของยาแต่ละครั้งโดยภาวะอวัยวะบกพร่องของวันนั้น แต่ปิดกั้นผลของยาครั้งก่อนที่ผ่านภาวะอวัยวะบกพร่อง และเปิดเส้นทางตัวแปรชนกันผ่านความเปราะบาง ในกลุ่มผู้ป่วยจำลอง แบบจำลองที่ปรับและนับวันที่ได้รับยาไปอยู่ที่ 0.0005 ต่อหนึ่งวันที่ได้รับยาในกลุ่มตัวอย่างใหญ่มาก เทียบกับค่าจริง -0.050
วิธีแก้: เมื่อการรักษาก่อนหน้าเปลี่ยนตัวกวน ให้จัดการตัวกวนผ่านน้ำหนัก ไม่ใช่ใส่เป็นตัวแปรร่วมในแบบจำลองผลลัพธ์
-
"ภาวะอวัยวะบกพร่องเป็นทั้งตัวแปรสื่อกลางและตัวกวน"
ประโยคนี้ยังไม่สมบูรณ์จนกว่าจะระบุยาครั้งนั้น ภาวะอวัยวะบกพร่องในวันที่ 2 เป็นตัวแปรสื่อกลางของยาวันที่ 0 และเป็นตัวกวนของยาวันที่ 2
วิธีแก้: ระบุเวลาของการรักษา ตัวกวนที่เปลี่ยนตามเวลาซึ่งการรักษาก่อนหน้าส่งผลต่อมันอยู่บนเส้นทางจากยาครั้งก่อน และกวนยาครั้งหลัง
-
"การปรับเสถียรเปลี่ยนคำตอบ"
ในตัวอย่างคำนวณด้วยมือ น้ำหนักแบบไม่ปรับเสถียรและแบบปรับเสถียรให้ความเสี่ยงเดียวกันคือ 0.15 และ 0.20 เพราะความเสี่ยงของแต่ละแผนคำนวณแยกกัน จึงไม่มีรูปแบบของแบบจำลองให้ผิด การปรับเสถียรทำให้การกระจายของน้ำหนักแคบลง ซึ่งมักทำให้ช่วงเชื่อมั่นแคบลง โดยไม่เปลี่ยนเป้าหมายตราบที่รูปแบบของ MSM ถูกต้องและตัวแปรร่วม ณ จุดเริ่มต้นทุกตัวในตัวเศษอยู่ใน MSM ด้วย ถ้ารูปแบบของ MSM ผิด การถ่วงน้ำหนักสองแบบอาจให้คำตอบต่างกัน
วิธีแก้: ตรวจว่าน้ำหนักแบบปรับเสถียรมีค่าเฉลี่ยใกล้ 1 รายงานการกระจายและค่าสูงสุดตามวัน และตรวจรูปแบบของ MSM เทียบกับรุ่นที่ยืดหยุ่นกว่า
-
"ตัวแปรร่วมใดก็ใส่ในตัวเศษได้"
ตัวแปรร่วมที่วัดหลังการรักษาเริ่มจะหักล้างออกจากน้ำหนัก และนำการกวนที่น้ำหนักตั้งใจจะขจัดกลับมา ตัวแปรร่วม ณ จุดเริ่มต้นที่อยู่ในตัวเศษแต่ไม่อยู่ใน MSM จะกลายเป็นตัวกวนการรักษาที่เหลืออยู่ในกลุ่มที่ถ่วงน้ำหนักแล้ว
วิธีแก้: ใส่เฉพาะการรักษาก่อนหน้าและตัวแปรร่วม ณ จุดเริ่มต้นในตัวเศษ และใส่ตัวแปรร่วม ณ จุดเริ่มต้นทุกตัวของตัวเศษใน MSM
-
"การถดถอยการเสียชีวิตบนการรักษาของแต่ละวัน รวมทุกวัน ประมาณผลของยา"
การถดถอยนั้นซ้ำผลลัพธ์เดียวของผู้ป่วยแต่ละคนในทุกแถว และเปรียบเทียบวันที่ได้รับยากับวันที่ไม่ได้รับ มันไม่มีการเปรียบเทียบแผนเป็นเป้าหมาย และในกลุ่มผู้ป่วยจำลองมันให้ +0.086 ต่อหนึ่งวันที่ได้รับยา ทั้งที่ยานี้ช่วยชีวิตได้
วิธีแก้: ระบุแผนก่อน แล้วประมาณแบบจำลองของความเสี่ยงภายใต้แต่ละแผน เช่น $E[Y^{\bar a}] = g_0 + g_1\,\mathrm{cum}(\bar a)$
-
"การจำลองการทดลองเป้าหมายกับ MSM เป็นสิ่งเดียวกัน"
การจำลองการทดลองเป้าหมายเป็นแบบแผนการศึกษา ส่วน MSM เป็นวิธีวิเคราะห์หนึ่งที่อยู่ภายในแบบแผนนั้นได้
วิธีแก้: ระบุการทดลองเป้าหมายก่อน แล้วเลือกการวิเคราะห์ที่กลยุทธ์และโครงสร้างการกวนของมันต้องการ
สิ่งที่ควรทำในการวิเคราะห์ของคุณเอง
- วาดแผนภาพตามเวลา และทำเครื่องหมายทุกตัวแปรร่วมที่ยาครั้งก่อนเปลี่ยนได้
- เขียนแผนและ estimand ก่อนประมาณอะไรทั้งสิ้น เช่นได้รับยาตลอดเทียบกับไม่ได้รับเลยในสามวันแรกที่ตัดสินใจ
- สร้างตัวส่วนจากประวัติที่น่าจะขับเคลื่อนการรักษา รวมค่าล่าสุดของตัวกวนที่เปลี่ยนตามเวลาแต่ละตัว และไม่ใส่ $L_t$ หรือตัวแปรหลังจุดเริ่มต้นอื่นในตัวเศษ กลุ่มผู้ป่วยจำลองบันทึกภาวะอวัยวะบกพร่องและอายุเป็นตัวแปรมี/ไม่มี แต่กับข้อมูลจริง ตัวกวนต่อเนื่อง เช่นอายุ มักดีที่สุดเมื่อคงเป็นตัวแปรต่อเนื่องในแบบจำลองน้ำหนัก เช่นใช้ spline (เส้นโค้งยืดหยุ่นที่ให้ผลของอายุโค้งงอได้)
- รายงานค่าเฉลี่ย การกระจาย ค่าสูงสุด และขนาดตัวอย่างประสิทธิผลของน้ำหนักแบบปรับเสถียรตามวัน และพิจารณาประมาณซ้ำด้วยน้ำหนักที่ตัดปลายเป็นการวิเคราะห์ความไว (sensitivity analysis)
- ใช้ค่าคลาดเคลื่อนมาตรฐานแบบ robust จัดกลุ่ม หรือบูตสแตรปที่ประมาณแบบจำลองน้ำหนักซ้ำ และเพิ่มน้ำหนักการเซ็นเซอร์หากผู้ป่วยออกก่อนทราบผลลัพธ์
- เมื่อทำได้ ให้เปรียบเทียบ MSM กับการวิเคราะห์ด้วย g-formula ถ้าไม่ตรงกัน แสดงว่ามีแบบจำลองที่ควรทบทวน
อภิธานศัพท์
- time-varying confounder (ตัวกวนที่เปลี่ยนตามเวลา)
- ตัวแปรร่วมที่วัดระหว่างติดตามผล ซึ่งมีผลต่อการรักษาครั้งหลังและต่อผลลัพธ์ และอาจถูกเปลี่ยนโดยการรักษาก่อนหน้า
- treatment-confounder feedback
- รูปแบบที่การรักษาก่อนหน้าเปลี่ยนตัวกวนที่เปลี่ยนตามเวลา ซึ่งตัวกวนนั้นขับเคลื่อนการรักษาครั้งหลัง
- regime
- กฎที่กำหนดการรักษาในทุกจุดตัดสินใจ เช่นให้ยา X ในทุกวันที่ตัดสินใจ
- sequential exchangeability
- ในทุกวันที่ตัดสินใจ ผู้ป่วยที่ได้รับและไม่ได้รับการรักษาซึ่งมีประวัติที่วัดไว้เหมือนกันมีการแจกแจงของผลลัพธ์ที่อาจเกิดขึ้นเหมือนกัน
- marginal structural model (แบบจำลองโครงสร้างระดับประชากร (marginal structural model, MSM))
- แบบจำลองของค่าเฉลี่ยผลลัพธ์ที่อาจเกิดขึ้นภายใต้แต่ละแผนการรักษา เฉลี่ยข้ามตัวกวนที่เปลี่ยนตามเวลาแทนการปรับด้วยตัวกวนเหล่านั้น โดยปกติประมาณด้วยน้ำหนักส่วนกลับของความน่าจะเป็น
- stabilised weight (น้ำหนักแบบปรับเสถียร (stabilised weight))
- น้ำหนักส่วนกลับของความน่าจะเป็นที่คูณด้วยความน่าจะเป็นของการรักษาเดียวกันเมื่อกำหนดเพียงการรักษาก่อนหน้าและตัวแปรร่วม ณ จุดเริ่มต้น สำหรับ MSM ที่กำหนดรูปแบบถูกต้อง มันคงเป้าหมายไว้เมื่อตัวแปรร่วม ณ จุดเริ่มต้นทุกตัวของตัวเศษอยู่ใน MSM ด้วย และมักทำให้การกระจายแคบลง
- IPCW (การถ่วงน้ำหนักด้วยส่วนกลับของความน่าจะเป็นที่ไม่ถูกเซ็นเซอร์ (IPCW))
- การถ่วงน้ำหนักด้วยส่วนกลับของความน่าจะเป็นที่ไม่ถูกเซ็นเซอร์ (inverse probability of censoring weighting): ถ่วงน้ำหนักผู้ป่วยแต่ละคนที่ยังอยู่ในการติดตามด้วยหนึ่งส่วนความน่าจะเป็นที่จะยังอยู่ เมื่อกำหนดประวัติที่วัดไว้
- target trial emulation (การจำลองการทดลองเป้าหมาย (target trial emulation))
- กรอบแบบแผนการศึกษาที่ระบุการทดลองแบบสุ่มซึ่งการวิเคราะห์เชิงสังเกตตั้งใจเลียนแบบ
- g-formula (g-formula)
- การปรับมาตรฐานที่เฉลี่ยผลลัพธ์ที่ทำนายจากแบบจำลองข้ามประวัติตัวแปรร่วมที่แต่ละแผนการรักษาจะก่อให้เกิด
- collider
- ตัวแปรที่ลูกศรสองเส้นบนเส้นทางชี้เข้าหา การปรับด้วยมันเปิดเส้นทางและสร้างความสัมพันธ์ลวงได้
- positivity (ข้อสมมติ positivity)
- โอกาสมากกว่าศูนย์ของแต่ละทางเลือกการรักษาสำหรับทุกประวัติที่วัดไว้ซึ่งเกิดขึ้นจริง
- consistency
- ผลลัพธ์ที่สังเกตได้เท่ากับผลลัพธ์ที่อาจเกิดขึ้นภายใต้แผนที่ผู้ป่วยทำตามจริง
เอกสารอ้างอิง
- Daniel RM, Cousens SN, De Stavola BL, Kenward MG, Sterne JAC. Methods for dealing with time-dependent confounding. Stat Med. 2013;32(9):1584-1618. doi:10.1002/sim.5686 https://doi.org/10.1002/sim.5686
- Robins JM, Hernán MA, Brumback B. Marginal structural models and causal inference in epidemiology. Epidemiology. 2000;11(5):550-560. doi:10.1097/00001648-200009000-00011 https://doi.org/10.1097/00001648-200009000-00011
- Hernán MA, Brumback B, Robins JM. Marginal structural models to estimate the causal effect of zidovudine on the survival of HIV-positive men. Epidemiology. 2000;11(5):561-570. doi:10.1097/00001648-200009000-00012 https://doi.org/10.1097/00001648-200009000-00012
- Cole SR, Hernán MA. Constructing inverse probability weights for marginal structural models. Am J Epidemiol. 2008;168(6):656-664. doi:10.1093/aje/kwn164 https://doi.org/10.1093/aje/kwn164
- Fewell Z, Hernán MA, Wolfe F, Tilling K, Choi H, Sterne JAC. Controlling for time-dependent confounding using marginal structural models. Stata J. 2004;4(4):402-420. doi:10.1177/1536867X0400400403 https://doi.org/10.1177/1536867X0400400403
- van der Wal WM, Geskus RB. ipw: an R package for inverse probability weighting. J Stat Softw. 2011;43(13):1-23. doi:10.18637/jss.v043.i13 https://doi.org/10.18637/jss.v043.i13
- Hernán MA, Robins JM. Causal inference: what if [Internet]. Boca Raton: Chapman & Hall/CRC; 2020. https://miguelhernan.org/whatifbook
- Hernán MA, Robins JM. Using big data to emulate a target trial when a randomized trial is not available. Am J Epidemiol. 2016;183(8):758-764. doi:10.1093/aje/kwv254 https://doi.org/10.1093/aje/kwv254
ประเด็นสำคัญ
- ตัวแปรร่วมที่การรักษาก่อนหน้าเปลี่ยน และขับเคลื่อนการรักษาครั้งหลังและการเสียชีวิต ทำให้การถดถอยแบบธรรมดาผิดพลาดไม่ว่าจะปรับด้วยตัวแปรนี้หรือไม่
- แบบจำลองโครงสร้างระดับประชากรเปรียบเทียบแผนการรักษาทั้งแผน โดยถ่วงน้ำหนักผู้ป่วยแต่ละคนด้วยส่วนกลับของความน่าจะเป็นของประวัติการรักษาที่ได้รับ คูณกันข้ามวันที่ตัดสินใจ
- น้ำหนักแบบปรับเสถียรคงเป้าหมายไว้เมื่อรูปแบบของ MSM ถูกต้องและ MSM มีตัวแปรร่วม ณ จุดเริ่มต้นทุกตัวที่อยู่ในตัวเศษ ตัวเศษควรมีเฉพาะการรักษาก่อนหน้าและตัวแปรร่วม ณ จุดเริ่มต้น
- ในกลุ่มผู้ป่วยไอซียูจำลอง การถดถอยแบบธรรมดาทำให้ยาดูเหมือนเป็นอันตรายหรือแทบไม่มีประโยชน์ ส่วน MSM ให้ -0.057 ต่อหนึ่งวันที่ได้รับยา เทียบกับค่าจริง -0.050
- MSM ขจัดความลำเอียงได้เฉพาะจากตัวกวนที่วัดไว้ ความน่าเชื่อถือของมันจึงขึ้นกับความแลกเปลี่ยนกันได้แบบลำดับ positivity ในทุกวัน และแบบจำลองน้ำหนักที่ถูกต้อง
อ่านต่อในวิกิ: [[inverse-probability-weighting-treatment-censoring-selection-th]] [[extreme-propensity-weights-stabilization-truncation-trimming-th]]