Topic 60 of

RFM Analysis: Segment Customers by Behavior

Zomato doesn't treat all users equally. Champions (ordered 3× last week) get free delivery. At-risk users (haven't ordered in 60 days) get 50% off. RFM analysis powers this personalization.

📚Intermediate
⏱️11 min
10 quizzes
🎯

What is RFM Analysis?

RFM analysis segments customers based on three behavioral dimensions: Recency (how recently), Frequency (how often), and Monetary (how much they spend).

The Three Dimensions

1. Recency (R)How recently did customer make a purchase?

Customer A: Last order 2 days ago (HIGH recency) Customer B: Last order 90 days ago (LOW recency) Why it matters: Recent customers are more engaged (easier to retain, more responsive to marketing)

2. Frequency (F)How often does customer purchase?

Customer A: 15 orders in last 6 months (HIGH frequency) Customer B: 2 orders in last 6 months (LOW frequency) Why it matters: Frequent customers are loyal (higher LTV, lower churn risk)

3. Monetary (M)How much does customer spend?

Customer A: ₹15,000 total spend (HIGH monetary) Customer B: ₹1,200 total spend (LOW monetary) Why it matters: High-value customers drive revenue (should receive VIP treatment)

RFM Scoring

Each customer gets a score (1-5) for R, F, and M:

  • 5 = Best (top 20%)
  • 4 = Good (next 20%)
  • 3 = Average (middle 20%)
  • 2 = Below average (next 20%)
  • 1 = Worst (bottom 20%)

Example:

Customer A: RFM = 5-5-5 (ordered yesterday, 15 orders, ₹15K spend) → "Champion" Customer B: RFM = 1-1-2 (ordered 90 days ago, 2 orders, ₹1.2K) → "Lost Customer" Customer C: RFM = 5-2-3 (ordered yesterday, 2 orders, ₹3K) → "New Customer"

