Histograms & Binning

Reviewed & published by Brayan K

A histogram groups numbers into ranges called bins and counts how many values fall into each one, and np.histogram computes those counts and bin edges for you in a single call.

Part of the free NumPy course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.

You'll set the number of bins and the range, normalize counts into a density, label each value's bin with np.digitize, and tally integer labels fast with np.bincount.

What You'll Learn in This Lesson

1 Counts and Bin Edges

np.histogram(data, bins, range) returns two arrays: the counts per bin and the bin_edges. There is always one more edge than count, because n bins need n+1 boundaries. Always set range when you want predictable bins, otherwise NumPy uses the data's min and max.

import numpy as np

data = np.array([1, 2, 2, 3, 3, 3, 4, 4, 5])

counts, edges = np.histogram(data, bins=4, range=(1, 5))
print(counts)   # [1 2 3 3]
print(edges)    # [1. 2. 3. 4. 5.]

# ✅ Expected output:
# [1 2 3 3]
# [1. 2. 3. 4. 5.]

The last bin is closed on both ends, so the value 5 is included in the final bin. Here is the same idea on seeded random data so the numbers are reproducible.

import numpy as np

rng = np.random.default_rng(42)
samples = rng.integers(0, 10, size=20)
print(samples)
# [0 7 6 4 4 8 0 6 2 0 5 9 7 7 7 7 5 1 8 4]

counts, edges = np.histogram(samples, bins=5, range=(0, 10))
print(counts)   # [4 1 5 7 3]
print(edges)    # [ 0.  2.  4.  6.  8. 10.]

2 Density: Normalizing the Counts

Raw counts depend on how many samples you have. Passing density=True normalizes the histogram so the total area equals 1, turning counts into a probability density. That lets you compare datasets of different sizes on the same axes.

import numpy as np

data = np.array([0, 1, 1, 2, 2, 2, 3, 3, 4])

counts, edges = np.histogram(data, bins=4, range=(0, 4))
print(counts)   # [1 2 3 3]

dens, edges = np.histogram(data, bins=4, range=(0, 4), density=True)
print(dens)     # [0.11111111 0.22222222 0.33333333 0.33333333]

widths = np.diff(edges)
print((dens * widths).sum())   # 1.0

# ✅ Expected output:
# [1 2 3 3]
# [0.11111111 0.22222222 0.33333333 0.33333333]
# 1.0

3 digitize and bincount

When you need a label per value instead of totals, use np.digitize(values, bins): it returns the index of the bin each value falls into. For non-negative integer labels, np.bincount tallies how many times each label appears — faster than a histogram for that case.

import numpy as np

values = np.array([0.2, 1.5, 2.7, 3.9, 5.1])
bins   = np.array([1, 2, 3, 4, 5])

# Which bin does each value fall into?
print(np.digitize(values, bins))   # [0 1 2 3 5]

# Tally integer labels directly
labels = np.array([0, 1, 1, 2, 2, 2, 3, 0])
print(np.bincount(labels))         # [2 2 3 1]

# ✅ Expected output:
# [0 1 2 3 5]
# [2 2 3 1]

digitize returns 0 for values below the first edge and len(bins) for values at or above the last edge — handy for spotting out-of-range data.

🎯 YOUR TURN: Fill in the Blanks

Replace each ___ so the program bins exam scores into four equal ranges from 50 to 100.

import numpy as np

scores = np.array([55, 62, 78, 81, 90, 95, 73, 68])

# Four bins spanning 50..100
counts, edges = np.___(scores, bins=4, ___=(50, 100))   # fill in: histogram, range

print(counts)   # [2 2 2 2]
print(edges)    # [ 50.   62.5  75.   87.5 100. ]

# Expected output:
# [2 2 2 2]
# [ 50.   62.5  75.   87.5 100. ]

Expected output: counts [2 2 2 2] and edges [50. 62.5 75. 87.5 100.]. (Answers: histogram, range.)

⚠️ Common Errors & Quick Reference

❌ Expecting counts and edges to be the same length

There is always one more edge than count, so zipping them naively drops a bin.

✅ Fix: remember len(edges) == len(counts) + 1; use edges[:-1] as bin starts.

❌ Bins shift when you forget range

Without range, edges depend on the data's min and max and change run to run.

✅ Fix: pass an explicit range=(low, high) so bins are reproducible and comparable.

TaskCode
Count into binsnp.histogram(a, bins=10, range=(0, 1))
Normalize to a densitynp.histogram(a, density=True)
Label each value's binnp.digitize(a, bins)
Tally integer labelsnp.bincount(labels)

