Written and maintained by CASRAI Editorial Board
Last updated
numpy.histogram() counts how many values of an array fall into each of a set of bins and returns two arrays: the counts and the bin edges. It does not draw anything; it is the calculation that plotting functions such as plt.hist call underneath. This guide is for researchers and analysts who need the numbers behind a histogram in Python, and who want to know exactly where a boundary value lands, what density=True returns, and how range changes the result. Outputs shown were run on NumPy 2.5.1 and match the behaviour in the NumPy reference documentation (read 2026-11-08).
What np.histogram is used for in research
The function turns a continuous variable into a frequency table: counts per interval. Researchers use it to check the shape of a distribution before choosing a test, to build a frequency table for a methods section, to compare two samples on identical bins, and to feed a histogram or a probability-density estimate into later calculations. Because it returns arrays rather than a figure, the result can be stored, compared and reused. For drawing the chart, see histogram in Matplotlib and Seaborn histogram; the Matplotlib function passes its bin arguments to np.histogram. To judge what shape you are looking at, see how to identify the distribution of your data.
Install or access
NumPy is installed from the official project. The install page (numpy.org/install) gives pip install numpy and, for conda users, conda install numpy from the defaults or conda-forge channel, and recommends working in a virtual environment. Then import it:
import numpy as np
Syntax and arguments
numpy.histogram(a, bins=10, range=None, density=None, weights=None)
It returns (hist, bin_edges). The arguments, per the NumPy reference:
| Argument | What it does |
|---|---|
a |
The input data (array_like). It is flattened, so a 2-D array is counted as one list of values. |
bins |
An integer gives that many equal-width bins across range. A sequence gives the bin edges, including the rightmost edge, and can have non-uniform widths. A string names an automatic method (see below). Default 10. |
range |
A (lower, upper) pair. Default is (a.min(), a.max()). Values outside the range are ignored. It also affects automatic bin calculation. |
density |
If True, returns the probability density function value per bin instead of counts, so the integral over the range is 1. |
weights |
An array of the same shape as a; each value contributes its weight to its bin instead of 1. The returned hist takes the dtype of the weights. |
Step-by-step: build and read a histogram table
- Import NumPy and load the values. Here, 200 simulated measurements from a seeded generator so the output is reproducible:
import numpy as np rng = np.random.default_rng(42) x = rng.normal(50, 10, 200) - Call np.histogram with a bin count.
hist, edges = np.histogram(x, bins=5) print(hist) # [22 75 78 21 4] print(edges.round(2)) # [28.68 38.77 48.86 58.95 69.05 79.14] - Pair counts with intervals. There is always one more edge than there are counts (
len(edges) == len(hist) + 1), so count i belongs to the interval fromedges[i]toedges[i+1]. Here 78 values fall between 48.86 and 58.95. - Fix the bins when you need to compare samples. Pass an explicit edge sequence and reuse it for every group:
edges = np.arange(30, 81, 10) hist_a, _ = np.histogram(group_a, bins=edges) hist_b, _ = np.histogram(group_b, bins=edges) - Plot or tabulate. Pass the result to your plotting tool, or build a table of intervals and counts.
Output and how to read it
Which bin does a boundary value land in?
All bins are half-open, [a, b), except the last, which is closed, [a, b]. With bins=[1, 2, 3, 4] the intervals are [1, 2), [2, 3) and [3, 4]. Check:
np.histogram([1, 2, 3, 4], bins=[1, 2, 3, 4])
# (array([1, 1, 2]), array([1, 2, 3, 4]))
The value 2 is counted in [2, 3), not [1, 2), and both 3 and 4 fall in the last bin because it includes its upper edge. This matters for integer-valued data such as counts or Likert scores, where many values sit exactly on edges.
What density=True returns
With density=True the result is a probability density, not a probability. The NumPy documentation states that the integral over the range is 1, and that the values sum to 1 only when every bin has width 1. On the seeded data above:
h, e = np.histogram(x, bins=5, density=True)
h.sum() # 0.0991 (not 1)
(h * np.diff(e)).sum() # 1.0
To get the share of observations in each bin, compute h * np.diff(e), or divide the plain counts by their total. The documentation’s own example, np.histogram(np.arange(4), bins=np.arange(5), density=True), returns 0.25 for each of four unit-width bins.
What range does
Values outside range are ignored, so the counts need not sum to the sample size. On the same data:
h, e = np.histogram(x, bins=5, range=(40, 60))
# h = [27 33 33 32 27], e = [40. 44. 48. 52. 56. 60.]
h.sum() # 152, not 200
The range also governs automatic bin selection: the width is optimised on the data inside the range, and the resulting bins fill the whole range.
Weights
np.histogram([1, 2, 1], bins=[0, 1, 2, 3], weights=[0.5, 1.5, 2.0]) returns [0., 2.5, 1.5]: the two 1s add 0.5 + 2.0 to the second bin. Integer weights give an integer array; float weights give floats. With density=True the weights are normalised so the integral is 1.
Automatic bin methods
Instead of a number, bins accepts a string. The same strings are documented for numpy.histogram_bin_edges, which returns just the edges so you can inspect them first (it does not support weighted data for automatic sizing):
| String | Documented behaviour |
|---|---|
'auto' |
Minimum bin width from the Sturges and Freedman-Diaconis estimators; described as good all-around performance. |
'fd' |
Freedman-Diaconis; robust to outliers. |
'doane' |
Improved Sturges estimator for non-normal data. |
'scott' |
Less robust; uses variability and size of the data. |
'stone' |
Leave-one-out cross-validation estimate of integrated squared error; a generalisation of Scott’s rule. |
'rice' |
Depends on data size only; commonly overestimates the number of bins. |
'sturges' |
R’s default; size only; optimal for Gaussian data, underestimates for large non-Gaussian data. |
'sqrt' |
Square root of data size; used by Excel and others. |
Troubleshooting
ValueError: autodetected range is not finite
If the data contain NaN (or infinity) and you do not pass range, NumPy cannot compute the minimum and maximum, and raises ValueError: autodetected range of [nan, nan] is not finite. Remove missing values first (for example x = x[~np.isnan(x)]) or supply a finite range.
Counts do not add up to the sample size
Check whether you passed a range or an edge sequence that does not cover all values; anything outside is dropped silently.
density=True values do not sum to 1
That is expected unless bin widths are 1; multiply by np.diff(edges) as shown above.
Data in a pandas column or a 2-D array
Pass df['col'].dropna() or the array directly; multi-dimensional input is flattened. To bin into labelled categories rather than count them, see pandas cut. Evenly spaced edges can be generated with numpy linspace, and ravel in NumPy explains flattening.
Free alternatives
| Option | Relation to np.histogram |
|---|---|
matplotlib.pyplot.hist |
Draws the histogram; its bin arguments are passed to np.histogram. |
numpy.histogram_bin_edges |
Returns only the bin edges, for inspecting or reusing automatic bins. |
FAQ
What does np.histogram return?
A tuple of two arrays: hist, the counts (or densities) per bin, and bin_edges, which is one element longer than hist.
How do I use histogram in NumPy with my own bin edges?
Pass a monotonically increasing sequence as bins, including the rightmost edge. Widths may differ between bins.
Is the last bin included?
Yes. Every bin is half-open except the last, which includes its upper edge.
Does numpy histogram plot the data?
No. It only computes counts and edges. Use Matplotlib or Seaborn to draw them.
Sources (official documentation, read 2026-11-08)
- NumPy reference: numpy.histogram
- NumPy reference: numpy.histogram_bin_edges
- NumPy: Installing NumPy







