【问题标题】:How to place minor ticks on symlog scale?如何在符号刻度上放置小刻度?
【发布时间】:2013-12-09 12:38:20
【问题描述】:

我使用 matplotlib 的 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()

不幸的是,这并没有产生我想要的:

请注意,在 0 附近有许多次要刻度,这可能是由于 LogLocator。此外,负轴上没有小刻度。

如果我改用和AutoLocator,则不会出现小勾号。 AutoMinorLocator 只支持均匀缩放的轴。因此,我的问题是如何实现所需的刻度位置?

【问题讨论】:

  • 我做了一些比接受的答案更简单的事情,这可能适用于我需要的示例。我使用了 ax.set_xticks 方法

标签: python matplotlib


【解决方案1】:

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

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

这会产生

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

【讨论】:

  • 能否在上游打开 PR 以将其添加到 mpl 代码库中?
  • 直接从图中的数据中获取阈值,我使用的是:LN = matplotlib.lines.Line2D threshold = 10**min([int(round(np.log(np.min(np.abs(line.get_xdata() if isinstance(line,LN) else line.get_offsets())))))-2 for line in ax.lines+ax.collections])
【解决方案2】:

OPs 解决方案运行良好,但如果坐标轴边缘不是线性阈值的倍数,则不会在坐标轴边缘生成刻度线。我已经破解了 OPs MinorSymLogLocator() 类以提供以下内容(通过在设置次要刻度位置时添加临时主要刻度位置来填充边缘):

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

【讨论】:

    【解决方案3】:

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

    我使用 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() 推断线性区域(即大约为零且不是 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”时被指定为参数。

    【讨论】:

      【解决方案4】:

      只是对大卫的回答进行了一些补充,解决了马特也强调的问题。 请注意,类 matplotlib.scale.SymmetricalLogScale 现在也有一个子参数,尽管在阈值之外和阈值内都没有。如果您有建议和更正,请发表评论。

      
              majorlocs = self.axis.get_majorticklocs()
              # my changes to previous solution
              # this adds one majortickloc below and above the axis range
              # to extend the minor ticks outside the range of majorticklocs
              # bottom of the axis (low values)
              first_major = majorlocs[0]
              if first_major == 0:
                  outrange_first = -self.linthresh
              else:
                  outrange_first = first_major * float(10) ** (- np.sign(first_major))
              # top of the axis (high values)
              last_major = majorlocs[-1]
              if last_major == 0:
                  outrange_last = self.linthresh
              else:
                  outrange_last = last_major * float(10) ** (np.sign(last_major))
              majorlocs = np.concatenate(([outrange_first], majorlocs, [outrange_last]))
      

      然后跟着大卫的回答数数……

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2012-12-03
        • 2022-01-02
        • 2010-11-16
        • 2013-01-09
        • 1970-01-01
        • 2013-02-21
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多