← Data Science & EngineeringEnglish

CUSTOMER ANALYTICS · INTERACTIVE LAB

RFM ไม่ได้มีไว้แค่แบ่งกลุ่มลูกค้า แต่ใช้มองการเคลื่อนที่ของความสัมพันธ์

เลือกลูกค้าหนึ่งราย แล้วอ่านหลักฐานสามด้านร่วมกัน: เขากลับมาล่าสุดเมื่อไร ซื้อบ่อยเพียงใด และสร้างมูลค่าเท่าไร จากนั้นติดตาม slope หลายช่วงเวลาเพื่อแยก “กำลังเงียบลง” ออกจาก “เคยซื้อสูงแต่ยังไม่ถึงเวลาซื้อซ้ำ”

ONLINE RETAIL · REAL DATA · 541,909 SOURCE ROWS

ต้องการทดลองเลย เริ่มตรงนี้

ทุกแล็บใช้ valid invoices 18,532 รายการ และลูกค้า anonymized 4,338 รายชุดเดียวกัน

01 · DATA GRAIN

ก่อนคำนวณ ต้องพิสูจน์ก่อนว่าเราติดตาม “ลูกค้าคนเดิม” ได้

แล็ปใช้ Online Retail จริง 541,909 แถว ช่วง 1 ธันวาคม 2010 ถึง 9 ธันวาคม 2011 ซึ่งเป็นชุดเดียวกับ SQL, notebook และสไลด์ต้นฉบับ เรากรอง Customer ID ที่หาย cancellation จำนวนติดลบและราคาศูนย์ ก่อนรวมหลาย product line ให้เหลือหนึ่งแถวต่อ invoice

Customer ID ถูก hash เป็นรหัส R-xxxxxxxx และไฟล์ดิบไม่อยู่ใน public web directory หลังเตรียมข้อมูลเหลือ 18,532 invoices จากลูกค้า 4,338 ราย โดยเลือก teaching cohort 12 รายที่มีพฤติกรรมต่างกันมาให้ทดลอง

COMMON DATA CONTRACTcustomer_id · invoice_id · occurred_at · amountหนึ่งแถว = หนึ่ง valid invoice หลังรวม product line; มูลค่าเป็น GBP

R · F · M ARE THREE DIFFERENT QUESTIONS

อย่ารวมสามตัวเร็วเกินไป เพราะแต่ละตัวเล่าเรื่องคนละด้าน

RFM มีเสน่ห์ตรงที่สูตรง่าย แต่การตีความไม่ตื้น เราไม่ได้ถามเพียงว่าใครซื้อเยอะที่สุด เรากำลังวัดทั้งระยะห่าง ความต่อเนื่อง และขนาดของความสัมพันธ์ ณ จุดเวลาหนึ่ง

R

Recency

ความสัมพันธ์ยัง “สด” แค่ไหน

นับจำนวนวันจากรายการล่าสุดถึง snapshot ยิ่งน้อยมักยิ่งดี แต่สินค้าที่ซื้อทุกวันกับสินค้าที่ซื้อปีละครั้งใช้เกณฑ์เดียวกันไม่ได้ Recency จึงต้องอ่านคู่กับ expected purchase cycle

R = snapshot_date − max(purchase_date)
F

Frequency

ความสัมพันธ์เกิดซ้ำจริงหรือไม่

ควรนับ distinct invoice ไม่ใช่จำนวนแถวสินค้า มิฉะนั้นตะกร้าหนึ่งใบที่มีสิบรายการจะดูเหมือนซื้อสิบครั้ง ต้องกำหนด window ให้ชัด เพราะ lifetime frequency มักให้รางวัลลูกค้าเก่าโดยอัตโนมัติ

F = count(distinct invoice_id)
M

Monetary

มูลค่าที่เกิดขึ้นในขอบเขตเดียวกัน

Gross sales, net sales, margin และ contribution ไม่ใช่สิ่งเดียวกัน แล็ปใช้ยอดชำระเพื่อให้เห็นสูตร แต่ระบบจริงควรเลือกค่าที่สอดคล้องกับการตัดสินใจและหัก refund หรือ cancellation ให้ถูกต้อง

