Как сделать квадратные подсети в matplotlib с помощью тепловых карт?

Я пытаюсь сделать простой подзаговор с дендрограммой в одном подзаголовке и тепловой карте в другом, сохраняя квадратные оси. Я пробую следующее:

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

это дает следующее:

enter image description here

но проблемы состоят в том, что оси не являются квадратными, а цветная панель считается частью второго подзаголовка. Я бы хотел, чтобы вместо этого выстроились за пределами сюжета, и сделайте так, чтобы ящик с дендрограммой и ящик с теплоизоляцией были квадратными и выровнены друг с другом (т.е. Того же размера).

Я попытался использовать aspect='equal', чтобы получить квадратные оси при вызове subplot, как предлагает документация, но это разрушило сюжет, давая это...

enter image description here

если я попытаюсь использовать plt.axis('equal') после каждого подзаголовка вместо aspect='equal', он странно квадратизирует тепловую карту, но не ее ограничивающий прямоугольник (см. ниже), одновременно уничтожая дендрограмму, а также испортил выравнивание ярлыков xtick.... - порождая этот беспорядок:

enter image description here

как это можно зафиксировать? Подводя итог, я пытаюсь построить что-то очень простое: квадратную дендрограмму в верхнем подзаголовке и квадратную карту тепла в нижней подзаголовке с цветной полосой справа. ничего необычного.

наконец, более общий вопрос: существует ли общее правило/принцип, чтобы следовать, чтобы заставить matplotlib всегда делать оси квадратными? Я не могу придумать ни одного случая, когда мне не нужны квадратные оси, но обычно это не поведение по умолчанию. Я бы хотел, чтобы все графики были квадратными, если это возможно.

Ответ 1

@HYRY ответ очень хорош и заслуживает всякого кредита. Но чтобы закончить ответ о выравнивании квадратов сюжетов, вы могли бы обмануть matplotlib, подумав, что оба сюжета имеют колориметры, только делая первый невидимым:

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

Ответ 2

aspect = "equal" означает, что одна и та же длина в пространстве данных будет одинаковой длины в пространстве экрана, но в вашем верхнем топе диапазоны данных xaxis и yaxis не совпадают, поэтому он не будет квадратом. Чтобы устранить эту проблему, вы можете установить аспект в отношении диапазона xaxis и диапазона 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()

Вот результат:

enter image description here

Чтобы указать цветную панель, в которой нужно написать класс ColorBarLocator, аргумент pad и width находится в пиксельной единице,

  • pad: установите пространство между осями и colobar
  • ширина: ширина цветной панели

замените plt.colorbar() на следующий код:

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

Ответ 3

Чтобы добавить к другим ответам, вам необходимо принять абсолютное значение аргументов .set_aspect:

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