Как использовать "экстент" в matplotlib ax.imshow (), не меняя положения наложенных дескрипторов ax.text ()? - PullRequest
1 голос
/ 13 марта 2020

Я пытаюсь комментировать тепловую карту. В документах matplotlib представлен пример , в котором предлагается создать вспомогательную функцию для форматирования аннотаций. Я чувствую, что должен быть более простой способ делать то, что я хочу. Я могу комментировать внутри полей тепловой карты, но эти тексты меняют положение при редактировании экстента тепловой карты . Мой вопрос заключается в том, как использовать extent в ax.imshow(...), а также ax.text(...), чтобы комментировать правильные позиции. Ниже приведен пример:

import numpy as np
import matplotlib.pyplot as plt
from matplotlib.colors import Normalize

def get_manhattan_distance_matrix(coordinates):
    shape = (coordinates.shape[0], 1, coordinates.shape[1])
    ct = coordinates.reshape(shape)
    displacement = coordinates - ct
    return np.sum(np.abs(displacement), axis=-1)

x = np.arange(11)[::-1]
y = x.copy()
coordinates = np.array([x, y]).T
distance_matrix = get_manhattan_distance_matrix(coordinates)

# print("\n .. {} COORDINATES:\n{}\n".format(coordinates.shape, coordinates))
# print("\n .. {} DISTANCE MATRIX:\n{}\n".format(distance_matrix.shape, distance_matrix))

norm = Normalize(vmin=np.min(distance_matrix), vmax=np.max(distance_matrix))

Здесь можно изменить значение extent.

extent = (np.min(x), np.max(x), np.min(y), np.max(y))
# extent = None

В соответствии с документами matplotlib , по умолчанию extent - None.

fig, ax = plt.subplots()
handle = ax.imshow(distance_matrix, cmap='plasma', norm=norm, interpolation='nearest', origin='upper', extent=extent)

kws = dict(ha='center', va='center', color='gray', weight='semibold', fontsize=5)
for i in range(len(distance_matrix)):
    for j in range(len(distance_matrix[i])):
        if i == j:
            ax.text(j, i, '', **kws)
        else:
            ax.text(j, i, distance_matrix[i, j], **kws)

plt.show()
plt.close(fig)

. Можно сгенерировать две цифры, изменив extent - просто раскомментируйте закомментированную строку и закомментируйте некомментированную строку. Ниже приведены две цифры:

Without setting extent

With setting extent

Это можно увидеть, установив extent, расположение пикселей меняется, что, в свою очередь, меняет положение маркеров ax.text(...). Есть ли простое решение, чтобы это исправить - установить произвольный extent и по-прежнему центрировать текстовые маркеры в каждом поле?

1 Ответ

1 голос
/ 13 марта 2020

Когда extent=None, эффективный экстент составляет от -0,5 до 10,5 как по x, так и по y. Итак, центры l ie на целочисленных позициях. Установка экстента от 0 до 10 не совпадает с пикселями. Вам нужно умножить на 10/11, чтобы получить их правильно.

Наилучшим подходом было бы установить extent = (np.min(x)-0.5, np.max(x)+0.5, np.min(y)-0.5, np.max(y)+0.5), чтобы вернуть центры в целочисленные позиции.

Также обратите внимание, что по умолчанию изображение отображается, начиная сверху, и ось Y перевернута. Если вы измените экстент, чтобы получить изображение в вертикальном положении, вам нужно ax.imshow(..., origin='lower'). (0,0 пикселя должен быть синим на примере графика.)

Чтобы поместить текст в центр пикселя, вы можете добавить 0,5 к горизонтальному индексу, разделить на ширину в пикселях и умножить на разницу по оси х. И аналогичный расчет для оси у. Чтобы улучшить читаемость, цвет текста можно сделать зависимым от цвета пикселя.

# ... 

extent = (np.min(x), np.max(x), np.min(y), np.max(y))
x0, x1, y0, y1 = extent
fig, ax = plt.subplots()
handle = ax.imshow(distance_matrix, cmap='plasma', norm=norm, interpolation='nearest', origin='lower', extent=extent)

kws = dict(ha='center', va='center', weight='semibold', fontsize=5)
height = len(distance_matrix)
width = len(distance_matrix[0])
for i in range(height):
    for j in range(width):
        if i != j:
            val = distance_matrix[i, j]
            ax.text(x0 + (j + 0.5) / width * (x1 - x0), y0 + (i + 0.5) / height * (y1 - y0),
                    f'{val}\n{i},{j}', color='white' if norm(val) < 0.6 else 'black', **kws)
plt.show()

example image

...