M = sum(valid transaction value)
คำถามก่อนเริ่ม

ถ้าเปลี่ยน snapshot หรือ window คำตอบควรเปลี่ยนหรือไม่?

  1. คาดการณ์ก่อนว่าลูกค้าที่เลือกจะอยู่กลุ่มใด
  2. เปลี่ยน 120 วันเป็น 90 และ 180 วัน แล้วอธิบายค่าที่เปลี่ยน
  3. แยกให้ออกว่าค่าที่เปลี่ยนมาจากพฤติกรรม หรือมาจากนิยาม
R = snapshot − last purchaseF = distinct invoices in windowM = Σ invoice amount in window
RECENCY · ยิ่งน้อยยิ่งใหม่วันR—
FREQUENCY · จำนวน invoiceครั้งF—
MONETARY · มูลค่ารวมในหน้าต่างTHBM—
RFM SEGMENTRFM —

RAW METRIC ≠ RFM SCORE

ค่า 14 วัน ไม่ได้แปลว่า R = 4 เสมอไป

ค่าดิบตอบว่าเกิดอะไรขึ้นกับลูกค้ารายนั้น ส่วนคะแนน 1–4 บอกตำแหน่งสัมพัทธ์เมื่อเทียบกับประชากร ณ snapshot เดียวกัน ในแล็ปนี้ใช้ quartile: Recency กลับทิศเพราะค่าน้อยดีกว่า ขณะที่ Frequency และ Monetary ค่าสูงดีกว่า

ค่าดิบR=14 · F=6 · M=18,400จัดอันดับกับประชากรQ4 · Q3 · Q4รหัสกลุ่มRFM 434

เมื่อประชากรหรือช่วงเวลาเปลี่ยน คะแนนอาจเปลี่ยนแม้ค่าดิบเท่าเดิม ดังนั้น feature store ควรเก็บทั้งค่าดิบ วิธีแบ่ง bin และรุ่นของ segmentation

04–05 · CUSTOMER MOVEMENT

ดูทิศทาง ไม่ตัดสินจาก snapshot เดียว

ทุกจุดคำนวณด้วยหน้าต่างย้อนหลังเท่ากัน แล้ว fit เส้นตรงกับค่าที่ normalize ภายในลูกค้ารายนั้น

Recency riskFrequencyMonetary
RECENCY SLOPE

ค่าบวก = วันตั้งแต่ซื้อครั้งล่าสุดเพิ่มเร็วขึ้น เป็นสัญญาณลบ

FREQUENCY SLOPE

ค่าลบ = ความถี่ใน rolling window ลดลง

MONETARY SLOPE

ค่าลบ = มูลค่าใน rolling window ลดลง

CHURN SIGNAL · NOT A CALIBRATED PROBABILITY

/ 100

HOW TO READ THREE SLOPES

ลูกค้าที่คะแนนลดเหมือนกัน อาจต้องการการตอบสนองคนละแบบ

R slopeF slopeM slopeการอ่านที่เป็นไปได้สิ่งที่ตรวจต่อ
เงียบลงทุกด้าน

สินค้าเดิม ช่องทางเดิม ปัญหาบริการ และคู่แข่ง

ซื้อห่างขึ้นแต่ตะกร้าใหญ่ขึ้น

อาจเป็นการรวมคำสั่งซื้อ ไม่ใช่ churn

ยังกลับมา แต่ใช้จ่ายลด

product mix, discount, unit price และเศรษฐกิจ

รอบซื้ออาจยาวขึ้น

เทียบ category cadence และ cohort เดียวกัน

06 · FROM SIGNAL TO ACTION

คะแนนไม่ควรสั่งการแทนเรา แต่ควรบอกว่าต้องตรวจอะไรต่อ

ข้อจำกัดที่ต้องพูดให้ชัด

Slope ในแล็ปนี้เป็น heuristic จากพฤติกรรม ไม่ใช่ churn probability ที่ผ่านการ train และ calibrate หากต้องการ prediction จริง ต้องนิยาม churn horizon, สร้าง label จากอนาคต, แบ่งข้อมูลตามเวลา, ประเมิน precision/recall และตรวจ calibration รวมทั้งวงจรซื้อซ้ำตามธรรมชาติของสินค้า

