Matplotlib log scale tick label number formatting

匿名 (未验证) 提交于 2019-12-03 02:47:02

问题:

With matplotlib when a log scale is specified for an axis, the default method of labeling that axis is with numbers that are 10 to a power eg. 10^6. Is there an easy way to change all of these labels to be their full numerical representation? eg. 1, 10, 100, etc.

Note that I do not know what the range of powers will be and want to support an arbitrary range (negatives included).

回答1:

Sure, just change the formatter.

For example, if we have this plot:

import matplotlib.pyplot as plt  fig, ax = plt.subplots() ax.axis([1, 10000, 1, 100000]) ax.loglog()  plt.show() 

You could set the tick labels manually, but then the tick locations and labels would be fixed when you zoom/pan/etc. Therefore, it's best to change the formatter:

from matplotlib.ticker import ScalarFormatter for axis in [ax.xaxis, ax.yaxis]:     axis.set_major_formatter(ScalarFormatter()) 



回答2:

I've found that using ScalarFormatter is great if all your tick values are greater than or equal to 1. However, if you have a tick at a number <1, the ScalarFormatter prints the tick label as 0.

I've used this lambda function with FuncFormatter to set numbers >= 1 to their integer value, and numbers <1 to their decimal value, with the minimum number of decimal places required (i.e. 0.1, 0.01, 0.001, etc). It assumes that you are only setting ticks on the base10 values.

import matplotlib.ticker as ticker import numpy as np  ax.yaxis.set_major_formatter(ticker.FuncFormatter(lambda y,pos: ('{{:.{:1d}f}}'.format(int(np.maximum(-np.log10(y),0)))).format(y))) 

For clarity, here's that lambda function written out in a more verbose, but also more understandable, way:

def myLogFormat(y,pos):     # Find the number of decimal places required     decimalplaces = int(np.maximum(-np.log10(y),0))     # =0 for numbers >=1     # Insert that number into a format string     formatstring = '{{:.{:1d}f}}'.format(decimalplaces)     # Return the formatted tick label     return formatstring.format(y)  ax.yaxis.set_major_formatter(ticker.FuncFormatter(myLogFormat)) 


回答3:

regarding these questions

What if I wanted to change the numbers to, 1, 5, 10, 20?

you can solve those issue like this with MINOR formatter:

ax.yaxis.set_minor_formatter(matplotlib.ticker.ScalarFormatter()) ax.yaxis.set_minor_formatter(matplotlib.ticker.FormatStrFormatter("%.8f")) ax.set_yticks([0.00000025, 0.00000015, 0.00000035]) 

in my application I'm using this format scheme, which I think solves most issues related to log scalar formatting; the same could be done for data > 1.0 or x axis formatting:

plt.ylabel('LOGARITHMIC PRICE SCALE') plt.yscale('log') ax.yaxis.set_major_formatter(matplotlib.ticker.ScalarFormatter()) ax.yaxis.set_major_formatter(matplotlib.ticker.FormatStrFormatter("%.8f")) ax.yaxis.set_minor_formatter(matplotlib.ticker.ScalarFormatter()) ax.yaxis.set_minor_formatter(matplotlib.ticker.FormatStrFormatter("%.8f")) ##################################################### #force 'autoscale' ##################################################### yd = [] #matrix of y values from all lines on plot for n in range(len(plt.gca().get_lines())):         line = plt.gca().get_lines()[n]         yd.append((line.get_ydata()).tolist()) yd = [item for sublist in yd for item in sublist] ymin, ymax = np.min(yd), np.max(yd) ax.set_ylim([0.9*ymin, 1.1*ymax]) ##################################################### z = [] for i in [0.0000001, 0.00000015, 0.00000025, 0.00000035,           0.000001, 0.0000015, 0.0000025, 0.0000035,           0.00001,  0.000015, 0.000025, 0.000035,           0.0001, 0.00015, 0.00025, 0.00035,           0.001, 0.0015, 0.0025, 0.0035,           0.01, 0.015, 0.025, 0.035,           0.1, 0.15, 0.25, 0.35]:      if ymin<i<ymax:         z.append(i)         ax.set_yticks(z)                 

for comments on "force autoscale" see: Python matplotlib logarithmic autoscale

which yields:

then to create a general use machine:

# user controls ##################################################### sub_ticks = [10,11,12,14,16,18,22,25,35,45] # fill these midpoints sub_range = [-8,8] # from 100000000 to 0.000000001 format = "%.8f" # standard float string formatting  # set scalar and string format floats ##################################################### ax.yaxis.set_major_formatter(matplotlib.ticker.ScalarFormatter()) ax.yaxis.set_major_formatter(matplotlib.ticker.FormatStrFormatter(format)) ax.yaxis.set_minor_formatter(matplotlib.ticker.ScalarFormatter()) ax.yaxis.set_minor_formatter(matplotlib.ticker.FormatStrFormatter(format))  #force 'autoscale' ##################################################### yd = [] #matrix of y values from all lines on plot for n in range(len(plt.gca().get_lines())):         line = plt.gca().get_lines()[n]         yd.append((line.get_ydata()).tolist()) yd = [item for sublist in yd for item in sublist] ymin, ymax = np.min(yd), np.max(yd) ax.set_ylim([0.9*ymin, 1.1*ymax])  # add sub minor ticks ##################################################### set_sub_formatter=[] for i in sub_ticks:     for j in range(sub_range[0],sub_range[1]):         set_sub_formatter.append(i*10**j) k = [] for l in set_sub_formatter:     if ymin<l<ymax:         k.append(l) ax.set_yticks(k) ##################################################### 

yields:



回答4:

I found Joe's and Tom's answers very helpful, but there are a lot of useful details in the comments on those answers. Here's a summary of the two scenarios:

Ranges above 1

Here's the example code like Joe's, but with a higher range:

import matplotlib.pyplot as plt  fig, ax = plt.subplots() ax.axis([1, 10000, 1, 1000000]) ax.loglog()  plt.show() 

That shows a plot like this, using scientific notation:

As in Joe's answer, I use a ScalarFormatter, but I also call set_scientific(False). That's necessary when the scale goes up to 1000000 or above.

import matplotlib.pyplot as plt from matplotlib.ticker import ScalarFormatter  fig, ax = plt.subplots() ax.axis([1, 10000, 1, 1000000]) ax.loglog() for axis in [ax.xaxis, ax.yaxis]:     formatter = ScalarFormatter()     formatter.set_scientific(False)     axis.set_major_formatter(formatter)  plt.show() 

Ranges below 1

As in Tom's answer, here's what happens when the range goes below 1:

import matplotlib.pyplot as plt from matplotlib.ticker import ScalarFormatter  fig, ax = plt.subplots() ax.axis([0.01, 10000, 1, 1000000]) ax.loglog() for axis in [ax.xaxis, ax.yaxis]:     formatter = ScalarFormatter()     formatter.set_scientific(False)     axis.set_major_formatter(formatter)  plt.show() 

That displays the first two ticks on the x axis as zeroes.

Switching to a FuncFormatter handles that. Again, I had problems with numbers 1000000 or higher, but adding a precision to the format string solved it.

import matplotlib.pyplot as plt from matplotlib.ticker import FuncFormatter  fig, ax = plt.subplots() ax.axis([0.01, 10000, 1, 1000000]) ax.loglog() for axis in [ax.xaxis, ax.yaxis]:     formatter = FuncFormatter(lambda y, _: '{:.16g}'.format(y))     axis.set_major_formatter(formatter)  plt.show() 



易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!