etichette degli assi pyplot per i grafici secondari


187

Ho la seguente trama:

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

Voglio essere in grado di creare etichette e titoli degli assi non solo per ciascuna delle due trame secondarie, ma anche etichette comuni che abbracciano entrambe le trame secondarie. Ad esempio, poiché entrambi i grafici hanno assi identici, ho bisogno di un solo set di etichette per assi xe y. Voglio comunque titoli diversi per ogni sottotrama.

Ho provato alcune cose ma nessuna ha funzionato bene

Risposte:


261

È possibile creare un grande sottotrama che copra i due sottotrame e quindi impostare le etichette comuni.

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

Un altro modo è utilizzare fig.text () per impostare direttamente le posizioni delle etichette comuni.

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


1
La funzione di sottotitolo utilizza la versione fig.text (). Quindi questo potrebbe essere il modo "ufficiale" per farlo?
PhML,

4
Vale la pena sottolineare che axdeve essere creato prima ax1e ax2, altrimenti la trama grande coprirà le trame piccole.
1 "

ax.grid (False) o plt.grid (False) è necessario anche se i parametri di stampa globali includono una griglia (visibile).
Næreen,

3
Sembra che il primo approccio non funzioni più con le versioni recenti di matplotplib (io uso 2.0.2): le etichette aggiunte all'ascia racchiusa non sono visibili.
M. Toya,

Come aggiungere y_labels a ogni singola sottotrama?
Fardin,

115

Un modo semplice usando 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")

1
ax.grid (False) o plt.grid (False) è necessario anche se i parametri di stampa globali includono una griglia (visibile).
Næreen,

1
Lo sto facendo per una sottotrama (5, 1) e la mia ylabel è lontana dal bordo sinistro della finestra invece che vicino alle sottotrame.
Evidlo,

1
Hai ottenuto un voto. ma per favore spiega sempre cosa sta facendo il codice, allega un'immagine o mostra un esempio, perché sicuramente ci è voluto un po 'di tempo per ottenerlo.
Kareem Jeiroudi,

4
Passa 'off'a Falsecon le versioni più recenti di Matplotlib (ho 2.2.2)
Ted

2
E poi come si aggiungono i grafici? for ax in axes: ax.plot(x, y)non sembra fare nulla di buono.
utente

16

La risposta di Wen-wei Liao è buona se non stai cercando di esportare la grafica vettoriale o se hai impostato i backend matplotlib per ignorare gli assi incolori; altrimenti gli assi nascosti verrebbero visualizzati nel grafico esportato.

La mia risposta suplabelqui è simile a quella fig.suptitleche utilizza la fig.textfunzione. Pertanto non esiste un artista che viene creato e reso incolore. Tuttavia, se provi a chiamarlo più volte, il testo verrà aggiunto uno sopra l'altro (come fig.suptitlepure). La risposta di Wen-wei Liao no, perché fig.add_subplot(111)restituirà lo stesso oggetto Axes se è già stato creato.

La mia funzione può anche essere chiamata dopo che i grafici sono stati creati.

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)

Questa è la migliore risposta imo. È facile da implementare e le etichette non si sovrappongono a causa dell'opzione labelpad.
Arthur Dent,

8

Ecco una soluzione in cui si imposta la ylabel di uno dei grafici e si regola la posizione in modo che sia centrata verticalmente. In questo modo si evitano i problemi menzionati da 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.)')

inserisci qui la descrizione dell'immagine


7

plt.setp() farà il lavoro:

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

inserisci qui la descrizione dell'immagine


C'è un modo per impostare anche la dimensione / peso del carattere con questo metodo?
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')

1

I metodi nelle altre risposte non funzioneranno correttamente quando gli ytick sono grandi. L'etichetta si sovrapporrà con le zecche, sarà ritagliata a sinistra o completamente invisibile / al di fuori della figura.

Ho modificato la risposta di Hagne in modo che funzioni con più di 1 colonna di sottotrame, sia per xlabel che per ylabel, e sposta la trama per mantenere visibile la ylabel nella figura.

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)

Funziona per il seguente esempio, mentre la risposta di Hagne non disegna lo ylabel (poiché è al di fuori dell'area di disegno) e lo ylabel di KYC si sovrappone alle etichette dei tick:

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

In alternativa, se stai bene con l'asse incolore, ho modificato la soluzione di Julian Chen in modo che lo ylabel non si sovrapponga alle etichette dei tick.

Fondamentalmente, dobbiamo solo impostare i gialli degli incolori in modo che corrispondano ai più grandi ylim delle sottotrame in modo che le etichette dei segni di spunta incolori impostino la posizione corretta per l'etichetta.

Ancora una volta, dobbiamo ridurre la trama per evitare il clipping. Qui ho hardcoded l'importo da ridurre, ma puoi giocare per trovare un numero che funzioni per te o calcolarlo come nel metodo sopra.

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()
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.