Matplotlib, как построить 1 цветную полосу для четырех 2D гистограммы - PullRequest
3 голосов
/ 29 марта 2019

Перед тем, как начать, я хочу сказать, что я пытался выполнить этот и этот пост по той же проблеме, однако они делают это с тепловыми картами imshow в отличие от 2d гистограммы, как я 'я делаю.

Вот мой код (фактические данные были заменены случайно сгенерированными данными, но суть та же):

import matplotlib.pyplot as plt
import numpy as np

def subplots_hist_2d(x_data, y_data, x_labels, y_labels, titles):
    fig, a = plt.subplots(2, 2)

    a = a.ravel()
    for idx, ax in enumerate(a):
        image = ax.hist2d(x_data[idx], y_data[idx], bins=50, range=[[-2, 2],[-2, 2]])
        ax.set_title(titles[idx], fontsize=12)
        ax.set_xlabel(x_labels[idx])
        ax.set_ylabel(y_labels[idx])
        ax.set_aspect("equal")
    cb = fig.colorbar(image[idx])
    cb.set_label("Intensity", rotation=270)

    # pad = how big overall pic is
    # w_pad = how separate they're left to right
    # h_pad = how separate they're top to bottom
    plt.tight_layout(pad=-1, w_pad=-10, h_pad=0.5)

x1, y1 = np.random.uniform(-2, 2, 10000), np.random.uniform(-2, 2, 10000)
x2, y2 = np.random.uniform(-2, 2, 10000), np.random.uniform(-2, 2, 10000)
x3, y3 = np.random.uniform(-2, 2, 10000), np.random.uniform(-2, 2, 10000)
x4, y4 = np.random.uniform(-2, 2, 10000), np.random.uniform(-2, 2, 10000)
x_data = [x1, x2, x3, x4]
y_data = [y1, y2, y3, y4]
x_labels = ["x1", "x2", "x3", "x4"]
y_labels = ["y1", "y2", "y3", "y4"]
titles = ["1", "2", "3", "4"]
subplots_hist_2d(x_data, y_data, x_labels, y_labels, titles)

И это то, что он генерирует:

Так что теперь моя проблема в том, что я не могу на всю жизнь заставить цветовую панель применить ко всем 4 гистограммам.Также по какой-то причине нижняя правая гистограмма ведет себя странно по сравнению с другими.В ссылках, которые я разместил, их методы, по-видимому, не используют a = a.ravel(), и я использую его только здесь, потому что это единственный способ, который позволяет мне отображать мои 4 гистограммы в качестве вспомогательных участков.Помогите?enter image description here

РЕДАКТИРОВАТЬ: Томас Кун, ваш новый метод фактически решил все мои проблемы, пока я не положил свои ярлыки и попытался использовать plt.tight_layout(), чтобы разобраться в перекрытиях.Кажется, что если я запишу определенные параметры в plt.tight_layout(pad=i, w_pad=0, h_pad=0), то цветовая полоса начнет плохо себя вести.Теперь я объясню свою проблему.

Я внес некоторые изменения в ваш новый метод, чтобы он соответствовал тому, что я хочу, например:

def test_hist_2d(x_data, y_data, x_labels, y_labels, titles):
    nrows, ncols = 2, 2
    fig, axes = plt.subplots(nrows, ncols, sharex=True, sharey=True)
    ##produce the actual data and compute the histograms
    mappables=[]
    for (i, j), ax in np.ndenumerate(axes):
        H, xedges, yedges = np.histogram2d(x_data[i][j], y_data[i][j], bins=50, range=[[-2, 2],[-2, 2]])
        ax.set_title(titles[i][j], fontsize=12)
        ax.set_xlabel(x_labels[i][j])
        ax.set_ylabel(y_labels[i][j])
        ax.set_aspect("equal")
        mappables.append(H)

    ##the min and max values of all histograms
    vmin = np.min(mappables)
    vmax = np.max(mappables)

    ##second loop for visualisation
    for ax, H in zip(axes.ravel(), mappables):
        im = ax.imshow(H,vmin=vmin, vmax=vmax, extent=[-2,2,-2,2])

    ##colorbar using solution from linked question
    fig.colorbar(im,ax=axes.ravel())
    plt.show()
#    plt.tight_layout
#    plt.tight_layout(pad=i, w_pad=0, h_pad=0)

Теперь, если я попытаюсь сгенерировать свои данные, в данном случае:

phi, cos_theta = get_angles(runs)

detector_x1, detector_y1, smeared_x1, smeared_y1 = detection_vectorised(1.5, cos_theta, phi)
detector_x2, detector_y2, smeared_x2, smeared_y2 = detection_vectorised(1, cos_theta, phi)
detector_x3, detector_y3, smeared_x3, smeared_y3 = detection_vectorised(0.5, cos_theta, phi)
detector_x4, detector_y4, smeared_x4, smeared_y4 = detection_vectorised(0, cos_theta, phi)

Здесь detector_x, detector_y, smeared_x, smeared_y - все списки точек данных. Теперь я поместил их в списки 2x2, чтобы их можно было распаковать соответствующим образом с помощью моей функции построения, например:

data_x = [[detector_x1, detector_x2], [detector_x3, detector_x4]]
data_y = [[detector_y1, detector_y2], [detector_y3, detector_y4]]
x_labels = [["x positions(m)", "x positions(m)"], ["x positions(m)", "x positions(m)"]]
y_labels = [["y positions(m)", "y positions(m)"], ["y positions(m)", "y positions(m)"]]
titles = [["0.5m from detector", "1.0m from detector"], ["1.5m from detector", "2.0m from detector"]]

