Общий xlabel / ylabel для подзаголовков matplotlib

144

У меня такой сюжет:

fig,ax = plt.subplots(5,2,sharex=True,sharey=True,figsize=fig_size)

и теперь я хотел бы дать этому графику общие метки оси x и метки оси y. Под «общим» я подразумеваю, что должна быть одна большая метка по оси абсцисс под всей сеткой подзаголовков и одна большая метка по оси ординат справа. Я ничего не могу найти об этом в документации plt.subplots, и мои поисковые запросы предполагают, что мне нужно plt.subplot(111)начать с чего-то большого, но как мне тогда использовать свои подзаговоры 5 * 2 plt.subplots?

jolindbe
источник
2
С обновлением вопроса и комментариями, оставленными в ответах ниже, это дубликат stackoverflow.com/questions/6963035/…
Отключено
Не совсем так, поскольку мой вопрос касается plt.subplots (), а вопрос, на который вы ссылаетесь, использует add_subplot - я не могу использовать этот метод, если не переключусь на add_subplot, чего я бы хотел избежать. Я мог бы использовать решение plt.text, которое указано в вашей ссылке в качестве альтернативного решения, но это не самое элегантное решение.
jolindbe
Чтобы уточнить, насколько я понимаю, plt.subplots не может генерировать набор подзаголовков в существующей среде оси, но всегда создает новую фигуру. Правильно?
jolindbe
Наиболее элегантное решение можно найти здесь: stackoverflow.com/questions/6963035/…
Mr.H
Ваша ссылка была предоставлена ​​пользователем, подключенным более 4 лет назад (всего несколько комментариев выше вашего). Как я сказал ранее, это решение относится к add_subplot, а не к plt.subplots ().
jolindbe

Ответы:

207

Это похоже на то, что вы действительно хотите. Он применяет тот же подход, что и этот ответ, к вашему конкретному случаю:

import matplotlib.pyplot as plt

fig, ax = plt.subplots(nrows=3, ncols=3, sharex=True, sharey=True, figsize=(6, 6))

fig.text(0.5, 0.04, 'common X', ha='center')
fig.text(0.04, 0.5, 'common Y', va='center', rotation='vertical')

Несколько графиков с общей меткой осей

divenex
источник
4
обратите внимание, что 0,5 для x-координаты x-метки не помещает метку в центр центрального подзаголовка. вам нужно будет немного больше, чтобы учесть yticklabels.
dbliss 07
2
Посмотрите на этот ответ метод, который не используется plt.text. Вы создаете свои подсюжеты, а затем добавляете однобитовый сюжет, делаете его невидимым и маркируете его x и y.
Джеймс Оверс
Спасибо, в целом сработало. Любое решение поломки при использовании tight_layout?
serv-inc
3
@ serv-inc с tight_layoutзаменой 0.04на 0вроде работает.
divenex
4
Использование fig.text- не лучшая идея. Это портит такие вещи, какplt.tight_layout()
Peaceful
55

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

import matplotlib.pyplot as plt
fig, axes = plt.subplots(5, 2, sharex=True, sharey=True, figsize=(6,15))
# add a big axis, hide frame
fig.add_subplot(111, frameon=False)
# hide tick and tick label of the big axis
plt.tick_params(labelcolor='none', top=False, bottom=False, left=False, right=False)
plt.xlabel("common X")
plt.ylabel("common Y")

Это приводит к следующему (с matplotlib версии 2.2.0):

Подграфик с 5 строками и 2 столбцами с общими метками осей x и y

бли
источник
4
Из-за простоты это должен быть принятый ответ. Очень просто. По-прежнему актуально для matplotlib v3.x.
Кайл Суонсон
Я хотел бы знать, как его можно использовать с несколькими объектами-фигурами? fig.xlabel ("foo") не работает.
Horror Vacui
К вашему сведению: теперь, когда люди используют темные темы в StackOverflow, метки едва читаются, поэтому лучше экспортировать ваши png с белым фоном
xyzzyqed
@xyzzyqed Я не знал, что в stackoverflow есть такая вещь, как «темы», и даже не помню, как я экспортировал рисунок. Как я могу контролировать фон при экспорте?
bli
2
Единственная проблема этого решения состоит в том, что оно не работает при использовании, constrained_layout=Trueпоскольку создает перекрывающиеся метки. В этом случае вам придется вручную настроить границы участков.
baccandr
35

