Build a User Friendly Chart with Matplotlib
By: Andrew Zhu
Tags: Programming, Data Science, Matplotlib, Python Programming, Python

When Matplotlib was born in 2000s, at a time there was no Pandas exists. (That is why Matplotlib do not natively support DataFrame from pandas) The lib is old, but is it outdated? Can we still use Matplotlib to create a nice, modern and user friendly data visualization. Even in 2021? Let’s try it out .

Two plotting styles, but which one to use?

The official site’s Usage Guide says we can create a chart in two styles.

Style 1:

import matplotlib.pyplot as plt

(fig, ax) = plt.subplots()

ax.plot([1, 2, 3, 4], [1, 4, 2, 3])

Style 2:

import matplotlib.pyplot as plt

plt.plot([1, 2, 3, 4], [1, 4, 2, 3])

They will give you exactly the same chart. The guide said you can use either one. The problem is, they are confusing, especially to those new to the Matplotlib kingdom (yes, it was me once upon a time). When you read sample codes from StackOverflow, from blogs, from the sample gallery. Some use style 1 and some use style 2. Which bring unnecessary mind burden and code change efforts.

There is a reason of the coexistence of both styles. When John Hunter firstly created Matplotlib as a patch to Python in 2002. He was intend to create a Python alternative for MATLAB users. It is the style 2: plt.plot(…) every time you plt.(dot) something, you move the chart state to its next stage.

While the Style 1 is called Object-Oriented style or interface. When you create figure and axes. (fig, ax) = plt.subplots() . You have a tuple which include object handlers to figure and axes objects. So, you can add and update your elements in any stages, because you have the access to the objects directly.

Some may still prefer to the MATLAB style, while I’d recommend the object-oriented style for better and precise controls.

To rewrite Matlab style to OO style. In most cases , you can:

1. Switching plt.plot() to ax.plot()

2. Adding set_ prefix to settings. for example, change plt.title() to ax.set_title()

You can use dir(ax) to see all callable functions and properties inside of the ax object.

The follow code runs in Jupyter notebook when I was writing this, If you write Python code and run in terminal, add one additional code line plt.show() to show your chart.

The initial chart from a sample dataset

I coined a revenue data of 2019 and 2020.

monthly_key = [20190101,20190201,20190301,20190401

,20190501,20190601,20190701,20190801

,20190901,20191001,20191101,20191201] revenue_2019 = [19819952.0,20353215.0,22370450.0

,22428864.0,23318743.0,23108457.0

,25371691.0,26082854.0,26082854.0

,22428864.0,23318743.0,23108457.0] revenue_2020 = [29319952.0,20653215.0,24370450.0

,32428864.0,33318743.0,33148457.0

,35371691.0,36082854.0,36082854.0

,35428864.0,37318743.0,39108457.0]

Draw a chart with Matplotlib:

import matplotlib.pyplot as plt

fig,ax = plt.subplots()

ax.set_title("MoM revenue 2020 vs 2019")

ax.plot(monthly_key,revene_2019)

ax.plot(monthly_key,revene_2020)

ax.legend(['2019','2020'], ncol=2, loc='upper left')

You shall get a chart like this (hm…traveled from 20 years ago?):

The original raw chart

Let’s take some actions to make it better.

Solutions to improve it

Action #1: Use a better theme, say ‘seaborn-whitegrid’

import matplotlib.pyplot as plt

plt.style.use('seaborn-whitegrid')

...

A bit better, right?

use ‘seaborn-whitegrid’ theme

Action #2: Show readable month names in x-axis

The x-axis is now showing the number, we need to convert monthly key from 20190101 to a date type. This can be done by datetime’s strptime function, here is a sample:

from datetime import datetime

a = 20190101

date = datetime.strptime(str(a),'%Y%m%d')

date.strftime("%b")

The “%b” will give you the short month name: Jan . replace with “%B” to get full month name January .

Now let’s use the cool Python comprehension to convert the monthly_key list to short month name list:

...

from datetime import datetime

month_name_list = [datetime.strptime(str(a),'%Y%m%d').strftime('%b') for a in monthly_key]

...

replace monthly_key with month_name_list

...

ax.plot(month_name_list,revene_2019)

ax.plot(month_name_list,revene_2020)

...

Render your chart again, the result shall come with a readable month x-axis instead of numbers.

apply month name in x-axis

Action #3: Show human readable abbreviated numbers in x and y axis

The y-axis shows number 4.0 * 1e7. it is not ideal, it would be better to show numbers like 1K, 1M or 1b etc. Here I created a function to convert it(put in gist for your convenience):

