Jeśli jest jakaś biblioteka w Pythonie, którą można nazwać Ojcem Chrzestnym wizualizacji, byłby to Matplotlib. Za myśl przewodnią całego wpisu niech posłuży nam poniższy cytat.
Metod może być milion i więcej, lecz zasad jest tylko kilka. Człowiek, który rozumie zasady, może z powodzeniem wybierać metody. Człowiek, który próbuje metod, ignorując zasady, wpędza się w tarapaty.
Ralph Waldo Emerson
Nawiązanie bliskich relacji z kimś wiąże się z poznaniem zasad i reguł jakimiś ktoś się kieruje. Zanim do tego dojdziemy, małe intro naszego Ojca Chrzestnego.
Stworzony przez Johna Huntera by, odtworzyć w Pythonie to, co potrafi MatLab w zakresie wykresów. Biblioteka grafiki 2D i 3D do prezentacji danych naukowych.
Główne zalety to:
- łatwy start z prostymi wykresami bez wchodzenia w szczegóły (podejście funkcyjne — wyjaśnię to później),
- obsługa niestandardowych etykiet i tekstów,
- kontrola każdego elementu (podejście obiektowe),
- wiele formatów wyjściowych w wysokiej jakości,
- nastawiony na wysoką konfigurowalność (podejście obiektowe)
Próbka możliwości na oficjalnej stronie projektu, gdzie jest dosyć pokaźna galeria.
W tym wpisie postaram się pokazać jak pracować z naszym bohaterem. Zacznijmy więc od zasad, jakim rządzi się Matplotlib.
Spis Treści
Architektura Matplotlib i Dwa Podejścia
Kluczem do poznania Matplolib-a jest poznanie jego architektury. Swoją budową przypomina… Matrioszkę.

Precyzyjniej wygląda to tak.

Istnieją więc 3 warstwy:
- Najniższa warstwa: Backend Layer. W Jupyterze, gdy wprowadzamy
%matplotlib inline
, to tak naprawdę informujemy Backend Layer, aby renderował wykresy bez wywoływaniaplt.show()
. Możemy także użyć tej warstwy, jeśli chcielibyśmy wybrać silnik do renderowania wykresów np.hvplot
,jshtml
. Są to najprawdopodobniej, jedyne przypadki, w których wchodzimy w interakcję z tą warstwą. - Poziom drugi: Artist Layer. Poziom ten umożliwia największą kontrolę nad całym wykresem poprzez zmianę niemal każdego jego elementu. Dostęp do tej warstwy uzyskujemy np. poprzez
ax1.plot()
. Korzystając z tej warstwy, używamy podejścia obiektowego. - Najwyższy poziom: Scripting Layer. Przydatny, gdy zależy nam na szybkiej wizualizacji, nie wchodząc zbyt głęboko w budowę wykresów. Korzystając z tego poziomu, używamy podejścia funkcyjnego.
Matplotlib Funkcyjnie Czy Obiektowo?
Od razu pojawia się pytanie. Które podejście jest najlepsze?
Najlepsza odpowiedź na tego typu pytania? TO ZALEŻY.
Najważniejszy jest cel, w jakim używamy danego narzędzia. Jeśli chcemy szybko zobaczyć wyniki, nie chcemy dużo zmieniać i nie mamy potrzeby kontroli każdego aspektu wykresu, podejście funkcyjne nam wystarczy.
Gdy chcemy wykres „dopieścić” albo pokazać coś, czego w standardowy sposób nie możemy, trzeba będzie przejść na poziom obiektowy.
Zaawansowani użytkownicy Matplolib-a wychodzą z założenia, że lepiej całkowicie przejść na podejście obiektowe. Jest kilka przesłanek ku temu:
- jawnie definiujemy to, co Matplolib i tak robi za nas, kiedy korzystamy z podejścia funkcyjnego (o tym za chwilę),
- mamy pełną kontrolę nad elementami wykresu,
- kiedy wszystko jest jawnie zdefiniowane, kod staje się czytelniejszy i łatwiejszy do ponownego użycia,
- znajomość podejścia obiektowego ułatwia pracę z innymi bibliotekami do wizualizacji np. seaborn.
Podsumowując, w Matplotlib wiele rzeczy możemy zrobić na kilka sposobów. Może to spowodować małe zamieszanie, ale jeśli rozumiemy strukturę tej biblioteki i dwa oferowane podejścia wszystko staje się prostsze. Korzystając z podejścia funkcyjnego, tak naprawdę przerzucamy na Matplotlib-a ciężar wywoływania obiektów.
Funkcyjnie | Co robi Matplotlib? | Obiektowo | |
plt.subplot() | Matplotlib wywołuje -> | fig = plt.figure() fig.add_subplot() | |
plt.axes() | Matplotlib wywołuje -> | fig = plt.figure() fig.add_axes() |
Skoro struktura jest omówiona, zobaczmy te dwa podejścia w praktyce. Zacznijmy od instalacji i importu biblioteki.
Instalacja i Import Matplotlib
Osoby korzystające z Anacondy mają Matplotlib w gratisie.
Zawsze możemy doinstalować go za pomocą polecenia w wierszu poleceń lub terminalu.
conda install matplotlib
lub
pip install matplotlib
Po instalacji robimy import w naszym edytorze:
import matplotlib.pyplot as plt
Gdy korzystamy z Jupytera, możemy dodać także:
%matplotlib inline
Powyższa linijka sprawia, że nie będziemy musieli za każdym razem wydawać polecenie plt.show()
, aby wygenerować wykres. W przypadku edytora innego niż Jupyter Notebook wydajemy na końcu polecenie plt.show()
.
Podejście Funkcyjne w Praktyce
Pierwszy Wykres Matplotlib
Wykorzystamy w naszym wykresie dwie tablice x
i y
wygenerowane dzięki numpy
.
import numpy as np x = np.linspace(0, 10, 11) y = x ** 2
Wykorzystując powyższe dane, możemy stworzyć bardzo prosty wykres używając jedynie kilka linijek kodu.
# do funkcji plt.plot() wstawiamy nasze dane x i y plt.plot(x, y, 'blue') # 'blue' ustawiamy kolor linii plt.xlabel('Nazwa osi X') plt.ylabel('Nazwa osi Y') plt.title('Tutaj mamy tytuł') plt.show()

