¿Cómo hacer subplots cuadradas en matplotlib con mapas de calor?

Estoy tratando de hacer una subplot simple con un dendrogtwig en una subplot y un mapa de calor en otra, mientras mantengo los ejes cuadrados. Intento lo siguiente:

from scipy.cluster.hierarchy import linkage from scipy.cluster.hierarchy import dendrogram from scipy.spatial.distance import pdist fig = plt.figure(figsize=(7,7)) plt.subplot(2, 1, 1) cm = matplotlib.cm.Blues X = np.random.random([5,5]) pmat = pdist(X, "euclidean") linkmat = linkage(pmat) dendrogram(linkmat) plt.subplot(2, 1, 2) labels = ["a", "b", "c", "d", "e", "f"] Y = np.random.random([6,6]) plt.xticks(arange(0.5, 7.5, 1)) plt.gca().set_xticklabels(labels) plt.pcolor(Y) plt.colorbar() 

esto produce lo siguiente:

introduzca la descripción de la imagen aquí

pero el problema es que los ejes no son cuadrados, y la barra de colores se considera parte de la segunda ttwig secundaria. En su lugar, me gustaría colgar fuera de la ttwig y hacer que la caja del dendrogtwig y la caja del mapa de calor estén cuadradas y alineadas entre sí (es decir, del mismo tamaño).

Intenté usar aspect='equal' para obtener ejes cuadrados cuando se llama subplot como sugiere la documentación, pero esto arruinó la ttwig, dándole a este …

introduzca la descripción de la imagen aquí

Si trato de usar plt.axis('equal') después de cada subplot en lugar de aspect='equal' , extraña el mapa de calor pero no su cuadro delimitador (ver más abajo), mientras destruyo el dendrogtwig por completo y también desordena la alineación. de las tags xtick …. – dando lugar a este lío:

introduzca la descripción de la imagen aquí

¿Cómo se puede arreglar esto? Para resumir, estoy tratando de trazar algo muy simple: un dendrogtwig cuadrado en la subplot superior, y un mapa de calor cuadrado en la subplot inferior, con la barra de color a la derecha. nada sofisticado.

finalmente, pregunta más general: ¿hay una regla / principio general a seguir para forzar a matplotlib a hacer siempre los ejes cuadrados? No puedo pensar en un solo caso en el que no quiera ejes cuadrados, pero generalmente no es el comportamiento predeterminado. Me gustaría forzar que todas las plots sean cuadradas si es posible.

La respuesta de @HRYRY es muy buena y merece todo el crédito. Pero para terminar la respuesta acerca de alinear bien las plots cuadradas, podrías engañar a matplotlib para que piense que ambas plots tienen barras de colores, haciendo que la primera sea invisible:

 from scipy.cluster.hierarchy import linkage from scipy.cluster.hierarchy import dendrogram from scipy.spatial.distance import pdist import matplotlib from matplotlib import pyplot as plt import numpy as np from numpy import arange fig = plt.figure(figsize=(5,7)) ax1 = plt.subplot(2, 1, 1) cm = matplotlib.cm.Blues X = np.random.random([5,5]) pmat = pdist(X, "euclidean") linkmat = linkage(pmat) dendrogram(linkmat) x0,x1 = ax1.get_xlim() y0,y1 = ax1.get_ylim() ax1.set_aspect((x1-x0)/(y1-y0)) plt.subplot(2, 1, 2, aspect=1) labels = ["a", "b", "c", "d", "e", "f"] Y = np.random.random([6,6]) plt.xticks(arange(0.5, 7.5, 1)) plt.gca().set_xticklabels(labels) plt.pcolor(Y) plt.colorbar() # add a colorbar to the first plot and immediately make it invisible cb = plt.colorbar(ax=ax1) cb.ax.set_visible(False) plt.show() 

salida de código

aspect = “igual” significa que la misma longitud en el espacio de datos será la misma en el espacio de la pantalla, pero en su eje superior, los rangos de datos de xaxis y yaxis no son los mismos, por lo que no será un cuadrado. Para solucionar este problema, puede establecer el aspecto en la relación entre el rango de xaxis y el rango de yaxis:

 from scipy.cluster.hierarchy import linkage from scipy.cluster.hierarchy import dendrogram from scipy.spatial.distance import pdist import matplotlib from matplotlib import pyplot as plt import numpy as np from numpy import arange fig = plt.figure(figsize=(5,7)) ax1 = plt.subplot(2, 1, 1) cm = matplotlib.cm.Blues X = np.random.random([5,5]) pmat = pdist(X, "euclidean") linkmat = linkage(pmat) dendrogram(linkmat) x0,x1 = ax1.get_xlim() y0,y1 = ax1.get_ylim() ax1.set_aspect((x1-x0)/(y1-y0)) plt.subplot(2, 1, 2, aspect=1) labels = ["a", "b", "c", "d", "e", "f"] Y = np.random.random([6,6]) plt.xticks(arange(0.5, 7.5, 1)) plt.gca().set_xticklabels(labels) plt.pcolor(Y) plt.colorbar() 

Aquí está la salida:

introduzca la descripción de la imagen aquí

Para ubicar la barra de colores necesitamos escribir una Clase ColorBarLocator, el pad y el argumento de ancho están en unidades de píxeles,

  • pad : establece el espacio entre los ejes y es colobar
  • ancho : el ancho de la barra de colores

Reemplace plt.colorbar() con el siguiente código:

 class ColorBarLocator(object): def __init__(self, pax, pad=5, width=10): self.pax = pax self.pad = pad self.width = width def __call__(self, ax, renderer): x, y, w, h = self.pax.get_position().bounds fig = self.pax.get_figure() inv_trans = fig.transFigure.inverted() pad, _ = inv_trans.transform([self.pad, 0]) width, _ = inv_trans.transform([self.width, 0]) return [x+w+pad, y, width, h] cax = fig.add_axes([0,0,0,0], axes_locator=ColorBarLocator(ax2)) plt.colorbar(cax = cax) 

introduzca la descripción de la imagen aquí

Para agregar a las otras respuestas, debe llevar el valor absoluto de los argumentos a .set_aspect :

 x0,x1 = ax1.get_xlim() y0,y1 = ax1.get_ylim() ax1.set_aspect(abs(x1-x0)/abs(y1-y0))