Combined RFM Score: Concatenate or sum scores for segmentation.

  • Champions: RFM = 5-5-5, 5-4-5, 5-5-4 (high on all dimensions)
  • At Risk: RFM = 2-3-3, 2-2-4 (low recency, but previously valuable)
  • Lost: RFM = 1-1-1, 1-1-2 (haven't ordered in months, low value)

Why RFM Analysis Matters

Without RFM Segmentation (Treat everyone equally):

Send 50% discount to ALL customers (1M users) Cost: 1M × ₹500 avg discount = ₹50 Cr budget Problem: Wasted discounts on: - Champions (would've bought without discount) - Lost customers (discount too small to win back)

With RFM Segmentation (Targeted campaigns):

Champions (100K, RFM 5-5-5): No discount (loyal, buy anyway) — Cost: ₹0 Loyal (200K, RFM 4-5-4): 10% discount (retain loyalty) — Cost: ₹1 Cr At Risk (150K, RFM 2-3-3): 30% discount (re-engage) — Cost: ₹2.25 Cr Lost (50K, RFM 1-1-1): 50% discount (win-back) — Cost: ₹1.25 Cr New (100K, RFM 5-1-2): Free delivery (encourage 2nd order) — Cost: ₹40L Total cost: ₹5 Cr (vs ₹50 Cr) — 10× more efficient Better targeting → Higher ROI (lost customers respond to 50%, champions don't need discounts)

Real Example: Zomato RFM Segmentation

Goal: Personalize offers based on customer behavior.

RFM Calculation (6-month window):

query.sqlSQL
WITH customer_rfm AS (
  SELECT
    customer_id,
    DATEDIFF('day', MAX(order_date), CURRENT_DATE) AS recency_days,
    COUNT(order_id) AS frequency,
    SUM(order_value) AS monetary
  FROM orders
  WHERE order_date >= CURRENT_DATE - INTERVAL '6 months'
  GROUP BY customer_id
)
SELECT
  customer_id,
  recency_days,
  frequency,
  monetary,
  NTILE(5) OVER (ORDER BY recency_days ASC) AS R_score,  -- Lower days = higher score
  NTILE(5) OVER (ORDER BY frequency DESC) AS F_score,
  NTILE(5) OVER (ORDER BY monetary DESC) AS M_score
FROM customer_rfm;

Segments & Actions:

Champions (R=5, F=5, M=5): 50K users - Strategy: VIP treatment (exclusive menu access, priority delivery) - Campaign: "Thanks for being a Champion! 🏆 Free dessert on next order" Loyal (R=4-5, F=4-5, M=4-5): 150K users - Strategy: Retention (small perks, loyalty program) - Campaign: "You've ordered 20× this month! Unlock Gold status (free delivery)" At Risk (R=1-2, F=3-5, M=3-5): 80K users - Strategy: Re-engagement (personalized offers) - Campaign: "We miss you! 40% off your favorite restaurant (expires in 48hrs)" Lost (R=1, F=1-2, M=1-2): 120K users - Strategy: Win-back (aggressive discounts) - Campaign: "Come back! 60% off + free delivery on first order back" New Customers (R=5, F=1, M=2-3): 200K users - Strategy: Onboarding (encourage repeat) - Campaign: "Order again within 7 days → Get ₹150 off + free delivery"

Impact:

  • 25% higher campaign response rate (vs generic campaigns)
  • 15% increase in repeat orders (targeted new customer offers)
  • ₹10 Cr saved annually (focused discounts on at-risk/lost, not champions)
Think of it this way...

RFM is like gym membership behavior. Champions = attend 5×/week, last visit yesterday (engaged). Loyal = attend 3×/week, last visit 3 days ago (consistent). At Risk = used to attend 4×/week, haven't been in 2 weeks (need nudge). Lost = paid for year, never attended (wasted membership). Gym should send retention offers to "At Risk" (free PT session), not "Champions" (already engaged).

💾

RFM Analysis with SQL

Step 1: Calculate RFM Metrics

Schema:

query.sqlSQL
orders (order_id, customer_id, order_date, order_value)

Query:

query.sqlSQL
WITH customer_metrics AS (
  SELECT
    customer_id,
    MAX(order_date) AS last_order_date,
    COUNT(DISTINCT order_id) AS frequency,
    SUM(order_value) AS monetary_value
  FROM orders
  WHERE order_date >= CURRENT_DATE - INTERVAL '12 months'  -- 12-month window
  GROUP BY customer_id
),
rfm_calc AS (
  SELECT
    customer_id,
    DATEDIFF('day', last_order_date, CURRENT_DATE) AS recency_days,
    frequency,
    monetary_value
  FROM customer_metrics
)
SELECT * FROM rfm_calc;

Output:

customer_id | recency_days | frequency | monetary_value C001 | 2 | 18 | 15400 C002 | 87 | 3 | 2200 C003 | 5 | 25 | 28900

Step 2: Assign RFM Scores (Quintile-Based)

Quintile Scoring (5 = top 20%, 1 = bottom 20%):

query.sqlSQL
WITH customer_metrics AS (
  SELECT
    customer_id,
    DATEDIFF('day', MAX(order_date), CURRENT_DATE) AS recency_days,
    COUNT(DISTINCT order_id) AS frequency,
    SUM(order_value) AS monetary_value
  FROM orders
  WHERE order_date >= CURRENT_DATE - INTERVAL '12 months'
  GROUP BY customer_id
),
rfm_scores AS (
  SELECT
    customer_id,
    recency_days,
    frequency,
    monetary_value,
    NTILE(5) OVER (ORDER BY recency_days ASC) AS R_score,     -- Lower recency = better
    NTILE(5) OVER (ORDER BY frequency DESC) AS F_score,       -- Higher frequency = better
    NTILE(5) OVER (ORDER BY monetary_value DESC) AS M_score   -- Higher spend = better
  FROM customer_metrics
)
SELECT
  customer_id,
  recency_days,
  frequency,
  monetary_value,
  R_score,
  F_score,
  M_score,
  CONCAT(R_score, '-', F_score, '-', M_score) AS RFM_segment
FROM rfm_scores;

Output:

customer_id | recency | frequency | monetary | R | F | M | RFM_segment C001 | 2 | 18 | 15400 | 5 | 5 | 5 | 5-5-5 (Champion) C002 | 87 | 3 | 2200 | 1 | 2 | 2 | 1-2-2 (Lost) C003 | 5 | 25 | 28900 | 5 | 5 | 5 | 5-5-5 (Champion) C004 | 45 | 12 | 8500 | 3 | 4 | 4 | 3-4-4 (Loyal)

Step 3: Create Named Segments

Map RFM scores to business segments:

query.sqlSQL
WITH rfm_scores AS (
  -- (Previous RFM scoring query)
  ...
),
rfm_segments AS (
  SELECT
    customer_id,
    R_score,
    F_score,
    M_score,
    CASE
      -- Champions (best on all dimensions)
      WHEN R_score >= 4 AND F_score >= 4 AND M_score >= 4 THEN 'Champions'

      -- Loyal (high frequency + monetary, any recency)
      WHEN F_score >= 4 AND M_score >= 4 THEN 'Loyal Customers'

      -- Potential Loyalists (recent + high spend, but low frequency)
      WHEN R_score >= 4 AND M_score >= 3 AND F_score <= 2 THEN 'Potential Loyalists'

      -- New Customers (very recent, low frequency)
      WHEN R_score >= 4 AND F_score <= 2 THEN 'New Customers'

      -- At Risk (low recency, but previously valuable)
      WHEN R_score <= 2 AND F_score >= 3 AND M_score >= 3 THEN 'At Risk'

      -- Hibernating (low recency + low frequency, but spent money)
      WHEN R_score <= 2 AND F_score <= 2 AND M_score >= 3 THEN 'Hibernating'

      -- Lost (low on all dimensions)
      WHEN R_score <= 2 AND F_score <= 2 AND M_score <= 2 THEN 'Lost'

      -- About to Sleep (recent, but low frequency + low spend)
      WHEN R_score >= 3 AND F_score <= 2 AND M_score <= 2 THEN 'About to Sleep'

      -- Need Attention (below average, not lost yet)
      ELSE 'Need Attention'
    END AS segment
  FROM rfm_scores
)
SELECT
  segment,
  COUNT(*) AS customer_count,
  ROUND(AVG(R_score), 1) AS avg_R,
  ROUND(AVG(F_score), 1) AS avg_F,
  ROUND(AVG(M_score), 1) AS avg_M
FROM rfm_segments
GROUP BY segment
ORDER BY customer_count DESC;

Output:

segment | customer_count | avg_R | avg_F | avg_M Champions | 45000 | 4.8 | 4.7 | 4.9 Loyal Customers | 120000 | 3.2 | 4.5 | 4.6 New Customers | 180000 | 4.9 | 1.2 | 2.8 At Risk | 75000 | 1.8 | 3.9 | 4.1 Lost | 95000 | 1.2 | 1.5 | 1.8 ...

⚠️ CheckpointQuiz error: Missing or invalid options array

🎯

RFM Segmentation Strategies

Segment-Specific Campaigns

| Segment | Definition | Size | Strategy | Campaign Example | |---------|------------|------|----------|------------------| | Champions | R≥4, F≥4, M≥4 | 5-10% | Reward loyalty, VIP treatment | "Exclusive: Early access to sale + free shipping" | | Loyal | F≥4, M≥4 | 15-20% | Retain with perks | "You're Gold tier! Unlock 10% cashback" | | Potential Loyalists | R≥4, M≥3, F≤2 | 10-15% | Increase frequency | "Subscribe & Save 15% on every order" | | New Customers | R≥4, F=1 | 20-25% | Encourage 2nd order | "First-time discount used! 20% off order #2" | | Promising | R≥3, F=2-3, M=2-3 | 10-15% | Upsell, cross-sell | "Complete the look — 15% off accessories" | | Need Attention | R=3, F=2-3, M=2-3 | 10-15% | Re-engage with offers | "We noticed you haven't visited — 25% off" | | At Risk | R≤2, F≥3, M≥3 | 5-10% | Win-back (personalized) | "Your favorites miss you! 40% off [top category]" | | Hibernating | R≤2, F≤2, M≥3 | 5-10% | Aggressive win-back | "Exclusive comeback offer — 50% off + free delivery" | | Lost | R≤2, F≤2, M≤2 | 10-15% | Low-effort win-back | "We want you back — 60% off sitewide (3 days only)" |


Example: Flipkart's RFM Campaign Strategy

Champions (5-5-5, 5-4-5): 500K users

Strategy: Maximize LTV (upsell, cross-sell) Campaign: - "Flipkart Plus Super" tier (free delivery forever) - Early access to Big Billion Days sale - Exclusive product drops (OnePlus phone pre-order) Cost per user: ₹200 (perks) Expected ROI: 5× (Champions spend ₹50K/year, perks cost ₹200 → net ₹49.8K)

Loyal (4-5-4, 4-4-4): 1.2M users

Strategy: Retain loyalty (prevent churn to Amazon) Campaign: - 10% cashback on electronics (top category for this segment) - "You've saved ₹5,000 with us this year!" (reinforce value) Cost per user: ₹150 Expected ROI: 3× (Loyal spend ₹18K/year, cost ₹150 → net ₹17.85K)

New Customers (5-1-2): 2M users

Strategy: Encourage 2nd order (convert to Loyal) Campaign: - "Order again in 7 days → ₹200 off + free delivery" - Personalized product recommendations (based on 1st order) Cost per user: ₹100 Expected ROI: 2× (40% convert to 2nd order → ₹2.5K avg 2nd order → net LTV ₹3K)

At Risk (2-3-4, 1-4-4): 600K users

Strategy: Re-engage before lost (time-sensitive offers) Campaign: - "Flash sale just for you — 40% off [favorite category]" - "Your cart misses you! Complete checkout → Extra 10% off" Cost per user: ₹300 (higher discount to win back) Expected ROI: 1.5× (30% respond → ₹4K order → net LTV ₹2.5K over 6 months)

Lost (1-1-1, 1-1-2): 1.5M users

Strategy: Minimal investment (low response rate expected) Campaign: - "One last offer — 60% off + ₹500 cashback (expires 48hrs)" - Generic email (no personalization, low cost) Cost per user: ₹50 (email only, no premium channels) Expected ROI: 0.8× (10% respond → ₹2K order → break-even to slight loss, but some revive)

Total Campaign Budget:

Champions: 500K × ₹200 = ₹10 Cr Loyal: 1.2M × ₹150 = ₹18 Cr New: 2M × ₹100 = ₹20 Cr At Risk: 600K × ₹300 = ₹18 Cr Lost: 1.5M × ₹50 = ₹7.5 Cr Total: ₹73.5 Cr Expected Return: ₹220 Cr (3× ROI) — targeted campaigns vs generic ₹100 Cr spend (1.5× ROI)
🚀

Advanced RFM Techniques

1. Weighted RFM Scoring

Problem: Equal weights (R, F, M) might not reflect business priorities.

Solution: Assign custom weights based on business goals.

Example — PhonePe (Fintech):

Recency: 50% weight (active users = engaged) Frequency: 30% weight (transaction frequency matters) Monetary: 20% weight (transaction value less important for digital payments) Weighted RFM = (0.5 × R) + (0.3 × F) + (0.2 × M) Customer A: R=5, F=4, M=3 → (0.5×5) + (0.3×4) + (0.2×3) = 2.5 + 1.2 + 0.6 = 4.3 Customer B: R=3, F=5, M=5 → (0.5×3) + (0.3×5) + (0.2×5) = 1.5 + 1.5 + 1.0 = 4.0 Customer A ranked higher (recency weighted more)

Use Case: Adjust weights per industry — SaaS prioritizes recency (churn risk), e-commerce prioritizes monetary (revenue focus).


2. RFM + Demographic Segmentation

Combine RFM with demographics (age, location, device) for hyper-targeting.

Example — Myntra: Champions in Tier 2 Cities:

query.sqlSQL
WITH rfm_scores AS (
  -- (RFM calculation)
  ...
),
segmented AS (
  SELECT
    r.customer_id,
    r.segment,
    u.city_tier,
    u.age_group,
    r.R_score,
    r.F_score,
    r.M_score
  FROM rfm_scores r
  JOIN users u ON r.customer_id = u.user_id
  WHERE r.segment = 'Champions'
)
SELECT
  city_tier,
  COUNT(*) AS champions_count,
  AVG(monetary_value) AS avg_spend
FROM segmented
GROUP BY city_tier;

Output:

city_tier | champions_count | avg_spend Tier 1 | 35000 | ₹18,500 Tier 2 | 12000 | ₹14,200 Tier 3 | 3000 | ₹9,800 Insight: Tier 1 champions spend 30% more — focus high-value campaigns on Tier 1

Campaign: "Tier 1 Champions — Exclusive luxury brand drop (Gucci, Prada) — free styling session"


3. RFM Migration Analysis

Track how customers move between segments (month-over-month).

Query:

query.sqlSQL
WITH rfm_jan AS (
  SELECT customer_id, segment AS segment_jan
  FROM rfm_analysis
  WHERE analysis_date = '2026-01-31'
),
rfm_feb AS (
  SELECT customer_id, segment AS segment_feb
  FROM rfm_analysis
  WHERE analysis_date = '2026-02-28'
)
SELECT
  r_jan.segment_jan AS from_segment,
  r_feb.segment_feb AS to_segment,
  COUNT(*) AS customer_count
FROM rfm_jan r_jan
JOIN rfm_feb r_feb ON r_jan.customer_id = r_feb.customer_id
WHERE r_jan.segment_jan != r_feb.segment_feb  -- Only migrations
GROUP BY from_segment, to_segment
ORDER BY customer_count DESC;

Output:

from_segment | to_segment | customer_count Loyal | At Risk | 12000 (WARNING: Loyal customers churning) Promising | Loyal | 8500 (GOOD: Moving up) At Risk | Lost | 6200 (BAD: Losing at-risk customers) New Customers | Promising | 15000 (GOOD: New → Engaged) Actions: - Investigate Loyal → At Risk (12K migrations) — Why are loyal customers disengaging? - Accelerate New → Promising (15K migrations) — Onboarding working, double down - Prevent At Risk → Lost (6.2K migrations) — More aggressive win-back for At Risk

4. RFM Prediction (Next-Best Action)

Use RFM to predict next purchase and trigger campaigns.

Example — Swiggy: Predict Churn:

Customer RFM trend: Month 1: 5-5-5 (Champion) Month 2: 4-5-5 (Loyal) Month 3: 3-4-4 (Need Attention) Month 4: 2-3-3 (At Risk) Pattern: R declining (5 → 2 over 4 months) = churn risk Automated Trigger (Month 4): - Recency drops to 2 → Send "We miss you! 40% off" within 24 hours - Frequency drops from 5 → 3 → Offer "Order 2× this week → Unlock Gold status" Result: 35% of triggered customers re-engage (vs 15% without trigger)

⚠️ FinalQuiz error: Missing or invalid questions array

⚠️ SummarySection error: Missing or invalid items array

Received: {"hasItems":false,"isArray":false}