Xlabel / ylabel comum para subparcelas matplotlib

142

Eu tenho o seguinte enredo:

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

e agora gostaria de fornecer a esse gráfico rótulos comuns do eixo xe do eixo y. Com "comum", quero dizer que deve haver um grande rótulo do eixo x abaixo de toda a grade de subtramas e um grande rótulo do eixo y à direita. Não consigo encontrar nada sobre isso na documentação plt.subplots, e minhas pesquisas sugerem que eu preciso fazer um grande investimento plt.subplot(111)para começar - mas como faço para colocar minhas subparcelas 5 * 2 nisso plt.subplots?

jolindbe
fonte
2
Com a atualização para a pergunta e os comentários deixados nas respostas abaixo, isso é uma duplicata do stackoverflow.com/questions/6963035/…
Hooked
Não, na verdade, já que minha pergunta é para plt.subplots (), e a pergunta que você vincula usa add_subplot - não posso usar esse método a menos que mude para add_subplot, o que eu gostaria de evitar. Eu poderia usar a solução plt.text, que é fornecida como uma solução alternativa no seu link, mas não é a solução mais elegante.
jolindbe
Para elaborar, tanto quanto eu entendo, os subplots plt.sub não podem gerar um conjunto de subtramas dentro de um ambiente de eixo existente, mas sempre cria uma nova figura. Certo?
jolindbe
Uma solução mais elegante pode ser encontrada aqui: stackoverflow.com/questions/6963035/…
Mr.H 8/17
Seu link foi fornecido pelo usuário Hooked mais de 4 anos atrás (apenas alguns comentários acima do seu). Como eu disse anteriormente, essa solução pertence ao add_subplot, e não ao plt.subplots ().
jolindbe

Respostas:

206

Parece com o que você realmente deseja. Aplica a mesma abordagem desta resposta ao seu caso específico:

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

Gráficos múltiplos com etiqueta de eixos comuns

divenex
fonte
4
observe que 0,5 para a coordenada x do rótulo x não coloca o rótulo no centro da subparcela central. você precisaria ir um pouco maior do que isso para explicar os rótulos dos ytick.
dbliss
2
Dê uma olhada nesta resposta para um método que não usa plt.text. Você cria suas subparcelas, mas depois adiciona uma plotagem de bit, torna-a invisível e rotula seus x e y.
James Owers
Obrigado, trabalhou em geral. Alguma solução para quebrar ao usar tight_layout?
serv-inc
3
@ serv-inc com a tight_layoutsubstituição 0.04por 0parece funcionar.
Divenex
3
Usar fig.textnão é uma boa ideia. Este mexe-se coisas comoplt.tight_layout()
Peaceful
54

Como considero relevante e elegante o suficiente (não há necessidade de especificar coordenadas para colocar o texto), copio (com uma leve adaptação) uma resposta para outra pergunta relacionada .

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

Isso resulta no seguinte (com matplotlib versão 2.2.0):

Subtramas de 5 linhas e 2 colunas com rótulos comuns dos eixos x e y

bli
fonte
4
Devido à simplicidade, essa deve ser a resposta aceita. Muito simples. Ainda relevante para o matplotlib v3.x.
Kyle Swanson
Gostaria de saber como ele pode ser usado com vários objetos de figura? fig.xlabel ("foo") não funciona.
Horror Vacui 7/11
FYI: Agora que as pessoas usam temas escuros no StackOverflow, os rótulos mal podem ser lidos, portanto é melhor exportar seus
pngs
@xyzzyqed Eu não sabia que havia "temas" no stackoverflow, e nem me lembro como exportei a figura. Como posso controlar o plano de fundo ao exportar?
bli 18/06
2
O único problema desta solução é que ela não funciona ao ser usada constrained_layout=Trueporque cria etiquetas sobrepostas. Nesse caso, você deve ajustar manualmente as bordas das subparcelas.
baccandr 13/07
35

Sem sharex=True, sharey=Truevocê ter:

insira a descrição da imagem aqui

Com ele, você deve ficar mais agradável:

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

insira a descrição da imagem aqui

Mas se você quiser adicionar outros rótulos, adicione-os apenas às plotagens de borda:

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

insira a descrição da imagem aqui

A adição de etiquetas para cada plotagem a prejudicaria (talvez haja uma maneira de detectar automaticamente etiquetas repetidas, mas não conheço uma).

Piotr Migdal
fonte
Isso é muito mais difícil se, por exemplo, o número de plotagens for desconhecido (por exemplo, você possui uma função de plotagem generalizada que funciona para qualquer número de sub plotagens).
precisa saber é o seguinte
15

Desde o comando:

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

