带有稀疏刻度标签且没有科学记数法的 LogFormatter

LogFormatter with sparse tick labels and no scientific notation

我有一个双对数图,想使用 LogFormatterminor_thresholds 选项来使用稀疏的次要刻度标签,同时避免使用科学记数法。我最好的尝试,使用与我的真实数据跨越相同范围的虚拟数据:

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)

这导致:

优点:

坏点:

我该如何解决? (我还想避免为不需要的数字添加小数点的固定表示法。)

编辑:

根据@tmdavison 的回答,我尝试制作一个考虑到 axes.formatter.min_exponent_num_to_string 版本。最简单的形式是

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)

这是基于 _pprint_val 会处理其他情况的假设,这是不正确的 - _print_valx 的唯一考虑是它使用 %d如果是1e4以下的整数。否则,它的格式基于范围 (vmax - vmin) .. 这对于基于日志的轴来说似乎主要是错误的。

此外,我观察到上述 _num_to_string 的一个非常奇怪的行为:该函数被每个轴调用 168 次,即使它只打印 12 个数字。例如,对于 x 轴 x 有 28 个值,从 0.002 到 6000(!),这个序列被调用了 6 次!这肯定是有原因的,但它看起来确实很奇怪......

这是因为在LogFormatter中,有一些固定号码。我们可以看这里的代码:https://matplotlib.org/stable/_modules/matplotlib/ticker.html#LogFormatter

class中的相关函数是_num_to_string函数,我将其粘贴在这里:

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

如您所见,对于任何大于 10000 或小于 1 的数字,格式都硬连接到 1.0e

这里的一个选择是子class LogFormatter class 并编写我们自己的 _num_to_string 函数,我在下面的示例中已经完成了。对于 0.001 和 1 之间的数字,我使用 %g 格式,它只使用所需的小数位数。显然,您可以调整我在这里使用的限制或格式以满足您的需要。

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()