如何在symlog规模上放置小蜱?

时间:2013-12-09 12:38:20

标签: python matplotlib

我使用matplotlib的symlog标度来覆盖向正方向和负方向延伸的大范围参数。不幸的是,symlog规模不是很直观,也可能不常用。因此,我想通过在主要刻度之间放置小刻度来使用过的缩放更明显。在比例的日志部分,我想在[2,3,...,9] * 10 ^ e处设置蜱,其中e是附近的主要蜱。此外,0到0.1之间的范围应覆盖均匀放置的次要刻度,相隔0.01。我尝试使用matplotlib.ticker API使用以下代码来达到这样的滴答:

import numpy as np
import matplotlib.pyplot as plt
from matplotlib.ticker import LogLocator, AutoLocator

x = np.linspace(-5, 5, 100)
y = x

plt.plot(x, y)
plt.yscale('symlog', linthreshy=1e-1)

yaxis = plt.gca().yaxis
yaxis.set_minor_locator(LogLocator(subs=np.arange(2, 10)))

plt.show()

不幸的是,这不会产生我想要的东西:

enter image description here

请注意,0附近有许多次要刻度,这可能是由LogLocator引起的。此外,负轴上没有小的嘀嗒声。

如果我使用AutoLocator代替,则不会出现小的滴答声。 AutoMinorLocator仅支持均匀缩放的轴。那么我的问题是如何实现所需的刻度位置?

3 个答案:

答案 0 :(得分:13)

深入研究这个问题,我注意到很难找到一个通用的解决方案。幸运的是,我可以假设我的数据受到一些限制,因此定制的类足以解决问题:

import numpy as np
import matplotlib.pyplot as plt
from matplotlib.ticker import Locator


class MinorSymLogLocator(Locator):
    """
    Dynamically find minor tick positions based on the positions of
    major ticks for a symlog scaling.
    """
    def __init__(self, linthresh):
        """
        Ticks will be placed between the major ticks.
        The placement is linear for x between -linthresh and linthresh,
        otherwise its logarithmically
        """
        self.linthresh = linthresh

    def __call__(self):
        'Return the locations of the ticks'
        majorlocs = self.axis.get_majorticklocs()

        # iterate through minor locs
        minorlocs = []

        # handle the lowest part
        for i in xrange(1, len(majorlocs)):
            majorstep = majorlocs[i] - majorlocs[i-1]
            if abs(majorlocs[i-1] + majorstep/2) < self.linthresh:
                ndivs = 10
            else:
                ndivs = 9
            minorstep = majorstep / ndivs
            locs = np.arange(majorlocs[i-1], majorlocs[i], minorstep)[1:]
            minorlocs.extend(locs)

        return self.raise_if_exceeds(np.array(minorlocs))

    def tick_values(self, vmin, vmax):
        raise NotImplementedError('Cannot get tick locations for a '
                                  '%s type.' % type(self))


x = np.linspace(-5, 5, 100)
y = x

plt.plot(x, y)
plt.yscale('symlog', linthreshy=1e-1)

yaxis = plt.gca().yaxis
yaxis.set_minor_locator(MinorSymLogLocator(1e-1))

plt.show()

这会产生

enter image description here

请注意,此方法仅在主要刻度之间放置刻度。如果您缩放并平移图像,这将变得明显。此外,线性阈值必须明确地提供给类,因为我发现无法从轴本身轻松而稳健地读取它。

答案 1 :(得分:1)

OPs解决方案效果很好,但如果它们不是线性阈值的倍数,则不会在轴的边缘产生刻度线。我已经攻击了OP MinorSymLogLocator()类以提供以下内容(通过在设置次要刻度locatoin时添加临时主要刻度位置来填充边缘):