FROM RFM HEURISTIC TO CHURN MODEL

ถ้าจะเรียกว่า Prediction ต้องมีอนาคตเป็นผู้เฉลย

RFM และ slope เป็น feature ที่ดีเพราะอธิบายได้ แต่ model จะน่าเชื่อถือก็ต่อเมื่อข้อมูลทุกตัวถูกสร้าง ณ prediction time โดยไม่เห็นอนาคต

01

นิยามเหตุการณ์

เช่น ไม่มีการซื้อภายใน 60 วันหลัง snapshot โดยต้องสัมพันธ์กับวงจรสินค้า

02

สร้าง Point-in-time Features

RFM, slope, tenure, interval variance, product diversity และ refund rate ใช้ข้อมูลก่อน snapshot เท่านั้น

03

แบ่งข้อมูลตามเวลา

Train ด้วยอดีต validate ด้วยช่วงถัดไป และ test ด้วยช่วงล่าสุด ห้ามสุ่ม row จนอนาคตไหลย้อน

04

วัดผลที่สอดคล้องกับงาน

ดู precision, recall, PR-AUC, lift@k และ calibration ร่วมกับต้นทุนการติดต่อและมูลค่าการรักษาลูกค้า

STUDENT INVESTIGATION

คำถามที่ควรตอบได้หลังจบแล็ป

  1. เหตุใดต้องใช้ distinct invoice ใน Frequency และจะผิดอย่างไรหากนับจำนวนแถวสินค้า?
  2. ลูกค้าคนเดิมเปลี่ยน segment เมื่อเปลี่ยน window หรือไม่ และการเปลี่ยนนั้นเป็นพฤติกรรมจริงหรือ artefact?
  3. กรณีใด Recency แย่ลง แต่ยังไม่ควรเรียกว่า churn?
  4. เหตุใดคะแนน 80/100 ในหน้านี้จึงไม่ใช่ churn probability 80%?
  5. ต้องเพิ่ม label และ validation แบบใดจึงเปลี่ยนจาก signal เป็น prediction ได้?

FORMULA → SQL

แปลงคำถามเดียวกันเป็น query ที่ตรวจสอบได้

SELECT customer_id,
       DATEDIFF(:snapshot, MAX(occurred_at))                         AS recency,
       COUNT(DISTINCT invoice_id)                                   AS frequency,
       SUM(amount)                                                  AS monetary
FROM transactions
WHERE occurred_at > :snapshot - INTERVAL 120 DAY
  AND occurred_at <= :snapshot
GROUP BY customer_id;

ใน production ควรสร้าง snapshot table รายสัปดาห์หรือรายเดือน แล้วคำนวณ slope ด้วย regression บนหลายจุดเวลา ไม่ใช่หารคะแนนสองช่วงเพียงครั้งเดียว

THE SAME RFM IDEA · TWO COMPUTATIONAL LANGUAGES

Notebook ช่วยให้เราเห็นกระบวนการ ส่วน SQL ช่วยให้กระบวนการอยู่ใกล้ข้อมูล

อ่านคำถามก่อน แล้วแปล formula เดียวกันระหว่าง dataframe operation กับ relational operation ตามลำดับในสไลด์

01 · Clean02 · Derive03 · Aggregate04 · Score05 · Movement
01
DATA QUALITY

ธุรกรรมใดควรเข้าสู่ RFM?

ตัด Customer ID ที่หาย รายการซ้ำ จำนวนติดลบ และราคาศูนย์ก่อน มิฉะนั้นสูตรอาจถูกแต่ความหมายผิด

PYTHON · PANDAS
df = df.dropna(subset=['CustomerID'])
df = df.drop_duplicates()
df = df[(df.Quantity > 0) & (df.UnitPrice > 0)]
SQL · FILTER
WHERE customer_id IS NOT NULL
  AND invoice_no NOT LIKE 'C%'
  AND quantity > 0
  AND unit_price > 0
02
FEATURE DERIVATION

สร้างมูลค่าต่อรายการ

