Matplotlib:轴负侧的不同比例

Matplotlib: different scale on negative side of the axis

背景


我试图在一个图上显示三个变量。我根据其他一些变量使用不同颜色的线连接了这三个点。这显示在这里


问题


我想要做的是在负 x 轴上有一个不同的比例。这将帮助我提供正 x_ticks、不同的轴标签以及图像左侧线条的清晰和整洁的表示


问题



附加信息


我已经检查了有关包含多个轴的其他问题,例如this and 。然而,这些问题并没有达到目的。

使用代码

font_size = 20
plt.rcParams.update({'font.size': font_size})

fig = plt.figure()
ax = fig.add_subplot(111)
#read my_data from file or create it

for case in my_data:

    #Iterating over my_data

    if condition1 == True:
        local_linestyle = '-'
        local_color = 'r'
        local_line_alpha = 0.6
    elif condition2 == 1:
        local_linestyle = '-'
        local_color = 'b'
        local_line_alpha = 0.6
    else:
        local_linestyle = '--'
        local_color = 'g'
        local_line_alpha = 0.6

    datapoint = [case[0], case[1], case[2]]

    plt.plot(datapoint[0], 0, color=local_color)
    plt.plot(-datapoint[2], 0, color=local_color)
    plt.plot(0, datapoint[1], color=local_color)
    plt.plot([datapoint[0], 0], [0, datapoint[1]], linestyle=local_linestyle, color=local_color)
    plt.plot([-datapoint[2], 0], [0, datapoint[1]], linestyle=local_linestyle, color=local_color)
plt.show()
exit()

以下是获得所需内容的方法。此解决方案使用两个孪生 axes 对象在原点的左侧和右侧获得不同的缩放比例,然后隐藏所有证据:

import matplotlib.pyplot as plt
import matplotlib as mpl
from numbers import Number

tickkwargs = {m+k:False for k in ('bottom','top','left','right') for m in ('','label')}

p = np.zeros((10, 3, 2))
p[:,0,0] -= np.arange(10)*.1 + .5
p[:,1,1] += np.repeat(np.arange(5), 2)*.1 + .3
p[:,2,0] += np.arange(10)*.5 + 2

fig = plt.figure(figsize=(8,6))
host = fig.add_subplot(111)
par = host.twiny()

host.set_xlim(-6, 6)
par.set_xlim(-1, 1)

for ps in p:
    # mask the points with negative x values
    ppos = ps[ps[:,0] >= 0].T
    host.plot(*ppos)

    # mask the points with positive x values
    pneg = ps[ps[:,0] <= 0].T
    par.plot(*pneg)

# hide all possible ticks/notation text that could be set by the second x axis
par.tick_params(axis="both", **tickkwargs)
par.xaxis.get_offset_text().set_visible(False)

# fix the x tick labels so they're all positive
host.set_xticklabels(np.abs(host.get_xticks()))

fig.show()

输出:

这是我在上面的代码中使用的点集 p 在正常绘制时的样子:

fig = plt.figure(figsize=(8,6))
ax = fig.gca()
for ps in p:
    ax.plot(*ps.T)
fig.show()

输出:

您可以定义自定义比例,其中零以下的值与零以上的值的比例不同。

import numpy as np
from matplotlib import scale as mscale
from matplotlib import transforms as mtransforms
from matplotlib.ticker import FuncFormatter

class AsymScale(mscale.ScaleBase):
    name = 'asym'

    def __init__(self, axis, **kwargs):
        mscale.ScaleBase.__init__(self)
        self.a = kwargs.get("a", 1)

    def get_transform(self):
        return self.AsymTrans(self.a)

    def set_default_locators_and_formatters(self, axis):
        # possibly, set a different locator and formatter here.
        fmt = lambda x,pos: "{}".format(np.abs(x))
        axis.set_major_formatter(FuncFormatter(fmt))

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

        def __init__(self, a):
            mtransforms.Transform.__init__(self)
            self.a = a

        def transform_non_affine(self, x):
            return (x >= 0)*x + (x < 0)*x*self.a

        def inverted(self):
            return AsymScale.InvertedAsymTrans(self.a)

    class InvertedAsymTrans(AsymTrans):

        def transform_non_affine(self, x):
            return (x >= 0)*x + (x < 0)*x/self.a
        def inverted(self):
            return AsymScale.AsymTrans(self.a)

使用它,您将提供一个缩放参数 a 来缩放轴的负部分。

# Now that the Scale class has been defined, it must be registered so
# that ``matplotlib`` can find it.
mscale.register_scale(AsymScale)

import matplotlib.pyplot as plt
fig, ax = plt.subplots()

ax.plot([-2, 0, 5], [0,1,0])
ax.set_xscale("asym", a=2)

ax.annotate("negative axis", xy=(.25,0), xytext=(0,-30), 
            xycoords = "axes fraction", textcoords="offset points", ha="center")
ax.annotate("positive axis", xy=(.75,0), xytext=(0,-30), 
            xycoords = "axes fraction", textcoords="offset points", ha="center")
plt.show()

关于需要什么 xticks 和标签的问题不是很清楚,所以我暂时忽略了它。

如其他答案所示,导出 mscale.ScaleBase 的 class 的方法对于您的目的而言可能过于复杂。 您可以将两个比例变换函数传递给 set_xscaleset_yscale,如下所示。

def get_scale(a=1):  # a is the scale of your negative axis
    def forward(x):
        x = (x >= 0) * x + (x < 0) * x * a
        return x

    def inverse(x):
        x = (x >= 0) * x + (x < 0) * x / a
        return x

    return forward, inverse

fig, ax = plt.subplots()

forward, inverse = get_scale(a=3)
ax.set_xscale('function', functions=(forward, inverse))  # this is for setting x axis

# do plotting

可以在 this doc 中找到更多示例。