você usou retorna uma tupla composta pela figura e uma lista das instâncias dos eixos, já é suficiente fazer algo como (lembre-se de que mudei fig,axpara 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')

Se você desejar alterar alguns detalhes de uma subparcela específica, poderá acessá-la através de axes[i]onde iitera suas subparcelas.

Também pode ser muito útil incluir um

fig.tight_layout()

no final do arquivo, antes do plt.show(), para evitar sobreposição de rótulos.

Marius
fonte
5
Me desculpe, eu estava um pouco claro acima. Com "comum", eu quis dizer um único rótulo x abaixo de todos os gráficos e um único rótulo y à esquerda dos gráficos, atualizei a pergunta para refletir isso.
jolindbe
2
@JohanLindberg: Sobre seus comentários aqui e acima: De fato plt.subplots(), criará uma nova instância de figura. Se você deseja seguir esse comando, pode adicionar facilmente a big_ax = fig.add_subplot(111), pois você já tem uma figura e pode adicionar outro eixo. Depois disso, você pode manipular big_axa maneira como é mostrado no link de Hooked.
Marius
Obrigado por suas sugestões, mas se eu fizer isso, tenho que adicionar o big_ax após plt.subplots () e coloco essa subtrama em cima de tudo o resto - posso torná-la transparente ou enviá-la de volta de alguma forma? Mesmo se eu definir todas as cores como nenhuma, como no link de Hooked, ainda será uma caixa branca cobrindo todas as minhas subparcelas.
jolindbe
2
@JohanLindberg, você está certo, eu não tinha verificado isso. Mas você pode facilmente definir a cor de plano de fundo do eixo grande nonefazendo: big_ax.set_axis_bgcolor('none')Você também deve criar a cor da etiqueta none(em oposição ao exemplo vinculado por Hooked):big_ax.tick_params(labelcolor='none', top='off', bottom='off', left='off', right='off')
Marius
2
Eu recebo um erro: AttributeError: 'numpy.ndarray' object has no attribute 'set_xlabel'na declaração ax.set_xlabel('Common x-label'). Você pode descobrir isso?
Hengxin 08/07/2014
5

Ficará melhor se você reservar espaço para os rótulos comuns, criando rótulos invisíveis para a subparcela no canto inferior esquerdo. Também é bom passar o tamanho da fonte do rcParams. Dessa forma, os rótulos comuns mudarão de tamanho com sua configuração de rc e os eixos também serão ajustados para deixar espaço para os rótulos comuns.

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

insira a descrição da imagem aqui insira a descrição da imagem aqui

EL_DON
fonte
1
Bom uso da etiqueta invisível! Obrigado
colelemonz 09/09/19
3

Encontrei um problema semelhante ao traçar uma grade de gráficos. Os gráficos consistiam em duas partes (superior e inferior). O rótulo y deveria estar centrado nas duas partes.

Eu não queria usar uma solução que dependesse de conhecer a posição na figura externa (como fig.text ()), então manipulei a posição y da função set_ylabel (). Geralmente é 0,5, no meio do gráfico ao qual é adicionado. Como o preenchimento entre as partes (hspace) no meu código era zero, eu pude calcular o meio das duas partes em relação à parte superior.

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

cenário

Desvantagens:

  • A distância horizontal ao gráfico é baseada na parte superior, os marcadores inferiores podem se estender para o rótulo.

  • A fórmula não leva em consideração o espaço entre as partes.

  • Lança uma exceção quando a altura da parte superior é 0.

Provavelmente existe uma solução geral que leva em consideração o preenchimento entre os números.

CPe
fonte
Ei, descobri uma maneira de fazer isso muito na veia de sua resposta, mas posso resolver alguns desses problemas; veja stackoverflow.com/a/44020303/4970632 (abaixo)
Luke Davis
2

Atualizar:

Esse recurso agora faz parte do pacote proplot matplotlib que eu liberei recentemente no pypi. Por padrão, quando você cria figuras, os rótulos são "compartilhados" entre os eixos.


Resposta original:

Eu descobri um método mais robusto:

Se você conhece o bottome topkwargs que foram GridSpeciniciados, ou conhece as posições das arestas dos seus eixos em Figurecoordenadas , também pode especificar a posição do marcador em Figurecoordenadas com alguma mágica "transformada" sofisticada. Por exemplo:

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

... e você deve ver que o rótulo ainda se ajusta de maneira adequada à esquerda e à direita para não se sobrepor aos ticklabels, como normalmente - mas agora ele será ajustado para estar sempre exatamente entre as subparcelas desejadas.

Além disso, se você nem usar set_position, o rótulo será exibido por padrão exatamente na metade da figura . Suponho que isso ocorre porque, quando o rótulo é finalmente desenhado, matplotlibusa 0,5 para o y-coordinate sem verificar se a transformação de coordenadas subjacente foi alterada.

Luke Davis
fonte