Data Analyst › Business metrics
RFM segmentation
ประมาณ 30 นาที
เป้าหมายของบท
- เข้าใจ Recency, Frequency, Monetary และทำไมสามตัวนี้บอกอะไรที่ยอดขายรวมบอกไม่ได้
- ให้คะแนนลูกค้าแต่ละมิติด้วย quartile (qcut)
- นิยาม segment (Champions, At Risk, Lost) และแนวทางทำกับแต่ละกลุ่ม
ทำไมยอดขายรวมไม่พอ
ลูกค้าสองคนที่ยอดขายรวมเท่ากันอาจมีพฤติกรรมต่างกันมาก — คนหนึ่งซื้อครั้งเดียวแพงๆ อีกคนซื้อบ่อยๆ ทีละน้อย RFM ช่วยแยกแยะพฤติกรรมนี้ด้วย 3 มิติ:
- Recency — ซื้อครั้งล่าสุดเมื่อไร (วัน ยิ่งน้อยยิ่งดี)
- Frequency — ซื้อบ่อยแค่ไหน (จำนวนออเดอร์)
- Monetary — ใช้จ่ายรวมเท่าไร
snapshot = pd.Timestamp('2026-01-01') # กำหนดวันที่คงที่ ไม่ใช้ pd.Timestamp.now()
recency = (snapshot - comp.groupby('customer_id')['order_date'].max()).dt.days
frequency = comp.groupby('customer_id').size()
monetary = rev.groupby('customer_id')['line'].sum()
ระวัง: ต้องกำหนด snapshot เป็นวันที่คงที่เสมอ ไม่ใช้ pd.Timestamp.now() เพราะจะทำให้ผลลัพธ์เปลี่ยนไปทุกครั้ง
ที่รันโค้ด
ให้คะแนนด้วย quartile
แปลงแต่ละมิติเป็นคะแนน 1-4 (4 = ดีที่สุด) ด้วย pd.qcut เพื่อเทียบกันได้ข้ามมิติ:
rfm['r_score'] = pd.qcut(rfm['recency'], 4, labels=[4, 3, 2, 1]).astype(int) # recency น้อย = คะแนนสูง
rfm['f_score'] = pd.qcut(rfm['frequency'].rank(method='first'), 4, labels=[1, 2, 3, 4]).astype(int)
rfm['m_score'] = pd.qcut(rfm['monetary'], 4, labels=[1, 2, 3, 4]).astype(int)
rfm['rfm_score'] = rfm['r_score'] + rfm['f_score'] + rfm['m_score']
ระวัง: frequency มีค่าซ้ำกันเยอะมาก (ลูกค้าหลายร้อยคนซื้อแค่ 1 ครั้งเท่ากัน) การใช้ pd.qcut ตรงๆ กับคอลัมน์นี้
จะได้ error ValueError: Bin edges must be unique เพราะแบ่ง 4 กลุ่มเท่าๆ กันไม่ได้ — ต้องใช้ .rank(method='first')
ก่อนเพื่อกระจายค่าที่ซ้ำกันให้ตัดกลุ่มได้
นิยาม segment
จากคะแนนสามมิติ นิยาม segment ที่ใช้บ่อยได้ เช่น:
| segment | เงื่อนไข | ความหมาย |
|---|---|---|
| Champions | r_score=4, f_score=4, m_score=4 | ลูกค้าดีเยี่ยมทุกด้าน ควรรักษาไว้ |
| At Risk | r_score=1, f_score≥3 | เคยซื้อบ่อยแต่นานแล้วไม่กลับมา เสี่ยงจะเสียไป |
| Lost | r_score=1, f_score=1 | ซื้อครั้งเดียวแล้วหายไปนาน |
แต่ละกลุ่มควรได้รับกลยุทธ์ต่างกัน — Champions ควรได้รับการดูแลรักษา ส่วน At Risk ควรได้รับแคมเปญกระตุ้นให้กลับมา
ตัวอย่างแก้โจทย์
กด "ขั้นต่อไป" ทีละขั้น ลองรันและแก้โค้ดในแต่ละขั้นได้
สร้างตาราง RFM
อยากจัดกลุ่มลูกค้าตามพฤติกรรมการซื้อ ไม่ใช่แค่ดูยอดขายรวมของแต่ละคน
คำนวณ recency (จำนวนวันตั้งแต่ซื้อครั้งล่าสุด), frequency (จำนวนออเดอร์ completed), monetary (รายได้รวม) ของลูกค้าแต่ละคน — กำหนด snapshot วันที่คงที่ (2026-01-01) เพื่อให้ผลลัพธ์เหมือนเดิมทุกครั้งที่รัน
ให้คะแนนด้วย quartile แล้วรวมเป็น RFM score
อยากแปลง recency/frequency/monetary ให้เป็นคะแนนเทียบกันได้ (1-4) แล้วรวมเป็นคะแนนเดียว
แบ่งแต่ละมิติเป็น 4 กลุ่มเท่าๆ กันด้วย qcut — recency ยิ่งน้อยยิ่งดี (labels กลับด้าน) ส่วน frequency/monetary ยิ่งมากยิ่งดี
เติมช่องว่าง
แทน ___ ด้วยโค้ดที่ถูกต้อง แล้วกดตรวจคำตอบ
คำนวณ recency (จำนวนวันตั้งแต่ซื้อครั้งล่าสุด) ของลูกค้าแต่ละคน เก็บไว้ในตัวแปร result เติม ___ ให้ครบ
แบบฝึกท้ายบท
บทนี้จะนับว่าเสร็จเมื่อผ่านข้อพื้นฐานและประยุกต์ครบ ข้อท้าทายได้ดาวเพิ่ม
🟢 พื้นฐาน
recency (วัน) เฉลี่ยของลูกค้าทั้งหมด (snapshot = 2026-01-01) เก็บไว้ในตัวแปร avg_recency
frequency (จำนวนออเดอร์ completed) เฉลี่ยของลูกค้าทั้งหมด เก็บไว้ในตัวแปร avg_frequency
monetary (รายได้รวม) เฉลี่ยของลูกค้าทั้งหมด เก็บไว้ในตัวแปร avg_monetary
จำนวนลูกค้าทั้งหมดในตาราง RFM (ลูกค้าที่เคยซื้อสำเร็จอย่างน้อย 1 ครั้ง) เก็บไว้ในตัวแปร n_customers_rfm
🟡 ประยุกต์
สร้างตาราง RFM แบบเต็ม (คอลัมน์ recency, frequency, monetary, r_score, f_score, m_score, rfm_score) เก็บไว้ในตัวแปร result — ใช้ pipeline เดียวกับตัวอย่าง x1/x2 เป๊ะ
ค่าเฉลี่ยของ rfm_score ทั้งหมด เก็บไว้ในตัวแปร avg_rfm_score
จำนวนลูกค้าในกลุ่ม Champions (r_score==4 และ f_score==4 และ m_score==4) เก็บไว้ในตัวแปร n_champions
🔴 ท้าทาย
จำนวนลูกค้าในกลุ่ม At Risk (r_score==1 และ f_score>=3) เก็บไว้ในตัวแปร n_at_risk
% ของรายได้รวมที่มาจากกลุ่ม Champions เก็บไว้ในตัวแปร champions_rev_share_pct
จำนวนลูกค้าในกลุ่ม Lost (r_score==1 และ f_score==1) และ % รายได้ที่กลุ่ม At Risk สร้าง (สองตัวแปร) เก็บไว้ในตัวแปร n_lost, at_risk_rev_share_pct
Quiz
สรุป
- RFM วัดพฤติกรรมลูกค้า 3 มิติ (Recency/Frequency/Monetary) ให้ภาพที่ละเอียดกว่ายอดขายรวมตัวเดียว
- ให้คะแนนแบบ quartile ต้องระวัง frequency ที่มีค่าซ้ำเยอะ — ใช้ rank(method='first') ก่อน qcut เสมอ
- Champions (168 คน) สร้างรายได้ 18.2% ของทั้งหมด ส่วน At Risk (163 คน) คือกลุ่มที่เคยซื้อบ่อยแต่กำลังจะหายไป ควรได้รับความสนใจต่างกัน
คำถามสัมภาษณ์
อธิบาย RFM segmentation ให้ทีมการตลาดที่ไม่ถนัดสถิติฟัง
RFM คือการดูลูกค้าสามด้าน — ซื้อล่าสุดเมื่อไร (Recency) ซื้อบ่อยแค่ไหน (Frequency) และใช้จ่ายรวมเท่าไร (Monetary) แล้วให้คะแนนแต่ละด้านเทียบกับลูกค้าคนอื่น จากนั้นรวมคะแนนเพื่อแบ่งลูกค้าเป็นกลุ่มๆ เช่น "ลูกค้าดีเยี่ยม" (คะแนนสูงทุกด้าน) หรือ "เคยดีแต่กำลังจะหายไป" (เคยซื้อบ่อยแต่นานแล้วไม่กลับมา) เพื่อให้ทีม การตลาดออกแบบแคมเปญที่เหมาะกับแต่ละกลุ่มแทนที่จะส่งข้อความเดียวกันให้ทุกคน
เจอลูกค้ากลุ่ม "At Risk" (เคยซื้อบ่อยแต่หายไป) ควรทำแคมเปญแบบไหนต่างจากกลุ่ม "Champions"
กลุ่ม At Risk ควรได้รับแคมเปญกระตุ้นให้กลับมา เช่น ส่วนลดพิเศษเฉพาะบุคคล หรือข้อความถามไถ่ว่าทำไมหายไป เพราะเป็นกลุ่มที่เคยมีคุณค่าสูงและมีความเสี่ยงจะเสียไปให้คู่แข่ง ส่วนกลุ่ม Champions ควรได้รับการดูแลรักษาไว้ (เช่น โปรแกรมสมาชิก VIP หรือสิทธิพิเศษ) มากกว่าแคมเปญกระตุ้นให้ซื้อ เพราะพวกเขาซื้ออยู่แล้วสม่ำเสมอ การส่งแคมเปญเดียวกันให้ทั้งสองกลุ่มจะสิ้นเปลืองงบและไม่ตรงจุด
ทำไมการกำหนด snapshot date ให้คงที่ (ไม่ใช้วันที่ปัจจุบันจริง) ถึงสำคัญตอนเขียนโค้ดวิเคราะห์แบบนี้
ถ้าใช้วันที่ปัจจุบันจริง (เช่น pd.Timestamp.now()) ค่า recency และคะแนนทุกอย่างที่คำนวณต่อจากนั้นจะเปลี่ยนไป
ทุกครั้งที่รันโค้ดในวันที่ต่างกัน ทำให้ผลลัพธ์ไม่ reproducible — คนอื่นรันโค้ดเดียวกันในวันอื่นจะได้คำตอบไม่ตรงกัน
และตรวจสอบความถูกต้องของโค้ดไม่ได้เลย การกำหนดวันที่คงที่ทำให้ทุกคนได้ผลลัพธ์เดียวกันเสมอ