Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Some links on this page are affiliate links: if you buy through them we may earn a commission, at no extra cost to you.

matplotlib.pyplot.hist() creates a one-dimensional histogram: it groups numeric observations into intervals called bins and plots the count or weighted amount in each interval. The simplest example is:

import matplotlib.pyplot as plt

plt.hist(data)
plt.show()

For reusable or multi-panel code, prefer the equivalent object-oriented form, Axes.hist():

fig, ax = plt.subplots()
ax.hist(data, bins=20)
plt.show()

The most important choices are statistical rather than cosmetic: choose meaningful bin edges, decide whether the y-axis should show counts or density, and use the same edges when comparing datasets.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

What a histogram shows

A histogram groups numeric observations into contiguous intervals and displays how much data falls into each interval. With ordinary unweighted data, each bar represents the number of observations in its bin.

A histogram is not the same as a bar chart. Histogram bars represent numeric ranges, such as 0–10 or 10–20; a bar chart represents discrete categories, such as product names or departments. The apparent shape of a histogram also depends on its bin width and boundaries, so bins is part of the analysis—not merely a styling option.

pyplot.hist() delegates the numerical binning to NumPy’s histogram machinery and renders the result with Matplotlib artists. The current API is documented at Matplotlib’s hist() reference.

Install Matplotlib and verify the environment

Install or upgrade Matplotlib with pip:

python -m pip install -U matplotlib

With conda, use:

conda install -c conda-forge matplotlib

Check which version the active Python environment is using:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
import matplotlib
print(matplotlib.__version__)

The stable documentation snapshot used for this guide is labeled Matplotlib 3.11.1. Matplotlib’s Python and NumPy requirements are release-specific, so check the current documentation and official installation guide for the version you install.

Create a basic histogram

import numpy as np
import matplotlib.pyplot as plt

rng = np.random.default_rng(42)
data = rng.normal(loc=0, scale=1, size=1_000)

plt.hist(data, bins=30, edgecolor="black")
plt.xlabel("Value")
plt.ylabel("Count")
plt.title("Distribution of values")
plt.show()
  • data supplies the observations.
  • bins=30 requests 30 equal-width bins across the relevant range.
  • edgecolor="black" separates adjacent bars visually.
  • The labels explain what the horizontal and vertical axes mean.
  • plt.show() explicitly displays the figure in scripts and many noninteractive environments. Jupyter commonly displays plots automatically, but using show() remains portable.

Understand the function signature

matplotlib.pyplot.hist(
    x,
    bins=None,
    *,
    range=None,
    density=False,
    weights=None,
    cumulative=False,
    bottom=None,
    histtype="bar",
    align="mid",
    orientation="vertical",
    rwidth=None,
    log=False,
    color=None,
    label=None,
    stacked=False,
    data=None,
    **kwargs
)

The **kwargs styling arguments are passed to the underlying bar or polygon artists. The properties accepted therefore depend partly on histtype.

Inspect the return values

hist() returns a three-item tuple:

counts, edges, artists = plt.hist(data, bins=5)

print(counts)
print(edges)
print(len(edges) - 1)  # number of bins
  1. counts contains the values plotted for each bin. These are normally counts, but density and weights change their meaning.
  2. edges contains the bin boundaries. It has one more element than the number of bins.
  3. artists contains the Matplotlib objects used to draw the histogram.

Even ordinary unweighted counts are returned as floating-point values. With multiple datasets, the first and third return values become lists—one entry per dataset—while the bin-edge array remains shared.

Choose bins carefully

Integer bin counts

plt.hist(data, bins=10)

An integer requests that many equal-width bins over the selected range. More bins do not automatically make a histogram more accurate: too few can hide structure, while too many can make random noise look like meaningful peaks.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Explicit bin edges

plt.hist(data, bins=[0, 1, 2, 5, 10])

A sequence supplies the edges directly and can create unequal-width bins. For edges [1, 2, 3, 4], the intervals are generally [1, 2), [2, 3), and [3, 4]: the final bin includes both endpoints.

