Python Data Visualization, SQL Connectivity & EDA
Back to THE SYSTEMATIC EXPLORATORY DATA ANALYSIS (EDA) WORKFLOW

Feature Engineering: Discretization & Binning (pd.cut vs pd.qcut)

Master equal-width binning (pd.cut) and quantile-based frequency binning (pd.qcut) for customer scoring.

7 minutesReading
๐ŸŽฏ Free Guest Mode: You are learning for free. Sign in to save your completion progress and quiz answers.

From Numbers to Tiers: Turning Continuous Spend into Business Buckets

Your stakeholder never says "Show me customers who spent $437.20 vs $441.80" โ€” they say "How many VIPs do we have?" or "Who is in the top 20%?". To answer, you must turn a continuous number like total_amount into a handful of labeled tiers. Pandas gives you two knives for that โ€” cut and qcut โ€” and they slice the same data in completely different ways. This article shows both on the same 10 customers so the difference is obvious.

1. The Data: 10 Customers Sorted by Total Spend

Same customers as the RFM article โ€” one row per customer, sorted low โ†’ high. This ordering makes both binning methods visible:

customer_idtotal_amount ($)Same value, what you will do with it
C00718Small spenders
C00390
C009210Middle
C004680
C005890Big โ€” but gaps vary wildly (890โ†’2740 in 3 steps)
C0011,274
C0021,380
C0102,740
C0088,500Single huge outlier โ€” 3ร— the next
C00612,000Extreme outlier โ€” would dominate any fixed-width bin

The spread is 18 โ†’ 12,000 with one monster at 12K. Watch how each method handles it.

2. Method A โ€” pd.cut: Same Dollar Width Per Bucket (Business Rules)

cut chops the value range into equal-width intervals. You pick the dollar boundaries โ€” like pricing tiers on a website.

bins   = [0, 500, 1500, 5000, float('inf')]   # dollar cutoffs you choose
labels = ['Low', 'Medium', 'High', 'VIP']      # one label per interval

df['spend_tier'] = pd.cut(df['total_amount'], bins=bins, labels=labels)
print(df[['customer_id','total_amount','spend_tier']])
#   customer_id  total_amount spend_tier
# 0        C007            18        Low
# 1        C003            90        Low
# 2        C009           210        Low
# 3        C004           680     Medium
# 4        C005           890     Medium
# 5        C001          1274     Medium
# 6        C002          1380     Medium
# 7        C010          2740       High
# 8        C008          8500        VIP
# 9        C006         12000        VIP
# Counts: Low=3, Medium=4, High=1, VIP=2  โ†’ buckets have VERY different sizes!
BucketDollar rangeWho lands hereCount
Low0 โ€“ 50018, 90, 2103
Medium500 โ€“ 1,500680, 890, 1,274, 1,3804
High1,500 โ€“ 5,0002,7401
VIP5,000 โ†’ โˆž8,500, 12,0002

When to use cut: The buckets mean the same dollars for everyone โ€” stakeholders set them ("VIP starts at $5,000"). Good for pricing tiers, age bands, score thresholds. Weakness: skewed data โ†’ lopsided bucket sizes โ€” High has 1 customer while Low has 3, and the 12K outlier sits comfortably in VIP.

3. Method B โ€” pd.qcut: Same Headcount Per Bucket (Fair Ranking)

qcut ignores dollar widths and forces equal headcount per bucket โ€” each bucket gets exactly 20% of customers when q=5. The dollar boundaries move to make that happen.

# 5 quintiles โ†’ exactly 2 customers per bucket (10 customers / 5 = 2)
df['quintile'] = pd.qcut(df['total_amount'], q=5, labels=[1, 2, 3, 4, 5])
print(df[['customer_id','total_amount','quintile']])
#   customer_id  total_amount quintile
# 0        C007            18        1
# 1        C003            90        1
# 2        C009           210        2
# 3        C004           680        2
# 4        C005           890        3
# 5        C001          1274        3
# 6        C002          1380        4
# 7        C010          2740        4
# 8        C008          8500        5
# 9        C006         12000        5
# Counts: 1โ†’2, 2โ†’2, 3โ†’2, 4โ†’2, 5โ†’2  โ†’ perfectly balanced!
# Boundaries were ~[18-210], [210-890], [890-1380], [1380-8500], [8500-12000]
QuintileWho lands here (by rank, not dollars)CountDollar span
1 (bottom 20%)18, 902$72 wide
2210, 6802$470 wide
3890, 1,2742$384 wide
41,380, 2,7402$1,360 wide
5 (top 20%)8,500, 12,0002$3,500 wide

When to use qcut: You need fair, equal-sized tiers โ€” exactly what RFM scoring needs (top 20% โ†’ score 5, next 20% โ†’ 4, โ€ฆ). Good for ranking, leaderboards, percentile bands. Weakness: bucket dollar widths are uneven and shift when data changes โ€” don't use it when stakeholders expect fixed $500 / $1,500 cutoffs.

4. Side-by-Side: Same Data, Different Story

Questionpd.cut (equal-width)pd.qcut (equal-frequency)
What stays equal?Dollar width of each bucketHeadcount in each bucket
What varies?Headcount (1โ€“4 per bucket)Dollar width ($72 โ†’ $3,500)
Pick whenโ€ฆBusiness defines fixed thresholds (VIP = $5K+)You need fair 20% tiers (RFM quintile scores)
Outlier effectOutlier sits in the top bucket, rest of buckets still meaningfulOutlier compresses top bucket but counts stay fair

How this feeds the next article: RFM scoring uses qcut โ€” not cut โ€” because "top 20% of spenders get score 5" must be fair regardless of the dollar spread. Read the next article's ยง5 to see why cut would mis-rank your best customers.

Quiz cue (Q3 Q04โ€“05): "Equal-width โ†’ cut / Equal-frequency (20% each) โ†’ qcut." "5 equal-sized tiers" is qcut(..., q=5).

Loading progress...