I have a log-log plot and want to use sparse minor-tick labels using the minor_thresholds
option of LogFormatter
and, at the same time, avoid using scientific notation. My best attempt, using dummy data spanning the same range as my real data:
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.ticker as ticker
x = np.array([1.2**e for e in range(-18, 13)])
y = 10 * x
plt.rcParams['axes.formatter.min_exponent'] = 3
fig = plt.figure()
ax = fig.add_subplot()
ax.scatter(x, y)
ax.set_xscale('log')
ax.set_yscale('log')
fmt = ticker.LogFormatter(labelOnlyBase=False, minor_thresholds=(3, 0.5))
ax.xaxis.set_minor_formatter(fmt)
ax.yaxis.set_minor_formatter(fmt)
This results in:
Good points:
Bad point:
axes.formatter.min_exponent
optionHow can I fix it? (I would also like to avoid fixed notation that adds a decimal point for numbers that do not need it.)
Edit:
Based on @tmdavison's answer, I tried to make a version of his _num_to_string
that would take into account axes.formatter.min_exponent
. The simplest form would then be
def _num_to_string(self, x, vmin, vmax):
min_exp = mpl.rcParams['axes.formatter.min_exponent']
fx = math.log(x) / math.log(self._base)
if abs(fx) < min_exp:
return '%g' % x
else:
return self._pprint_val(x, vmax - vmin)
This is based on the assumption that _pprint_val
would take care of the other cases, which is not correct - the only consideration _print_val
has for x
is that it uses %d
if it is an integer below 1e4. Otherwise, it bases the formatting on the range ( vmax - vmin
).. which seems principally wrong for a log-based axes.
Moreover, I observe a very strange behaviour with the above _num_to_string
: the function gets called 168 times for each axis, even if it only prints 12 numbers. For example, for x-axis x
has 28 values from 0.002 to 6000(,), and this sequence is called 6 times. There is surely a reason for this. but it surely looks weird...
This is because in the LogFormatter
, there are some hardwired numbers. We can look at the code here: https://matplotlib.org/stable/_modules/matplotlib/ticker.html#LogFormatter
The relevant function in that class is the _num_to_string
function, which I will paste here:
def _num_to_string(self, x, vmin, vmax):
if x > 10000:
s = '%1.0e' % x
elif x < 1:
s = '%1.0e' % x
else:
s = self._pprint_val(x, vmax - vmin)
return s
As you can see, the format is hardwired to 1.0e
for any number bigger than 10000 or smaller than 1.
One option here is to subclass the LogFormatter
class and write our own _num_to_string
function, which I have done in the example below. For numbers between 0.001 and 1, I use the %g
format, which uses only as many decimal places as required. Obviously you can adjust the limits or formats I used here to suit your needs.
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.ticker as ticker
x = np.array([1.2**e for e in range(-18, 13)])
y = 10 * x
plt.rcParams['axes.formatter.min_exponent'] = 3
fig = plt.figure()
ax = fig.add_subplot()
ax.scatter(x, y)
ax.set_xscale('log')
ax.set_yscale('log')
class myformatter(ticker.LogFormatter):
def _num_to_string(self, x, vmin, vmax):
if x > 10000:
s = '%1.0e' % x
elif x < 1 and x >= 0.001:
s = '%g' % x
elif x < 0.001:
s = '%1.0e' % x
else:
s = self._pprint_val(x, vmax - vmin)
return s
# Create separate instances of formatter for each axis
xfmt = myformatter(labelOnlyBase=False, minor_thresholds=(3, 0.5))
yfmt = myformatter(labelOnlyBase=False, minor_thresholds=(3, 0.5))
ax.xaxis.set_minor_formatter(xfmt)
ax.yaxis.set_minor_formatter(yfmt)
plt.show()
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.