Без sharex=True, sharey=Trueтебя получится:

введите описание изображения здесь

С ним у вас должно получиться получше:

fig, axes2d = plt.subplots(nrows=3, ncols=3,
                           sharex=True, sharey=True,
                           figsize=(6,6))

for i, row in enumerate(axes2d):
    for j, cell in enumerate(row):
        cell.imshow(np.random.rand(32,32))

plt.tight_layout()

введите описание изображения здесь

Но если вы хотите добавить дополнительные метки, вы должны добавлять их только на краевые графики:

fig, axes2d = plt.subplots(nrows=3, ncols=3,
                           sharex=True, sharey=True,
                           figsize=(6,6))

for i, row in enumerate(axes2d):
    for j, cell in enumerate(row):
        cell.imshow(np.random.rand(32,32))
        if i == len(axes2d) - 1:
            cell.set_xlabel("noise column: {0:d}".format(j + 1))
        if j == 0:
            cell.set_ylabel("noise row: {0:d}".format(i + 1))

plt.tight_layout()

введите описание изображения здесь

Добавление метки для каждого сюжета испортит его (возможно, есть способ автоматически обнаруживать повторяющиеся метки, но я не знаю ни одного).

Петр Мигдал
источник
Это намного сложнее, если, например, количество графиков неизвестно (например, у вас есть функция обобщения графика, которая работает для любого количества участков).
naught101
15

Поскольку команда:

fig,ax = plt.subplots(5,2,sharex=True,sharey=True,figsize=fig_size)

вы использовали, возвращает кортеж, состоящий из фигуры и списка экземпляров осей, уже достаточно сделать что-то вроде (помните, что я изменил fig,axна fig,axes):

fig,axes = plt.subplots(5,2,sharex=True,sharey=True,figsize=fig_size)

for ax in axes:
    ax.set_xlabel('Common x-label')
    ax.set_ylabel('Common y-label')

Если вы хотите изменить некоторые детали на определенном подзаголовке, вы можете получить к нему доступ через axes[i]where iитерация по вашим подзаголовкам.

Также было бы очень полезно включить

fig.tight_layout()

в конце файла, перед plt.show(), во избежание перекрытия меток.

Marius
источник
5
Мне жаль, что я немного не понял выше. Под «общим» я имел в виду одну метку x под всеми графиками и одну метку y слева от графиков. Я обновил вопрос, чтобы отразить это.
jolindbe
2
@JohanLindberg: Относительно ваших комментариев здесь и выше: Indeed plt.subplots()создаст новый экземпляр фигуры. Если вы хотите придерживаться этой команды, вы можете легко добавить big_ax = fig.add_subplot(111), поскольку у вас уже есть фигура и вы можете добавить еще одну ось. После этого вы можете управлять big_axтем, как он отображается в ссылке из Hooked.
Мариус
Спасибо за ваши предложения, но если я это сделаю, мне придется добавить big_ax после plt.subplots (), и я получу этот подзаголовок поверх всего остального - могу ли я сделать его прозрачным или как-то отправить обратно? Даже если я установлю для всех цветов значение none, как в ссылке Hooked, это все равно будет белое поле, покрывающее все мои подзаголовки.
jolindbe
2
@JohanLindberg, ты прав, я это не проверял. Но вы можете легко установить цвет фона большой оси none, выполнив: big_ax.set_axis_bgcolor('none')Вы также должны сделать цвет метки none(в отличие от примера, связанного с Hooked):big_ax.tick_params(labelcolor='none', top='off', bottom='off', left='off', right='off')
Мариус
2
Получаю ошибку: AttributeError: 'numpy.ndarray' object has no attribute 'set_xlabel'в выписке ax.set_xlabel('Common x-label'). Вы можете понять это?
hengxin 08
5

Будет лучше, если вы зарезервируете место для общих меток, сделав невидимые метки для подзаголовка в нижнем левом углу. Также хорошо передать размер шрифта из rcParams. Таким образом, общие метки изменят размер в зависимости от вашей настройки rc, а оси также будут скорректированы, чтобы оставить место для общих меток.

