Saturday, June 1, 2019

Published 1:06 PM by with 0 comment

SI Notation and Custom Tick Marks in Matplotlib

How do you get SI notation and other custom tick marks in matplotlib?
If you've ever plotted something in matplotlib with large x and/or y values, you've probably seen something like the x-axis here:



I personally really dislike that. The '1e7' off to the side is kind of hidden, and in general it would be great to be able to format the tick marks. There are some obvious ways to do it:
  • set your own ticks and labels manually; this requires you to handle figuring them out, padding appropriately, etc., and it's a hassle
  • let matplotlib set ticks and labels, and then update them; this can be very slow
There's another option that is pretty handy. Setting formatters can do this for you. There's a default one for engineering notation hereFuncFormatter lets you specify the format function for the tick labels explicitly for more control. Here's an example:

def powers_of_1000(x, pos):
    if x == 0:
      return x
    bins = [1000000000000.0, 1000000000.0, 1000000.0, 1000.0, 1, 0.001, 0.000001, 0.000000001]
    abbrevs = ['E12', 'E9', 'E6', 'E3', '', 'E-3', 'E-6', 'E-9']
    label = x
    for i in range(len(bins)):
        if abs(x) >= bins[i]:
            label = '{1:.{0}f}'.format(2, x/bins[i]) + abbrevs[i]
            break
    
    return label

This puts all tick labels in terms of powers of 1000 (E3, E6, etc.). What does that look like in the plot?



Pretty nice. How do you actually call that? Here's an example:


plt.figure(figsize=[12, 5])
plt.plot(x, y)
plt.title('Powers of 1000')
plt.gca().xaxis.set_major_formatter(FuncFormatter(powers_of_1000))

Simple. Note that you can use this for the yaxis also by copying the last line and changing 'xaxis' to 'yaxis'.

How do you write these functions in general? x is the value of the tick (it can be x or y axis...x doesn't mean 'x value' here). pos is the index of the tick in the list (0 is first). All you have to do is return a string that is the text you want displayed at that tick.

One more example...what about SI notation? This is representing 2500 as 2.5k, 10000000 as 10M, and so on. That looks almost like the powers of 1000 one:


def SI(x, pos):
    if x == 0:
        return x
    bins = [1000000000000.0, 1000000000.0, 1000000.0, 1000.0, 1, 0.001, 0.000001, 0.000000001]
    abbrevs = ['T', 'G', 'M', 'k', '', 'm', 'u', 'n']
    label = x
    for i in range(len(bins)):
        if abs(x) >= bins[i]:
            label = '{1:.{0}f}'.format(2, x/bins[i]) + abbrevs[i]
            break
    
    return label



Simple again. This is a really powerful feature that took me some time to find, so hopefully this makes it more obvious to anyone else looking for it. A full example can be found here.


      edit

0 comments:

Post a Comment