etykiety osi pyplot dla podplotów

187

Mam następujący wątek:

import matplotlib.pyplot as plt

fig2 = plt.figure()
ax3 = fig2.add_subplot(2,1,1)
ax4 = fig2.add_subplot(2,1,2)
ax4.loglog(x1, y1)
ax3.loglog(x2, y2)
ax3.set_ylabel('hello')

Chcę mieć możliwość tworzenia etykiet osi i tytułów nie tylko dla każdej z dwóch podplotów, ale także wspólnych etykiet obejmujących oba podploty. Na przykład, ponieważ obie wykresy mają identyczne osie, potrzebuję tylko jednego zestawu etykiet osi xi osi. Chcę jednak różnych tytułów dla każdej podploty.

Próbowałem kilku rzeczy, ale żadna z nich nie działała poprawnie

farqwag25
źródło

Odpowiedzi:

261

Możesz utworzyć duży podplot obejmujący dwa podploty, a następnie ustawić wspólne etykiety.

import random
import matplotlib.pyplot as plt

x = range(1, 101)
y1 = [random.randint(1, 100) for _ in xrange(len(x))]
y2 = [random.randint(1, 100) for _ in xrange(len(x))]

fig = plt.figure()
ax = fig.add_subplot(111)    # The big subplot
ax1 = fig.add_subplot(211)
ax2 = fig.add_subplot(212)

# Turn off axis lines and ticks of the big subplot
ax.spines['top'].set_color('none')
ax.spines['bottom'].set_color('none')
ax.spines['left'].set_color('none')
ax.spines['right'].set_color('none')
ax.tick_params(labelcolor='w', top=False, bottom=False, left=False, right=False)

ax1.loglog(x, y1)
ax2.loglog(x, y2)

# Set common labels
ax.set_xlabel('common xlabel')
ax.set_ylabel('common ylabel')

ax1.set_title('ax1 title')
ax2.set_title('ax2 title')

plt.savefig('common_labels.png', dpi=300)

common_labels.png

Innym sposobem jest użycie rys.text () do bezpośredniego ustawienia wspólnych etykiet.

import random
import matplotlib.pyplot as plt

x = range(1, 101)
y1 = [random.randint(1, 100) for _ in xrange(len(x))]
y2 = [random.randint(1, 100) for _ in xrange(len(x))]

fig = plt.figure()
ax1 = fig.add_subplot(211)
ax2 = fig.add_subplot(212)

ax1.loglog(x, y1)
ax2.loglog(x, y2)

# Set common labels
fig.text(0.5, 0.04, 'common xlabel', ha='center', va='center')
fig.text(0.06, 0.5, 'common ylabel', ha='center', va='center', rotation='vertical')

ax1.set_title('ax1 title')
ax2.set_title('ax2 title')

plt.savefig('common_labels_text.png', dpi=300)

common_labels_text.png

Wen-Wei Liao
źródło
1
Funkcja napisów korzysta z wersji rys.text (). Czy to może być „oficjalny” sposób na zrobienie tego?
PhML
4
Warto podkreślić, że axmusi zostać utworzone przed ax1i ax2, inaczej duża działka pokryje małych działek.
1 ''
ax.grid (False) lub plt.grid (False) jest również potrzebny, jeśli globalne parametry kreślenia obejmują (widoczną) siatkę.
Næreen
3
Wydaje się, że pierwsze podejście już nie działa z najnowszymi wersjami matplotplib (używam 2.0.2): etykiety dodane do otaczającego topora nie są widoczne.
M. Toya
Jak dodać y_labels do poszczególnych podplotów?
Fardin
115

Jeden prosty sposób przy użyciu subplots:

import matplotlib.pyplot as plt

fig, axes = plt.subplots(3, 4, sharex=True, sharey=True)
# add a big axes, hide frame
fig.add_subplot(111, frameon=False)
# hide tick and tick label of the big axes
plt.tick_params(labelcolor='none', top='off', bottom='off', left='off', right='off')
plt.grid(False)
plt.xlabel("common X")
plt.ylabel("common Y")
Julian Chen
źródło
1
ax.grid (False) lub plt.grid (False) jest również potrzebny, jeśli globalne parametry kreślenia obejmują (widoczną) siatkę.
Næreen
1
Robię to dla (5, 1) wątku podrzędnego, a mój ylabel jest daleko na lewej krawędzi okna zamiast w pobliżu wątków podrzędnych.
Evidlo,
1
Masz głos pozytywny. ale zawsze wyjaśniaj, co robi kod, dołącz obraz lub pokaż przykład, ponieważ na pewno zajęło to trochę czasu.
Kareem Jeiroudi
4
Zmień 'off'na Falsenowsze wersje Matplotlib (mam 2.2.2)
Ted
2
A w jaki sposób dodajesz wykresy? for ax in axes: ax.plot(x, y)wydaje się nie robić nic dobrego.
numer użytkownika
16