Wiele Wykresów Na Jednym „Płótnie”
Wprowadźmy umowne pojęcie „płótna”, na którym będziemy umieszczać nasze wykresy. Dalej wyjaśnię, czym operujemy przy okazji podejścia obiektowego.
Aby na jednym „płótnie” umieścić więcej niż jeden wykres robimy tak jak poniżej.
# funkcja plt.subplot(liczba_wierszy, liczba_kolumn, numer_wykresu) plt.subplot(1,2,1) # jeden wiersz, dwie kolumny, wykres pierwszy plt.plot(x, y, 'b--') # więcej o stylach linii później plt.subplot(1,2,2) # jeden wiersz, dwie kolumny, wykres drugi plt.plot(y, x, 'r*-') plt.show()

Powyższe podejście to wierzchołek „góry lodowej”. Więcej możliwości daje przejście na podejście obiektowe i praca na drugim poziomie Artist Layer.
Podejście Obiektowe
Widzieliśmy już podejście funkcyjne, pora zejść niżej do podejścia obiektowego. Tutaj zaczniemy tworzyć obiekty, by wywoływać metody lub korzystać z atrybutów tych obiektów. Aby lepiej wiedzieć, na czym operujemy, zacznijmy od budowy wykresu.Matplotlib
Budowa Wykresu w Matplotlib
By zrozumieć, jak Matplotlib buduje wykresy, wprowadźmy kilka pojęć:
- Figure — obiekt, który porównajmy do „płótna”, na którym będziemy „malować” nasze wykresy,
- Axes — przestrzeń naszego wykresu, gdzie mamy osie, wykres, tytuł, etykiety itd.,
- Axis — inaczej osie wykresu, dla 2D (x,y) dla 3D (x,y,z).

