Transition matrix for Probability of Default (PD) Model #2
Building the transition matrix with Python.
ตอนก่อนหน้านี้ได้อธิบายเกี่ยวกับ Transition matrix ในเชิงทฤษฎีกันไปแล้ว เนื้อหาของ Blog ตอนนี้จึงภาคต่อที่เริ่มลงมือสร้าง Transition matrix โดยการใช้เครื่องมือเป็นภาษา Python เนื้อหาจะเน้นที่การทำ Data preparation รวมไปถึงการตั้ง Assumption ต่าง ๆ จากลักษณะข้อมูลที่นำมาใช้พัฒนาโมเดล
เขียนถึงตรงนี้… อาจมีทฤษฎีเข้ามาเกี่ยวข้องอยู่พอสมควร ดังนั้นเพื่อให้เกิดความเข้าใจมากขึ้น สามารถย้อนกลับไปอ่านตอนแรกได้จาก Link ด้านล่าง
Dataset
สำหรับการทำ Credit risk model ต้องใช้ข้อมูลที่ค่อนข้างเยอะอย่างหลีกเลี่ยงไม่ได้เลย ในที่นี้กำลังกล่าวถึงข้อมูล Loan transaction ซึ่งโดยปกติข้อมูลในลักษณะนี้ถูกเก็บในลักษณะของข้อมูลรายเดือน สำหรับเนื้อหาในตอนนี้ ขอใช้ข้อมูลเดิมที่เคยใช้ในการสร้าง Survival analysis model แต่มีการ Flag columns เพิ่มเติม ซึ่งเป็นเนื้อหาทั้งหมดใน Blog ตอนนี้
Data preparation
สิ่งที่ต้องทำเป็นลำดับต่อไปหลังจากที่ Import dataset คือการ Flag columns ตามเงื่อนไขที่ต้องการต่าง ๆ เช่น Default transaction หรือ Closed / Write-off เป็นต้น โดยโมเดลนี้ ขอ Risk grades ตาม Survival analysis model ที่แบ่งออกเป็นทั้งหมด 5 Risk grades
ดังนั้นเมื่อรวมกับ Closed / Write-off bucket แล้ว Risk grade จึงมีทั้งหมด 6 Buckets คือตาม Aging ทางบัญชี (0–4) และมี Buckets สำหรับ Closed / Write-off แยกออกมาต่างหาก โดยกำหนดให้เป็น -1
ตาม Code ด้านบน
Column: Aging1 created
Column: Aging2 created
.
.
.
Column: Aging12 created
ขอกำหนดให้โมเดลนี้มี Performance window เท่ากับ 12 เดือน หมายความว่าโมเดลหาโอกาสที่จะ Default (Probability of Default: PD) ในอีก 12 เดือนข้างหน้า
เมื่อ Assumption เป็นแบบนี้แล้ว สามารถทำ Data ตาม Assumption ที่ตั้งเอาไว้ได้ ซึ่งเทคนิคในการ Coding อาจแตกต่างกันไปในแต่ละคน อาจใช้ .rolling(window)
ในการ Status ของ 12 เดือนถัดไป หรืออาจใช้ Code ที่เขียนไว้ตัวอย่างได้ เช่น การใช้ .shift()
แต่ไม่ว่าเป็นเทคนิคใด สิ่งที่ต้องระวังคือการเรียงลำดับข้อมูล โดยต้องเรียงข้อมูลให้แต่ละ Account เรียงลำดับตามเดือนให้ถูกต้อง จากตัวอย่างคือการ .sort_values()
ตาม Account number และจำนวนเดือนจากน้อยไปมาก (1, 2, 3, …) และใช้ .shift()
เพื่อดึงเอา Aging
(ที่ Flag ไว้ก่อนหน้านี้) ของเดือนถัดไปมาแปะไว้ที่เดือนที่ Observe วน Process แบบนี้ไปเรื่อย ๆ จนครบทั้งหมด 12 ครั้ง ผลลัพธ์ที่ได้คือ ณ Observation month ใด ๆ จะมี Aging
ของ 12 ถัดไปแปะไว้ที่ Column สุดท้าย
จากนั้นหา Maximum ของ Aging ที่ Shift ไปของเดือนที่ 1 ไปจนถึงเดือนที่ 12 โดยต้องการหา Observation ที่เกิด Default ระหว่างทาง (First default) ซึ่งเมื่อเกิด Default ระหว่างทาง ค่า Maximum ต้องเท่ากับ 4
ต่อมาให้เก็บ Status สุดท้ายของแต่ละ Account ไว้ก่อน เพราะต้องมี Process การทำ Status adjustment ของกลุ่มที่เป็น Performing loan (ไม่ Default) ข้อควรระวังคือต้องเก็บ Status สุดท้าย ก่อนทำ Exclusion ไม่เช่นนั้นอาจทำให้โมเดล Capture ข้อมูลผิดไป
วิธีการเก็บ Status สุดท้ายของแต่ละ Account อาจทำได้หลายวิธี ซึ่งจาก Code ที่ Comment ปิดไว้คือการใช้ .mask()
และใช้ .ffill()
แต่ Operation ลักษณะนี้ด้วยข้อมูลหลักหลายล้าน อาจทำให้ Performance ไม่ดีมากนัก ดังนั้นแนะนำให้เก็บเป็น Dictionary โดยที่ให้ Keys เป็น Account number และ Status สุดท้ายเป็น Values
การใช้งาน Dictionary ให้เริ่มจากการหยิบเอา Transaction สุดท้ายของแต่ละ Account ด้วย .groupby()
และ .tail(1)
จากนั้น .set_index()
ด้วย Column ที่เป็น Account number เพื่อเปลี่ยนให้เป็น ‘keys’
ใน Python dictionary จากนั้นใช้ .to_dict()
เพื่อให้เก็บค่าที่เหลือให้เป็น ‘values’
ผลลัพธ์ที่ได้คือ {‘keys’: ‘values’}
ขั้นตอนต่อมาคือการทำ Exclusion หรือการตัดข้อมูลที่ไม่ต้องการออกจากการคำนวณ โดย Rules ที่ใช้สำหรับข้อมูลชุดนี้มีทั้งหมด 3 ข้อคือ
- Inactive account กลุ่ม Transaction ที่ไม่มีการเคลื่อนไหว
- Performance window ไม่ครบ 12 เดือน หมายความว่าปีสุดท้ายของ Dataset ไม่สามารถนำมาใช้งานได้ เพราะเมื่อ Shift status แล้วได้ไม่ครบ 12 เดือน เช่น ข้อมูลทั้งหมดมีถึง Dec-2019 ดังนั้น Observation สุดท้ายที่ใช้งานได้ตาม Performance window ที่กำหนดไว้เป็น 12 เดือนคือ Dec-2018 เป็นต้น
- Transaction สุดท้ายของ Account นั้น ๆ เพราะ Transaction สุดท้าย จะไม่สามารถหา Probability ได้อีกต่อไป (Shift แล้วเป็น Missing)
ส่วนต่อไปนี้เป็น 12-Month status adjustment ของกลุ่ม Performing loan โดยที่ได้ Maximum status เอาไว้ก่อนหน้านี้ เริ่มจากต้องเปลี่ยนให้เป็น Aging ที่ Maximum ได้ของเดือนที่ 12 เช่น ณ ที่ Aging10
มีค่าเป็น 3
แต่ Aging12
มีค่าเป็น 2
หากไม่มีการปรับค่าที่ออกมาคือเท่ากับ 3
แต่ในความเป็นจริงค่าที่เกิดขึ้นคือ 2
เป็นต้น จึงต้องเปลี่ยน Status จุดนี้ใหม่
เช่นเดียวกับกรณีด้านบนในกลุ่มที่เป็น Performing loan แต่มีข้อมูลไม่ครบ 12 เดือน เช่น ณ จุดที่ Observe คือ 2 เดือนก่อนหน้าการปิดบัญชี (Closed / Write-off) จึงทำให้ Performance ที่ได้คือมี 1เดือนเท่านั้น แต่เนื่องจากกลุ่มที่เป็น Non-performing loan คิดการเกิด Default ระหว่างทาง หมายความว่าแม้ Performance window มีค่าน้อยกว่า 12 เดือน แต่ก็ยังติด Flag ที่เป็น Maximum ดังนั้นจึงควรใช้ Logic เดียวกันกับกลุ่มที่เป็น Performing loan ด้วยเช่นกัน เพื่อไม่ให้เกิด Bias ขึ้นในโมเดล
หากเป็นข้อมูลที่ดีแล้ว Status ที่ Flag ไว้ควรมีค่าเป็น -1
ตามเงื่อนไขในตอนแรก แต่ข้อมูลจริงอาจไม่ได้ดีเสมอไป อาจเจอกรณีที่ Aging สุดท้ายเป็น 2
แล้ว Transaction ก็หายไปเฉย ๆ Assumption ที่ใช้ได้คือใช้ Status สุดท้ายที่ข้อมูลมี ดังนั้นจากตัวอย่างนี้ Status สุดท้ายที่ Assign ให้คือ 2
Last status ที่เก็บไว้ก่อนหน้านี้ ให้นำมาใช้ใน Step นี้ ซึ่งสามารถใช้ Dictionary มาเพื่อ .map()
ตามเลข Account number
ต่อมาเป็นกลุ่ม Closed / Write-off ให้เปลี่ยนค่าใน 12MStatus
จากเดิมที่เป็นค่า Maximum ให้เป็นค่า -1
เพราะหาก Aging12
มีค่าเป็น -1
หมายความว่ามีการปิดสัญญาเกิดขึ้นระหว่างทางในช่วง 12 เดือน
สุดท้ายกลับมาทำกลุ่ม Non-performing loan เพื่อให้สอดคล้องกับ Concept การหา Default ที่เกิดขึ้นระหว่างทางที่ Observe ดังนั้น Observation ที่ Default ณ Transaction ที่ทำการ Observe ควรเป็น Default ในอีก 12 เดือนข้างหน้าด้วยเช่นกัน
Next step
ไม่อยากอัดเนื้อหาไปมากกว่านี้ เพราะแค่เรื่องการทำ Dataset ก็มีรายละเอียดที่เยอะพอสมควรแล้ว ดังนั้นเพื่อให้กระชับเนื้อหา Blog ขอยกส่วนที่เป็นโมเดลทั้งหมดไปไว้ที่ตอนต่อไป