ds@mcpanalytics.aiEmail your data and your question and get an answer about your own data, with the method shown.Email your data and question. Get an answer.

How to Use RFM Customer Segmentation in Shopify: Step-by-Step Tutorial

Updated October 2026

Category: Shopify Analytics | Reading Time: 12 minutes

Have an order history? Run RFM segmentation on your CSV →

Introduction to RFM Customer Segmentation

Understanding your customers is the foundation of effective marketing, but with hundreds or thousands of customers in your Shopify store, where do you begin? RFM (Recency, Frequency, Monetary) segmentation is a proven marketing analytics technique that helps you categorize customers based on their purchasing behavior, enabling you to create highly targeted campaigns that drive revenue and improve customer retention.

To see which of your own customers are most valuable and which are slipping away, try the RFM segmentation tool.

RFM segmentation analyzes three critical dimensions of customer behavior:

By scoring customers across these three dimensions, you can identify your most valuable customers (Champions), customers at risk of churning, and those who need re-engagement campaigns. This tutorial walks through RFM segmentation on a Shopify order export with Python and pandas, from order rows to scored customers to a named list you can act on. The figures shown come from a synthetic data set generated for this tutorial (about 2,450 made-up paid orders from about 870 made-up customers), not from a real store, so your counts will differ.

Prerequisites and Data Requirements

Before beginning this tutorial, ensure you have the following:

Required Access and Tools

Data Fields Required

RFM needs order-level rows, one per order, so that you can choose the date window yourself before anything is summed. Shopify's order export includes the columns used here:

Do not use a per-customer summary with lifetime totals for this. A column like "total spent" and a "last order date" cannot tell you how much a customer spent inside a given window, so you could not change the window later. If a summary is all you have, you can still score it, but then recency, frequency and spend all describe the customer's whole life and you cannot restrict them to a period.

What You'll Accomplish

  1. Export paid orders from Shopify
  2. Aggregate orders to one row per customer within a date window
  3. Score recency, frequency and spend from 1 to 5
  4. Assign each customer to exactly one named segment using ordered rules
  5. Turn a segment into a decision: a ranked list of customers to contact

Step-by-Step Implementation Guide

Step 1: Export Orders from Shopify

In your Shopify admin, open Orders, click Export, choose the orders to export (for example by date), pick a CSV format and export. Larger exports are emailed to you. Open the file and check that these columns are present:

Name,Email,Created at,Total,Financial Status
#1342,[email protected],2025-01-30 14:02:11 -0500,58.20,paid
#1341,[email protected],2025-01-30 09:15:40 -0500,112.75,paid

These two rows are made-up examples of the layout. Save the file as shopify_orders_export.csv.

Step 2: Aggregate Orders to Customers

The script keeps paid orders, applies a 12-month window before summing, and builds one row per customer. Recency is days from the order to a reference date that you set; frequency is the number of distinct orders; monetary is the sum of order totals.

import numpy as np
import pandas as pd

# One row per ORDER, as exported from Shopify (Orders > Export)
orders = pd.read_csv('shopify_orders_export.csv')
orders['Created at'] = pd.to_datetime(orders['Created at'], utc=True)

# Keep orders that were actually paid; refunded and voided orders are not purchases
orders = orders[orders['Financial Status'] == 'paid']

# The reference date for recency: the day after your export. Set it yourself.
as_of = pd.Timestamp('2025-02-01', tz='UTC')

# Optional historical window: filter the ORDERS before aggregating (here, 12 months)
orders = orders[orders['Created at'] >= as_of - pd.DateOffset(months=12)]

# Aggregate orders to one row per customer
rfm = orders.groupby('Email').agg(
    last_order=('Created at', 'max'),
    frequency=('Name', 'nunique'),
    monetary=('Total', 'sum'),
)
rfm['recency_days'] = (as_of - rfm['last_order']).dt.days

print(f"Customers: {len(rfm)}")
print(f"Orders used: {len(orders)}")
print(rfm[['recency_days', 'frequency', 'monetary']].describe().round(1).loc[['min', '50%', 'max']])

Expected Output

Customers: 869
Orders used: 2451
     recency_days  frequency  monetary
min           0.0        1.0      11.1
50%         148.0        2.0      85.6
max         365.0       14.0    4512.5

These figures describe the synthetic data set. On your export the counts and ranges will be yours; if "Customers" is far below the number of buyers you expect, check the Financial Status filter and the window.

Step 3: Score and Assign Segments

Each dimension is cut into five equal-sized groups, 5 being best. For recency a smaller number of days scores higher; for frequency and spend a larger value scores higher, so the percentile bands run in opposite directions: for R, the 5 band is the 20% of customers with the fewest days since their last order, then 4 is the next 20%, down to 1 for the longest gap. For F and M, 5 is the top 20% by orders or spend and 1 is the bottom 20%.

Segment names (Champions, Loyal Customers and so on) are common labels in RFM write-ups, but there is no single standard definition of them, so this tutorial defines its own. The rules below are checked in order and the first match wins, which makes the segments mutually exclusive. A customer scored 555 is a Champion, not also a Loyal Customer, and one scored 222 is Hibernating only if no earlier rule claimed them.