Use domain-specific edges when thresholds have real meaning—for example, age bands, temperature ranges, or quality-control limits. For exploratory analysis, compare a few reasonable bin choices rather than trusting one setting blindly.

Automatic strategies

plt.hist(data, bins="auto")

Documented automatic strategies include auto, fd, doane, scott, stone, rice, sturges, and sqrt. These are useful starting points, not universal answers. The best choice depends on sample size, skew, outliers, and the question the chart must answer.

Use the same edges for comparisons

When comparing groups, independently chosen automatic bins can make the plots look different for reasons unrelated to the data. Create one edge array and pass it to every dataset:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
import numpy as np

lower = min(data_a.min(), data_b.min())
upper = max(data_a.max(), data_b.max())
common_edges = np.linspace(lower, upper, 31)

plt.hist(data_a, bins=common_edges, alpha=0.5, label="Group A")
plt.hist(data_b, bins=common_edges, alpha=0.5, label="Group B")
plt.legend()

Control the plotted range

plt.hist(data, bins=20, range=(0, 100))

range sets the lower and upper limits used for binning. Values outside the interval are ignored; it is not merely a visual zoom. If outliers matter, inspect or report how many observations were excluded.

If bins is an explicit sequence, range has no effect. In either case, make sure your edges cover the observations you intend to analyze.

Counts versus density

Counts

plt.hist(data, bins=20)
plt.ylabel("Count")

Counts answer: “How many observations fall in each interval?” They are useful when the sample size itself matters.

Probability density

plt.hist(data, bins=20, density=True)
plt.ylabel("Density")

With density=True, each bin height is proportional to:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
count / (total_count * bin_width)

The area of the bars integrates to approximately 1. The heights do not necessarily sum to 1, particularly when bins have unequal widths:

density_values, edges = np.histogram(data, bins=20, density=True)
area = np.sum(density_values * np.diff(edges))
print(area)  # approximately 1

Use density when comparing distribution shapes or groups with different sample sizes. Never label a density axis “Count,” and do not interpret a density height as the probability of an entire bin without accounting for its width.

Density with unequal-width bins

edges = [0, 1, 2, 5, 10, 20]

fig, ax = plt.subplots()
ax.hist(data, bins=edges, density=True, edgecolor="black")
ax.set_xlabel("Value")
ax.set_ylabel("Density")
plt.show()

Unequal widths make raw bar heights especially easy to misread. For a probability-density histogram, compare areas, not just heights.

Use weights

weights = np.array([...])
plt.hist(data, bins=20, weights=weights)

Each observation contributes its corresponding weight rather than exactly one count. weights must have the same shape as x. This is useful when observations represent different amounts, such as survey weights or exposure time.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

For a weighted density histogram, the weights are normalized so the density integrates to 1 over the plotted range. Label the y-axis according to what the weights represent if the result is not an ordinary count.

Compare multiple datasets

Overlay distributions

fig, ax = plt.subplots()

ax.hist(
    data_a,
    bins=common_edges,
    density=True,
    histtype="step",
    linewidth=2,
    label="Group A"
)
ax.hist(
    data_b,
    bins=common_edges,
    density=True,
    histtype="step",
    linewidth=2,
    label="Group B"
)

ax.set_xlabel("Value")
ax.set_ylabel("Density")
ax.legend()
plt.show()

An outline histogram makes overlap easier to see than opaque bars. For filled bars, use transparency with alpha, but be cautious: overlapping colors can obscure which group is responsible for a region.

Stack or separate

plt.hist(
    [data_a, data_b],
    bins=common_edges,
    label=["Group A", "Group B"],
    stacked=True
)
plt.legend()
  • Use an overlay to compare shapes.
  • Use stacked=True to show composition and total volume.
  • Use side-by-side bars for a small number of groups when direct bin-by-bin comparison is useful.
  • Use separate subplots when groups have very different scales or sample sizes.

