具有两个缩放共享原点的Matplotlib轴

具有两个缩放共享原点的Matplotlib轴,matplotlib,scale,axis,Matplotlib,Scale,Axis,我需要在Matplotlib中用不同的Y轴比例覆盖两个数据集。数据包含正值和负值。我希望两个轴共享一个原点,但默认情况下Matplotlib不会对齐两个比例 import numpy as np import matplotlib.pyplot as plt fig = plt.figure() ax1 = fig.add_subplot(111) ax2 = ax1.twinx() ax1.bar(range(6), (2, -2, 1, 0, 0, 0)) ax2.plot(range(

我需要在Matplotlib中用不同的Y轴比例覆盖两个数据集。数据包含正值和负值。我希望两个轴共享一个原点,但默认情况下Matplotlib不会对齐两个比例

import numpy as np
import matplotlib.pyplot as plt

fig = plt.figure()
ax1 = fig.add_subplot(111)
ax2 = ax1.twinx()

ax1.bar(range(6), (2, -2, 1, 0, 0, 0))
ax2.plot(range(6), (0, 2, 8, -2, 0, 0))
plt.show()
我想可以使用
执行一些计算。get_ylim()
将两个刻度对齐。有更简单的解决办法吗

使用align_yaxis()函数:

import numpy as np
import matplotlib.pyplot as plt

def align_yaxis(ax1, v1, ax2, v2):
    """adjust ax2 ylimit so that v2 in ax2 is aligned to v1 in ax1"""
    _, y1 = ax1.transData.transform((0, v1))
    _, y2 = ax2.transData.transform((0, v2))
    inv = ax2.transData.inverted()
    _, dy = inv.transform((0, 0)) - inv.transform((0, y1-y2))
    miny, maxy = ax2.get_ylim()
    ax2.set_ylim(miny+dy, maxy+dy)


fig = plt.figure()
ax1 = fig.add_subplot(111)
ax2 = ax1.twinx()

ax1.bar(range(6), (2, -2, 1, 0, 0, 0))
ax2.plot(range(6), (0, 2, 8, -2, 0, 0))

align_yaxis(ax1, 0, ax2, 0)
plt.show()

为了确保y边界保持不变(因此不会将数据点移出绘图),并平衡两个y轴的调整,我对@HYRY的答案做了一些补充:

def align_yaxis(ax1, v1, ax2, v2):
    """adjust ax2 ylimit so that v2 in ax2 is aligned to v1 in ax1"""
    _, y1 = ax1.transData.transform((0, v1))
    _, y2 = ax2.transData.transform((0, v2))
    adjust_yaxis(ax2,(y1-y2)/2,v2)
    adjust_yaxis(ax1,(y2-y1)/2,v1)

def adjust_yaxis(ax,ydif,v):
    """shift axis ax by ydiff, maintaining point v at the same location"""
    inv = ax.transData.inverted()
    _, dy = inv.transform((0, 0)) - inv.transform((0, ydif))
    miny, maxy = ax.get_ylim()
    miny, maxy = miny - v, maxy - v
    if -miny>maxy or (-miny==maxy and dy > 0):
        nminy = miny
        nmaxy = miny*(maxy+dy)/(miny+dy)
    else:
        nmaxy = maxy
        nminy = maxy*(miny+dy)/(maxy+dy)
    ax.set_ylim(nminy+v, nmaxy+v)

@在绘制以下两个点序列时,drevicko的回答对我来说是失败的:

l1 = [0.03, -0.6, 1, 0.05]
l2 = [0.8,  0.9,  1,  1.1]
fig, ax1 = plt.subplots()
ax1.plot(l1)
ax2 = ax1.twinx()
ax2.plot(l2, color='r')
align_yaxis(ax1, 0, ax2, 0)

。。。以下是我的版本:

def align_yaxis(ax1, ax2):
    """Align zeros of the two axes, zooming them out by same ratio"""
    axes = (ax1, ax2)
    extrema = [ax.get_ylim() for ax in axes]
    tops = [extr[1] / (extr[1] - extr[0]) for extr in extrema]
    # Ensure that plots (intervals) are ordered bottom to top:
    if tops[0] > tops[1]:
        axes, extrema, tops = [list(reversed(l)) for l in (axes, extrema, tops)]

    # How much would the plot overflow if we kept current zoom levels?
    tot_span = tops[1] + 1 - tops[0]

    b_new_t = extrema[0][0] + tot_span * (extrema[0][1] - extrema[0][0])
    t_new_b = extrema[1][1] - tot_span * (extrema[1][1] - extrema[1][0])
    axes[0].set_ylim(extrema[0][0], b_new_t)
    axes[1].set_ylim(t_new_b, extrema[1][1])
def align_yaxis_np(ax1, ax2):
    """Align zeros of the two axes, zooming them out by same ratio"""
    axes = np.array([ax1, ax2])
    extrema = np.array([ax.get_ylim() for ax in axes])
    tops = extrema[:,1] / (extrema[:,1] - extrema[:,0])
    # Ensure that plots (intervals) are ordered bottom to top:
    if tops[0] > tops[1]:
        axes, extrema, tops = [a[::-1] for a in (axes, extrema, tops)]

    # How much would the plot overflow if we kept current zoom levels?
    tot_span = tops[1] + 1 - tops[0]

    extrema[0,1] = extrema[0,0] + tot_span * (extrema[0,1] - extrema[0,0])
    extrema[1,0] = extrema[1,1] + tot_span * (extrema[1,0] - extrema[1,1])
    [axes[i].set_ylim(*extrema[i]) for i in range(2)]
原则上,对齐零(或其他值,其他提供的解决方案可以接受)的可能性是无限的:无论将零放置在y轴的何处,都可以缩放两个系列中的每一个,以使其适合。我们只是选择这样的位置,在转换后,两个覆盖相同高度的垂直间隔。 或者换言之,我们将它们最小化,使其具有与不对齐图相同的因子。 (这并不意味着0位于绘图的一半:例如,如果一个绘图全部为负值,而另一个绘图全部为正值,则会发生这种情况。)

Numpy版本:

def align_yaxis(ax1, ax2):
    """Align zeros of the two axes, zooming them out by same ratio"""
    axes = (ax1, ax2)
    extrema = [ax.get_ylim() for ax in axes]
    tops = [extr[1] / (extr[1] - extr[0]) for extr in extrema]
    # Ensure that plots (intervals) are ordered bottom to top:
    if tops[0] > tops[1]:
        axes, extrema, tops = [list(reversed(l)) for l in (axes, extrema, tops)]

    # How much would the plot overflow if we kept current zoom levels?
    tot_span = tops[1] + 1 - tops[0]

    b_new_t = extrema[0][0] + tot_span * (extrema[0][1] - extrema[0][0])
    t_new_b = extrema[1][1] - tot_span * (extrema[1][1] - extrema[1][0])
    axes[0].set_ylim(extrema[0][0], b_new_t)
    axes[1].set_ylim(t_new_b, extrema[1][1])
def align_yaxis_np(ax1, ax2):
    """Align zeros of the two axes, zooming them out by same ratio"""
    axes = np.array([ax1, ax2])
    extrema = np.array([ax.get_ylim() for ax in axes])
    tops = extrema[:,1] / (extrema[:,1] - extrema[:,0])
    # Ensure that plots (intervals) are ordered bottom to top:
    if tops[0] > tops[1]:
        axes, extrema, tops = [a[::-1] for a in (axes, extrema, tops)]

    # How much would the plot overflow if we kept current zoom levels?
    tot_span = tops[1] + 1 - tops[0]

    extrema[0,1] = extrema[0,0] + tot_span * (extrema[0,1] - extrema[0,0])
    extrema[1,0] = extrema[1,1] + tot_span * (extrema[1,0] - extrema[1,1])
    [axes[i].set_ylim(*extrema[i]) for i in range(2)]

我已经制定了一个解决方案,从上面开始,它将对齐任意数量的轴:

def align_yaxis_np(axes):
    """Align zeros of the two axes, zooming them out by same ratio"""
    axes = np.array(axes)
    extrema = np.array([ax.get_ylim() for ax in axes])

    # reset for divide by zero issues
    for i in range(len(extrema)):
        if np.isclose(extrema[i, 0], 0.0):
            extrema[i, 0] = -1
        if np.isclose(extrema[i, 1], 0.0):
            extrema[i, 1] = 1

    # upper and lower limits
    lowers = extrema[:, 0]
    uppers = extrema[:, 1]

    # if all pos or all neg, don't scale
    all_positive = False
    all_negative = False
    if lowers.min() > 0.0:
        all_positive = True

    if uppers.max() < 0.0:
        all_negative = True

    if all_negative or all_positive:
        # don't scale
        return

    # pick "most centered" axis
    res = abs(uppers+lowers)
    min_index = np.argmin(res)

    # scale positive or negative part
    multiplier1 = abs(uppers[min_index]/lowers[min_index])
    multiplier2 = abs(lowers[min_index]/uppers[min_index])

    for i in range(len(extrema)):
        # scale positive or negative part based on which induces valid
        if i != min_index:
            lower_change = extrema[i, 1] * -1*multiplier2
            upper_change = extrema[i, 0] * -1*multiplier1
            if upper_change < extrema[i, 1]:
                extrema[i, 0] = lower_change
            else:
                extrema[i, 1] = upper_change

        # bump by 10% for a margin
        extrema[i, 0] *= 1.1
        extrema[i, 1] *= 1.1

    # set axes limits
    [axes[i].set_ylim(*extrema[i]) for i in range(len(extrema))]
def align\u yaxis\u np(轴):
“”“将两个轴的零点对齐,按相同的比例缩小”
轴=np.阵列(轴)
extrema=np.array([ax.get_ylim()表示轴中的ax])
#重置为零除问题
对于范围内的i(len(极值)):
如果np.isclose(极值[i,0],0.0):
极值[i,0]=-1
如果np.isclose(极值[i,1],0.0):
极值[i,1]=1
#上限和下限
下限=极值[:,0]
上限=极值[:,1]
#如果全部为pos或neg,则不要缩放
全阳性=假
全部否定=错误
如果降低.min()>0.0:
全部为正=真
如果uppers.max()小于0.0:
全部为负=真
如果全部为负或全部为正:
#不要缩放
返回
#选择“最中心”轴
res=abs(上部+下部)
最小索引=np.argmin(res)
#缩放正或负部分
乘数1=abs(上[最小指数]/下[最小指数])
乘数2=abs(降低[最小指数]/升高[最小指数])
对于范围内的i(len(极值)):
#缩放正或负的部分,根据该部分导出有效的
如果我min_索引:
下限变化=极值[i,1]*-1*乘法器2
上限变化=极值[i,0]*-1*乘法器1
如果上限变化<极值[i,1]:
极值[i,0]=下限变化
其他:
极值[i,1]=上限变化
#增加10%的利润
极值[i,0]*=1.1
极值[i,1]*=1.1
#设置轴限制
[轴[i]。为范围(len(extrema))中的i设置_ylim(*extrema[i])]
4个随机序列的示例(您可以在4组单独的y轴标签上看到离散范围):

此处的其他答案似乎过于复杂,不一定适用于所有场景(例如,ax1为负,ax2为正)。有两种简单的方法始终有效:

总是在图的中间放置0个y轴
  • 有点花哨,并保留了正负比,见下文
  • 的解决方案适用于两个以上的轴:

    import numpy as np
    
    def align_yaxis(axes): 
        y_lims = np.array([ax.get_ylim() for ax in axes])
    
        # force 0 to appear on all axes, comment if don't need
        y_lims[:, 0] = y_lims[:, 0].clip(None, 0)
        y_lims[:, 1] = y_lims[:, 1].clip(0, None)
    
        # normalize all axes
        y_mags = (y_lims[:,1] - y_lims[:,0]).reshape(len(y_lims),1)
        y_lims_normalized = y_lims / y_mags
    
        # find combined range
        y_new_lims_normalized = np.array([np.min(y_lims_normalized), np.max(y_lims_normalized)])
    
        # denormalize combined range to get new axes
        new_lims = y_new_lims_normalized * y_mags
        for i, ax in enumerate(axes):
            ax.set_ylim(new_lims[i])    
    

    我需要对齐两个子地块,但不在它们的零处。其他的解决方案对我来说不太管用

    我的程序的主代码如下所示。子地块未对齐。此外,我只更改
    align_yaxis
    功能,并保持所有其他代码不变

    import matplotlib.pyplot as plt
    
    def align_yaxis(ax1, v1, ax2, v2):
      return 0
    
    x  = range(10)
    y1 = [3.2, 1.3, -0.3, 0.4, 2.3, -0.9, 0.2, 0.1, 1.3, -3.4]
    y2, s = [], 100
    for i in y1:
        s *= 1 + i/100
        y2.append(s)
    
    fig = plt.figure()
    ax1 = fig.add_subplot()
    ax2 = ax1.twinx()
    
    ax1.axhline(y=0, color='k', linestyle='-', linewidth=0.5)
    ax1.bar(x, y1, color='tab:blue')
    ax2.plot(x, y2, color='tab:red')
    
    fig.tight_layout()
    align_yaxis(ax1, 0, ax2, 100)
    plt.show()
    

    使用@HYRY的解决方案,我得到了对齐的子图,但是第二个子图不在图中。你看不到它

    def align_yaxis(ax1, v1, ax2, v2):
        """adjust ax2 ylimit so that v2 in ax2 is aligned to v1 in ax1"""
        _, y1 = ax1.transData.transform((0, v1))
        _, y2 = ax2.transData.transform((0, v2))
        inv = ax2.transData.inverted()
        _, dy = inv.transform((0, 0)) - inv.transform((0, y1-y2))
        miny, maxy = ax2.get_ylim()
        ax2.set_ylim(miny+dy, maxy+dy)
    

    使用@drevicko的解决方案,我也得到了对齐的绘图。但是现在第一个子图不在图片中,第一个Y轴非常奇怪

    def align_yaxis(ax1, v1, ax2, v2):
        """adjust ax2 ylimit so that v2 in ax2 is aligned to v1 in ax1"""
        _, y1 = ax1.transData.transform((0, v1))
        _, y2 = ax2.transData.transform((0, v2))
        adjust_yaxis(ax2,(y1-y2)/2,v2)
        adjust_yaxis(ax1,(y2-y1)/2,v1)
    
    def adjust_yaxis(ax,ydif,v):
        """shift axis ax by ydiff, maintaining point v at the same location"""
        inv = ax.transData.inverted()
        _, dy = inv.transform((0, 0)) - inv.transform((0, ydif))
        miny, maxy = ax.get_ylim()
        miny, maxy = miny - v, maxy - v
        if -miny>maxy or (-miny==maxy and dy > 0):
            nminy = miny
            nmaxy = miny*(maxy+dy)/(miny+dy)
        else:
            nmaxy = maxy
            nminy = maxy*(miny+dy)/(maxy+dy)
        ax.set_ylim(nminy+v, nmaxy+v)
    
    

    所以我对@drevicko的解决方案进行了一些调整,得到了我想要的

    def align_yaxis(ax1, v1, ax2, v2):
        """adjust ax2 ylimit so that v2 in ax2 is aligned to v1 in ax1"""
        _, y1 = ax1.transData.transform((0, v1))
        _, y2 = ax2.transData.transform((0, v2))
        adjust_yaxis(ax1,(y2 - y1)/2,v1)
        adjust_yaxis(ax2,(y1 - y2)/2,v2)
    
    def adjust_yaxis(ax,ydif,v):
        """shift axis ax by ydiff, maintaining point v at the same location"""
        inv = ax.transData.inverted()
        _, dy = inv.transform((0, 0)) - inv.transform((0, ydif))
        miny, maxy = ax.get_ylim()
    
        nminy = miny - v + dy - abs(dy)
        nmaxy = maxy - v + dy + abs(dy)
        ax.set_ylim(nminy+v, nmaxy+v)
    

    这可能不是您想要的,但这帮助我将整数排列在两个不同的垂直轴上:

    ax1.set_ylim(0,4000)
    ax2.set_ylim(0,120)
    ax2.set_yticks(np.linspace(ax2.get_yticks()[0], ax2.get_yticks()[-1], len(ax1.get_yticks())))
    
    

    请您对if/else声明添加评论。我发现这种方法仍然会切断数据。如果不查看您的数据,很难做到这一点(从而找出数据被切断的原因)。你能提供更多的信息吗?也许调试并建议编辑?(在这里ping我,以防你编辑,这样我就可以接受它-代码编辑通常不被接受!)谢谢今天稍后我将整理一个可复制的示例。如果您能解释if/else和rescalingok的逻辑,那就太好了,
    if
    基本上决定了
    miny
    maxy
    的绝对值是否更大(
    abs(miny)
    只有当它为负值时才更大)。换句话说,它离0点更远(实际上,
    v
    -点,因为你可以在某个值上对齐)。@devicko:这里很难给出一个可复制的例子,所以我提出了一个新问题