问题
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. By default, a logarithmic scale uses a LogFormatter, which will format the values in scientific notation. To change the formatter to the default for linear axes (ScalarFormatter) use e.g.
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.
We can use a FuncFormatter from the matplotlib ticker module to fix this issue. The simplest way to do this is with a lambda function and the g format specifier (thanks to @lenz in comments).
import matplotlib.ticker as ticker
ax.yaxis.set_major_formatter(ticker.FuncFormatter(lambda y, _: '{:g}'.format(y)))
Note in my original answer I didn't use the g format, instead I came up with 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:
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()
回答4:
regarding these questions
What if I wanted to change the numbers to, 1, 5, 10, 20?
– aloha Jul 10 '15 at 13:26I would like to add ticks in between, like 50,200, etc.., How can I do that? I tried, set_xticks[50.0,200.0] but that doesn't seem to work! – ThePredator Aug 3 '15 at 12:54
But with ax.axis([1, 100, 1, 100]), ScalarFormatter gives 1.0, 10.0, ... which is not what I desire. I want it to give integers... – CPBL Dec 7 '15 at 20:22
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:
来源:https://stackoverflow.com/questions/21920233/matplotlib-log-scale-tick-label-number-formatting