matplotlib: изменить отметки оси гистограммы ndim, построенной с помощью seaborn.heatmap - PullRequest
1 голос
/ 12 апреля 2019

Мотивация:

Я пытаюсь визуализировать набор данных из многих n-мерных векторов (скажем, у меня есть 10k векторов с n = 300 измерениями). То, что я хотел бы сделать, это рассчитать гистограмму для каждого из n измерений и построить ее в виде одной линии в бинарной карте * n.

Пока у меня есть это:

import numpy as np
import matplotlib
from matplotlib import pyplot as plt
%matplotlib inline
import seaborn as sns

# sample data:
vectors = np.random.randn(10000, 300) + np.random.randn(300)

def ndhist(vectors, bins=500):
    limits = (vectors.min(), vectors.max())
    hists = []
    dims = vectors.shape[1]
    for dim in range(dims):
        h, bins = np.histogram(vectors[:, dim], bins=bins, range=limits)
        hists.append(h)
    hists = np.array(hists)
    fig = plt.figure(figsize=(16, 9))
    sns.heatmap(hists)
    axes = fig.gca()
    axes.set(ylabel='dimensions', xlabel='values')
    print(dims)
    print(limits)

ndhist(vectors)

Создает следующий вывод:

300
(-6.538069472429366, 6.52159540162285)

bad axes ticks

Проблема / Вопрос:

Как мне поменять тики осей?

  • для оси y я бы просто хотел изменить это значение на значение по умолчанию для matplotlib, чтобы оно выбирало такие хорошие тики, как 0, 50, 100, ..., 250 (бонусные баллы за 299 или 300)
  • для оси x я хотел бы преобразовать показанные индексы бинов в границы бина (слева), затем, как и выше, я бы хотел изменить это обратно на выбор по умолчанию для matplotlib некоторых "хороших" тиков, таких как -5, -2.5, 0, 2.5, 5 (бонусные баллы также включают фактические лимиты -6.538, 6.522)

Собственные попытки решения:

Я уже пробовал много вещей, подобных следующей:

def ndhist_axlabels(vectors, bins=500):
    limits = (vectors.min(), vectors.max())
    hists = []
    dims = vectors.shape[1]
    for dim in range(dims):
        h, bins = np.histogram(vectors[:, dim], bins=bins, range=limits)
        hists.append(h)
    hists = np.array(hists)
    fig = plt.figure(figsize=(16, 9))
    sns.heatmap(hists, yticklabels=False, xticklabels=False)
    axes = fig.gca()
    axes.set(ylabel='dimensions', xlabel='values')
    #plt.xticks(np.linspace(*limits, len(bins)), bins)
    plt.xticks(range(len(bins)), bins)
    axes.xaxis.set_major_locator(matplotlib.ticker.AutoLocator())
    plt.yticks(range(dims+1), range(dims+1))
    axes.yaxis.set_major_locator(matplotlib.ticker.AutoLocator())
    print(dims)
    print(limits)

ndhist_axlabels(vectors)

even worse axes ticks

Как видите, метки осей довольно неправильны. Я предполагаю, что экстент или пределы сохраняются где-то в исходной оси, но теряются при переключении обратно на AutoLocator. Буду очень признателен за толчок в правильном направлении.

Ответы [ 3 ]

1 голос
/ 12 апреля 2019

Может быть, вы думаете об этом.Для построения графических данных можно использовать imshow и получить тиканье и форматирование бесплатно.

import numpy as np
from matplotlib import pyplot as plt

# sample data:
vectors = np.random.randn(10000, 300) + np.random.randn(300)

def ndhist(vectors, bins=500):
    limits = (vectors.min(), vectors.max())
    hists = []
    dims = vectors.shape[1]

    for dim in range(dims):
        h, _ = np.histogram(vectors[:, dim], bins=bins, range=limits)
        hists.append(h)
    hists = np.array(hists)

    fig, ax = plt.subplots(figsize=(16, 9))

    extent = [limits[0], limits[-1], hists.shape[0]-0.5, -0.5]  
    im = ax.imshow(hists, extent=extent, aspect="auto")
    fig.colorbar(im)

    ax.set(ylabel='dimensions', xlabel='values')

ndhist(vectors)
plt.show()

enter image description here

0 голосов
/ 12 апреля 2019

Наконец-то вышла версия, которая сейчас работает для меня и использует AutoLocator, основанную на простом линейном отображении ...

def ndhist(vectors, bins=1000, title=None):
    t = time.time()
    limits = (vectors.min(), vectors.max())
    hists = []
    dims = vectors.shape[1]
    for dim in range(dims):
        h, bs = np.histogram(vectors[:, dim], bins=bins, range=limits)
        hists.append(h)
    hists = np.array(hists)

    fig = plt.figure(figsize=(16, 12))
    sns.heatmap(
        hists,
        yticklabels=50,
        xticklabels=False
    )

    axes = fig.gca()
    axes.set(
        ylabel=f'dimensions ({dims} total)',
        xlabel=f'values (min: {limits[0]:.4g}, max: {limits[1]:.4g}, {bins} bins)',
        title=title,
    )

    def val_to_idx(val):
        # calc (linearly interpolated) index loc for given val
        return bins*(val - limits[0])/(limits[1] - limits[0])
    xlabels = [round(l, 3) for l in limits] + [
        v for v in matplotlib.ticker.AutoLocator().tick_values(*limits)[1:-1]
    ]
    # drop auto-gen labels that might be too close to limits
    d = (xlabels[4] - xlabels[3])/3
    if (xlabels[1] - xlabels[-1]) < d:
        del xlabels[-1]
    if (xlabels[2] - xlabels[0]) < d:
        del xlabels[2]
    xticks = [val_to_idx(val) for val in xlabels]
    axes.set_xticks(xticks)
    axes.set_xticklabels([f'{l:.4g}' for l in xlabels])

    plt.show()
    print(f'histogram generated in {time.time() - t:.2f}s')

ndhist(np.random.randn(100000, 300), bins=1000, title='randn')

ndim hist

Спасибо Полу за его ответ , который дал мне идею.

Если есть более простое или более элегантное решение, мне все равно будет интересно.

0 голосов
/ 12 апреля 2019

Если вы прочитаете документы , вы заметите, что аргументы xticklabels / yticklabels перегружены, так что если вы предоставите целое число вместо строки, он будет интерпретировать аргумент как xtickevery / ytickevery и ставьте галочки только в соответствующих местах. Так что в вашем случае seaborn.heatmap(hists, yticklabels=50) исправит вашу проблему с осью Y.

enter image description here

Что касается ваших ярлыков xtick, я бы просто предоставил их явно:

xtickevery = 50 
xticklabels = ['{:.1f}'.format(b) if ii%xtickevery == 0 else '' for ii, b in enumerate(bins)]
sns.heatmap(hists, yticklabels=50, xticklabels=xticklabels)

enter image description here

...