superficies punto matrices graficos graficas graficar ejes dispersion dibujar diagrama matplotlib scale axis

punto - matplotlib python 3



Eje Matplotlib con dos escalas de origen compartido (4)

use la función 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()

Necesito dos conjuntos de datos superpuestos con diferentes escalas del eje Y en Matplotlib. Los datos contienen valores positivos y negativos. Quiero que los dos ejes compartan un origen, pero Matplotlib no alinea las dos escalas por defecto.

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

Supongo que es posible realizar algunos cálculos con .get_ylim() y .set_ylim() dos alinean las dos escalas. ¿Hay una solución más fácil?


Para garantizar que se mantengan los límites y (para que no haya puntos de datos desplazados de la trama), y para equilibrar el ajuste de ambos ejes y, realicé algunas adiciones a la respuesta de @ 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)


La respuesta de @drevicko falla para mí al trazar las siguientes dos secuencias de puntos:

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)

... así que aquí está mi versión:

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

En principio, existen infinitas posibilidades diferentes de alinear los ceros (u otros valores, que las otras soluciones proporcionadas aceptan): dondequiera que coloque cero en el eje y, puede acercar cada una de las dos series para que encaje. Simplemente escogemos la posición de manera que, después de la transformación, los dos cubran un intervalo vertical de la misma altura. O en otros términos, los minimizamos de un mismo factor en comparación con el gráfico no alineado. (Esto no significa que 0 está en la mitad de la trama: esto sucederá, por ejemplo, si una gráfica es negativa y la otra es positiva).

Numpy versión:

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


He preparado una solución a partir de lo anterior que alineará cualquier número de ejes:

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

ejemplo: