etiquetas de eixos pyplot para subtramas

187

Eu tenho o seguinte enredo:

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

Quero poder criar rótulos e títulos de eixos, não apenas para cada uma das duas subparcelas, mas também rótulos comuns que abranjam ambas as subparcelas. Por exemplo, como os dois gráficos têm eixos idênticos, preciso apenas de um conjunto de rótulos de eixos xe y. Eu quero títulos diferentes para cada subtrama.

Eu tentei algumas coisas, mas nenhuma delas funcionou direito

farqwag25
fonte

Respostas:

261

Você pode criar uma grande sub-plot que cubra as duas sub-plot e depois defina os rótulos comuns.

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

Outra maneira é usar fig.text () para definir os locais dos rótulos comuns diretamente.

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
fonte
1
A função suptitle usa a versão fig.text (). Então essa pode ser a maneira "oficial" de fazer isso?
PhML 26/03
4
Vale ressaltar que isso axdeve ser criado antes ax1e ax2, caso contrário, o grande enredo encobrirá os pequenos enredos.
1 ''
ax.grid (False) ou plt.grid (False) também é necessário se os parâmetros de plotagem global incluírem uma grade (visível).
Næreen
3
Parece que a primeira abordagem não funciona mais com versões recentes do matplotplib (eu uso o 2.0.2): os rótulos adicionados ao machado anexo não são visíveis.
Toya
Como adicionar y_labels a cada subtrama individual?
Fardin
115

Uma maneira simples de usar 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
fonte
1
ax.grid (False) ou plt.grid (False) também é necessário se os parâmetros de plotagem global incluírem uma grade (visível).
Næreen
1
Estou fazendo isso para uma subparcela (5, 1) e meu rótulo está bem na borda esquerda da janela, em vez de perto das subparcelas.
Evidlo
1
Você recebeu uma votação. mas sempre explique o que o código está fazendo, anexe uma imagem ou mostre um exemplo, porque definitivamente demorou um pouco para obtê-lo.
Kareem Jeiroudi 17/04/19
4
Altere 'off'para Falsecom as versões mais recentes do Matplotlib (tenho 2.2.2)
Ted
2
E então, como você adiciona os gráficos? for ax in axes: ax.plot(x, y)não parece fazer nenhum bem.
userNumber
16

A resposta de Wen-wei Liao é boa se você não está tentando exportar gráficos vetoriais ou se configurou os back-ends do matplotlib para ignorar os eixos incolores; caso contrário, os eixos ocultos apareceriam no gráfico exportado.

Minha resposta suplabelaqui é semelhante à fig.suptitleque usa a fig.textfunção. Portanto, não há artista de eixos sendo criado e tornado incolor. No entanto, se você tentar chamá-lo várias vezes, você adicionará texto um sobre o outro (como fig.suptitletambém). A resposta de Wen-wei Liao não, porque fig.add_subplot(111)retornará o mesmo objeto Axes se ele já estiver criado.

Minha função também pode ser chamada após a criação dos gráficos.

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
fonte
Esta é a melhor resposta imo. É fácil de implementar e os rótulos não se sobrepõem devido à opção do rótulo.
Arthur Dent
8

Aqui está uma solução em que você define o rótulo de uma das plotagens e ajusta a posição da mesma para que fique centralizada verticalmente. Dessa forma, você evita os problemas mencionados pela 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.)')

insira a descrição da imagem aqui

Hagne
fonte
7

plt.setp() fará o trabalho:

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

insira a descrição da imagem aqui

MohammadReza
fonte
Existe uma maneira de também definir o tamanho / peso da fonte com esse método?
pfabri 10/07
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
fonte
1

Os métodos nas outras respostas não funcionarão corretamente quando os yticks forem grandes. O rótulo será sobreposto com carrapatos, será cortado à esquerda ou completamente invisível / fora da figura.

Modifiquei a resposta de Hagne para que funcione com mais de uma coluna de subparcelas, para xlabel e ylabel, e muda o gráfico para manter o rótulo visível na 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)

Ele funciona no exemplo a seguir, enquanto a resposta de Hagne não desenha o rótulo de rótulo (já que está fora da tela) e o rótulo de rótulo do KYC se sobrepõe aos rótulos de 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()

Como alternativa, se você estiver bem com o eixo incolor, modifiquei a solução de Julian Chen para que o ylabel não se sobreponha aos rótulos dos tiquetaques.

Basicamente, basta definir os ylims do incolor para que ele corresponda aos maiores ylims das subparcelas, para que os rótulos de marca incolor definam o local correto para o rótulo.

Novamente, temos que reduzir o gráfico para evitar cortes. Aqui, eu codifiquei a quantidade a encolher, mas você pode procurar um número que funcione para você ou calculá-lo como no método acima.

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
fonte