Główna Idea
Główną ideą korzystania z bardziej formalnej obiektowego podejścia jest tworzenie obiektów, a następnie wywoływanie metod na tym obiekcie lub atrybutów tego obiektu. Takie podejście zyskuje już w momencie, kiedy na jednym „płótnie” mamy więcej wykresów.
Na początek tworzymy „płótno” czyli obiekt Figure, a następnie dodajemy wykres, czyli obiekt Axes.
# tworzymy obiekt Figure, czyli nasze "płótno" fig = plt.figure() # dodajemy nasz wykres, w nawiasach kwadratowych mamy podaną pozycję wykresu # 0.1 start z lewej, 0.1 start od dołu, 0.8 szerokość wykresu, 0.8 wysokość wykresu axes = fig.add_axes([0.1, 0.1, 0.8, 0.8]) # wartości z zakresu (0 - 1 ) # dodanie naszych danych do wykresu, ustawiwnie etykiet i tytułu axes.plot(x, y, 'b--') axes.set_xlabel('Nazwa osi X') # Notice the use of set_ to begin methods axes.set_ylabel('Nazwa osi Y') axes.set_title('Tutaj mamy tutuł') plt.show()

Kod jest nieco bardziej skomplikowany, ale zaletą jest to, że teraz mamy pełną kontrolę nad tym, gdzie umieszczony jest wykres. Możemy też swobodnie dodawać kolejne wykresy do tego samego obiektu Figure.
Mniejszy i Większy
Przykład poniżej pokazuje, jak możemy kontrolować dodawanie, położenie i wielkość kolejnych wykresów.
# tworzymy obiekt Figure, czyli nasze "płótno" fig = plt.figure() # pamiętamy że oznaczenia w nawiasach kwadratowych to # start z lewej, start od dołu, szerokość i wysokość (skala 0-1) axes1 = fig.add_axes([0.1, 0.1, 0.8, 0.8]) # większy wykres axes2 = fig.add_axes([0.2, 0.5, 0.4, 0.3]) # mniejszy wykres # Większy wykres axes1.plot(x, y, 'b--') axes1.set_xlabel('Oś X większego wykresu') axes1.set_ylabel('Oś Y większego wykresu') axes1.set_title('Tytuł większego wykresu') # Mniejszy wykres axes2.plot(y, x, 'r*-') axes2.set_xlabel('Oś X mniejszego wykresu') axes2.set_ylabel('Oś Y mniejszego wykresu') axes2.set_title('Tytuł mniejszego wykresu') plt.show()

Menedżer Wykresów Matplotlib
Korzystając z plt.subplots(), przekazujemy informację, że Matplotlib ma zadbać o układ naszych wykresów. Działa to trochę tak jak automatyczny menadżer wykresów.
# za pomocą krotki fig, axes przechwytujemy obiekty wywołane przez # plt.subplots() fig, axes = plt.subplots() # wykorzystujemy obiekt axes by dodać nasze dane do wykresu axes.plot(x, y, 'r') axes.set_xlabel('oś x') axes.set_ylabel('oś y') axes.set_title('tytuł') plt.show()

Możemy zrobić jednak coś więcej. Możemy podać liczbę wierszy i kolumn przy wywoływaniu metody plt.subplots()
.
# Puste "płótno" z dwoma wykresami czekającymi na dane fig, axes = plt.subplots(nrows=1, ncols=2) plt.show()

Nasz obiekt axes
jest tak naprawdę listą wykresów, w naszym wypadku dwóch pustych wykresów. Możemy przejść przez naszą listę, stosując pętle for
.
for ax in axes: ax.plot(x, x, 'b') ax.set_xlabel('x') ax.set_ylabel('y') ax.set_title('tytuł') # wyświetlenie "płótna", czyli obiektu Figure fig

Częstym problemem związanym z Matplolib są nakładające się na siebie wykresy. Jest prosty sposób na naprawienie tego. Wykorzystujemy metody fig.tight_layout()
lub plt.tight_layout()
, które automatycznie dostosowują pozycje obu wykresów tak, aby nie nakładały się na siebie.
fig, axes = plt.subplots(nrows=1, ncols=2) for ax in axes: ax.plot(x, y, 'g') ax.set_xlabel('x') ax.set_ylabel('y') ax.set_title('title') fig plt.tight_layout()

Wielkość Płótna i DPI
Matplotlib w momencie tworzenia daje możliwość ustalenia wielkości naszego „płótna” oraz ustalić dpi (przydatna funkcja, gdy będziemy tworzyć wykresu do druku).
Służą do tego dwa atrybuty:
- figsize — krotka, w której podajemy szerokość i wysokość w calach,
- dpi inaczej dots-per-inch czyli liczba pikseli na cal.
Możemy to zrobić tak:
fig = plt.figure(figsize=(8,4), dpi=300)
lub w menadżerze wykresów:
fig, axes = plt.subplots(figsize=(8,4),dpi=300) axes.plot(x, y, 'r') axes.set_xlabel('x') axes.set_ylabel('y') axes.set_title('tytuł') plt.show()

