2012-05-07 11 views
16

Ho bisogno di sovrapporre due set di dati con diverse scale dell'asse Y in Matplotlib. I dati contengono sia valori positivi che negativi. Voglio che i due assi condividano un'unica origine, ma Matplotlib non allinea le due scale di default.Asse Matlock con due scale di origine condivisa

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

immagino sia possibile eseguire alcuni calcoli con .get_ylim() e .set_ylim() due allineare i due scale. C'è una soluzione più semplice?

Output from the sample above

risposta

27

utilizzare la) funzione 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

Al fine di garantire che le y-limiti sono mantenuti (quindi senza punti dati sono spostati fuori del terreno) e per bilanciare la regolazione di entrambi gli assi y, ho apportato alcune aggiunte alla risposta di @ 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

@ risposta di drevicko fallisce per me durante la stampa le seguenti due sequenze di punti:

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

... ecco la mia versione:

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

Ci sono in linea di principio infinite diverso possibilità di allineare gli zeri (o altri valori, che le altre soluzioni fornite accettano): ovunque si posizioni zero sull'asse y, è possibile ingrandire ciascuna delle due serie in modo che si adatti. Selezioniamo semplicemente la posizione in modo che, dopo la trasformazione, i due coprano un intervallo verticale della stessa altezza. O in altri termini, li riduciamo al minimo di uno stesso fattore rispetto al grafico non allineato. (questo non significa che 0 è a metà della trama: questo accadrà ad esempio se una trama è tutto negativo e l'altro tutti positivi.)

versione

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

I' ve cucinato una soluzione partendo da quanto precede che si allineerà qualsiasi numero di assi:

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

esempio: aligned axes