Notebook สร้าง column ใน memory ส่วน SQL สร้าง expression ใน query plan แต่เป็นสูตรเดียวกัน

PYTHON
df['TotalSum'] = df['UnitPrice'] * df['Quantity']
SQL
unit_price * quantity AS line_value
04
RELATIVE SCORING

qcut เทียบกับ NTILE

Recency ต้องกลับทิศเพราะจำนวนวันน้อยดีกว่า และ tied values อาจทำให้สองวิธีแบ่งขอบต่างกัน

PYTHON · QCUT
r = pd.qcut(rfm.Recency, 4, labels=[4,3,2,1])
f = pd.qcut(rfm.Frequency, 4, labels=[1,2,3,4])
m = pd.qcut(rfm.Monetary, 4, labels=[1,2,3,4])
SQL · WINDOW
5 - NTILE(4) OVER (ORDER BY recency) AS r,
NTILE(4) OVER (ORDER BY frequency) AS f,
NTILE(4) OVER (ORDER BY monetary) AS m
05
CUSTOMER MOVEMENT

จาก Q1/Q2 ratio ไปสู่ slope หลายช่วง

Notebook เดิมใช้สองช่วงเพื่อเปิดแนวคิด movement ส่วนแล็ปนี้ขยายเป็นหลาย snapshot และแยก slope ของ R, F และ M

ORIGINAL NOTEBOOK
movement['Slope'] = (
  movement.RFM_ScoreQ2 /
  movement.RFM_ScoreQ1
)
EXTENDED SQL IDEA
REGR_SLOPE(recency_norm, snapshot_no) r_slope,
REGR_SLOPE(frequency_norm, snapshot_no) f_slope,
REGR_SLOPE(monetary_norm, snapshot_no) m_slope

Notebook เหมาะกับ

สำรวจ missing/distribution, visualization, clustering และอธิบายทีละ cell

SQL เหมาะกับ

คำนวณใกล้ข้อมูล สร้าง view/snapshot ที่ทำซ้ำได้ และรักษา governance

ทางที่ใช้จริง

SQL สร้าง point-in-time feature table; notebook ตรวจคุณภาพ ทำกราฟ และทดลองโมเดล

จุดที่บทเรียนปรับจากไฟล์ต้นฉบับ

SQL เดิมใช้ COUNT(*) และจัดอันดับ last_order_date โดยตรง บทเรียนนี้ใช้ COUNT DISTINCT invoice และคำนวณจำนวนวันจาก snapshot ก่อน เพื่อให้ความหมายของ Frequency และ Recency ตรงกับนิยาม

FROM THE ORIGINAL LECTURE · VISUAL READING

ภาพจากสไลด์ช่วยให้เห็นว่า RFM เดินทางจากตาราง ไปสู่ segment และ movement อย่างไร

ภาพไม่ได้วางเป็น gallery เฉย ๆ แต่เรียงตามบทบาทในการคิด: นิยาม → query → metric → scoring → visualization → churn signal

RFM definition slide showing Recency Frequency and Monetary
01 · DEFINEเริ่มจากคำถามสามข้อที่ต่างกันก่อนรวมเป็นคะแนนเดียว
SQL clean view for RFM
02 · CLEAN VIEWSQL รวม transaction ตามลูกค้าและกำหนดเงื่อนไขข้อมูลที่ยอมรับได้
SQL RFM analysis using NTILE
03 · SCORE IN SQLWindow function เปลี่ยน metric ดิบเป็นตำแหน่งสัมพัทธ์ในประชากร
Python calculation of RFM metrics
04 · CALCULATENotebook ทำให้เห็น snapshot, aggregation และตารางผลลัพธ์ในขั้นเดียว
Python RFM actionable scoring
05 · SCORE IN PYTHONqcut แบ่ง quartile และประกอบ R, F, M เป็นรหัสที่อ่านได้
RFM snake plot comparing customer groups
06 · READ SHAPESnake plot แสดงว่าแต่ละกลุ่มเด่นหรืออ่อนที่ตัวแปรใด ไม่ได้ดูแค่คะแนนรวม
RFM heat maps comparing segment profiles
07 · COMPARE PROFILESHeat map ทำให้เห็น relative importance ของ RFM ระหว่าง segment
RFM segment dashboard
08 · SEGMENT MAPพื้นที่ของ segment บอกจำนวนลูกค้า ส่วนตำแหน่งและชื่อกลุ่มช่วยสื่อสารกับทีมธุรกิจ
RFM customer movement toward churn prediction
09 · MOVEMENTภาพการเคลื่อนกลุ่มคือสะพานจาก segmentation ไปสู่ churn signal แต่ยังไม่ใช่ probability
ชวนให้นักศึกษาสังเกต

