Q&A library Python

How do I group a numeric column into ranges (bins) in pandas?

Python Intermediate binningcutqcut

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.

Need this solved on your own data?

The library is free and always will be. When the problem is bigger than an answer, we take on the work.

Start a project