How do I group a numeric column into ranges (bins) in pandas?
Short answer
Use pd.cut with a list of bin edges to sort numbers into labelled ranges, or pd.qcut to split them into equal-sized groups. Watch which side of each edge is included, because the default is right-closed.
The short version
Use pd.cut to sort a numeric column into ranges you define with a list of edges, optionally giving each range a label. Use pd.qcut when you instead want groups of roughly equal size, such as quartiles. The main gotcha is edge behaviour: by default each bin includes its right edge but not its left, so decide deliberately which side a boundary value lands on.
Fixed ranges with cut
import pandas as pd
df = pd.DataFrame({"temp": [3, 12, 18, 27]})
df["band"] = pd.cut(
df["temp"],
bins=[0, 10, 20, 40],
labels=["cold", "mild", "warm"],
)
print(df)
temp band
0 3 cold
1 12 mild
2 18 mild
3 27 warm
You supply the edges, so bins=[0, 10, 20, 40] creates three ranges, and labels names them. Without labels, the result shows the interval itself, like (10, 20].
The edge gotcha
By default the intervals are right-closed, written (low, high], so the high edge belongs to the bin and the low edge does not.
pd.cut(pd.Series([10, 20]), bins=[0, 10, 20]).tolist()
# [(0, 10], (10, 20]] -> 10 falls in the first bin, not the second
If a value equals the lowest edge it falls outside all bins and becomes NaN, which surprises people. Pass include_lowest=True to pull the very lowest edge into the first bin, or right=False to make every interval left-closed instead. Choose consciously, because an off-by-one at a boundary quietly misclassifies rows.
Equal-sized groups with qcut
cut uses fixed value ranges, so the bins can hold very different counts. When you want each group to hold a similar number of rows, use qcut, which chooses the edges from the data's quantiles.
s = pd.Series([1, 2, 3, 4, 5, 6, 7, 8])
pd.qcut(s, q=4).value_counts().sort_index().tolist() # [2, 2, 2, 2]
Here four quartiles each contain two values. Use cut when the boundaries have real meaning (for example price tiers), and qcut when you want balanced group sizes regardless of where the cut points land.
When you need arbitrary rules instead
cut only splits on a single numeric axis. If your categories depend on conditions across several columns, or on non-numeric logic, cut is the wrong tool; use numpy.select with a list of conditions instead. Reach for cut specifically when you are slicing one continuous number into ordered ranges.