Zapisywanie „Płótna”
Matplotlib może generować wysokiej jakości wykresy w wielu formatach, w tym: PNG, JPG, EPS, SVG, PGF i PDF.
Wykresy zapisujemy poprzez wywołanie metody plt.savefig()
z podaniem ścieżki naszego pliku.
fig.savefig("nazwa_pliku.png")
Ewentualnie podajemy jeszcze dpi.
fig.savefig("nazwa_pliku.png", dpi=200)
Tytuły, Nazwy Osi i Legenda
Z poprzednich przykładów wiemy jak nadawać tytułu naszym wykresom:
ax.set_title("tytuł")
oraz nazywać osie:
ax.set_xlabel("x") ax.set_ylabel("y")
Przydałoby się mieć legendę dla wykresów. W tym celu używamy argumentu label = "etykieta dla danych"
, gdy dodajemy dane do wykresu. Wszytko, po to, aby na końcu wygenerować legendę dla wykresu.
Pokażemy na przykładzie sinusa i cosinusa i danych wygenerowanych z numpy
.
# wygenerujmy sobie z numpy dane dla sinusa i cosinusa X = np.linspace(-np.pi, np.pi, 256, endpoint=True) C, S = np.cos(X), np.sin(X) fig = plt.figure() ax = fig.add_axes([0.1,0.1,0.8,0.8]) ax.plot(S, label="sinus") ax.plot(C, label="cosinus") ax.legend() plt.show()

Domyślne ustawienie legendy jest takie, aby znaleźć optymalne miejsce dla siebie.Używamy atrybutu loc
aby określić miejsce legendy.
ax.legend(loc=0) # domyślne najbardziej optymalne miejsce ax.legend(loc=1) # górny prawy róg ax.legend(loc=2) # górny lewy róg ax.legend(loc=3) # dolny lewy róg ax.legend(loc=4) # dolny prawy róg
Kolory, Style i Grubość Linii
Matplotlib ma wiele możliwości dostosowania wykresu do własnych preferencji. Można wykorzystać składnię z MatLab-a, stosując uproszczoną notację (rezygnując tym samym z czytelności kodu).
# wygenerujmy sobie z numpy dane dla sinusa i cosinusa X = np.linspace(-np.pi, np.pi, 256, endpoint=True) C, S = np.cos(X), np.sin(X) fig = plt.figure() ax = fig.add_axes([0.1,0.1,0.8,0.8]) ax.plot(S, 'b--', label="sinus") # niebieski kolor i linia kreskowana ax.plot(C, 'g:', label="cosinus") # zielony kolor i linia kropkowana ax.legend()