Теперь я запускаю свой код с

test_hist_2d(data_x, data_y, x_labels, y_labels, titles)

при включенном plt.show(), это дает:

enter image description here

, что замечательно, потому что с точки зрения данных и визуального представления это именно то, что я хочу, т.е. цветовая карта соответствует всем 4 гистограммам.Однако, поскольку метки перекрываются с названиями, я подумал, что просто запустил бы то же самое, но на этот раз с plt.tight_layout(pad=a, w_pad=b, h_pad=c), надеясь, что смогу решить проблему с перекрывающимися метками.Однако на этот раз не имеет значения, как я изменяю числа a, b и c, я всегда получаю свою цветную полосу, лежащую на втором столбце графиков, например:

enter image description here

Теперь изменение a только увеличивает или уменьшает общие подзаговоры, и лучшее, что я мог сделать, это настроить его с помощью plt.tight_layout(pad=-10, w_pad=-15, h_pad=0), который выглядит следующим образом

enter image description here

Таким образом, кажется, что независимо от того, что делает ваш новый метод, он заставил весь график потерять свою настраиваемость.Ваше решение, столь же прекрасное, как и решение одной проблемы, взамен создало другую.Так что было бы лучше всего сделать здесь?

Редактировать 2:

Используя fig, axes = plt.subplots(nrows, ncols, sharex=True, sharey=True, constrained_layout=True) вместе с plt.show() gives

enter image description here

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

1 Ответ

1 голос
/ 29 марта 2019

Редактировать

Как было отмечено в комментариях, самая большая проблема здесь на самом деле состоит в том, чтобы сделать цветовую полосу для многих гистограмм значимой, поскольку ax.hist2d всегда будет масштабировать данные гистограммы, которые она получает от numpy. Поэтому может быть лучше сначала рассчитать данные 2-й гистограммы, используя numpy, а затем снова использовать imshow для их визуализации. Таким образом, могут быть применены и решения связанного вопроса . Чтобы сделать проблему с нормализацией более заметной, я приложил некоторые усилия к созданию качественно различных 2-мерных гистограмм с использованием scipy.stats.multivariate_normal, который показывает, как высота гистограммы может довольно сильно измениться, даже если число выборок на каждом рисунке одинаково .

import numpy as np
import matplotlib.pyplot as plt
from matplotlib import gridspec as gs
from scipy.stats import multivariate_normal

##opening figure and axes
nrows=3
ncols=3
fig, axes = plt.subplots(nrows,ncols)

##generate some random data for the distributions
means = np.random.rand(nrows,ncols,2)
sigmas = np.random.rand(nrows,ncols,2)
thetas = np.random.rand(nrows,ncols)*np.pi*2

##produce the actual data and compute the histograms
mappables=[]
for mean,sigma,theta in zip( means.reshape(-1,2), sigmas.reshape(-1,2), thetas.reshape(-1)):

    ##the data (only cosmetics):    
    c, s = np.cos(theta), np.sin(theta)
    rot = np.array(((c,-s), (s, c)))
    cov = rot@np.diag(sigma)@rot.T
    rv = multivariate_normal(mean,cov)
    data = rv.rvs(size = 10000)

    ##the 2d histogram from numpy
    H,xedges,yedges = np.histogram2d(data[:,0], data[:,1], bins=50, range=[[-2, 2],[-2, 2]])

    mappables.append(H)

##the min and max values of all histograms
vmin = np.min(mappables)
vmax = np.max(mappables)

##second loop for visualisation
for ax,H in zip(axes.ravel(),mappables):
    im = ax.imshow(H,vmin=vmin, vmax=vmax, extent=[-2,2,-2,2])

##colorbar using solution from linked question
fig.colorbar(im,ax=axes.ravel())

plt.show()

Этот код создает такую ​​фигуру:

result of above code

Старый ответ :

Одним из способов решения вашей проблемы является явное создание пространства для вашей цветовой панели. Вы можете использовать экземпляр GridSpec, чтобы определить, насколько широкой должна быть ваша цветовая панель. Ниже вашей функции subplots_hist_2d() с несколькими модификациями. Обратите внимание, что использование tight_layout() сместило цветовую панель в забавное место, отсюда и замена. Если вы хотите, чтобы графики были ближе друг к другу, я бы порекомендовал поиграть с соотношением сторон рисунка.

def subplots_hist_2d(x_data, y_data, x_labels, y_labels, titles):

##    fig, a = plt.subplots(2, 2)
    fig = plt.figure()
    g = gs.GridSpec(nrows=2, ncols=3, width_ratios=[1,1,0.05])
    a = [fig.add_subplot(g[n,m]) for n in range(2) for m in range(2)]
    cax = fig.add_subplot(g[:,2])


##    a = a.ravel()
    for idx, ax in enumerate(a):
        image = ax.hist2d(x_data[idx], y_data[idx], bins=50, range=[[-2, 2],[-2, 2]])
        ax.set_title(titles[idx], fontsize=12)
        ax.set_xlabel(x_labels[idx])
        ax.set_ylabel(y_labels[idx])
        ax.set_aspect("equal")
##    cb = fig.colorbar(image[-1],ax=a)
    cb = fig.colorbar(image[-1], cax=cax)
    cb.set_label("Intensity", rotation=270)

    # pad = how big overall pic is
    # w_pad = how separate they're left to right
    # h_pad = how separate they're top to bottom
##    plt.tight_layout(pad=-1, w_pad=-10, h_pad=0.5)
    fig.tight_layout()

Используя эту модифицированную функцию, я получаю следующий вывод:

result of OP's code with the above function

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...