2012-08-09 13 views
11

Estoy tratando de hacer una subtrama simple con un dendrograma en una subtrama y un mapa de calor en otra, manteniendo los ejes cuadrados. Intento lo siguiente:cómo hacer subtramas cuadradas en matplotlib con heatmaps?

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:

enter image description here

pero los problemas son que los ejes no son cuadrados, y la barra de colores se considera parte de la segunda trama secundaria. Me gustaría colgar fuera de la trama y hacer que la caja de dendrograma y la de calor estén alineadas y cuadradas (es decir, del mismo tamaño).

Intenté usar aspect='equal' para obtener ejes cuadrados cuando llamando subplot como la documentación sugiere, pero esto arruinó la trama, dando a este ...

enter image description here

si trato de usar plt.axis('equal') después de cada subtrama en lugar de aspect='equal', cuadra extrañamente el mapa de calor, pero no su cuadro delimitador (ver a continuación), mientras destruye el dendograma por completo y también arruina la alineación de las etiquetas xtick .... - dando lugar a este lío:

enter image description here

¿Cómo puede ser fijo? para resumir, intento trazar algo muy simple: un dendrograma cuadrado en la subparcela superior y un mapa de calor cuadrado en la subparcela inferior, con la barra de color a la derecha. nada sofisticado.

finalmente, pregunta más general: ¿hay una regla general/principio a seguir para forzar matplotlib a siempre hacer 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 obligar a todas las tramas a ser cuadradas si es posible.

Respuesta

9

@ respuesta de Hyry es muy buena y merece todo el crédito. Pero para acabar con la respuesta sobre el revestimiento de las parcelas cuadradas muy bien, se podía engañar a matplotlib en el pensamiento de que ambas parcelas tienen colorbars, sólo de hacer el primer 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() 

code output

13

aspect = "equal" significa que la misma longitud en el espacio de datos será la misma longitud en espacio de pantalla, pero en su hacha superior, los rangos de datos de xaxis y yaxis no son iguales, por lo que no será un cuadrado .Para solucionar este problema, puede configurar el aspecto de la relación del eje x rango y eje Y rango:

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:

enter image description here

a la ubicación de la barra de colores que necesitamos escribir una ColorBarLocator Clase, la almohadilla y el argumento anchura están en la unidad pixel,

  • almohadilla: ajustar el espacio entre los ejes y es col Obar
  • ancho: el ancho de la barra de colores

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

enter image description here

+0

Gracias por la respuesta, pero sus parcelas aún no están alineados como tramas secundarias regulares. Los cuadros delimitadores para la parte superior e inferior no están alineados. Me gustaría que se alineen verticalmente con el color que cuelga hacia la derecha, algo así como lo tienes pero no desalineado. Alguna idea sobre esto? – user248237dfsf

+1

Edité la respuesta, por favor verifíquela. – HYRY

+0

gracias por su respuesta: ¡es exactamente el resultado correcto, pero el código parece inmensamente complejo! ¿hay una manera mas facil? parece que tienes que ser uno de los desarrolladores de matplotlib para saber cómo escribir algo como esto, solo para obtener una barra de color para alinear ... – user248237dfsf

0

Para añadir a la otra respuestas, usted necesita tomar el valor absoluto de argumentos para .set_aspect:

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