Reputation: 358
This question addresses a problem I'm currently facing. I have some data, say [12.301, 12.318, 12.302]
which, when plotted, results in an offset displayed as +1.23e1
.
I don't mind the offset itself, but that exponential form instead of just writing 12.3
is not very nice. Can I force somehow the exponent to only appear for multiple-of-three powers of ten? 1e3
instead of 1000
makes sense, 1e1
instead of 10
not at all.
I found this other question somewhat related, but that is more related to only having an integer form, whereas I don't mind decimals.
Upvotes: 1
Views: 365
Reputation: 9830
With a little bit of searching in the web and adapting the answers found here, here, and here plus a print(dir(ScalarFormatter))
, I was able to adapt the first linked post:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.ticker import ScalarFormatter, FormatStrFormatter
#from https://stackoverflow.com/a/45332730/2454357
def fexp(f):
return int(np.floor(np.log10(abs(f)))) if f != 0 else 0
#from https://stackoverflow.com/a/45332730/2454357
def fman(f):
return f/10**fexp(f)
#adapted from https://stackoverflow.com/a/3679918/2454357
class PowerMultipleOfThreeFormatter(ScalarFormatter):
"""Formats axis ticks using scientific notation with a constant order of
magnitude"""
def __init__(self, useOffset=True, useMathText=False):
ScalarFormatter.__init__(self, useOffset=useOffset,
useMathText=useMathText)
def _set_orderOfMagnitude(self, range):
"""Over-riding this to avoid having orderOfMagnitude reset elsewhere"""
exponent = fexp(range)
if -3 < exponent < 3:
self.orderOfMagnitude = 0
else:
new_exp = (exponent//3)*3
self.orderOfMagnitude = new_exp
def format_data(self, *args, **kwargs):
##make sure that format_data does everyting it shoud:
super(PowerMultipleOfThreeFormatter, self).format_data(
*args, **kwargs
)
##compute the offset in the right format
exponent = fexp(self.offset)
mantissa = fman(self.offset)
if -3 < exponent < 3:
return '{:g}'.format(self.offset)
new_exp = (exponent//3)*3
factor = 10**new_exp
##from https://stackoverflow.com/a/2440786/2454357
man_string = '{}'.format(self.offset/factor).rstrip('0').rstrip('.')
return man_string+'e{}'.format(new_exp)
# Generate some random data...
x = np.linspace(55478, 55486, 100)
y = np.random.random(100) - 0.5
y = np.cumsum(y)
y *= 1e-8
# Plot the data...
fig,axes = plt.subplots(nrows=2, ncols=2)
for ax, y0 in zip(axes.ravel(), [-1e4, 1.15e-4, 12, -0.1]):
ax.plot(x, y+y0, 'b-')
ax.yaxis.set_major_formatter(PowerMultipleOfThreeFormatter())
fig.tight_layout()
plt.show()
In general, the idea is to compute the exponent and mantissa of a number and manipulate the two such that the exponent is a multiple of 3 (with (exponent//3)*3
and exponent%3
). For the multiplier, it was already demonstrated here, where and how these calculations should be added (i.e. in _set_orderOfMagnitude
). The offset value is stored in ScalarFormatter.offset
and the string representation is computed in the function format_data()
. Overloading that function we can thus change how the offset is displayed. The code also contains an example how to use the new formatter (the way how to generate the data is again shamelessly copied from here). The result of the code looks like this:
Upvotes: 1