A sequence of arrays may contain datasets with different lengths. A two-dimensional NumPy array is interpreted by columns, so do not casually assume that a list of arrays and every two-dimensional representation have identical behavior.

Create cumulative histograms

plt.hist(data, bins=20, cumulative=True)

The final bin represents the total count. For a normalized cumulative distribution:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
fig, ax = plt.subplots()
ax.hist(
    data,
    bins=40,
    density=True,
    cumulative=True,
    histtype="step",
    linewidth=2
)
ax.set_xlabel("Value")
ax.set_ylabel("Cumulative proportion")
ax.set_ylim(0, 1)
plt.show()

Use cumulative=-1 to accumulate from high values toward low values:

plt.hist(data, bins=20, density=True, cumulative=-1)

For this reverse-normalized form, the first bin is normalized to 1. A cumulative histogram remains affected by bin boundaries. If you want a distribution display without binning artifacts, consider Matplotlib’s current ECDF functionality.

Customize the appearance and axes

fig, ax = plt.subplots(figsize=(8, 5))
ax.hist(
    data,
    bins=25,
    color="cornflowerblue",
    edgecolor="white",
    alpha=0.8,
    rwidth=0.9,
    label="Sample"
)
ax.set(
    title="Distribution of measurements",
    xlabel="Measurement",
    ylabel="Frequency"
)
ax.legend()
fig.tight_layout()
plt.show()
  • color sets the bar or line color.
  • edgecolor separates neighboring bars.
  • alpha controls transparency.
  • rwidth sets the bar width as a fraction of the bin width. It is ignored for step and stepfilled.
  • label supplies legend text; call legend() to display it.

Histogram types

plt.hist(data, histtype="bar")         # ordinary bars
plt.hist(data, histtype="barstacked")  # stacked bars for multiple datasets
plt.hist(data, histtype="step")        # unfilled outline
plt.hist(data, histtype="stepfilled")  # filled outline

bar is a good default, step is often clearest for overlays, and barstacked emphasizes composition. stepfilled can be effective for one distribution but may obscure overlaps.

Alignment and orientation

plt.hist(data, bins=10, align="left")
plt.hist(data, bins=10, align="mid")
plt.hist(data, bins=10, align="right")

The default alignment is mid. Alignment changes how bars are positioned relative to their bins, but selecting correct explicit edges is more important for statistical interpretation.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
plt.hist(data, orientation="horizontal")

orientation="horizontal" draws a horizontal histogram, changing which axis carries the bin values and which carries the counts.

Logarithmic axes are not logarithmic data

plt.hist(data, bins=30, log=True)

log=True makes the histogram axis logarithmic; it does not transform the observations. These are different operations:

plt.hist(data, log=True)       # logarithmic plotted count axis
plt.hist(np.log10(data))       # bin the log-transformed values

If you use a logarithmic x-axis or logarithmically transformed data, zero and negative values cannot be represented by the logarithm. Validate and explain how nonpositive observations are handled.

Prefer Axes.hist() for maintainable code

pyplot.hist() uses the current axes implicitly. That is convenient for quick scripts, but explicit axes are easier to manage in reusable code, dashboards, and multi-panel figures:

What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
import matplotlib.pyplot as plt

fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(10, 4))

ax1.hist(data_a, bins=20, color="steelblue")
ax1.set_title("Group A")

ax2.hist(data_b, bins=20, color="darkorange")
ax2.set_title("Group B")

fig.tight_layout()
plt.show()

The pyplot and axes methods use the same histogram concepts and parameters; the object-oriented form simply makes the destination explicit.

Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Plot precomputed histograms with NumPy and stairs()

Use numpy.histogram() when you need numerical counts and edges without drawing a chart:

counts, edges = np.histogram(data, bins=100)

Render those values with stairs():

fig, ax = plt.subplots()
ax.stairs(counts, edges)
ax.set_xlabel("Value")
ax.set_ylabel("Count")
plt.show()

