Как сделать квадратные подсети в 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))