Binning Values
What You Will Learn in This Lesson
By the end of this lesson, you will be able to:
- Divide a numeric range into labelled bands with
cut. - Say which side of a boundary a value falls on.
- Choose between equal-width and equal-frequency bands.
- Count how many landed in each band.
Why This Matters
You cannot count a continuous measurement — every value is its own group. Binning is what turns "age" into "age group" and "score" into "grade", and the edges you choose are an argument you are making about the data, which is why cut makes you write them down.
Step 1: cut divides a range into labelled bands
Grades, age groups and price brackets are all the same operation: choose the edges, name the bands.
Edges and labels
There is always one more edge than there are labels, because each band sits between two edges.
import pandas as pd
scores = pd.Series([95, 85, 75, 60])
bands = pd.cut(scores, bins=[-1, 69, 79, 89, 100],
labels=["F", "C", "B", "A"])
print(bands.tolist())
Counting the bands
This is what the binning was for. value_counts on the banded column gives the distribution.
import pandas as pd
scores = pd.Series([95, 85, 75, 60, 92])
bands = pd.cut(scores, bins=[-1, 69, 79, 89, 100],
labels=["F", "C", "B", "A"])
print(bands.value_counts().sort_index())
Mini Practice #1: Band and count
Try It YourselfBand these scores into the four grades and print how many are in each.
Step 2: The edges are a decision
Whether 89 is a B or an A is your call, and cut makes you write it down rather than leaving it implied.
Which side a boundary falls on
By default each band includes its right-hand edge and excludes its left. So with an edge at 89, a score of 89 is in the band ending at 89 — and 90 starts the next one.
import pandas as pd
edges = [-1, 69, 79, 89, 100]
labels = ["F", "C", "B", "A"]
print(pd.cut(pd.Series([89, 90]), bins=edges, labels=labels).tolist())
The lowest edge is -1 rather than 0 so that a score of exactly 0 has a band. An edge equal to the lowest possible value excludes it, and the result is NaN.
Anything outside the edges is NaN
Not an error — a value with no band. Worth counting, because it usually means the edges did not cover the real range.
import pandas as pd
banded = pd.cut(pd.Series([50, 150]), bins=[0, 100], labels=["in range"])
print(banded.tolist())
print("unbanded:", int(banded.isna().sum()))
Equal width, or equal counts
cut with a number makes bands of equal width. qcut makes bands holding roughly equal numbers of rows. They answer different questions and can look very different on skewed data.
import pandas as pd
values = pd.Series([1, 2, 3, 4, 100])
print(pd.cut(values, bins=2).value_counts().tolist())
print(pd.qcut(values, q=2).value_counts().tolist())
Mini Practice #2: Equal width against equal counts
Try It YourselfThis column has one large outlier. Band it both ways and compare how many land in each band.
Step 3: When to Use It
Grade bands and age groups
cut turns a measurement into labelled ranges you can count and group by.
pd.cut(df["score"], bins=[0, 60, 80, 100], labels=["low", "mid", "high"])
Equal-sized groups
qcut splits into quantiles when you want the same number of rows in each band.
When Not to Use It
Do not forget which edge a bin includes. Values exactly on a boundary, or outside the outer edges, land in a different band or become NaN.
End-of-Lesson Exercises
Exercise 1: Letter grades
Write grade(scores) that returns a list of letter grades: 90 and above is an A, 80 to 89 a B, 70 to 79 a C, and anything below 70 an F.
cut with edges at -1, 69, 79, 89 and 100 puts each boundary score in the band above it, because the right-hand edge belongs to its band.
Exercise 2: How many in each band
Write band_counts(scores) that bands the scores into the same four grades and returns a dictionary from grade to how many scored it. A grade nobody reached still appears, with 0.
cut gives a categorical column, and value_counts on it keeps every category including the empty ones. Sort by the category order and build the dict with int() values.