SQL และ Python ให้ค่าคล้ายกันเมื่อ data contract, snapshot, filter และวิธีแบ่ง quartile เหมือนกัน หากผลไม่ตรง อย่ารีบตัดสินว่า engine ผิด ให้ตรวจ grain, cancelled invoice, distinct invoice, tie handling และ snapshot boundary ก่อน

BOUNDED PYTHON RUNNER · LIVE DASHBOARD

เลือกขั้นตอน แล้วให้ Python รันสูตรจริง

Dashboard นี้เปิดให้เปลี่ยน parameter แต่ไม่เปิด arbitrary code execution โค้ดที่แสดงคือ operation ที่ runner ใช้จริง นักศึกษาจึงอ่าน code, รัน และตรวจ output ได้โดยไม่เปิด shell บน server

CONNECTING…— ms
PYTHON CODE · READ ONLY
# Select an operation and run.
RESULT · JSON
{}
ขอบเขตความปลอดภัย

รองรับเฉพาะ 4 operation, ลูกค้า 12 รหัส, snapshot และ window ที่กำหนด ไม่มี eval/exec ไม่มี package install ไม่มี filesystem access จากผู้ใช้ และ Python service ไม่มี public port

ROLLING TIME WINDOW · DYNAMIC RFM

เมื่อเวลาเดิน แม้ไม่มีแถวใหม่ RFM ก็เปลี่ยนได้

เลื่อน Snapshot หรือกด Play แล้วสังเกตสองขอบพร้อมกัน: ขอบขวาคือ “วันนี้” ที่ทำให้ Recency เพิ่มขึ้น ขอบซ้ายคือวันหมดอายุของหลักฐาน เมื่อ transaction หลุดขอบซ้าย Frequency และ Monetary จะลดลง

หมดอายุแล้ว
ROLLING WINDOW
อนาคต
+ 0 รายการเข้าหน้าต่าง0 รายการหลุดหน้าต่าง0 รายการอยู่ในหน้าต่าง
RECENCYวันนับจากรายการล่าสุด
FREQUENCYinvoice ในหน้าต่าง
MONETARYTHB ในหน้าต่าง
Recency riskFrequencyMonetary
01 · ไม่มีการซื้อใหม่

Recency เพิ่มตามเวลา ส่วน F และ M ยังเท่าเดิมจนกว่าขอบซ้ายจะชน transaction

02 · รายการใหม่เข้ามา

Recency reset ใกล้ศูนย์ F เพิ่มหนึ่ง และ M เพิ่มตามมูลค่ารายการ

03 · รายการเก่าหลุดออก

ไม่มี DELETE ในฐานข้อมูล แต่ feature เปลี่ยนเพราะนิยาม window ไม่มองรายการนั้นแล้ว

RFM SEGMENT MAP · CUSTOMER MOVEMENT

ลูกค้าที่เลือกอยู่ตรงไหน และกำลังเคลื่อนไปทางใด

แผนที่นี้คำนวณจากลูกค้าจริง 4,338 รายตาม snapshot และ window ที่เลือก เส้นประคือตำแหน่งก่อนหน้า กรอบทึบคือตำแหน่งปัจจุบัน

LOADING POPULATION…4,338 ลูกค้าในการคำนวณ
อ่านอย่างระวัง

Segment คือกฎธุรกิจที่แปลคะแนน RFM เป็นภาษาที่ทีมใช้ร่วมกัน ไม่ใช่ความจริงถาวร เมื่อ snapshot, window หรือขอบ quartile เปลี่ยน ลูกค้าก็อาจเคลื่อน segment ได้