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

อยากจัดกลุ่มลูกค้าตามพฤติกรรมการซื้อ ไม่ใช่แค่ดูยอดขายรวมของแต่ละคน

  1. คำนวณ recency (จำนวนวันตั้งแต่ซื้อครั้งล่าสุด), frequency (จำนวนออเดอร์ completed), monetary (รายได้รวม) ของลูกค้าแต่ละคน — กำหนด snapshot วันที่คงที่ (2026-01-01) เพื่อให้ผลลัพธ์เหมือนเดิมทุกครั้งที่รัน

    Python

ให้คะแนนด้วย quartile แล้วรวมเป็น RFM score

อยากแปลง recency/frequency/monetary ให้เป็นคะแนนเทียบกันได้ (1-4) แล้วรวมเป็นคะแนนเดียว

  1. แบ่งแต่ละมิติเป็น 4 กลุ่มเท่าๆ กันด้วย qcut — recency ยิ่งน้อยยิ่งดี (labels กลับด้าน) ส่วน frequency/monetary ยิ่งมากยิ่งดี

    Python

เติมช่องว่าง

แทน ___ ด้วยโค้ดที่ถูกต้อง แล้วกดตรวจคำตอบ

ยังไม่ได้ทำ

คำนวณ 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

1. RFM ย่อมาจากอะไร
2. recency ยิ่งค่าน้อย (วันน้อย) หมายความว่าอะไร
3. ทำไมต้องใช้ rank(method='first') ก่อน qcut กับ frequency
4. กลุ่ม "At Risk" ในบทนี้นิยามว่าอะไร
5. ทำไม RFM ถึงมีประโยชน์กว่าดูแค่ยอดขายรวมของลูกค้าคนหนึ่ง

สรุป

  • 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 — คนอื่นรันโค้ดเดียวกันในวันอื่นจะได้คำตอบไม่ตรงกัน และตรวจสอบความถูกต้องของโค้ดไม่ได้เลย การกำหนดวันที่คงที่ทำให้ทุกคนได้ผลลัพธ์เดียวกันเสมอ