# 5 = best on every dimension. Recency: fewer days since the last order is better.
# rank(method='first') breaks ties by row order, so customers with identical
# values (for example every one-order customer) are split across scores arbitrarily.
rfm['R'] = pd.qcut(rfm['recency_days'].rank(method='first'), 5, labels=[5, 4, 3, 2, 1]).astype(int)
rfm['F'] = pd.qcut(rfm['frequency'].rank(method='first'), 5, labels=[1, 2, 3, 4, 5]).astype(int)
rfm['M'] = pd.qcut(rfm['monetary'].rank(method='first'), 5, labels=[1, 2, 3, 4, 5]).astype(int)
rfm['RFM'] = rfm['R'].astype(str) + rfm['F'].astype(str) + rfm['M'].astype(str)

# Segments are assigned in ORDER: the first rule a customer matches wins,
# so every customer lands in exactly one segment.
rules = [
    ('Champions',          (rfm.R >= 4) & (rfm.F >= 4) & (rfm.M >= 4)),
    ("Can't Lose Them",    (rfm.R <= 2) & (rfm.F >= 4) & (rfm.M >= 4)),
    ('Loyal Customers',    (rfm.F >= 4) & (rfm.R >= 3)),
    ('Potential Loyalists', (rfm.R >= 4) & (rfm.M >= 3)),
    ('At Risk',            (rfm.R <= 2) & (rfm.F >= 3)),
    ('Hibernating',        (rfm.R <= 2)),
]
rfm['segment'] = np.select([cond for _, cond in rules], [name for name, _ in rules],
                           default='Needs Attention')

summary = rfm.groupby('segment').agg(
    customers=('segment', 'size'),
    median_days_since_order=('recency_days', 'median'),
    median_orders=('frequency', 'median'),
    median_spend=('monetary', 'median'),
).sort_values('customers', ascending=False)
summary['share'] = (summary['customers'] / len(rfm) * 100).round(1)
print(summary.round(1).to_string())
print(f"\nTotal: {summary['customers'].sum()} customers in {len(summary)} segments")

Expected Output

                     customers  median_days_since_order  median_orders  median_spend  share
segment                                                                                    
Needs Attention            229                    113.0            1.0          44.9   26.4
Champions                  158                     21.0            4.0         341.5   18.2
Hibernating                148                    273.0            1.0          45.2   17.0
Can't Lose Them            104                    225.0            5.0         247.6   12.0
At Risk                     96                    263.0            2.0          73.1   11.0
Loyal Customers             71                    107.0            3.0         105.6    8.2
Potential Loyalists         63                     48.0            1.0          91.3    7.2

Total: 869 customers in 7 segments

Read the table row by row. Needs Attention is the catch-all for customers none of the six rules claimed (for example recent one-time buyers with low spend). The segment sizes are a result of these rules on this synthetic data; there is no right distribution to aim for. If one segment holds far more customers than you expected, look at the rule order and at ties first: rank(method='first') splits tied customers arbitrarily, so tie-heavy columns such as frequency (many customers with one order) get scores that depend on row order. If that matters for your store, replace the quintile cut with fixed thresholds that you choose, such as 1 order, 2 orders, 3 to 4 orders, 5 or more.

Step 4: Turn a Segment into a Decision

A segment is only useful if it changes what you do. Here, the Can't Lose Them customers (bought often and spent heavily, but not recently) are ranked by what they have spent, so the people with the most at stake are contacted first.

# The decision: who gets a personal win-back message first?
cant_lose = (rfm[rfm['segment'] == "Can't Lose Them"]
             .sort_values('monetary', ascending=False)
             [['recency_days', 'frequency', 'monetary', 'RFM']])
print(cant_lose.head(5).round(2).to_string())
cant_lose.to_csv('cant_lose_them.csv')           # import this list into your email tool
rfm.to_csv('rfm_segments.csv')

Expected Output

                          recency_days  frequency  monetary  RFM
Email                                                           
[email protected]           222          8   1011.52  255
[email protected]           221          8    712.96  255
[email protected]           248          8    684.41  255
[email protected]           213          7    636.08  255
[email protected]           205          7    621.83  255

In this synthetic example the decision is to send the top of this list a personal message first. The scores do not tell you why these customers stopped buying, so use the first message to ask, and treat the segment as a list of customers worth investigating rather than customers who have churned. Whether any of them comes back, and whether a discount was needed, is something to measure on your own store.

Interpreting Your Results

These are starting ideas for each segment, not benchmarks. None of them is tested here, and you should measure each against a holdout group of similar customers who receive nothing.

How to Check That It Worked

Run It on Your Own Shopify Export

Export your orders from Shopify as a CSV and upload it at MCP Analytics. The analysis runs an RFM segmentation (recency, frequency, monetary) on your file and shows the result with the method used. You choose the columns; nothing connects to your Shopify account.

Try it on your data

Next Steps with Shopify Analytics

Once you've mastered RFM segmentation, consider these advanced analytics techniques to further optimize your Shopify store:

Advanced Customer Analytics

Marketing Optimization

Integration Opportunities

Continuous Improvement

Data analysis is an iterative process. Consider exploring AI-first data analysis pipelines to automate your segmentation updates and continuously refine your customer understanding as your business grows.

Troubleshooting Common Issues

Problem: One Segment Holds Most Customers

Problem: Missing or Odd Customer Data

Problem: The Champions Segment Is Too Large

Problem: Segmented Campaigns Do Not Beat a Broadcast Email

Problem: Importing the List into an Email Tool

Your turn

Bring your own data and the question you actually need answered.

MCP AnalyticsData science by email Send us your data and your question, and we will reply. ds@mcpanalytics.ai

Explore more: Shopify Analytics: all tools, tutorials, and guides →