2012-05-07 63 views
16

我需要在Matplotlib中兩個覆蓋兩個具有不同Y軸刻度的數據集。數據包含正值和負值。我希望兩個軸共享一個原點,但Matplotlib默認不會對齊兩個原點。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().set_ylim() 2個對準兩個尺度進行一些計算。有更簡單的解決方案嗎?

Output from the sample above

回答

27

使用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() 

enter image description here

14

爲了確保在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) 
3

@ 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) 

enter image description here

...所以這裏是我的版本:

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]) 

原則上有無限的不同對齊零點(或其他提供的解決方案所接受的其他值)的可能性:無論您將零置於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)] 
0

我VE炮製開始從上面將對齊的任何數量的軸的溶液:

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))] 

例如: aligned axes

相關問題