Histograms with Python’s Matplotlib | by Thiago Carvalho | Towards Data Science

Karl Pearson coined the term histogram, but it’s hard to tell who invented the visualization, and it’s most likely that it was used way before Pearson named it.
William Playfair is considered the inventor of bar charts or the first to publish such graphs, so it’s not hard to imagine that he would have drawn a couple of those charts to visualize a frequency in the late 1700s or early 1800s.
After all, that’s pretty much what histograms are; they’re bar charts, usually visualized with the bars connected, where the values are separated into equal ranges, called bins or classes. The heights of the bars represent the number of records in that class, also known as frequency.
In this article, I’ll go through the basics of this visualization, and we’ll also explore some of Matplotlib’s many customization options while we learn more about histograms.
I’ll run my code in Jupyter, using Pandas, Numpy, and Matplotlib to develop the visuals.
import pandas as pdimport numpy as npimport matplotlib.pyplot as pltfrom matplotlib.ticker import AutoMinorLocatorfrom matplotlib import gridspec
In this example, the dataset we’ll explore has data on all space missions since 1957 and was scraped from nextspaceflight.com by Agirlcoding.
df = pd.read_csv('medium/data/Space_Corrected.csv')df
After loading the dataset, we can proceed to some cleaning and minor adjustments.
# date column to datetimedf['Datum'] = pd.to_datetime(df['Datum'], utc=True)# costs column to numericdf['Rocket'] = pd.to_numeric(df[' Rocket'], errors='coerce')# drop columns# ' Rocket' had an extra space and was renameddf.drop([' Rocket', 'Unnamed: 0', 'Unnamed: 0.1'], axis=1, inplace=True)
Now that’s all set, we can effortlessly plot our histogram with Matplotlib.
Mostly I want to visualize the data on the ‘Rocket’ column, which is the cost of the mission in millions of USD. My idea is to observe the distribution of values in that column.
plt.hist(df.Rocket)plt.show()
That’s ok, the default chart gives us a simple x-axis and y-axis, and the bars are automatically divided into bins.
Before going any further, let’s assign the bins of our histogram to a variable to get a better look at it.
n, bins, patches = plt.hist(df.Rocket)bins
That means our values are divided into ten bins, like so:
5.3 ≤ n < 49.77
49.77 ≤ n < 94.24
...
405.53 ≤ n ≤ 450
Note that the top value of each bin is excluded (<), but the last range includes it (≤).
Cool, now that we have a list with the edges of our bins, let’s try using it as the ticks for the x-axis.
Let’s also add a figure and increase the size of our graph.
fig = plt.figure(figsize=(16,6))n, bins, patches = plt.hist(df.Rocket)plt.xticks(bins)plt.show()
That’s better.
Since we didn’t give Matplotlib any information about the bins, it automatically defined its numbers and ranges.
We can set the bins by passing a list of edges when defining the plot; this allows us to create unevenly spaced bins, which is not typically recommended — but there’s that.
Another way we can set them is using an integer with the number of bins we want.
fig = plt.figure(figsize=(16,6))n, bins, patches = plt.hist(df.Rocket, bins=16)plt.xticks(bins)plt.show()
The main point of a histogram is to visualize the distribution of our data. We don’t want our chart to have too many bins because that could hide the concentrations in our data; simultaneously, we don’t want a low number of classes because we could misinterpret the distribution.
Choosing the number of classes in our histogram is sometimes very intuitive, but other times is quite a struggle. Luckily, we got plenty of algorithms for that, and Matplotlib allows us to chose which one to use.
fig = plt.figure(figsize=(16,6))# 'auto', 'sturges', 'fd', 'doane', 'scott', 'rice' or 'sqrt'n, bins, patches = plt.hist(df.Rocket, bins='rice')plt.xticks(bins)plt.show()
Quite simply, we already know the basics of how a histogram works. Now we can try to customize it.
We could use some gridlines in the x-axis to better visualize where the bins start and end. A title would also be great.
fig = plt.figure(figsize=(16,6))n, bins, patches = plt.hist(df.Rocket)plt.xticks(bins)plt.grid(color='white', lw = 0.5, axis='x')plt.title('Histogram of Space Missions Costs', loc = 'left', fontsize = 18)plt.show()
It would be better if the ticks were in the center of the bars and displayed both the lower and upper boundaries of the range.
We could define the labels by going through all the bins but the last while joining the current value with the next one.
Something like this:
# x ticks labels[ "{:.2f} - {:.2f}".format(value, bins[idx+1]) for idx, value in enumerate(bins[:-1])]
And the ticks positions should be at the center of the two values, as so:
# x ticks positions[(bins[idx+1] + value)/2 for idx, value in enumerate(bins[:-1])]
Cool, when we add this to our plot, we need to redefine the grids. If we draw the grid lines with the ticks, we’ll have the line in the middle of the bar.
To fix that, we’ll use AutoMinorLocator, the class we imported at the beginning. That class will help us set the minor ticks, which we can use to draw the grid.
fig = plt.figure(figsize=(16,6))n, bins, patches = plt.hist(df.Rocket)# define minor ticks and draw a grid with themminor_locator = AutoMinorLocator(2)plt.gca().xaxis.set_minor_locator(minor_locator)plt.grid(which='minor', color='white', lw = 0.5)# x ticksxticks = [(bins[idx+1] + value)/2 for idx, value in enumerate(bins[:-1])]xticks_labels = [ "{:.2f}\nto\n{:.2f}".format(value, bins[idx+1]) for idx, value in enumerate(bins[:-1])]plt.xticks(xticks, labels = xticks_labels)plt.title('Histogram of Space Missions Costs (Millions of USD)', loc = 'left', fontsize = 18)
It’s starting to look great; let’s remove the spines of our chart and the markings of the ticks to make it look cleaner.
fig, ax = plt.subplots(1, figsize=(16,6))n, bins, patches = plt.hist(df.Rocket)# define minor ticks and draw a grid with themminor_locator = AutoMinorLocator(2)plt.gca().xaxis.set_minor_locator(minor_locator)plt.grid(which='minor', color='white', lw = 0.5)# x ticksxticks = [(bins[idx+1] + value)/2 for idx, value in enumerate(bins[:-1])]xticks_labels = [ "{:.2f}\nto\n{:.2f}".format(value, bins[idx+1]) for idx, value in enumerate(bins[:-1])]plt.xticks(xticks, labels = xticks_labels)# remove major and minor ticks from the x axis, but keep the labelsax.tick_params(axis='x', which='both',length=0)# Hide the right and top spinesax.spines['bottom'].set_visible(False)ax.spines['left'].set_visible(False)ax.spines['right'].set_visible(False)ax.spines['top'].set_visible(False)plt.title('Histogram of Space Missions Costs (Millions of USD)', loc = 'left', fontsize = 18)
For the y-axis, we could print the values on top of the bars and remove the y ticks.
n the first variable we get from plotting our histograms holds a list with the counts for each bin.
We can get the x positions for xticks from the list we built earlier and the labels and y values from n.
fig, ax = plt.subplots(1, figsize=(16,6))n, bins, patches = plt.hist(df.Rocket)# define minor ticks and draw a grid with themminor_locator = AutoMinorLocator(2)plt.gca().xaxis.set_minor_locator(minor_locator)plt.grid(which='minor', color='white', lw = 0.5)# x ticksxticks = [(bins[idx+1] + value)/2 for idx, value in enumerate(bins[:-1])]xticks_labels = [ "{:.2f}\nto\n{:.2f}".format(value, bins[idx+1]) for idx, value in enumerate(bins[:-1])]plt.xticks(xticks, labels = xticks_labels)ax.tick_params(axis='x', which='both',length=0)# remove y ticksplt.yticks([])# Hide the right and top spinesax.spines['bottom'].set_visible(False)ax.spines['left'].set_visible(False)ax.spines['right'].set_visible(False)ax.spines['top'].set_visible(False)# plot values on top of barsfor idx, value in enumerate(n):    if value > 0:        plt.text(xticks[idx], value+5, int(value), ha='center')plt.title('Histogram of Space Missions Costs (Millions of USD)', loc = 'left', fontsize = 18)plt.show()
Awesome!
The elements are in place; all that’s left to do is change the colors, font sizes, add some labels on the x and y-axis, and customize the chart as desired.
facecolor = '#EAEAEA'color_bars = '#3475D0'txt_color1 = '#252525'txt_color2 = '#004C74'fig, ax = plt.subplots(1, figsize=(20,6), facecolor=facecolor)ax.set_facecolor(facecolor)n, bins, patches = plt.hist(df.Rocket, color=color_bars, bins='doane')#gridminor_locator = AutoMinorLocator(2)plt.gca().xaxis.set_minor_locator(minor_locator)plt.grid(which='minor', color=facecolor, lw = 0.5)xticks = [(bins[idx+1] + value)/2 for idx, value in enumerate(bins[:-1])]xticks_labels = [ "{:.0f}-{:.0f}".format(value, bins[idx+1]) for idx, value in enumerate(bins[:-1])]plt.xticks(xticks, labels=xticks_labels, c=txt_color1, fontsize=13)# remove major and minor ticks from the x axis, but keep the labelsax.tick_params(axis='x', which='both',length=0)# remove y ticksplt.yticks([])# Hide the right and top spinesax.spines['bottom'].set_visible(False)ax.spines['left'].set_visible(False)ax.spines['right'].set_visible(False)ax.spines['top'].set_visible(False)for idx, value in enumerate(n):    if value > 0:        plt.text(xticks[idx], value+5, int(value), ha='center', fontsize=16, c=txt_color1)plt.title('Histogram of Space Missions Costs\n', loc = 'left', fontsize = 20, c=txt_color1)plt.xlabel('\nMillions of USD', c=txt_color2, fontsize=14)plt.ylabel('Number of Space Missions', c=txt_color2, fontsize=14)plt.tight_layout()plt.savefig('costs.png', facecolor=facecolor)
Great! We got a numerical field and described its distribution with a beautiful chart. Now let’s have a look at how to handle dates in histograms.
To figure out the bins on this one, we can start by looking at the earliest and latest date in our data.
We can easily select the bins for numerical fields with the many different algorithms that Matplotlib supports, but those techniques may not yield the best results when dealing with dates.
Don’t get me wrong, as much as you can find that the optimal size of bin to describe the distribution of your variable is 378 days, using a whole year is way more understandable.
Alright, so let’s convert our date-time objects to a number format that Matplotlib can handle, then we’ll adjust our ticks and see how it looks.
import matplotlib.dates as mdates# convert the date format to matplotlib date format plt_date = mdates.date2num(df['Datum'])bins = mdates.datestr2num(["{}/01/01".format(i) for i in np.arange(1957, 2022)])# plot itfig, ax = plt.subplots(1, figsize=(22,6))n, bins, patches = plt.hist(plt_date, bins=bins)# x ticks and limitax.xaxis.set_major_locator(mdates.YearLocator())ax.xaxis.set_major_formatter(mdates.DateFormatter('%y'))plt.xlim(mdates.datestr2num(['1957/01/01','2021/12/31']))plt.show()
That’s very interesting. We can see the space race taking shape from 57 to the late ’70s and a more recent increase in space programs in the last five years.
Now we can adapt our previous design to our new histogram.
facecolor = '#EAEAEA'color_bars = '#3475D0'txt_color1 = '#252525'txt_color2 = '#004C74'# convert the date format to matplotlib date format plt_date = mdates.date2num(df['Datum'])bins = mdates.datestr2num(["{}/01/01".format(i) for i in np.arange(1957, 2022)])# plot itfig, ax = plt.subplots(1, figsize=(22,8), facecolor=facecolor)ax.set_facecolor(facecolor)n, bins, patches = plt.hist(plt_date, bins=bins, color=color_bars)ax.xaxis.set_major_locator(mdates.YearLocator())ax.xaxis.set_major_formatter(mdates.DateFormatter('%y'))plt.xlim(mdates.datestr2num(['1957/01/01','2021/12/31']))#gridplt.grid(axis='y', color=color_bars, lw = 0.5, alpha=0.7)plt.grid(axis='x', color=facecolor, lw = 0.5)#remove major and minor ticks from the x axis, but keep the labelsax.tick_params(axis='both', which='both',length=0)# Hide the right and top spinesax.spines['bottom'].set_visible(False)ax.spines['left'].set_visible(False)ax.spines['right'].set_visible(False)ax.spines['top'].set_visible(False)ax.spines['left'].set_position(('outward', 10))plt.xticks(c=txt_color1, fontsize=12)plt.yticks(c=txt_color1, fontsize=12)plt.title('Histogram of Space Missions Dates\n', loc = 'left', fontsize = 20, c=txt_color1)plt.xlabel('\nYear', c=txt_color2, fontsize=14)plt.ylabel('Number of Space Missions', c=txt_color2, fontsize=14)plt.tight_layout()plt.savefig('hist.png', facecolor=facecolor)
And that’s it! We built two histograms, got a look at the different ways we have to define the bins and classes, changed lots of visual elements to make our chart look just like we wanted to, explored date formats, major and minor ticks, grid lines, and texts.
Thanks for reading my article. I hope you enjoyed it.
You can find more tutorials about Python and Matplotlib here.
Resources:Data;Code (GitHub);Matplotlib .hist;Matplotlib Dates;Matplotlib Tick Formatters;Matplotlib Date Tick Labels — Example;History of Histograms;