Podając odpowiednie argumenty, możemy ustawić wszystkie opcje jawnie (zyskując na czytelności kodu):
- color — w postaci kodu szesnastkowego lub nazwy (lista nazw kolorów tutaj),
- alpha — poziom przezroczystości koloru w przedziale 0-1,
- linewidth lub lw — grubość linii,
- linestyle lub ls — style linii,
- marker — ustawiamy symbol znacznika,
- markersize — wielkość znacznika,
- markerfacecolor — kolor główny znacznika,
- markeredgewidth — grubość obramowania znacznika,
- markeredgecolor — kolor obramowania znacznika.
Zbiorcze zestawienie i porównanie atrybutów.
fig, ax = plt.subplots(figsize=(12,8)) # kolor szestnaskowy z 50% przezroczystością ax.plot(x, x, color="#8B008B", alpha=0.5) # różne grubości linii# wygenerujmy sobie z numpy dane dla sinusa i cosinusa X = np.linspace(-np.pi, np.pi, 256, endpoint=True) C, S = np.cos(X), np.sin(X) fig = plt.figure() ax = fig.add_axes([0.1,0.1,0.8,0.8]) ax.plot(S, label="sinus") ax.plot(C, label="cosinus") ax.legend() plt.show() ax.plot(x, x+1, color="red", linewidth=0.25) ax.plot(x, x+2, color="red", linewidth=0.50) ax.plot(x, x+3, color="red", linewidth=1.00) ax.plot(x, x+4, color="red", linewidth=2.00) # style linii możliwe opcje: ‘-‘, ‘–’, ‘-.’, ‘:’, ax.plot(x, x+5, color="green", lw=3, linestyle='-') ax.plot(x, x+6, color="green", lw=3, ls='-.') ax.plot(x, x+7, color="green", lw=3, ls=':') # możliwe style markerów: '+', 'o', '*', 's', ',', '.', '1', '2', '3', '4', ... ax.plot(x, x+ 9, color="blue", lw=3,Jeśli jest jakaś biblioteka w Pythonie, którą można nazwać Ojcem Chrzestnym wizualizacji, byłby to Matplotlib. Niech poniższy cytat będzie myślą przewodnią całego wpisu. ls='-', marker='+') ax.plot(x, x+10, color="blue", lw=3, ls='--', marker='o') ax.plot(x, x+11, color="blue", lw=3, ls='-', marker='s') ax.plot(x, x+12, color="blue", lw=3, ls='--', marker='1') # wielkość i kolory markerów ax.plot(x, x+13, color="purple", lw=1, ls='-', marker='o', markersize=2) ax.plot(x, x+14, color="purple", lw=1, ls='-', marker='o', markersize=4) ax.plot(x, x+15, color="purple", lw=1, ls='-', marker='o', markersize=8, markerfacecolor="red") ax.plot(x, x+16, color="purple", lw=1, ls='-', marker='s', markersize=8, markerfacecolor="yellow", markeredgewidth=3, markeredgecolor="green") plt.show()

Granice Wykresu
Możemy skonfigurować granice osi wykresów przy użyciu metod set_ylim
i set_xlim
. Możemy także wydać polecenie aby, Matplotlib sam zawężał wykres za pomocą polecenia axis('tight')
.
fig, axes = plt.subplots(1, 3, figsize=(12, 4)) axes[0].plot(C) axes[0].plot(S) axes[0].set_title("domyślne granice wykresu") axes[1].plot(C) axes[1].plot(S) axes[1].axis('tight') axes[1].set_title("zawężony_automatycznie") axes[2].plot(C) axes[2].plot(S) axes[2].set_ylim([0,1]) axes[2].set_xlim([50, 200]) axes[2].set_title("ręczne_ograniczenie") plt.show()

Typy Wykresów Matplotlib
Do tej pory korzystaliśmy z wykresów liniowych plot()
. Pokażę kilka kluczowych, które już niedługo przydadzą się nam przy okazji eksploracji danych.
Histogram
Do sprawdzenia rozkładu wartości numerycznych.
import numpy as np srednia, odchylenie = 500, 100 rozklad = np.random.normal(srednia,odchylenie,1000) plt.hist(rozklad) plt.show()

Bar Plot
Do obserwacji kategorii przyda się Bar Plot.
sprzedaz = (100, 20, 70, 150, 45) grupy = ['G1', 'G2', 'G3', 'G4', 'G5'] fig, axes = plt.subplots() axes.bar(labels,sprzedaz) axes.set_xlabel('Grupy') axes.set_ylabel('Sprzedaż') axes.set_title('Sprzedaż z podziałem na grupy') plt.show()

Box Plot
Do obserwacji segmentacji, czyli relacji pomiędzy wartościami numerycznymi a kategoriami zastosujemy Box Plot.
dane = [np.random.normal(0, odchylenie, 100) for odchylenie in range(1, 4)] plt.boxplot(dane) plt.show()

Heatmap
Do badania korelacji miedzy danymi numerycznymi będzie pomocny Heatmap.
a = np.random.random((16, 16)) plt.imshow(a)

Źródła, Linki, Inspiracje
Lista adresów, z których korzystałem w tym wpisie, i które warto odwiedzić:
- http://www.matplotlib.org – oficjalna strona projektu Matplotlib,
- https://github.com/matplotlib/matplotlib – kod źródłowy projektu,
- http://matplotlib.org/gallery.html – galeria wykresów,
- https://www.tutorialspoint.com/matplotlib/index.htm – dobry tutorial,
- https://www.kaggle.com/python10pm/plotting-with-python-learn-80-plots-step-by-step – kolejny tutorial z nastawieniem na rodzaje wykresów,
- Na koniec motyw towarzyszący podczas tego wpisu.