fig_size = [8, 6]
fig, ax = plt.subplots(5, 2, sharex=True, sharey=True, figsize=fig_size)
# Reserve space for axis labels
ax[-1, 0].set_xlabel('.', color=(0, 0, 0, 0))
ax[-1, 0].set_ylabel('.', color=(0, 0, 0, 0))
# Make common axis labels
fig.text(0.5, 0.04, 'common X', va='center', ha='center', fontsize=rcParams['axes.labelsize'])
fig.text(0.04, 0.5, 'common Y', va='center', ha='center', rotation='vertical', fontsize=rcParams['axes.labelsize'])

введите описание изображения здесь введите описание изображения здесь

EL_DON
источник
1
Хорошее использование невидимой этикетки! Спасибо
colelemonz 09
3

Я столкнулся с аналогичной проблемой при построении сетки графиков. Графики состояли из двух частей (верхней и нижней). Y-метка должна была располагаться по центру обеих частей.

Я не хотел использовать решение, которое зависит от знания позиции на внешнем рисунке (например, fig.text ()), поэтому я манипулировал y-позицией функции set_ylabel (). Обычно это 0,5, это прибавляется к середине графика. Поскольку отступ между частями (hspace) в моем коде был равен нулю, я мог вычислить середину двух частей относительно верхней части.

import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec

# Create outer and inner grid
outerGrid = gridspec.GridSpec(2, 3, width_ratios=[1,1,1], height_ratios=[1,1])
somePlot = gridspec.GridSpecFromSubplotSpec(2, 1,
               subplot_spec=outerGrid[3], height_ratios=[1,3], hspace = 0)

# Add two partial plots
partA = plt.subplot(somePlot[0])
partB = plt.subplot(somePlot[1])

# No x-ticks for the upper plot
plt.setp(partA.get_xticklabels(), visible=False)

# The center is (height(top)-height(bottom))/(2*height(top))
# Simplified to 0.5 - height(bottom)/(2*height(top))
mid = 0.5-somePlot.get_height_ratios()[1]/(2.*somePlot.get_height_ratios()[0])
# Place the y-label
partA.set_ylabel('shared label', y = mid)

plt.show()

картина

Недостатки:

  • Горизонтальное расстояние до графика определяется по верхней части, нижние отметки могут заходить на метку.

  • В формуле не учитывается расстояние между частями.

  • Выдает исключение, если высота верхней части равна 0.

Вероятно, существует общее решение, учитывающее отступы между цифрами.

CPE
источник
Эй, я придумал способ сделать это в духе вашего ответа, но могу решить некоторые из этих проблем; см. stackoverflow.com/a/44020303/4970632 (ниже)
Люк Дэвис
2

Обновить:

Эта функция теперь является частью пакета proplot matplotlib, который я недавно выпустил на pypi. По умолчанию, когда вы делаете фигуры, метки «разделяются» между осями.


Оригинальный ответ:

Я обнаружил более надежный метод:

Если вы знаете bottomи topkwargs, которые вошли в GridSpecинициализацию, или вы знаете положение краев ваших осей в Figureкоординатах , вы также можете указать положение ylabel в Figureкоординатах с помощью некоторой причудливой магии «преобразования». Например:

import matplotlib.transforms as mtransforms
bottom, top = .1, .9
f, a = plt.subplots(nrows=2, ncols=1, bottom=bottom, top=top)
avepos = (bottom+top)/2
a[0].yaxis.label.set_transform(mtransforms.blended_transform_factory(
       mtransforms.IdentityTransform(), f.transFigure # specify x, y transform
       )) # changed from default blend (IdentityTransform(), a[0].transAxes)
a[0].yaxis.label.set_position((0, avepos))
a[0].set_ylabel('Hello, world!')

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

Более того, если вы даже не используете set_position, ylabel по умолчанию будет отображаться ровно на полпути вверх по цифре . Я предполагаю, что это связано с тем, что, когда метка, наконец, нарисована, matplotlibиспользуется 0,5 для y-координаты без проверки, изменилось ли базовое преобразование координат.

Люк Дэвис
источник