A Python function will convert number to its abbreviation

Add two lines to the previous code block:

import matplotlib.pyplot as plt

import matplotlib as mpl

...

# end of previous code block

ax.get_yaxis().set_major_formatter(mpl.ticker.FuncFormatter(lambda x, p:human_format(x)))

Now, we have a chart like this, a bit better y axis.

show xxM in y-axis

Action #4: Remove all unnecessary pixels, and increase the font size

When you are presenting to leadership, to your conference audiences. All pixels in your visualization will consume their mind efforts. Make your audience easier to grab the idea is the top priority instead of presenting a complex chart and show how good you are at making charts.

Remove the grid:

ax.grid(False)

Remove the axes border:

plt.style.use('seaborn')

#or

plt.box(False)

Increase title font size:

ax.set_title("MoM revenue 2020 vs 2019",fontsize = 20)

Increase x and y axis font size:

# end of previous code block

ax.xaxis.set_tick_params(labelsize=15)

ax.yaxis.set_tick_params(labelsize=15)

Bold the lines:

...

ax.plot(month_name_list,revene_2019,linewidth=4)

ax.plot(month_name_list,revene_2020,linewidth=4)

...

Now, you have a new chart with clear wording and clear lines.

Action #5: Use better colors for trending lines

In the current chart, two lines show in Red and Blue. In real usage, users are more wanting to see the line of 2020 revenue, the 2019 line is there as a comparison purpose. So, we can consider using a dark color for 2020 line as a highlight and use light color for 2019 as a reference line.

Define the dark and light blue colors:

dark_blue = "#003366"

light_blue = "#A8C5E0"

Apply these colors in two lines

...

ax.plot(month_name_list,revenue_2019,linewidth=4,color=light_blue)

ax.plot(month_name_list,revenue_2020,linewidth=4,color=dark_blue)

...

Now, audience can easily distinguish the 2020 line from 2019, and it is also more friendly to those color blindness.

Apply dark blue and light blue

Action #6: Show legend text along the trending line

Do we really need to show the legend in the top left corner? whenever I see a chart with legend, I need to check the legend text and color and then find its corresponded lines. Why not show the legend labels along the trending lines.

And it is not that hard:

...

# end of previous code block

ax.text(max(month_name_list),revenue_2020[len(revenue_2020)-2],"2020 Revenue",color=dark_blue) ax.text(max(month_name_list),revenue_2019[len(revenue_2019)-4],"2019 Revenue",color=light_blue)

In the code, max(month_name_list) defines the x-axis position. revenue_2020[len(revenue_2020)-2] defines the y-axis position. In your case, you can adjust the -2 to other number slightly move text label up and down.

Show legend labels along with trending line

Action #7: Use your desired font

In Microsoft, lots of slide use font called “Segoe UI”. It would great if the label in charts use font “Segoe UI” too. And Matplotlib support custom fonts.

Since we are now using custom font and label size, we can define those in one place. Here I defined two style sets for title and label:

title_text_font = {'size':'20','weight':'bold','fontname':'Segoe UI'} label_text_font = {'size':'18','weight':'bold','fontname':'Segoe UI'}

Apply the style in title:

ax.set_title("MoM revenue 2020 vs 2019",**title_text_font)

Apply the style to chart label:

ax.text(max(month_name_list),revenue_2020[len(revenue_2020)-1],"2020 Revenue",color=dark_blue,**label_text_font) ax.text(max(month_name_list),revenue_2019[len(revenue_2019)-4],"2019 Revenue",color=light_blue,**label_text_font)

Also apply the font to x and y tick labels:

for tick in ax.get_xticklabels():

tick.set_fontname("Segoe UI") for tick in ax.get_yticklabels():

tick.set_fontname("Segoe UI")

Note that if you don’t see any changes after applying the code, you may need to install the font file first. here is a reference solution.

The Final Result

Last, let slightly tune the title, move it to left and add a bit margin below it.

ax.set_title("MoM Revenue 2020 vs 2019",**title_text_font,loc='left',y=1.08)

And give the y-axis a name, I will omit the x axis label since the month name already make itself clear.

...

ax.set_ylabel("MoM Revenue - USD",**label_text_font)

...

Finally, our raw old style chart is becoming this:

We can further improve the chart according to real needs, but this one, is way better the first raw chart, right?

Here is the complete code for your reference:

A large portion of these actions I learnt from our visualization expert: Nancy Organ.

Please share you comments, suggestions and any error spotted. Thanks for reading,