Odpowiedź Wen-wei Liao jest dobra, jeśli nie próbujesz eksportować grafiki wektorowej lub jeśli masz ustawione zaplecze matplotlib, aby ignorować bezbarwne osie; w przeciwnym razie ukryte osie pojawią się na eksportowanej grafice.

Moja odpowiedź suplabeltutaj jest podobna do tej, fig.suptitlektóra korzysta z fig.textfunkcji. Dlatego nie ma żadnego artysty tworzącego siekiery i bezbarwnego. Jeśli jednak spróbujesz wywołać go wiele razy, tekst zostanie dodany jeden na drugim (tak jak i fig.suptitleto). Odpowiedź Wen-wei Liao nie, ponieważ fig.add_subplot(111)zwróci ten sam obiekt Axes, jeśli jest już utworzony.

Moją funkcję można również wywołać po utworzeniu wykresów.

def suplabel(axis,label,label_prop=None,
             labelpad=5,
             ha='center',va='center'):
    ''' Add super ylabel or xlabel to the figure
    Similar to matplotlib.suptitle
    axis       - string: "x" or "y"
    label      - string
    label_prop - keyword dictionary for Text
    labelpad   - padding from the axis (default: 5)
    ha         - horizontal alignment (default: "center")
    va         - vertical alignment (default: "center")
    '''
    fig = pylab.gcf()
    xmin = []
    ymin = []
    for ax in fig.axes:
        xmin.append(ax.get_position().xmin)
        ymin.append(ax.get_position().ymin)
    xmin,ymin = min(xmin),min(ymin)
    dpi = fig.dpi
    if axis.lower() == "y":
        rotation=90.
        x = xmin-float(labelpad)/dpi
        y = 0.5
    elif axis.lower() == 'x':
        rotation = 0.
        x = 0.5
        y = ymin - float(labelpad)/dpi
    else:
        raise Exception("Unexpected axis: x or y")
    if label_prop is None: 
        label_prop = dict()
    pylab.text(x,y,label,rotation=rotation,
               transform=fig.transFigure,
               ha=ha,va=va,
               **label_prop)
KYC
źródło
To jest najlepsza odpowiedź imo. Jest łatwy do wdrożenia, a etykiety nie nakładają się na siebie z powodu opcji labelpad.
Arthur Dent
8

Oto rozwiązanie, w którym ustawisz etykietę jednego z wykresów i dostosujesz jego położenie tak, aby była wyśrodkowana pionowo. W ten sposób unikniesz problemów wymienionych przez KYC.

import numpy as np
import matplotlib.pyplot as plt

def set_shared_ylabel(a, ylabel, labelpad = 0.01):
    """Set a y label shared by multiple axes
    Parameters
    ----------
    a: list of axes
    ylabel: string
    labelpad: float
        Sets the padding between ticklabels and axis label"""

    f = a[0].get_figure()
    f.canvas.draw() #sets f.canvas.renderer needed below

    # get the center position for all plots
    top = a[0].get_position().y1
    bottom = a[-1].get_position().y0

    # get the coordinates of the left side of the tick labels 
    x0 = 1
    for at in a:
        at.set_ylabel('') # just to make sure we don't and up with multiple labels
        bboxes, _ = at.yaxis.get_ticklabel_extents(f.canvas.renderer)
        bboxes = bboxes.inverse_transformed(f.transFigure)
        xt = bboxes.x0
        if xt < x0:
            x0 = xt
    tick_label_left = x0

    # set position of label
    a[-1].set_ylabel(ylabel)
    a[-1].yaxis.set_label_coords(tick_label_left - labelpad,(bottom + top)/2, transform=f.transFigure)

length = 100
x = np.linspace(0,100, length)
y1 = np.random.random(length) * 1000
y2 = np.random.random(length)

f,a = plt.subplots(2, sharex=True, gridspec_kw={'hspace':0})
a[0].plot(x, y1)
a[1].plot(x, y2)
set_shared_ylabel(a, 'shared y label (a. u.)')

wprowadź opis zdjęcia tutaj

Hagne
źródło
7

plt.setp() wykona zadanie:

# plot something
fig, axs = plt.subplots(3,3, figsize=(15, 8), sharex=True, sharey=True)
for i, ax in enumerate(axs.flat):
    ax.scatter(*np.random.normal(size=(2,200)))
    ax.set_title(f'Title {i}')

# set labels
plt.setp(axs[-1, :], xlabel='x axis label')
plt.setp(axs[:, 0], ylabel='y axis label')

wprowadź opis zdjęcia tutaj

MohammadReza
źródło
Czy istnieje sposób na ustawienie rozmiaru / wagi czcionki za pomocą tej metody?
pfabri
3
# list loss and acc are your data
fig = plt.figure()
ax1 = fig.add_subplot(121)
ax2 = fig.add_subplot(122)

ax1.plot(iteration1, loss)
ax2.plot(iteration2, acc)

ax1.set_title('Training Loss')
ax2.set_title('Training Accuracy')

ax1.set_xlabel('Iteration')
ax1.set_ylabel('Loss')

