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?

jolindbe
źródło
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

divenex
źródło
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.

bli
źródło
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).

Piotr Migdal
źródło
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.

Marius
źródło
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

EL_DON
źródło
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.

CPe
źródło
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.

Luke Davis
źródło