Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Matplotlib/pyplot: Auto adjust unit of y Axis

Tags:

matplotlib

I would like to modify the Y axis unit of the plot indicated below. Preferable would be the use of units like M (Million), k (Thousand) for large numbers. For example, the y Axis should look like: 50k, 100k, 150k, etc.

The plot below is generated by the following code snippet:

plt.autoscale(enable=True, axis='both')
plt.title("TTL Distribution")
plt.xlabel('TTL Value')
plt.ylabel('Number of Packets')
y = graphy  # data from a sqlite query
x = graphx  # data from a sqlite query
width = 0.5
plt.bar(x, y, width, align='center', linewidth=2, color='red', edgecolor='red')
fig = plt.gcf()
plt.show()

I saw this post and thought I could write my own formatting function:

def y_fmt(x, y):
    if max_y > 1000000:
        val = int(y)/1000000
        return '{:d} M'.format(val)
    elif max_y > 1000:
        val = int(y) / 1000
        return '{:d} k'.format(val)
    else:
        return y

But I missed that there is no plt.yaxis.set_major_formatter(tick.FuncFormatter(y_fmt)) function available for the bar plot I am using.

How I can achieve a better formatting of the Y axis?

[TTL Distribution Plot]

like image 960
Patrick Avatar asked Nov 12 '16 18:11

Patrick


People also ask

How do I move the Y axis in Matplotlib?

MatPlotLib with Python To shift the Y-axis ticks from left to right, use ax. yaxis. tick_right() where ax is axis created using add_subplot(xyz) method.


1 Answers

In principle there is always the option to set custom labels via plt.gca().yaxis.set_xticklabels().

However, I'm not sure why there shouldn't be the possibility to use matplotlib.ticker.FuncFormatter here. The FuncFormatter is designed for exactly the purpose of providing custom ticklabels depending on the ticklabel's position and value. There is actually a nice example in the matplotlib example collection.

In this case we can use the FuncFormatter as desired to provide unit prefixes as suffixes on the axes of a matplotlib plot. To this end, we iterate over the multiples of 1000 and check if the value to be formatted exceeds it. If the value is then a whole number, we can format it as integer with the respective unit symbol as suffix. On the other hand, if there is a remainder behind the decimal point, we check how many decimal places are needed to format this number.

Here is a complete example:

import numpy as np
import matplotlib.pyplot as plt
from matplotlib.ticker import FuncFormatter

def y_fmt(y, pos):
    decades = [1e9, 1e6, 1e3, 1e0, 1e-3, 1e-6, 1e-9 ]
    suffix  = ["G", "M", "k", "" , "m" , "u", "n"  ]
    if y == 0:
        return str(0)
    for i, d in enumerate(decades):
        if np.abs(y) >=d:
            val = y/float(d)
            signf = len(str(val).split(".")[1])
            if signf == 0:
                return '{val:d} {suffix}'.format(val=int(val), suffix=suffix[i])
            else:
                if signf == 1:
                    print val, signf
                    if str(val).split(".")[1] == "0":
                       return '{val:d} {suffix}'.format(val=int(round(val)), suffix=suffix[i]) 
                tx = "{"+"val:.{signf}f".format(signf = signf) +"} {suffix}"
                return tx.format(val=val, suffix=suffix[i])

                #return y
    return y


fig, ax = plt.subplots(ncols=3, figsize=(10,5))

x = np.linspace(0,349,num=350) 
y = np.sinc((x-66.)/10.3)**2*1.5e6+np.sinc((x-164.)/8.7)**2*660000.+np.random.rand(len(x))*76000.  
width = 1

ax[0].bar(x, y, width, align='center', linewidth=2, color='red', edgecolor='red')
ax[0].yaxis.set_major_formatter(FuncFormatter(y_fmt))

ax[1].bar(x[::-1], y*(-0.8e-9), width, align='center', linewidth=2, color='orange', edgecolor='orange')
ax[1].yaxis.set_major_formatter(FuncFormatter(y_fmt))

ax[2].fill_between(x, np.sin(x/100.)*1.7+100010, np.cos(x/100.)*1.7+100010, linewidth=2, color='#a80975', edgecolor='#a80975')
ax[2].yaxis.set_major_formatter(FuncFormatter(y_fmt))

for axes in ax:
    axes.set_title("TTL Distribution")
    axes.set_xlabel('TTL Value')
    axes.set_ylabel('Number of Packets')
    axes.set_xlim([x[0], x[-1]+1])

plt.show()

which provides the following plot:

enter image description here

like image 133
ImportanceOfBeingErnest Avatar answered Sep 28 '22 21:09

ImportanceOfBeingErnest