🎯 Mini Challenge: Assign Letter Grades

Take 30 reproducible exam scores, bucket them into grade bands at 60/70/80/90 with digitize, then count how many fall in each band with bincount.

import numpy as np

rng = np.random.default_rng(7)
exam = rng.integers(0, 101, size=30)

grade_bins = np.array([60, 70, 80, 90])
grades = np.digitize(exam, grade_bins)   # 0=F,1=D,2=C,3=B,4=A

print(np.bincount(grades, minlength=5))
# [17  2  2  5  4]   (F D C B A)

❓ Frequently Asked Questions

Lesson complete — histograms unlocked!

You can now bin data with np.histogram, read counts against n+1 edges, normalize with density=True, label each value with np.digitize, and tally fast with np.bincount.

Practice quiz

What two arrays does np.histogram return?

  • counts and bin_edges
  • min and max
  • mean and std
  • values and labels

Answer: counts and bin_edges. It returns the per-bin counts and the bin_edges array.

For n bins, how many bin edges are there?

  • n
  • n - 1
  • n + 1
  • 2n

Answer: n + 1. n bins need n+1 boundaries, so there is always one more edge than count.

For data [1,2,2,3,3,3,4,4,5], what does np.histogram(data, bins=4, range=(1,5)) give as counts?

  • [2, 2, 2, 3]
  • [1, 2, 3, 3]
  • [3, 3, 2, 1]
  • [1, 1, 1, 1]

Answer: [1, 2, 3, 3]. The four equal bins over 1..5 hold 1, 2, 3, and 3 values respectively.

What does density=True do in np.histogram?

  • Doubles the bin count
  • Sorts the data first
  • Removes empty bins
  • Normalizes so the area integrates to 1

Answer: Normalizes so the area integrates to 1. With density=True each bar is count/(total*bin_width) and the area sums to 1.

What does np.digitize return?

  • The bin index each value falls into
  • The total count per bin
  • The bin edges
  • A normalized density

Answer: The bin index each value falls into. digitize labels every value with the index of the bin it lands in.

Which function quickly tallies non-negative integer labels?

  • np.histogram
  • np.bincount
  • np.digitize
  • np.unique

Answer: np.bincount. np.bincount counts how many times each non-negative integer appears.

Why should you pass an explicit range to np.histogram?

  • It is required or it errors
  • Otherwise bins depend on the data's min and max and shift run to run
  • It speeds up sorting
  • It changes the dtype

Answer: Otherwise bins depend on the data's min and max and shift run to run. Without range, edges come from the data min/max, so bins are not reproducible.

What does np.bincount([0,1,1,2,2,2,3,0]) return?

  • [2, 2, 3, 1]
  • [1, 2, 3, 2]
  • [0, 1, 2, 3]
  • [8]

Answer: [2, 2, 3, 1]. Label 0 appears twice, 1 twice, 2 three times, 3 once: [2, 2, 3, 1].

What does np.digitize returning len(bins) for a value indicate?

  • The value is exactly at the first edge
  • The value is below the first edge
  • The value is at or above the last edge
  • An error occurred

Answer: The value is at or above the last edge. digitize returns len(bins) for values at or above the last edge (out of range high).

How does np.histogram differ from np.digitize?

  • histogram counts per bin; digitize labels each value's bin
  • They are identical
  • digitize returns counts; histogram returns labels
  • Only digitize works on floats

Answer: histogram counts per bin; digitize labels each value's bin. histogram gives a distribution summary; digitize tags every input element.

Continue this course

Frequently asked questions

What does np.histogram return?

np.histogram returns two arrays: the counts (how many values fall into each bin) and the bin_edges. There is always one more edge than there are counts, because n bins need n+1 boundaries. Note that np.histogram only computes the numbers — it does not draw anything; you pass the result to a plotting library if you want a bar chart.

What is the difference between np.histogram and np.digitize?

np.histogram counts how many values land in each bin and gives you totals. np.digitize instead labels each individual value with the index of the bin it falls into, returning one label per input value rather than a count. Use histogram when you want a distribution summary, and digitize when you need to tag or group every element by its bin.

What does density=True do in np.histogram?

With density=True the histogram is normalized so that the area under it integrates to 1, turning counts into a probability density. Each bar's height becomes count / (total * bin_width) instead of a raw count, which lets you compare distributions with different sample sizes or bin widths on the same scale.