Wspólne xlabel / ylabel dla podplotów matplotlib


154

Mam następującą fabułę:

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

a teraz chciałbym nadać temu wykresowi wspólne etykiety osi X i Y. Przez „wspólne” mam na myśli, że pod całą siatką wykresów cząstkowych powinna znajdować się jedna duża etykieta osi X, a po prawej stronie jedna duża etykieta osi y. Nie mogę znaleźć nic na ten temat w dokumentacji dla plt.subplots, a moi pracownicy Google sugerują, że muszę plt.subplot(111)zacząć od czegoś dużego - ale jak następnie umieścić w tym wątki 5 * 2 plt.subplots?


2
Wraz z aktualizacją pytania i komentarzami pozostawionymi w odpowiedziach poniżej jest to duplikat stackoverflow.com/questions/6963035/ ...
Hooked

Niezupełnie, ponieważ moje pytanie dotyczy plt.subplots (), a pytanie, do którego linkujesz, używa add_subplot - nie mogę użyć tej metody, chyba że przełączę się na add_subplot, czego chciałbym uniknąć. I mógłby użyć roztworu plt.text który jest podany jako alternatywne rozwiązanie w linku, ale nie jest to najbardziej eleganckie rozwiązanie.
jolindbe

Aby rozwinąć, o ile rozumiem, plt.subplots nie może wygenerować zestawu wykresów cząstkowych w istniejącym środowisku osi, ale zawsze tworzy nową figurę. Dobrze?
jolindbe,

Najbardziej eleganckie rozwiązanie można znaleźć tutaj: stackoverflow.com/questions/6963035/…
Mr.H

Twój link został dostarczony przez użytkownika Hooked ponad 4 lata temu (tylko kilka komentarzy nad Twoim). Jak powiedziałem wcześniej, rozwiązanie to dotyczy add_subplot, a nie plt.subplots ().
jolindbe

Odpowiedzi:


229

To wygląda na to, czego naprawdę chcesz. Stosuje to samo podejście tej odpowiedzi do konkretnego przypadku:

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

Wiele wykresów ze wspólną etykietą osi


4
zauważ, że 0.5 dla współrzędnej x etykiety x nie umieszcza etykiety w środku środkowego wykresu podrzędnego. musiałbyś być nieco większy, aby uwzględnić etykiety yticklabels.
dbliss

3
Spójrz na tę odpowiedź, aby znaleźć metodę, która nie jest używana plt.text. Tworzysz podploty, a następnie dodajesz jeden bitowy wykres, sprawiasz, że jest niewidoczny i oznaczasz jego x i y.
James Owers

Dzięki, ogólnie działało. Jakieś rozwiązanie na pęknięcie podczas używania tight_layout?
serv-inc

3
@ Serv-ink z tight_layoutwymianą 0.04ze 0wydaje się działać.
divenex

4
Używanie fig.textnie jest dobrym pomysłem. To psuje takie rzeczy, jakplt.tight_layout()
Spokojny

68

Ponieważ uważam to za odpowiednie i wystarczająco eleganckie (nie ma potrzeby podawania współrzędnych, aby umieścić tekst), kopiuję (z niewielkim dostosowaniem) odpowiedź na inne powiązane pytanie .

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

Z tego wynika (w wersji matplotlib 2.2.0):

Wykresy cząstkowe z 5 wierszami i 2 kolumnami ze wspólnymi etykietami osi X i Y.


8
Ze względu na prostotę powinna to być akceptowana odpowiedź. Bardzo proste. Nadal dotyczy matplotlib v3.x.
Kyle Swanson

Chciałbym wiedzieć, jak można go używać z obiektami o wielu figurach? fig.xlabel ("foo") nie działa.
Horror Vacui

FYI: Teraz, gdy ludzie używają ciemnych motywów w StackOverflow, etykiety ledwo można odczytać, więc lepiej wyeksportować png z białym tłem
xyzzyqed

@xyzzyqed Nie wiedziałem, że w stosie jest coś takiego jak „motywy” i nawet nie pamiętam, jak wyeksportowałem figurę. Jak mogę kontrolować tło podczas eksportowania?
bli

2
Jedynym problemem tego rozwiązania jest to, że nie działa podczas używania, constrained_layout=Trueponieważ tworzy nakładające się etykiety. W takim przypadku musisz ręcznie dostosować granice działek pomocniczych.
baccandr

36

Bez sharex=True, sharey=TrueCiebie otrzymasz:

wprowadź opis obrazu tutaj

Dzięki niemu powinieneś ładniej:

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

wprowadź opis obrazu tutaj

Ale jeśli chcesz dodać dodatkowe etykiety, powinieneś dodać je tylko do wydruków krawędzi:

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

wprowadź opis obrazu tutaj

Dodanie etykiety do każdego wykresu zepsułoby to (być może istnieje sposób na automatyczne wykrycie powtarzających się etykiet, ale ja nie znam żadnego).


Jest to o wiele trudniejsze, jeśli, na przykład, liczba działek jest nieznana (np. Masz uogólnioną funkcję wykresu, która działa dla dowolnej liczby wątków pobocznych).
naught101

15

Od polecenia:

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