ax2.set_xlabel('Iteration')
ax2.set_ylabel('Accuracy')
J.Zhao
źródło
1

Metody w pozostałych odpowiedziach nie będą działały poprawnie, gdy tagi są duże. Ylabel będzie albo nakładał się na kleszcze, zostanie przypięty po lewej stronie lub całkowicie niewidoczny / na zewnątrz figury.

Zmodyfikowałem odpowiedź Hagne'a, aby działała z więcej niż 1 kolumną wykresów podrzędnych, zarówno dla xlabel, jak i ylabel, i przesuwa fabułę, aby utrzymać ylabel widoczny na rysunku.

def set_shared_ylabel(a, xlabel, ylabel, labelpad = 0.01, figleftpad=0.05):
    """Set a y label shared by multiple axes
    Parameters
    ----------
    a: list of axes
    ylabel: string
    labelpad: float
        Sets the padding between ticklabels and axis label"""

    f = a[0,0].get_figure()
    f.canvas.draw() #sets f.canvas.renderer needed below

    # get the center position for all plots
    top = a[0,0].get_position().y1
    bottom = a[-1,-1].get_position().y0

    # get the coordinates of the left side of the tick labels
    x0 = 1
    x1 = 1
    for at_row in a:
        at = at_row[0]
        at.set_ylabel('') # just to make sure we don't and up with multiple labels
        bboxes, _ = at.yaxis.get_ticklabel_extents(f.canvas.renderer)
        bboxes = bboxes.inverse_transformed(f.transFigure)
        xt = bboxes.x0
        if xt < x0:
            x0 = xt
            x1 = bboxes.x1
    tick_label_left = x0

    # shrink plot on left to prevent ylabel clipping
    # (x1 - tick_label_left) is the x coordinate of right end of tick label,
    # basically how much padding is needed to fit tick labels in the figure
    # figleftpad is additional padding to fit the ylabel
    plt.subplots_adjust(left=(x1 - tick_label_left) + figleftpad)

    # set position of label, 
    # note that (figleftpad-labelpad) refers to the middle of the ylabel
    a[-1,-1].set_ylabel(ylabel)
    a[-1,-1].yaxis.set_label_coords(figleftpad-labelpad,(bottom + top)/2, transform=f.transFigure)

    # set xlabel
    y0 = 1
    for at in axes[-1]:
        at.set_xlabel('')  # just to make sure we don't and up with multiple labels
        bboxes, _ = at.xaxis.get_ticklabel_extents(fig.canvas.renderer)
        bboxes = bboxes.inverse_transformed(fig.transFigure)
        yt = bboxes.y0
        if yt < y0:
            y0 = yt
    tick_label_bottom = y0

    axes[-1, -1].set_xlabel(xlabel)
    axes[-1, -1].xaxis.set_label_coords((left + right) / 2, tick_label_bottom - labelpad, transform=fig.transFigure)

Działa w następującym przykładzie, podczas gdy odpowiedź Hagne'a nie rysuje ylabel (ponieważ jest poza płótnem), a ylabel KYC pokrywa się z etykietami zaznaczenia:

import matplotlib.pyplot as plt
import itertools

fig, axes = plt.subplots(3, 4, sharey='row', sharex=True, squeeze=False)
fig.subplots_adjust(hspace=.5)
for i, a in enumerate(itertools.chain(*axes)):
    a.plot([0,4**i], [0,4**i])
    a.set_title(i)
set_shared_ylabel(axes, 'common X', 'common Y')
plt.show()

Alternatywnie, jeśli jesteś w porządku z bezbarwną osią, zmodyfikowałem rozwiązanie Juliana Chena, aby ylabel nie nakładał się na etykiety kleszczowe.

Zasadniczo musimy po prostu ustawić ylimy bezbarwne, aby pasowały one do największych ylimów podplotów, aby bezbarwne etykiety kleszczy ustawiły właściwą lokalizację dla ylabel.

Ponownie musimy zmniejszyć fabułę, aby zapobiec wycinaniu. Tutaj zakodowałem na stałe kwotę do zmniejszenia, ale możesz pobawić się, aby znaleźć liczbę, która będzie dla ciebie odpowiednia, lub obliczyć ją jak w powyższej metodzie.

import matplotlib.pyplot as plt
import itertools

fig, axes = plt.subplots(3, 4, sharey='row', sharex=True, squeeze=False)
fig.subplots_adjust(hspace=.5)
miny = maxy = 0
for i, a in enumerate(itertools.chain(*axes)):
    a.plot([0,4**i], [0,4**i])
    a.set_title(i)
    miny = min(miny, a.get_ylim()[0])
    maxy = max(maxy, a.get_ylim()[1])

# add a big axes, hide frame
# set ylim to match the largest range of any subplot
ax_invis = fig.add_subplot(111, frameon=False)
ax_invis.set_ylim([miny, maxy])

# 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")

# shrink plot to prevent clipping
plt.subplots_adjust(left=0.15)
plt.show()
Tim
źródło