Matplotlib轴有两个比例共享原点

lys*_*ing 23 axis matplotlib scale

我需要在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()
Run Code Online (Sandbox Code Playgroud)

我想可以执行一些计算,.get_ylim()并且.set_ylim()两个对齐两个标度.有更简单的解决方案吗?

上面样本的输出

HYR*_*YRY 38

使用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()
Run Code Online (Sandbox Code Playgroud)

在此输入图像描述


dre*_*cko 23

为了确保维持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)
Run Code Online (Sandbox Code Playgroud)


Tim*_*Tim 8

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

  1. 始终将 0 放置在两个 y 轴的图表中间
  2. 有点花哨,并且在某种程度上保留了正负比,见下文
def align_yaxis(ax1, ax2):
    y_lims = numpy.array([ax.get_ylim() for ax in [ax1, ax2]])

    # force 0 to appear on both 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 both 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 = numpy.array([numpy.min(y_lims_normalized), numpy.max(y_lims_normalized)])

    # denormalize combined range to get new axes
    new_lim1, new_lim2 = y_new_lims_normalized * y_mags
    ax1.set_ylim(new_lim1)
    ax2.set_ylim(new_lim2)
Run Code Online (Sandbox Code Playgroud)


Pie*_*ton 6

当绘制以下两个点序列时,@ 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)
Run Code Online (Sandbox Code Playgroud)

在此处输入图片说明

...所以这是我的版本:

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])
Run Code Online (Sandbox Code Playgroud)

原则上,对齐零(或其他提供的解决方案接受的其他值)的可能性无穷无尽:在y轴上放置零的任何地方,都可以缩放两个系列中的每一个以使其适合。我们只是选择一个位置,以便在转换之后,两个覆盖相同高度的垂直间隔。或者换句话说,与未对齐图相比,我们将具有相同因子的它们最小化。(这确实不是意味着0是在情节的一半:如果一个情节都是负的,其他一切积极会出现这种情况如。)

numpy版本:

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)]
Run Code Online (Sandbox Code Playgroud)