How do I get bin numbers instead of interval labels from cut in pandas?
Short answer
Pass labels=False to cut and you get integer bin numbers rather than interval objects. This is handy for modelling, but keep the boundaries somewhere, because a bare number means nothing to a reader.
The short version
Pass labels=False to cut and you get zero-based integer bin numbers instead of interval objects like (0, 10]. This is convenient for feeding into models or joins, but the numbers alone are meaningless to a human, so keep the boundaries somewhere you can look them up.
The three label options
import pandas as pd
s = pd.Series([1, 5, 9])
pd.cut(s, bins=3) # interval objects
pd.cut(s, bins=3, labels=False).tolist() # [0, 1, 2]
pd.cut(s, bins=3, labels=["low", "mid", "high"]) # your own names
The default gives intervals, which are self-documenting but awkward to sort, join or store. labels=False gives plain integers. Named labels give the most readable output for a report.
When numbers are useful
Integer bins are compact, join cleanly, and can be fed to a model. They also sort correctly, whereas interval objects and text labels sort in ways that may surprise you (alphabetically, in the case of plain strings).
When numbers mislead
A bin number is a label, not a quantity. Bin 2 is not twice bin 1, and the gap between bins 1 and 2 is not necessarily the same as between 2 and 3, unless the bins are equal width. So do not treat the bin number as a measurement in an arithmetic sense, any more than you would treat a category code as one.
For a model, the honest choice depends on whether the bins are ordered. If they are (low, medium, high), an integer encoding may be defensible. If not, one-hot encode them.
Keeping the boundaries
cut can return the edges it used, which is what you need to explain the bins later.
binned, edges = pd.cut(s, bins=3, labels=False, retbins=True)
print(edges)
Save those edges. Without them, a column of 0s, 1s and 2s is undocumented and nobody, including you in three months, will know what it means.
Reusing the same bins on new data
This is the more important reason to keep the edges. If you bin a training set and later bin new data, bins=3 will compute different boundaries from the new data's range, so the same number means different things in the two datasets. Pass the saved edges explicitly.
pd.cut(new_data, bins=edges, labels=False)
This silent inconsistency is a genuine source of wrong results when a model trained on one binning meets data binned another way.
Ordered categories instead
If you want both readable labels and correct ordering, use named labels and mark the result as an ordered category, which gives you sensible sorting and comparison without resorting to bare integers.
pd.cut(s, bins=3, labels=["low", "mid", "high"], ordered=True)