This separates calculation from presentation and is often clearer for already-binned data. It can also be more efficient than rendering thousands of individual rectangles. Matplotlib recommends stairs() or a step-style histogram for very large bin counts; exact performance depends on the backend and plotting environment.

If you must use hist() with precomputed counts, use bin edges as the positions and counts as weights:

Free tools Windows power users keep installed

One-click scans. No signup required.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
counts, edges = np.histogram(data, bins=20)

plt.hist(
    edges[:-1],
    bins=edges,
    weights=counts
)

Do not pass bin centers as though they were raw observations without explaining the weighting. For precomputed values, stairs(counts, edges) is usually the more direct expression.

Troubleshoot common problems

Nothing appears

  • Confirm Matplotlib is installed in the same Python environment that runs the script.
  • Print matplotlib.__version__ to verify the active installation.
  • Call plt.show() in scripts.
  • Try a standalone script instead of relying on an IDE’s plotting integration.
  • On a headless machine, use a noninteractive backend such as Agg and save the output:
plt.savefig("histogram.png", dpi=150, bbox_inches="tight")

See Matplotlib’s installation and backend documentation for environment-specific issues.

The histogram has the wrong number of bins

Remember that an edge array has one more value than the number of bins. For example, five bins require six edges. Also check whether you supplied an integer, an explicit edge sequence, or an automatic strategy.

Outliers disappeared

Check range and explicit bin edges. Values outside the selected interval are excluded from the histogram, not merely hidden by the display. Compare the number of finite input values with the number represented by the bins.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

The density looks wrong

Verify that the y-axis says “Density,” not “Count,” and remember that density is normalized by bin width. Check the area:

values, edges = np.histogram(data, bins=edges, density=True)
print(np.sum(values * np.diff(edges)))  # approximately 1

Groups do not line up

Use one shared edge array for every dataset. Avoid plotting one group with bins="auto" and another with a separate automatic choice when the purpose is visual comparison.

The input is empty or nonfinite

Clean data explicitly when appropriate:

clean = np.asarray(data)
clean = clean[np.isfinite(clean)]

if clean.size == 0:
    raise ValueError("No finite observations to plot")

plt.hist(clean, bins=20)

This is general NumPy data-cleaning practice, not a guarantee that every invalid input will produce the same behavior across Matplotlib versions. Also note that the current API documentation does not support masked arrays for hist().

The logarithmic plot fails

Check for zero or negative values before using logarithmic data or x-axis scaling. A logarithmic y-axis also cannot display zero-height bins in the same way as a linear axis, so choose the scale and treatment of empty bins deliberately.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Alternatives to pyplot.hist()

  • numpy.histogram(): calculate counts and edges without creating a chart.
  • plt.stairs(): draw precomputed histograms, especially with many bins.
  • bar(): display category counts. For labels such as product types, first count categories rather than using a numeric histogram:
categories, counts = np.unique(labels, return_counts=True)
ax.bar(categories, counts)
  • hist2d(): show the joint distribution of two numeric variables:
ax.hist2d(x, y, bins=30)
  • hexbin(): another option for dense two-dimensional numeric data:
ax.hexbin(x, y, gridsize=30)

Do not force two-dimensional data into repeated one-dimensional histograms when the relationship between variables is the question.

Quick decision guide

Question Recommended choice
How many observations fall in each interval? Default counts with meaningful bins
How do distributions compare across unequal sample sizes? density=True, with a clearly labeled density axis
How should groups be compared fairly? One shared edge array for all groups
Are there meaningful domain thresholds? Explicit, documented bin edges
Are there many precomputed bins? np.histogram() followed by stairs()
Are the values categorical? A counted bar chart instead of hist()
Do you need a cumulative distribution without bin artifacts? Consider an ECDF

Use pyplot.hist() for quick one-dimensional plots, but treat binning, normalization, weighting, and range selection as analytical decisions. In current code, use density rather than the obsolete historical normed parameter.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.