Seaborn Heatmap Auto-Order этикетки для сглаживания цветовых сдвигов - PullRequest
0 голосов
/ 19 марта 2019

Мне было интересно, есть ли встроенная функциональность или, по крайней мере, «умный» способ упорядочения x- и y-меток по их значениям в сочетании с тепловыми картами морского происхождения.

Скажем, неупорядоченная тепловая картавыглядит следующим образом:

unordered heatmap

Однако цель состоит в том, чтобы изменить порядок меток, имеющих сглаженные цветовые сдвиги.Впоследствии это должно выглядеть примерно так:

ordered heatmap

Спасибо за ваш совет!

С уважением

Ответы [ 2 ]

0 голосов
/ 20 марта 2019

Нужно как-то количественно определить "сглаженные смещения цвета".Для этой цели можно определить функцию стоимости.В простейшем случае это может быть сумма разностей между соседними пикселями.Если эта сумма мала, соседние пиксели имеют небольшую разницу в цвете.

Затем можно произвольно поменять местами столбцы и строки в матрице и проверить, произведена ли меньшая стоимость.Делая это итеративно, в какой-то момент получится сглаженная тепловая карта.Однако это, конечно, будет зависеть от степени случайности в начальной тепловой карте.Для полностью случайных пикселей можно ожидать не слишком большую оптимизацию.

Следующий класс реализует такую ​​оптимизацию.Это потребовало бы nrand различных начальных перестановок, и для каждой сделайте обмен niter раз.Лучший результат этого сохраняется и может быть получен через .get_opt.

import matplotlib.pyplot as plt
import numpy as np

class ReOrder():
    def __init__(self, array, nrand=2, niter=800):
        self.a = array
        self.indi = np.arange(self.a.shape[0])
        self.indj = np.arange(self.a.shape[1])
        self.i = np.arange(self.a.shape[0])
        self.j = np.arange(self.a.shape[1])
        self.nrand = nrand
        self.niter = niter

    def apply(self, a, i, j):
        return a[:,j][i,:]

    def get_opt(self):
        return self.apply(self.a, self.i, self.j)

    def get_labels(self, x=None, y=None):
        if x is None:
            x = self.indj
        if y is None:
            y = self.indi
        return np.array(x)[self.j], np.array(y)[self.i]

    def cost(self, a=None):
        if a is None:
            a = self.get_opt()
        m = a[1:-1, 1:-1]
        b = 0.5 * ((m - a[0:-2, 0:-2])**2 + \
                   (m - a[2:  , 2:  ])**2 + \
                   (m - a[0:-2, 2:  ])**2 + \
                   (m - a[2:  , 0:-2])**2) + \
            (m - a[0:-2, 1:-1])**2 + \
            (m - a[1:-1, 0:-2])**2 + \
            (m - a[2:  , 1:-1])**2 + \
            (m - a[1:-1, 2:  ])**2 
        return b.sum()

    def randomize(self):
        newj = np.random.permutation(self.a.shape[1])
        newi = np.random.permutation(self.a.shape[0])
        return newi, newj

    def compare(self, i1, j1, i2, j2, a=None):
        if a is None:
            a = self.a
        if self.cost(self.apply(a,i1,j1)) < self.cost(self.apply(a,i2,j2)):
            return i1, j1
        else:
            return i2, j2

    def rowswap(self, i, j):
        rows = np.random.choice(self.indi, replace=False, size=2)
        ir = np.copy(i)
        ir[rows] = ir[rows[::-1]]
        return ir, j

    def colswap(self, i, j):
        cols = np.random.choice(self.indj, replace=False, size=2)
        jr = np.copy(j)
        jr[cols] = jr[cols[::-1]]
        return i, jr

    def swap(self, i, j):
        ic, jc = self.rowswap(i,j)
        ir, jr = self.colswap(i,j)
        io, jo = self.compare(ic,jc, ir,jr)
        return self.compare(i,j, io,jo)

    def optimize(self, nrand=None, niter=None):
        nrand = nrand or self.nrand
        niter = niter or self.niter
        i,j = self.i, self.j
        for kk in range(niter):
            i,j = self.swap(i,j)
        self.i, self.j = self.compare(i,j, self.i, self.j)
        print(self.cost())
        for ii in range(nrand):
            i,j = self.randomize()
            for kk in range(niter):
                i,j = self.swap(i,j)
            self.i, self.j = self.compare(i,j, self.i, self.j)
            print(self.cost())
        print("finished")

Так что давайте возьмем два начальных массива,

def get_sample_ord():
    x,y = np.meshgrid(np.arange(12), np.arange(10))
    z = x+y
    j = np.random.permutation(12)
    i = np.random.permutation(10)
    return z[:,j][i,:] 

def get_sample():
    return np.random.randint(0,120,size=(10,12))

и проведем его через вышеуказанный класс.

def reorder_plot(nrand=4, niter=10000):
    fig, ((ax1, ax2),(ax3,ax4)) = plt.subplots(nrows=2, ncols=2, 
                                               constrained_layout=True)
    fig.suptitle("nrand={}, niter={}".format(nrand, niter))

    z1 = get_sample()
    r1 = ReOrder(z1)
    r1.optimize(nrand=nrand, niter=niter)
    ax1.imshow(z1)
    ax3.imshow(r1.get_opt())
    xl, yl = r1.get_labels()
    ax1.set(xticks = np.arange(z1.shape[1]),
            yticks = np.arange(z1.shape[0]),
            title=f"Start, cost={r1.cost(z1)}")
    ax3.set(xticks = np.arange(z1.shape[1]), xticklabels=xl, 
            yticks = np.arange(z1.shape[0]), yticklabels=yl, 
            title=f"Optimized, cost={r1.cost()}")

    z2 = get_sample_ord()   
    r2 = ReOrder(z2)
    r2.optimize(nrand=nrand, niter=niter)
    ax2.imshow(z2)
    ax4.imshow(r2.get_opt())
    xl, yl = r2.get_labels()
    ax2.set(xticks = np.arange(z2.shape[1]),
            yticks = np.arange(z2.shape[0]),
            title=f"Start, cost={r2.cost(z2)}")
    ax4.set(xticks = np.arange(z2.shape[1]), xticklabels=xl, 
            yticks = np.arange(z2.shape[0]), yticklabels=yl, 
            title=f"Optimized, cost={r2.cost()}")


reorder_plot(nrand=4, niter=10000)

plt.show()

enter image description here

Полностью случайная матрица (левый столбец) только сглаживается очень мало - все же она выглядит немного более отсортированной.Значения стоимости все еще довольно высоки.Однако не столь случайная матрица идеально сглажена, а стоимость значительно снижена.

0 голосов
/ 19 марта 2019

Этот второй график упорядочен по меткам оси x и y, а не по значениям. Вы не сможете получить случайные данные в виде упорядоченных данных. Вы можете отсортировать данные по значениям для одной строки и одного столбца, но остальные данные будут фиксированными. Вот код, который строит тепловую карту, отсортированную по значениям для строки 0 и столбца 0. Обратите внимание на «крест» в середине графика:

import numpy as np; np.random.seed(0)
import seaborn as sns; sns.set()

uniform_data = np.random.rand(10, 12)
df = pd.DataFrame(uniform_data)
df2 = df.sort_values(by=0).T.sort_values(by=0).T
ax = sns.heatmap(df2)

Semi-ordered heat map

...