How to Use RFM Customer Segmentation in Shopify: Step-by-Step Tutorial
Updated October 2026
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:
- Recency (R): How recently did the customer make a purchase?
- Frequency (F): How often does the customer purchase from your store?
- Monetary (M): How much money has the customer spent in the period you analyse?
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
- Shopify Admin Access: You need permission to export orders from your store admin.
- Order history: Enough orders that each customer has had a chance to buy again. How long that takes depends on what you sell, so pick a window that is a few typical repurchase cycles long. A longer window costs nothing but computing time; a window shorter than your repurchase cycle makes loyal customers look lapsed.
- Customers: Quintile scores split customers into five equal groups, so with only a few dozen customers each group holds very few people; with a small customer base use fewer bands or read the groups as rough orderings. This is a matter of stable groups, not of statistical significance, because no hypothesis test is involved.
- Python 3 with
pandasandnumpy.
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:
- Email: the customer identifier (customers who checked out as guests with different emails will count as different people)
- Created at: when the order was placed (Recency)
- Total: the order total (Monetary)
- Name: the order number (Frequency, counted as distinct orders)
- Financial Status: used to keep only paid orders
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
- Export paid orders from Shopify
- Aggregate orders to one row per customer within a date window
- Score recency, frequency and spend from 1 to 5
- Assign each customer to exactly one named segment using ordered rules
- 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.
- Champions: bought recently, often and for the most. Keep them close: early access, a thank-you, a referral ask.
- Loyal Customers: frequent buyers who bought within the last few months. Test bundles or a loyalty perk aimed at raising spend per order.
- Potential Loyalists: recent and decent spend but not frequent. A follow-up that encourages a second or third order.
- At Risk: bought more than once but not lately. These are candidates for a check-in, not proof that they are leaving; compare their gap with their own usual time between orders.
- Can't Lose Them: frequent, high-spend, not recent. Personal outreach first, as in Step 4.
- Hibernating: long gone and low frequency. Low-cost contact only, and consider removing unresponsive addresses from active lists.
- Needs Attention: everyone else. Review it, since it is where rules that need refining will show up.
How to Check That It Worked
- Every customer has exactly one segment: the segment counts in Step 3 add up to the number of customers in Step 2 (869 in the example).
- Score columns span 1 to 5 for each of R, F and M. If one is missing a value, the data has too few distinct customers for five groups.
- Outcomes: to learn whether a campaign helped, split a segment at random into contacted and not contacted, and compare repeat orders over the next period. Set your own targets from your store's past results.
- Re-run it on a schedule that you choose (for example monthly) and compare each customer's segment with last time to see who moved. That comparison, not a single set of scores, is what shows whether customers are drifting away.
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.
Troubleshooting Common Issues
Problem: One Segment Holds Most Customers
- Many one-time buyers are normal in e-commerce, and they cluster in Needs Attention, Hibernating or Potential Loyalists depending on how recently they bought.
- A recent promotion can pull many first orders into the top recency bands. Exclude the promotion period, or compare with and without it.
- Switch from quintiles to fixed thresholds, and consider a separate first-time-buyer segment.
Problem: Missing or Odd Customer Data
- Rows with an empty
Emailare dropped bygroupby; guest checkouts with different emails count as different customers. - Check that the export includes every date range you need, and that
Financial Statusvalues match what you filter on (for examplepaid). - Make sure the file is UTF-8 and that
Totalparses as a number.
Problem: The Champions Segment Is Too Large
- Tighten the rule (for example require R, F and M all equal to 5) or use fixed thresholds instead of quintiles.
- Check ties in frequency, which can move many customers into the same scores.
Problem: Segmented Campaigns Do Not Beat a Broadcast Email
- Make sure each segment gets genuinely different messaging and offers.
- Compare against a holdout group, not against last month's numbers.
- Review the segment rules; they may need refining for your business.
Problem: Importing the List into an Email Tool
- Export plain CSV (
cant_lose_them.csvin Step 4) and match the column headers your email tool expects. - Remove special characters if the import rejects rows.
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 →