如何使用Matplotlib对齐两个y轴刻度的网格线?

Art*_*örk 35 python plot matplotlib seaborn

我正在绘制y轴上具有不同单位的两个数据集.有没有办法让刻度线和网格线在两个y轴上对齐?

第一张图片显示了我得到的内容,第二张图片显示了我想要获得的内容.

这是我用来绘制的代码:

import seaborn as sns
import numpy as np
import pandas as pd

np.random.seed(0)
fig = plt.figure()
ax1 = fig.add_subplot(111)
ax1.plot(pd.Series(np.random.uniform(0, 1, size=10)))
ax2 = ax1.twinx()
ax2.plot(pd.Series(np.random.uniform(10, 20, size=10)), color='r')
Run Code Online (Sandbox Code Playgroud)

不需要的行为示例

想要行为的例子

Leo*_*Leo 28

我不确定这是否是最漂亮的方法,但它确实用一行修复它:

import matplotlib.pyplot as plt
import seaborn as sns
import numpy as np
import pandas as pd

np.random.seed(0)
fig = plt.figure()
ax1 = fig.add_subplot(111)
ax1.plot(pd.Series(np.random.uniform(0, 1, size=10)))
ax2 = ax1.twinx()
ax2.plot(pd.Series(np.random.uniform(10, 20, size=10)), color='r')

# ADD THIS LINE
ax2.set_yticks(np.linspace(ax2.get_yticks()[0], ax2.get_yticks()[-1], len(ax1.get_yticks())))

plt.show()
Run Code Online (Sandbox Code Playgroud)

  • 不要太挑剔,但蓝线现在在网格下面,红线在网格上方.所以我在ax2.set_yticks行之后添加了"ax2.grid(None)"并得到相同的刻度线和网格线,但现在这两行都在网格之上. (8认同)

arn*_*ldo 10

我可以通过ax.grid(None)在其中一个网格轴上停用来解决它:

import matplotlib.pyplot as plt
import seaborn as sns
import numpy as np
import pandas as pd

fig = plt.figure()
ax1 = fig.add_subplot(111)
ax1.plot(pd.Series(np.random.uniform(0, 1, size=10)))
ax2 = ax1.twinx()
ax2.plot(pd.Series(np.random.uniform(10, 20, size=10)), color='r')
ax2.grid(None)

plt.show()
Run Code Online (Sandbox Code Playgroud)

图结果

  • 不幸的是,这会从一个轴上移除网格,而不是对齐刻度线和网格.在您的示例中,碰巧两个轴上的刻度位于相同的位置,但如果另一个轴在不同的位置,则该轴的刻度将不与网格对齐. (4认同)
  • 运行你的代码几次,你就会明白我的意思。 (3认同)

Jas*_*son 10

我创建了一种方法来对齐多个 y 轴(可能超过 2 个)的刻度,不同轴上的刻度可能不同。

下图是一个示例图: 在此输入图像描述

有 3 个 y 轴,左侧一个蓝色,右侧一个绿色和一个红色。3 条曲线以相应的颜色绘制在 y 轴上。请注意,它们都有非常不同的数量级。

  • 左图:未对齐。
  • 中图:在每个 y 轴的(大约)下限处对齐。
  • 右图:按指定值对齐:0蓝色、2.2*1e8红色和44绿色。这些是任意选择的。

我正在做的是将每个 y 数组缩放到 1-100 的范围内,然后将所有缩放后的 y 值合并到一个数组中,使用该数组创建一组新的刻度MaxNLocator。然后使用相应的缩放因子缩小这组新的刻度,以获得每个轴的新刻度。如果需要某种特定的对齐方式,则 y 数组在缩放之前会移动,然后再向后移动。

完整代码在这里(关键函数是alignYaxes()):

import matplotlib.pyplot as plt
import numpy as np

def make_patch_spines_invisible(ax):
    '''Used for creating a 2nd twin-x axis on the right/left

    E.g.
        fig, ax=plt.subplots()
        ax.plot(x, y)
        tax1=ax.twinx()
        tax1.plot(x, y1)
        tax2=ax.twinx()
        tax2.spines['right'].set_position(('axes',1.09))
        make_patch_spines_invisible(tax2)
        tax2.spines['right'].set_visible(True)
        tax2.plot(x, y2)
    '''

    ax.set_frame_on(True)
    ax.patch.set_visible(False)
    for sp in ax.spines.values():
        sp.set_visible(False)