class MinorSymLogLocator(Locator):
    """
    Dynamically find minor tick positions based on the positions of
    major ticks for a symlog scaling.
    """
    def __init__(self, linthresh, nints=10):
        """
        Ticks will be placed between the major ticks.
        The placement is linear for x between -linthresh and linthresh,
        otherwise its logarithmically. nints gives the number of
        intervals that will be bounded by the minor ticks.
        """
        self.linthresh = linthresh
        self.nintervals = nints

    def __call__(self):
        # Return the locations of the ticks
        majorlocs = self.axis.get_majorticklocs()

        if len(majorlocs) == 1:
            return self.raise_if_exceeds(np.array([]))

        # add temporary major tick locs at either end of the current range
        # to fill in minor tick gaps
        dmlower = majorlocs[1] - majorlocs[0]    # major tick difference at lower end
        dmupper = majorlocs[-1] - majorlocs[-2]  # major tick difference at upper end

        # add temporary major tick location at the lower end
        if majorlocs[0] != 0. and ((majorlocs[0] != self.linthresh and dmlower > self.linthresh) or (dmlower == self.linthresh and majorlocs[0] < 0)):
            majorlocs = np.insert(majorlocs, 0, majorlocs[0]*10.)
        else:
            majorlocs = np.insert(majorlocs, 0, majorlocs[0]-self.linthresh)

        # add temporary major tick location at the upper end
        if majorlocs[-1] != 0. and ((np.abs(majorlocs[-1]) != self.linthresh and dmupper > self.linthresh) or (dmupper == self.linthresh and majorlocs[-1] > 0)):
            majorlocs = np.append(majorlocs, majorlocs[-1]*10.)
        else:
            majorlocs = np.append(majorlocs, majorlocs[-1]+self.linthresh)

        # iterate through minor locs
        minorlocs = []

        # handle the lowest part
        for i in xrange(1, len(majorlocs)):
            majorstep = majorlocs[i] - majorlocs[i-1]
            if abs(majorlocs[i-1] + majorstep/2) < self.linthresh:
                ndivs = self.nintervals
            else:
                ndivs = self.nintervals - 1.

            minorstep = majorstep / ndivs
            locs = np.arange(majorlocs[i-1], majorlocs[i], minorstep)[1:]
            minorlocs.extend(locs)

        return self.raise_if_exceeds(np.array(minorlocs))

    def tick_values(self, vmin, vmax):
        raise NotImplementedError('Cannot get tick locations for a '
                          '%s type.' % type(self))

答案 2 :(得分:0)

我找到了一种更简单的方法,可能适用:

我使用了ax.set_xticks方法和下面函数的输出

def gen_tick_positions(scale_start=100, scale_max=10000):

    start, finish = np.floor(np.log10((scale_start, scale_max)))
    finish += 1
    majors = [10 ** x for x in np.arange(start, finish)]
    minors = []
    for idx, major in enumerate(majors[:-1]):
        minor_list = np.arange(majors[idx], majors[idx+1], major)
        minors.extend(minor_list[1:])
    return minors, majors

对于OP的例子,你可以从ax.get_yticks()中推断出线性区域(即大约为0且不是因子10的值不同于0-1 / 10)

y_ticks = ax.get_yticks()
total_scale = list(y_ticks)

zero_point = total_scale.index(0.0)
post_zeroes = np.log10(total_scale[zero_point+1:])
first_log = []
for idx, value in enumerate(post_zeroes[:-1]):
    if 1.005 > post_zeroes[idx+1] - value > 0.995:
        first_log = total_scale[idx + zero_point]

这会为您提供一个开始值,以便放入上面的函数,scale_max是您喜欢的任何内容,例如total_scale[-1]

您可以使用first_log的正负区域生成线性刻度,然后合并列表。

lin_ticks = list(np.linspace(first_log * -1, first_log, 21))
pos_log_ticks_minors, pos_log_ticks_majors = gen_tick_positions(first_log, scale_max)
neg_log_ticks_minors = [x * -1 for x in pos_log_ticks_minors]
neg_log_ticks_majors = [x * -1 for x in pos_log_ticks_majors]

final_scale_minors = neg_log_ticks_minors + lin_ticks + pos_log_ticks_minors

The merged list can then be passed into e.g.

ax.set_yticks(final_scale_minors, minor=True)

虽然我确实发现你不需要从绘图或轴上读取线性阈值,因为当你应用“symlog”时它被指定为参数。