如何在 Matplotlib 中注册倒数 (1/x) 比例?

问题描述 投票:0回答:2

目前,我从我的 Python 脚本中得到这个图:

频率-周期关系不是线性的,它是倒数,是

period = 1./frequency
或者,如果表示为幂,则为
period = frequency**(-1)
。辅助 x 轴刻度在我的脚本中是线性的,这就是为什么基于频率 ~0.123365408 Hz 的红色虚线显示错误的周期,它应该显示 ~8.106 s,但它显示 ~8.155 s。所以,我需要为辅助 x 轴注册一个新的刻度,但我不知道该怎么做。

我在这里发现了类似的问题: 使用 matplotlib/python 的平方根尺度

基本一样,因为根标度是0.5的幂,而倒数标度是(-1)的幂。所以,我已经尝试用那个解决方案替换两者

return np.array(a)**0.5

return np.array(a)**2

return np.array(a)**(-1)

并将 SquareRoot 重命名为 Reciprocal。 但这对我没有帮助,我的辅助 x 轴上出现了一些奇怪的东西(没有刻度,只有一个刻度标签)。我想,我还必须处理新秤的滴答声。然后,我发现了这个问题: python 1/x 绘图比例格式,刻度位置

但我不明白如何合并这两个解决方案。有人可以帮我吗?提前致谢!

这是相关代码摘录:

import matplotlib.pyplot as plt
import matplotlib.scale as mscale
import matplotlib.transforms as mtransforms
import matplotlib.ticker as ticker

class ReciprocalScale(mscale.ScaleBase):
    """
    ScaleBase class for generating reciprocal scale.
    """

    name = 'reciprocal'

    def __init__(self, axis, **kwargs):
        mscale.ScaleBase.__init__(self)

    def set_default_locators_and_formatters(self, axis):
        axis.set_major_locator(ticker.AutoLocator())
        axis.set_major_formatter(ticker.ScalarFormatter())
        axis.set_minor_locator(ticker.NullLocator())
        axis.set_minor_formatter(ticker.NullFormatter())

    def limit_range_for_scale(self, vmin, vmax, minpos):
        return  max(0., vmin), vmax

    class ReciprocalTransform(mtransforms.Transform):
        input_dims = 1
        output_dims = 1
        is_separable = True

        def transform_non_affine(self, a): 
            return np.array(a)**(-1)

        def inverted(self):
            return ReciprocalScale.InvertedReciprocalTransform()

    class InvertedReciprocalTransform(mtransforms.Transform):
        input_dims = 1
        output_dims = 1
        is_separable = True

        def transform(self, a):
            return np.array(a)**(-1)

        def inverted(self):
            return ReciprocalScale.ReciprocalTransform()

    def get_transform(self):
        return self.ReciprocalTransform()

mscale.register_scale(ReciprocalScale)

ax2 = ax1.twiny()
ax2.set_xscale('reciprocal')
ax2.set_xticks(np.arange(pdm_freq[0],pdm_freq[-1])**(-1))
ax2.minorticks_on()
ax2.set_xlim((1./pdm_freq[0]), (1./pdm_freq[-1]))
ax2.set_xlabel('period [s]')

这就是我得到的:

python matplotlib scale
2个回答
2
投票

耶!我自己弄明白了。 :) 我猜对了,我必须更改

set_default_locators_and_formatters()
limit_range_for_scale()
中的
class ReciprocalScale()
。所以,最后它实际上是我问题中提到的两个案例的合并。

这是新的代码摘录:

import matplotlib.pyplot as plt
import matplotlib.ticker as ticker
import matplotlib.scale as mscale
import matplotlib.transforms as mtransforms

class ReciprocalScale(mscale.ScaleBase):
    """
    ScaleBase class for generating reciprocal scale.
    """

    name = 'reciprocal'

    def __init__(self, axis, **kwargs):
        mscale.ScaleBase.__init__(self)

    def set_default_locators_and_formatters(self, axis):
        class ReciprocalLocator(ticker.Locator):
            def __init__(self, numticks = 5):
                self.numticks = numticks
            def __call__(self):
                vmin, vmax = self.axis.get_view_interval()
                ticklocs = np.reciprocal(np.linspace(1/vmax, 1/vmin, self.numticks))
                return self.raise_if_exceeds(ticklocs)
        axis.set_major_locator(ReciprocalLocator(numticks=12))

    class ReciprocalTransform(mtransforms.Transform):
        input_dims = 1
        output_dims = 1
        is_separable = True

        def transform_non_affine(self, a): 
            return np.array(a)**(-1)

        def inverted(self):
            return ReciprocalScale.InvertedReciprocalTransform()

    class InvertedReciprocalTransform(mtransforms.Transform):
        input_dims = 1
        output_dims = 1
        is_separable = True

        def transform(self, a):
            return np.array(a)**(-1)

        def inverted(self):
            return ReciprocalScale.ReciprocalTransform()

    def get_transform(self):
        return self.ReciprocalTransform()

mscale.register_scale(ReciprocalScale)

ax2 = ax1.twiny()
ax2.set_xscale('reciprocal')
ax2.set_xlim((1./pdm_freq[-1]), (1./pdm_freq[0]))
ax2.set_xlabel('period [s]')

附言但是,有一个小问题。我得到

RuntimeWarning: divide by zero encountered in reciprocal
return np.array(a)**(-1)
是正确的,但我会把它静音,因为在我的脚本中,我的输入数据永远不会有 0 Hz 频率。

这就是我用新代码得到的:


0
投票

谢谢sergiuspro的回答,确实有效。

然而,可能出现的一个问题是,当用户在交互模式下平移或缩放时,轴会翻转。

这与互惠时交换的顺序有关,导致 xmin>xmax。

为了解决这个问题,可以向轴添加回调以防止这种情况发生。

有人会像这样在代码的末尾添加一些东西:

def limit_range(evt):
    xlim = evt.get_xlim()
    xmin, xmax = xlim
    if xmin > xmax:
        evt.set_xlim(xmax, xmin)
ax.callbacks.connect('xlim_changed', limit_range)
© www.soinside.com 2019 - 2024. All rights reserved.