Beschriftungen der Pyplot-Achsen für Unterplots

186

Ich habe die folgende Handlung:

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')

Ich möchte in der Lage sein, Achsenbeschriftungen und -titel nicht nur für jede der beiden Unterzeichnungen zu erstellen, sondern auch gemeinsame Beschriftungen, die beide Unterzeichnungen umfassen. Da zum Beispiel beide Diagramme identische Achsen haben, benötige ich nur einen Satz Beschriftungen für die x- und y-Achse. Ich möchte jedoch unterschiedliche Titel für jede Nebenhandlung.

Ich habe ein paar Dinge ausprobiert, aber keines hat richtig funktioniert

farqwag25
quelle

Antworten:

259

Sie können eine große Unterzeichnung erstellen, die die beiden Unterzeichnungen abdeckt, und dann die allgemeinen Beschriftungen festlegen.

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

Eine andere Möglichkeit ist die Verwendung von fig.text (), um die Positionen der allgemeinen Beschriftungen direkt festzulegen.

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
quelle
1
Die Suptitle-Funktion verwendet die Version fig.text (). Das könnte also der "offizielle" Weg sein, dies zu tun?
PhML
4
Es ist zu betonen , dass axerstellt werden muss , bevor ax1und ax2, da sonst das große Grundstück erstreckt sich auf die kleinen Parzellen auf.
1
ax.grid (False) oder plt.grid (False) wird ebenfalls benötigt, wenn die globalen Plotparameter ein (sichtbares) Raster enthalten.
Næreen
3
Es scheint, dass der erste Ansatz mit neueren Versionen von matplotplib (ich verwende 2.0.2) nicht mehr funktioniert: Beschriftungen, die der umschließenden Axt hinzugefügt wurden, sind nicht sichtbar.
M. Toya
Wie füge ich jedem einzelnen Unterplot y_labels hinzu?
Fardin
115

Ein einfacher Weg mit 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
quelle
1
ax.grid (False) oder plt.grid (False) wird ebenfalls benötigt, wenn die globalen Plotparameter ein (sichtbares) Raster enthalten.
Næreen
1
Ich mache dies für eine (5, 1) Nebenhandlung und mein ylabel befindet sich weit links am Fensterrand anstatt in der Nähe der Nebenhandlungen.
Evidlo
1
Du hast eine Gegenstimme. Aber bitte erklären Sie immer, was der Code tut, fügen Sie ein Bild hinzu oder zeigen Sie ein Beispiel, da es definitiv einige Zeit gedauert hat, es zu bekommen.
Kareem Jeiroudi
4
Wechseln Sie 'off'zu Falseneueren Versionen von Matplotlib (ich habe 2.2.2)
Ted
2
Und wie fügt man dann die Diagramme hinzu? for ax in axes: ax.plot(x, y)scheint nichts Gutes zu tun.
Benutzernummer
16

Die Antwort von Wen-wei Liao ist gut, wenn Sie nicht versuchen, Vektorgrafiken zu exportieren, oder wenn Sie Ihre Matplotlib-Backends so eingerichtet haben, dass farblose Achsen ignoriert werden. Andernfalls werden die ausgeblendeten Achsen in der exportierten Grafik angezeigt.

Meine Antwort suplabelhier ähnelt der, fig.suptitledie die fig.textFunktion verwendet. Daher wird kein Achsenkünstler geschaffen und farblos gemacht. Wenn Sie jedoch versuchen, es mehrmals aufzurufen, wird (wie fig.suptitleauch) Text übereinander hinzugefügt . Wen-wei Liaos Antwort antwortet nicht, da fig.add_subplot(111)dasselbe Axes-Objekt zurückgegeben wird, wenn es bereits erstellt wurde.

Meine Funktion kann auch aufgerufen werden, nachdem die Diagramme erstellt wurden.

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
quelle
Dies ist die beste Antwort imo. Es ist einfach zu implementieren und die Beschriftungen überlappen sich aufgrund der Labelpad-Option nicht.
Arthur Dent
8

Hier ist eine Lösung, bei der Sie das ylabel eines der Diagramme festlegen und dessen Position so anpassen, dass es vertikal zentriert ist. Auf diese Weise vermeiden Sie von KYC erwähnte Probleme.

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.)')

Geben Sie hier die Bildbeschreibung ein

Hagne
quelle
7

plt.setp() wird den Job machen:

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

Geben Sie hier die Bildbeschreibung ein

MohammadReza
quelle
2
# 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
quelle
1

Die Methoden in den anderen Antworten funktionieren nicht richtig, wenn die yticks groß sind. Das ylabel überlappt sich entweder mit Zecken, wird links abgeschnitten oder ist völlig unsichtbar / außerhalb der Figur.

Ich habe die Antwort von Hagne so geändert, dass sie mit mehr als einer Spalte von Unterplots sowohl für xlabel als auch für ylabel funktioniert, und sie verschiebt den Plot, um das ylabel in der Abbildung sichtbar zu halten.

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)

Es funktioniert für das folgende Beispiel, während Hagchs Antwort kein ylabel zeichnet (da es sich außerhalb der Leinwand befindet) und das ylabel von KYC sich mit den Häkchen überschneidet:

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()

Wenn Sie mit der farblosen Achse gut zurechtkommen, habe ich die Lösung von Julian Chen so geändert, dass sich das Etikett nicht mit den Häkchen überschneidet.

Grundsätzlich müssen wir nur die Ylims der Farblosen so einstellen, dass sie mit den größten Ylims der Unterzeichnungen übereinstimmen, sodass die farblosen Häkchen die richtige Position für die Ylabels festlegen.

Auch hier müssen wir die Handlung verkleinern, um ein Abschneiden zu verhindern. Hier habe ich den zu verkleinernden Betrag hart codiert, aber Sie können herumspielen, um eine Zahl zu finden, die für Sie funktioniert, oder sie wie in der obigen Methode berechnen.

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
quelle