Pandas qcut: Why Bin Edges Must Be Unique
Debug repeated quantile boundaries without silently relabeling two groups as four quartiles.
By Prakhar Shrivastava · October 14, 2026
The error means two or more requested quantile boundaries have the same value. Repeated observations can produce this outcome. Setting duplicates='drop' removes repeated edges, but can leave fewer groups than requested. Check the returned edges, actual category count, and membership before naming the output.
Reproduce the problem with customer spend
Imagine a fictional US and UK reporting team reviewing six customers from one eligible cohort for one completed month. Spend has already been converted to USD under the team’s reporting policy. Each customer appears once, all six amounts are known, and four customers genuinely spent zero. Zero is an observed amount here, not a replacement for missing data.
import pandas as pd
spend = pd.Series([0, 0, 0, 0, 100, 200], name='spend_usd')
pd.qcut(spend, q=4) # raises ValueError: bin edges must be uniqueThere is little information with which to separate the bottom of this distribution: four of six customers have exactly the same spend. The requested boundaries are 0, 0, 0, 75, and 200 with the tested version. Multiple zero boundaries cannot define distinct nonempty numeric intervals.
What does duplicates=’drop’ actually return?
bands, edges = pd.qcut(
spend, q=4, duplicates='drop', retbins=True
)
print(edges.tolist())
print(bands.cat.codes.tolist())
print(bands.value_counts(sort=False).tolist())Expected results: edges [0.0, 75.0, 200.0], category codes [0, 0, 0, 0, 1, 1], and counts [4, 2]. There are two categories, not four. The zero-spend customers remain together, and the other two share the upper interval. Neither category contains one quarter of the cohort.
The lower displayed interval may include a small adjustment below zero so that the minimum is included. Inspect the numeric edges returned by retbins=True instead of treating formatted interval labels as a business threshold specification.
Do not attach four labels before inspecting the bins
A hard-coded label list such as Q1 through Q4 assumes four resulting categories. That assumption fails after duplicate edges are dropped. Start without custom labels, check the category count, then decide whether the result is still useful. If a report requires four meaningful value segments, two categories are a failed requirement even though the function now runs.
For this fixture, a review could report two observed spend bands with their actual counts. Another policy could separate zero-spend customers from positive-spend customers before analyzing the positive amounts. That changes the population and must be explained; it does not recover four quartiles of the original six customers.
Use fixed boundaries when the policy is value-based
If the agreed fictional policy is below $100 versus at least $100, express that policy directly. It remains a value threshold rather than a quantile estimate.
fixed = pd.cut(
spend,
bins=[0, 100, float('inf')],
right=False,
labels=['under_100', '100_plus']
)
print(fixed.tolist())The output contains four under_100 labels and two 100_plus labels. The left-inclusive intervals place exactly $100 into the upper band. This example assumes finite, nonnegative amounts; negative adjustments and missing inputs need separate rules. The $100 cutoff is illustrative, not a recommendation about customer value.
Why ranking first changes the question
Applying qcut to unique row ranks can avoid repeated spend boundaries, but the input is then rank positions. Equal-spend customers may receive different segments depending on the tie-break rule. That may suit assignment batches, but it does not show a difference in spending. See the SQL NTILE example for a separate, reproducible demonstration of row-based grouping.
Checks to keep with the report
Five checks passed in pandas 2.2.3: the expected exception, exact returned edges, complete category-code sequence, group counts, and fixed-boundary labels. Re-run these checks in your deployed version. Record the requested and actual bin counts, excluded or missing values, eligibility period, and currency basis.
For separate US, UK, or European market reports, decide whether boundaries are estimated separately or shared. A locally highest band does not automatically represent the same USD threshold across markets. Similarly, re-estimating quantiles each month can change a customer’s segment without a change in that customer’s spend. Save the edges and population definition when comparing periods.
Primary references: pandas qcut and pandas cut. Continue with the pandas practice reference and invalid numeric input audit before assigning segments to unvalidated CSV values.