def alignYaxes(axes, align_values=None):
    '''Align the ticks of multiple y axes

    Args:
        axes (list): list of axes objects whose yaxis ticks are to be aligned.
    Keyword Args:
        align_values (None or list/tuple): if not None, should be a list/tuple
            of floats with same length as <axes>. Values in <align_values>
            define where the corresponding axes should be aligned up. E.g.
            [0, 100, -22.5] means the 0 in axes[0], 100 in axes[1] and -22.5
            in axes[2] would be aligned up. If None, align (approximately)
            the lowest ticks in all axes.
    Returns:
        new_ticks (list): a list of new ticks for each axis in <axes>.

        A new sets of ticks are computed for each axis in <axes> but with equal
        length.
    '''
    from matplotlib.pyplot import MaxNLocator

    nax=len(axes)
    ticks=[aii.get_yticks() for aii in axes]
    if align_values is None:
        aligns=[ticks[ii][0] for ii in range(nax)]
    else:
        if len(align_values) != nax:
            raise Exception("Length of <axes> doesn't equal that of <align_values>.")
        aligns=align_values

    bounds=[aii.get_ylim() for aii in axes]

    # align at some points
    ticks_align=[ticks[ii]-aligns[ii] for ii in range(nax)]

    # scale the range to 1-100
    ranges=[tii[-1]-tii[0] for tii in ticks]
    lgs=[-np.log10(rii)+2. for rii in ranges]
    igs=[np.floor(ii) for ii in lgs]
    log_ticks=[ticks_align[ii]*(10.**igs[ii]) for ii in range(nax)]

    # put all axes ticks into a single array, then compute new ticks for all
    comb_ticks=np.concatenate(log_ticks)
    comb_ticks.sort()
    locator=MaxNLocator(nbins='auto', steps=[1, 2, 2.5, 3, 4, 5, 8, 10])
    new_ticks=locator.tick_values(comb_ticks[0], comb_ticks[-1])
    new_ticks=[new_ticks/10.**igs[ii] for ii in range(nax)]
    new_ticks=[new_ticks[ii]+aligns[ii] for ii in range(nax)]

    # find the lower bound
    idx_l=0
    for i in range(len(new_ticks[0])):
        if any([new_ticks[jj][i] > bounds[jj][0] for jj in range(nax)]):
            idx_l=i-1
            break

    # find the upper bound
    idx_r=0
    for i in range(len(new_ticks[0])):
        if all([new_ticks[jj][i] > bounds[jj][1] for jj in range(nax)]):
            idx_r=i
            break

    # trim tick lists by bounds
    new_ticks=[tii[idx_l:idx_r+1] for tii in new_ticks]

    # set ticks for each axis
    for axii, tii in zip(axes, new_ticks):
        axii.set_yticks(tii)

    return new_ticks

def plotLines(x, y1, y2, y3, ax):

    ax.plot(x, y1, 'b-')
    ax.tick_params('y',colors='b')

    tax1=ax.twinx()
    tax1.plot(x, y2, 'r-')
    tax1.tick_params('y',colors='r')

    tax2=ax.twinx()
    tax2.spines['right'].set_position(('axes',1.2))
    make_patch_spines_invisible(tax2)
    tax2.spines['right'].set_visible(True)
    tax2.plot(x, y3, 'g-')
    tax2.tick_params('y',colors='g')

    ax.grid(True, axis='both')

    return ax, tax1, tax2

#-------------Main---------------------------------
if __name__=='__main__':

    # craft some data to plot
    x=np.arange(20)
    y1=np.sin(x)
    y2=x/1000+np.exp(x)
    y3=x+x**2/3.14

    figure=plt.figure(figsize=(12,4),dpi=100)

    ax1=figure.add_subplot(1, 3, 1)
    axes1=plotLines(x, y1, y2, y3, ax1)
    ax1.set_title('No alignment')

    ax2=figure.add_subplot(1, 3, 2)
    axes2=plotLines(x, y1, y2, y3, ax2)
    alignYaxes(axes2)
    ax2.set_title('Default alignment')

    ax3=figure.add_subplot(1, 3, 3)
    axes3=plotLines(x, y1, y2, y3, ax3)
    alignYaxes(axes3, [0, 2.2*1e8, 44])
    ax3.set_title('Specified alignment')

    figure.tight_layout()
    figure.show()
Run Code Online (Sandbox Code Playgroud)


Sco*_*ard 8

我写了这个函数,它采用Matplotlib轴对象ax1,ax2和浮动minresax1 minresax2:

def align_y_axis(ax1, ax2, minresax1, minresax2):
    """ Sets tick marks of twinx axes to line up with 7 total tick marks

    ax1 and ax2 are matplotlib axes
    Spacing between tick marks will be a factor of minresax1 and minresax2"""

    ax1ylims = ax1.get_ybound()
    ax2ylims = ax2.get_ybound()
    ax1factor = minresax1 * 6
    ax2factor = minresax2 * 6
    ax1.set_yticks(np.linspace(ax1ylims[0],
                               ax1ylims[1]+(ax1factor -
                               (ax1ylims[1]-ax1ylims[0]) % ax1factor) %
                               ax1factor,
                               7))
    ax2.set_yticks(np.linspace(ax2ylims[0],
                               ax2ylims[1]+(ax2factor -
                               (ax2ylims[1]-ax2ylims[0]) % ax2factor) %
                               ax2factor,
                               7))
Run Code Online (Sandbox Code Playgroud)

它计算并设置刻度,使得有七个刻度.最低刻度对应于当前最低刻度并增加最高刻度,使得每个刻度之间的间隔是minrexax1或minrexax2的整数倍.

为了使其一般化,您可以通过更改7您看到的刻度总数来设置所需的刻度总数,并更改6为刻度总数减1.

我将一个pull请求放入matplotlib.ticker.LinearLocator中:

https://github.com/matplotlib/matplotlib/issues/6142

在未来(也许是Matplotlib 2.0?),试试:

import matplotlib.ticker
nticks = 11
ax1.yaxis.set_major_locator(matplotlib.ticker.LinearLocator(nticks))
ax2.yaxis.set_major_locator(matplotlib.ticker.LinearLocator(nticks))
Run Code Online (Sandbox Code Playgroud)

这应该只是工作,并为两个y轴选择方便的滴答.