简体   繁体   中英

LogFormatter with sparse tick labels and no scientific notation

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:

  • only a reasonable subset of minor ticks is labeled
  • labels of the major ticks have the correct format (see '0.1')

Bad point:

  • labels of the minor ticks still use scientific notation - it looks like it ignores the axes.formatter.min_exponent option

How 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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM