How to make bar and hbar charts with labels using matplotlib | by Jacob Toftgaard Rasmussen | Towards Data Science

The first time I made a bar chart I could not immediately figure out how to add labels to the bars. Especially the horizontal bar charts were troublesome for me. So don’t worry if you also cannot make it work. In this post you will learn how this is done along with some tips and tricks for styling the diagram.
NOTE: If you only need to see how to add labels you can skip to the section: Creating bar charts with labels. I have included all the code that I have written for you to copy if you want to type along as you read, as well as detailed explanations of how the code works.
To allow me to teach you how to make bar charts with labels I needed some data to work with. Therefore, I decided to grab some game data from the online game League of Legends. This makes the guide more fun for fans of the game, (myself included). The first part of the post is a walk through for how I clean up the data to get it into my preferred format. I encourage you to read that part as well as it teaches you a way to convert data from JSON format to a Pandas DataFrame.
The post has the following structure:
Introduction
Preparing the data (JSON to Pandas DataFrame)
Creating bar charts with labels
Summary and goodbyes
I have a JSON file that contains the base stats (characteristics) of all the champions (playable characters) in League of Legends. You can access the data file here. The data looks like this (see screenshot):
In total, there are 153 champions that all include the same keys in their dictionaries.
Ultimately we will make a bar chart that shows the starting hp (hit points) of champions, to get an overview of which champions start out with the most health. If you are curious about other statistics it will be super easy for you to change what the bar chart shows, for example you might be interested in seeing which champions have the most ad (attack damage).
Next I would like to convert this data from the JSON format that it is currently in to a Pandas DataFrame instead.
I encourage you to try to code along as this will give you the best understanding! Learning by doing you know.
import pandas as pdimport matplotlib.pyplot as pltimport json
I start out by importing the necessary libraries that we will use. Pandas allows us to make DataFrames which is a nice structure to work with when doing data science. Matplotlib.pyplot will be used to make the charts. json will allow you to load the data from the JSON file into memory for us to work with.
with open('champ_stats.json') as f:  data = json.load(f)champion = data['Aatrox']stat_names = list(champion.keys())columns = ['champ'] + stat_namesdf = pd.DataFrame()
I open the data file and load it into the variable called data. Next I would like to have a list that contains the names of all the different stats,(statistics/characteristics of a champion), as this will be the column names for the finished DataFrame. The list of stats is included in all the champion dictionaries in the data, because of this we just need to dive into one of them and take the keys from that dictionary. We access the 'Aatrox' key as this is the first champion the the data. This gives us the the corresponding value which we save to the champion variable. This value is another dictionary where all the keys are the names that we are looking for, therefore we use the .keys() function to get its keys and the returned value is transformed to a list as the function was called inside the list() function. If this step confuses you I have written another guide that explains how to access data in python dictionaries that you can find here.
The column names for the DataFrame are going to be 'champ' for the first column which will contain all the names of the champions, and the other columns will be named after the items in stat_names. We save this to the variable columns. Finally we use pd.DataFrame() to create an empty DataFrame that we save to the variable df.
for name, stats in data.items():  values = [stats[x] for x in columns[1:]]  row = pd.DataFrame([[name] + values], columns=columns)  df = df.append(row)df.head()
From the previous step we had an empty DataFrame and it is now time to populate it with rows of data, where each row represents a champion. We now make a for statement that loops through all the key value pairs in the data dictionary, (name is the key and stats is the value). We want a list of all the stat values and to get that I use list comprehension and save the resulting list to the variable values. The list comprehension works like this: For every name (x) in columns[1:] we take that name and use it as the key to access the corresponding value from stats, and that value is added to the list.
Next we make a new DataFrame that we save to the variable row. This DataFrame is instantiated with two parameters. Firstly, a list inside a list, where the inner list contains the name and all the values for the current champion. Secondly, the formal columns= attribute is set to our list variable also called columns, this will set the names of the columns in the DataFrame as we want them.Next we append the row to the main DataFrame that we called df, this returns a new DataFrame with which we override the previous df.
Finally, we call the head() method on df to display the first rows of the DataFrame. If you have coded along you should now get the following output:
The data is now nicely formatted as a DataFrame and in the next step we will finally create the bar charts and add labels.
df_sorted_by_hp = df.sort_values('hp', ascending=False)x = df_sorted_by_hp['champ'][:15]y = df_sorted_by_hp['hp'][:15]
To improve the diagram I have chosen to sort the rows in the DataFrame by the 'hp' value, and ascending=False sorts the values in descending order. Afterwards, we save the champ column to the variable named x and similarly the hp values to the variable y. To make sure that the diagram is not too cluttered I have chosen only to include the first 15 champions this is done with the [:15] suffix.
fig, ax = plt.subplots(figsize=(20,4))bars = ax.bar(x, y, width=0.5)
We use plt.subplots(figsize=(20,4)) to create a figure object and an axes object, which are saved to the variables fig and ax. We set the figure size to be 20 by 4 inches, this might not be the absolute best size, but you can play around with the numbers until you find out what size suits your chart best. ax.bar(x, y, width=0.5) creates the bar chart with our x and y values and a width of 0.5, again, you can try out different sizes between 0 and 1 for the width. I save the returned object in the variable bars which we will use shortly.
The code will produce the following chart, (Just a part of it to make it fit better on Medium):
... Not very cool
First of all, the height difference for the bars is super hard to distinguish, and secondly, it is impossible to read the exact height of each of the bars. This is where the labels will be useful! Lets add them now!
Note: Append this following code snippet to the previous one.
for bar in bars:  height = bar.get_height()  label_x_pos = bar.get_x() + bar.get_width() / 2  ax.text(label_x_pos, height, s=f'{height}', ha='center',  va='bottom')
We can loop through the bars variable to go over every bar in the chart. We save the height of each bar to a variable called height by getting it from the bar.get_height() function. Next we need the x position for the label for the current bar in the loop. We get this position from the bar.get_x() function and add the width of the bar divided by 2 to get the x value for the center of the bar.
Finally, we use ax.text(label_x_pos, height, s=f'{height}', ha='center') to create the label/text. This function takes a value for the x position and one for the y position, then we give it the string that it will display, which in this case is the height of the bar. Lastly ha='center' further helps align the label at the center of the bar, and va='bottom' places the label just above the bar.
This will produce the following chart, (Just a part of it to make it fit better on Medium):
Yes! Now we have labels for the bars displaying their individual height! This is clearly better, but it can still be improved upon.
I think that the names under the bars are a bit hard to read due to their size. Also I would like a title for the chart along with descriptions for the axes. Furthermore, there is no point in having the y axis start from 0. Finally, I think the diagram could be spiced up with some color!
Note: Append this following code snippet to the previous one.
for tick in ax.xaxis.get_major_ticks():  tick.label.set_fontsize(14)for bar in bars[::2]:  bar.set_color('r')plt.title('The 15 champions with the most HP')plt.xlabel('Champion name')plt.ylabel('Starting hp')plt.ylim([550,650])plt.show()
You can set the font size for each tick by looping over the ticks returned from ax.xaxis.get_major_ticks() and provide a font size with tick.label.set_fontsize(14). Similarly you can access every second bar with for bar in bars[::2]: and set the color with bar.set_color('r'). The title, x label and y label can be set with their corresponding functions. Finally, you can limit the y axis (same goes for the x axis), with plt.ylim([550, 650]). I find that starting with 550 and ending with 650 worked will for this diagram.
plt.show() will show the finished bar chart, (Just a part of it to make it fit better on Medium):
A big improvement I think!
To make a horizontal bar chart there is very little that we have to change. Because of this I will show the code in a single snippet, and highlight the changes in bold. Also, keep in mind that the data is the same as before and because of this we still have all the names in the x variable and all the hp values in the y variable.
fig, ax = plt.subplots(figsize=(4, 10))bars = ax.barh(x,y, 0.5)for bar in bars:  width = bar.get_width() #Previously we got the height  label_y_pos = bar.get_y() + bar.get_height() / 2  ax.text(width, label_y_pos, s=f'{width}', va='center')for tick in ax.yaxis.get_major_ticks():  tick.label.set_fontsize(14)for bar in bars[::2]:  bar.set_color('r')plt.title('The 15 champions with the most HP')plt.xlabel('Starting hp')plt.ylabel('Champion name')plt.xlim([550, 650])plt.show()
Same as before we create the figure and axes objects and set the size, however, this time a size of (4, 10) works better, (taller than wide). To make a horizontal bar chart we use ax.barh() instead of ax.bar(). Next, instead of getting the height of each bar we get the width. We need to get the y position of the bars using bar.get_y() instead of the x position, and we add the height of the bar divided by 2, (notice that height has a different meaning here compared to before).
We again set the label for the bar with ax.text(), however, we use width for the x position and for the string to be displayed. Additionally, we use the argument va='center', this helps to place the label at the center of the bar. Try removing it to see the difference.
For the ticks we simply change the size of the y ticks instead of the x ticks. Finally, you have to switch the labels for the axes as well as limiting the x axis opposed to the y axis.The result:
Voilá you now know how to make both vertical and horizontal bar charts, as well as adding labels to make them more readable.
If you followed the whole guide you have now created a small project where you both transformed JSON data into a Pandas DataFrame and afterwards created bar charts with labels.
Personally I prefer the horizontal bar chart as I find it easier to read in this case. Remember that you can explore the data further if you are interested. Try finding out which champion has the least hp, or show more champions on your charts.
If you have any questions, comments or tips for improving the post, please do not hesitate to reach out to me!
Thank you for taking the time to read this post, I hope you learned something useful!
Keep learning! — Jacob Toftgaard Rasmussen