użyłeś zwraca krotkę składającą się z figury i listy instancji osi, wystarczy już zrobić coś takiego (pamiętaj, że zmieniłem fig,axna 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')

Jeśli zdarzy ci się chce zmienić kilka szczegółów na konkretnym poletko, można uzyskać do niego dostęp za pośrednictwem axes[i]gdzie iiteracje nad swoimi wątków.

Bardzo pomocne może być również dołączenie pliku

fig.tight_layout()

na końcu pliku, przed plt.show(), aby uniknąć nakładania się etykiet.


5
Przepraszam, że powyżej byłem trochę niejasny. Przez „wspólne” miałem na myśli jedną pojedynczą etykietę x poniżej wszystkich wykresów i jedną etykietę y po lewej stronie wykresów, zaktualizowałem pytanie, aby to odzwierciedlić.
jolindbe

2
@JohanLindberg: W odniesieniu do twoich komentarzy tutaj i powyżej: Rzeczywiście plt.subplots()utworzy nową instancję figury. Jeśli chcesz trzymać się tego polecenia, możesz łatwo dodać a big_ax = fig.add_subplot(111), ponieważ masz już figurę i możesz dodać kolejną oś. Następnie możesz manipulować big_axsposobem, w jaki jest wyświetlany w linku z Hooked.
Marius

Dziękuję za sugestie, ale jeśli to zrobię, muszę dodać big_ax po plt.subplots () i otrzymuję ten subplot na wszystko inne - czy mogę uczynić go przezroczystym, czy jakoś wysłać go na koniec? Nawet jeśli ustawię wszystkie kolory na żadne, jak w łączu Hookeda, nadal jest to białe pole obejmujące wszystkie moje wątki cząstkowe.
jolindbe

2
@JohanLindberg, masz rację, nie sprawdzałem tego. Ale możesz łatwo ustawić kolor tła dużej osi none, wykonując następujące czynności: big_ax.set_axis_bgcolor('none')Powinieneś także zrobić labelcolor none(w przeciwieństwie do przykładu połączonego przez Hooked):big_ax.tick_params(labelcolor='none', top='off', bottom='off', left='off', right='off')
Marius

2
Pojawia się błąd: AttributeError: 'numpy.ndarray' object has no attribute 'set_xlabel'w wyciągu ax.set_xlabel('Common x-label'). Czy możesz to rozgryźć?
hengxin

6

Będzie wyglądać lepiej, jeśli zarezerwujesz miejsce na wspólne etykiety, tworząc niewidoczne etykiety dla wykresu pomocniczego w lewym dolnym rogu. Dobrze jest również przekazać rozmiar czcionki z rcParams. W ten sposób wspólne etykiety zmienią rozmiar wraz z konfiguracją rc, a osie zostaną również dostosowane, aby zostawić miejsce na wspólne etykiety.

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

wprowadź opis obrazu tutaj wprowadź opis obrazu tutaj


1
Niezłe użycie niewidocznej etykiety! Dziękuję
colelemonz

3

Podobny problem napotkałem podczas kreślenia siatki wykresów. Wykresy składały się z dwóch części (górnej i dolnej). Etykieta Y miała być wyśrodkowana na obu częściach.

Nie chciałem używać rozwiązania polegającego na znajomości pozycji na zewnętrznej figurze (np. Rys.text ()), więc manipulowałem pozycją y funkcji set_ylabel (). Zwykle wynosi 0,5, środek działki, do którego jest dodawany. Ponieważ wypełnienie między częściami (hspace) w moim kodzie wynosiło zero, mogłem obliczyć środek dwóch części w stosunku do górnej części.

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

obrazek

Wady:

  • Pozioma odległość do wykresu jest oparta na górnej części, dolne znaczniki mogą sięgać do etykiety.

  • Formuła nie uwzględnia odstępów między częściami.

  • Zgłasza wyjątek, gdy wysokość górnej części wynosi 0.

Prawdopodobnie istnieje ogólne rozwiązanie uwzględniające wypełnienie między liczbami.


Hej, znalazłem sposób, aby to zrobić w duchu twojej odpowiedzi, ale może rozwiązać niektóre z tych problemów; patrz stackoverflow.com/a/44020303/4970632 (poniżej)
Luke Davis

3

Aktualizacja:

Ta funkcja jest teraz częścią pakietu proplot matplotlib, który niedawno wydałem na pypi. Domyślnie podczas tworzenia figur etykiety są „wspólne” między osiami.


Oryginalna odpowiedź:

Odkryłem solidniejszą metodę:

Jeśli znasz bottomi topkwargs, które przeszły podczas GridSpecinicjalizacji, lub w inny sposób znasz położenie krawędzi swoich osi we Figurewspółrzędnych , możesz również określić położenie ylabel we Figurewspółrzędnych za pomocą jakiejś fantazyjnej magii „transformacji”. Na przykład:

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

... i powinieneś zobaczyć, że etykieta nadal odpowiednio dostosowuje się od lewej do prawej, aby nie nakładać się na etykiety znaczników, tak jak zwykle - ale teraz dostosuje się, aby zawsze znajdować się dokładnie między pożądanymi polami.

Ponadto, jeśli nawet nie używasz set_position, ylabel pojawi się domyślnie dokładnie w połowie liczby . Zgaduję, że dzieje się tak dlatego, że kiedy etykieta jest ostatecznie rysowana, matplotlibużywa 0,5 dla y-korzędnej bez sprawdzania, czy podstawowa transformacja współrzędnych uległa zmianie.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.