Unit 6 • Lesson 5

Binning Values

Overview

Turning a measurement into a band you can count.

Intermediate 20–25 min

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.

1

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())
2

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 Yourself

Band these scores into the four grades and print how many are in each.

Press Run to see output

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.

1

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.

2

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()))
3

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 Yourself

This column has one large outlier. Band it both ways and compare how many land in each band.

Press Run to see output

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.

Press Run to try it, then Check my work to mark it.

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.

Press Run to try it, then Check my work to mark it.