Programowanie ¶
Wykład 6 - Przegląd bibliotek naukowych: matplotlib
Plan wykładu:
Wprowadzenie Prawie jak Matlab
Matplotlib i obiektowe API Wybrane typy wykresów 2D Jeszcze o układzie wykresów Mapy kolorów i wykresy konturowe Wykresy 3D
Animacje
Materiały do wykładu:
http://matplotlib.org/ (http://matplotlib.org/)
wykład J.R. Johanssona "Scientific Python" dostępny pod adresem
http://github.com/jrjohansson/scientific-python-lectures (http://github.com/jrjohansson/scientific-python- lectures)
In [5]:
Wprowadzenie
Matplotlib to biblioteka do wykresów 2D i 3D:
łatwa w obsłudze
zainspirowana Matlabem różne typy wykresów
"publikacyjna" jakość
formatowanie etykiet i tekstów przy pomocy -a
duża kontrola nad wszystkimi elementami wykresu, z DPI włącznie
GUI do interaktywnej pracy z wykresem lub przetwarzanie wsadowe z zapisem wykresu do pliku wsparcie dla wielu formatów wyjściowych (m.in. PNG, PDF, SVG, EPS i PGF)
możliwość osadzania wykresów we własnych GUI współpracuje z NumPY
Aby móc używać Matplotlib z poziomu programu napisanego w Pythonie, musimy zaimportować bibliotekę.
Możemy to zrobić na dwa sposoby. Albo korzystając z modułu pylab:
LT X
AE
# Poniższe polecenie skonfiguruje matplotlib w taki sposób, aby
# wygenerowane rysunki były wstawiane bezpośrednio do notatnika IPythona,
%matplotlib inline
albo importując matplotlib.pyplot:
In [7]:
Moduł pylab łączy ze sobą funkcjonalność pyplot (tworzenie wykresów) z numpy (operacje na macierzach) w jednej przestrzeni nazw. Praca z (I)Pythonem przypomina wówczas pracę w Matlabie. Natomiast ten drugi sposób jest preferowany jako bardziej przejrzysty, mimo że z reguły jest trochę bardziej uciążliwy w
użytkowaniu. Jeżeli planujemy osadzać wykresy we własnych GUI, pracujemy z modułem matplotlib i kilkoma jego podmodułami.
Prawie jak Matlab
Moduł pylab zaprojektowano w taki sposób, aby polecenia rysowania funkcji były kompatybilne z poleceniami Matlaba (ma to ułatwić rozpoczęcie pracy z Pythonem ludziom znającym Matlaba).
In [8]:
Najprostszy wykres:
In [9]:
Wykres zależności funkcyjnej:
Out[9]:
[<matplotlib.lines.Line2D at 0x7ff7583cdb70>]
from pylab import *
import matplotlib.pyplot as plt
from pylab import *
plot([1,2,3,4])
In [11]:
Większość matlabowych poleceń działa:
Out[11]:
<matplotlib.text.Text at 0x7ff758329e10>
x = linspace(0, 5, 10) y = x ** 2
figure()
plot(x, y, 'r') #'r' to czerwona linia xlabel('x') # opis osi x
ylabel('y') # opis osi y
title('$y=x^2$') # tytuł wykresu, formatowanie LaTeX-a
In [13]:
Typy linii i punktów:
Symbol Znaczenie
- linia ciągła (domyślny styl) -- linia przerywana
-. linia przerywana (kreska-kropka-kreska) Out[12]:
[<matplotlib.lines.Line2D at 0x7ff758291e48>]
Out[13]:
[<matplotlib.lines.Line2D at 0x7ff7583aa080>]
subplot(1,2,1) #jeden wiersz, dwie kolumny, rysunek pierwszy plot(x, y, 'r--') #czerwona linia przerywana
subplot(1,2,2) #jeden wiersz, dwie kolumny, rysunek drugi plot(y, x, 'g*-') #zielona linia+punkty w formie gwiazdek
#zmiana zakresów axis([-1,6,-1,26])
plot(x, y, 'ro-') #czerwona linia + punkty w formie dysków
: linia kropkowana . punkty
, piksele o dyski
^ trójkąty skierowane do góry v trójkąty skierowane w dół
< trójkąty skierowane w lewo
> trójkąty skierowane w prawo s kwadraty
D diamenty
d "odchudzone" diamenty + symbole +
x symbole x
* symbole *
1 trójnogi skierowane w dół 2 trójnogi skierowane w górę 3 trójnogi skierowane w lewo 4 trójnogi skierowane w prawo
h sześciokąty (wierzchołkiem do góry) H sześciokąty (bokiem do góry) p pięciokąty
| linie pionowe _ linie poziome
steps styl steps z Gnuplota Kolory:
Symbol Znaczenie
b niebieski (domyślny) g zielony
r czerwony c cyjan m magenta y żółty k czarny
In [14]:
Właściwości linii:
In [15]:
Out[14]:
[<matplotlib.lines.Line2D at 0x7ff758159be0>, <matplotlib.lines.Line2D at 0x7ff758159d68>, <matplotlib.lines.Line2D at 0x7ff75815f6d8>]
t = arange(0.0,5.2,0.2)
plot(t,t,'ro',t,t**2,'bs',t,t**3,'g^')
lines = plot([1,2,3,4])
agg_filter: unknown
alpha: float (0.0 transparent through 1.0 opaque) animated: [True | False]
antialiased or aa: [True | False]
axes: an :class:`~matplotlib.axes.Axes` instance
clip_box: a :class:`matplotlib.transforms.Bbox` instance clip_on: [True | False]
clip_path: [ (:class:`~matplotlib.path.Path`, :class:`~matplotlib.t ransforms.Transform`) | :class:`~matplotlib.patches.Patch` | None ]
color or c: any matplotlib color contains: a callable function
dash_capstyle: ['butt' | 'round' | 'projecting']
dash_joinstyle: ['miter' | 'round' | 'bevel']
dashes: sequence of on/off ink in points
drawstyle: ['default' | 'steps' | 'steps-pre' | 'steps-mid' | 'step s-post']
figure: a :class:`matplotlib.figure.Figure` instance
fillstyle: ['full' | 'left' | 'right' | 'bottom' | 'top' | 'none']
gid: an id string
label: string or anything printable with '%s' conversion.
linestyle or ls: ['solid' | 'dashed', 'dashdot', 'dotted' | (offse t, on-off-dash-seq) | ``'-'`` | ``'--'`` | ``'-.'`` | ``':'`` | ``'No ne'`` | ``' '`` | ``''``]
linewidth or lw: float value in points
marker: :mod:`A valid marker style <matplotlib.markers>`
markeredgecolor or mec: any matplotlib color markeredgewidth or mew: float value in points markerfacecolor or mfc: any matplotlib color
markerfacecoloralt or mfcalt: any matplotlib color markersize or ms: float
markevery: [None | int | length-2 tuple of int | slice | list/array of int | float | length-2 tuple of float]
path_effects: unknown
picker: float distance in points or callable pick function ``fn(art ist, event)``
pickradius: float distance in points rasterized: [True | False | None]
sketch_params: unknown snap: unknown
solid_capstyle: ['butt' | 'round' | 'projecting']
solid_joinstyle: ['miter' | 'round' | 'bevel']
transform: a :class:`matplotlib.transforms.Transform` instance url: a url string
visible: [True | False]
xdata: 1D array ydata: 1D array zorder: any number setp(lines)
In [18]:
Wybrane własności:
Własność Znaczenie
alpha przezroczystość (od 0. do 1.) Out[17]:
[<matplotlib.lines.Line2D at 0x7ff7580ae0b8>, <matplotlib.lines.Line2D at 0x7ff7580ae358>]
Out[18]:
[<matplotlib.lines.Line2D at 0x7ff758089cc0>, <matplotlib.lines.Line2D at 0x7ff758089f60>]
x = arange(0,2*pi,pi/100) si = sin(x)
co = cos(x)
plot(x,si,'b-.',x,co,'r--')
x = arange(0,2*pi,pi/100) si = sin(x)
co = cos(x)
plot(x,si,'b-.',x,co,'r--',linewidth=3.0)
antialiased antyaliasing (True lub False)
color kolor
data_cliping obcinanie danych (True lub False) label łańcuch znaków używany jako legenda linestyle styl linii
linewidth grubość linii
marker symbol przy wykreślaniu danych dyskretnych markeredgewidth grubość krawędzi symbolu
makeredgecolor kolor krawędzi symbolu makerfacecolor kolor symbolu
markersize wielkość symbolu
Jeszcze o trybie wielowykresowym:
In [19]:
In [20]:
Out[19]:
<matplotlib.figure.Figure at 0x7ff758024710>
<matplotlib.figure.Figure at 0x7ff758024710>
figure(1) #aktywny rysunek
t1 = arange(0.0,5.0,0.1) t2 = arange(0.0,2.0,0.01) def f(t):
return cos(t*t)
Na wykresie możemy również umieszczać tekst. Możliwości jest kilka:
opisy osi tytuł wykresu legenda
tekst wstawiany "ręcznie"
Coś prostego na początek, czyli opisy osi i tytuł wykresu:
Out[21]:
[<matplotlib.lines.Line2D at 0x7ff757ffa978>]
#tworzymy rysunek złożonych z dwóch wykresów położonych jeden nad drugim subplot(211)
plot(t1,f(t1),linewidth=3.0) subplot(212)
plot(t2,f(t2),'r--',linewidth=8.0)
Dodajemy legendę:
Out[22]:
<matplotlib.text.Text at 0x7ff757f92080>
x = arange(0.0,2*pi,pi/100) si = sin(x)
co = cos(x)
plot(x,si,'g-',x,co,'r--',linewidth=3.0)
#opis osi x xlabel('x')
ylabel('sin(x), cos(x)')
#tytuł wykresu
title('Funkcje trygonometryczne')
Dodajemy do wykresu dowolny tekst:
Out[23]:
<matplotlib.legend.Legend at 0x7ff757e94f28>
#dodajemy legendę
plot(x,si,'g-',x,co,'r--',linewidth=3.0)
#opis osi x xlabel('x')
ylabel('sin(x), cos(x)')
#tytuł wykresu
title('Funkcje trygonometryczne')
legend(('sin(x)','cos(x)')) #etykiety w kolejności wywołań w funkcji plot
W pewnych przypadkach warto jest skorzystać z formatowania $\LaTeX$-a:
Out[24]:
<matplotlib.text.Text at 0x7ff757e89550>
plot(x,si,'g-',x,co,'r--',linewidth=3.0) xlabel('x')
ylabel('sin(x), cos(x)')
title('Funkcje trygonometryczne') legend(('sin(x)','cos(x)'))
#tekst o początku w punkcie (3,0.5) text(3,0.5,'trajektoria')
Matplotlib i obiektowe API
Pylab ma z pewnością dwie zalety:
ułatwia rozpoczęcie pracy osobom mającym doświadczenie z Matlaba i GNU Octave redukuje ilość kodu niezbędną do stworzenia prostego wykresu do minimum.
Mimo to warto jest poświęcić trochę uwagi obiektowemu API oferowanemu przez matplotlib, które
wprawdzie nie jest zgodne z Matlabem i wymaga większego nakładu pracy przy kodowaniu, ale za to pozwala na dużo większą kontrolę nad tworzonymi wykresami.
Dla przypomnienia, matplotlib importujemy teraz w następujący sposób:
In [26]:
In [27]:
Stwórzmy teraz prosty wykres. W przeciwieństwie do pylab, będzie on miał charakter lokalny, a referencję do niego możemy przypisać pewnej zmiennej:
Out[25]:
<matplotlib.text.Text at 0x7ff757ec5940>
def f(x):
return (x**3)*exp(-x**2/4.) x = arange(0.0,10.0,0.5)
y = f(x)
plot(x,y,'ro')
xlabel('$x$',fontsize=24)
ylabel('$x^{3}e^{-x^2/4}$',fontsize=24)
%matplotlib inline
import matplotlib.pyplot as plt
Rozważmy teraz trochę bardziej skomplikowany przykład - wykres w wykresie. Dzięki pyplot mamy pełną kontrolę nad tym, gdzie wstawiać poszczególne układy współrzędnych i jak je opisać:
Figure(432x288)
fig = plt.figure() #tworzymy instancję obiektu Figure print(fig)
#dodajemy do rysunku osie układu współrzędnych
axes = fig.add_axes([0.1, 0.1, 0.8, 0.8]) # lewy, dolny, szerokość, wysokość (zakre
#do osi dodajemy wykres axes.plot(x, y, 'ro')
#opisujemy wykres axes.set_xlabel('x') axes.set_ylabel('y')
axes.set_title('$x^{3}e^{-x^2/4}$',fontsize=24);
Jeżeli mamy do wykonania wiele wykresów na jednym rysunku, ale nie interesuje nas za bardzo, gdzie każdy z nich powinien być wstawiony, możemy skorzystać z funkcji zarządzających ich układem na stronie. Jedną z nich jest subplots:
x = linspace(0,5,10) y = x**2
fig = plt.figure()
axes1 = fig.add_axes([0.1, 0.1, 0.8, 0.8]) # główny układ współrzędnych
axes2 = fig.add_axes([0.2, 0.5, 0.4, 0.3]) # układ współrzędnych wstawionego wykres
# główny wykres
axes1.plot(x, y, 'r') axes1.set_xlabel('x') axes1.set_ylabel('y')
axes1.set_title(u'Główny wykres')
# wstawka
axes2.plot(y, x, 'g') axes2.set_xlabel('y') axes2.set_ylabel('x')
axes2.set_title('Wstawka');
In [31]:
Jedyny problem, jaki tutaj się pojawił, to nachodzenie etykiety osi jednego wykresu na drugi. Możemy to naprawić przy pomocy metody fig.tight_layout:
fig, axes = plt.subplots() axes.plot(x, y, 'r')
axes.set_xlabel('x') axes.set_ylabel('y') axes.set_title('title');
#dwa wykresy obok siebie
fig, axes = plt.subplots(nrows=1, ncols=2) for ax in axes:
ax.plot(x, y, 'r') ax.set_xlabel('x') ax.set_ylabel('y') ax.set_title(u'Tytuł')
In [33]:
fig, axes = plt.subplots(nrows=1, ncols=2) for ax in axes:
ax.plot(x, y, 'r') ax.set_xlabel('x') ax.set_ylabel('y') ax.set_title(u'Tytuł')
fig.tight_layout()
#i jeszcze raz to samo, tylko teraz jeden wykres nad drugim:
fig, axes = plt.subplots(nrows=2, ncols=1) for ax in axes:
ax.plot(x, y, 'r') ax.set_xlabel('x') ax.set_ylabel('y') ax.set_title(u'Tytuł')
fig.tight_layout()
Tworząc instancję obiektu Figure możemy określić, jak duży ma być rysunek (w calach), oraz w jakiej rozdzielczości narysowany. Np. aby stworzyć wykres o rozmiarze 800x400 pikseli w rozdzielczości 100 dpi, wykonamy polecenie:
In [34]:
Tych samych argumentów możemy użyć w funkcjach zarządzających układem wykresów:
In [35]:
Zapisywanie wykresów
Wykres zapiszemy do pliku przy pomocy metody savefig:
In [36]:
Opcjonalnie, możemy podać rozdzielczość oraz zmienić format wyjściowy:
In [37]:
Dostępne formaty:
Figure(800x400)
<matplotlib.figure.Figure at 0x7ff757d2d9e8>
fig = plt.figure(figsize=(8,4), dpi=100) # (szerokość, wysokość) print(fig)
fig, axes = plt.subplots(figsize=(12,3)) axes.plot(x, y, 'r')
axes.set_xlabel('x') axes.set_ylabel('y') axes.set_title(u'Tytuł');
fig.savefig("filename.png")
fig.savefig("filename.pdf", dpi=200)
SVG PGF PDF
W zastosowaniach naukowych ciągle chyba najbardziej popularny jest format EPS ("naturalny" format
$\LaTeX$-a), jednak coraz częściej stosuje się PDF jako formatu zapisu wykresów. Jest to związane również z tym, że pdflatex pozwala na bezpośrednie wstawianie wykresów PDF.
Legenda, opisy osi oraz tutył wykresu
Tytuł rysunku może być dodany oddzielnie do każdej instancji osi na rysunku:
In [38]:
Podobnie dodajemy opisy osi:
Out[38]:
<matplotlib.text.Text at 0x7ff7926e7b38>
fig, ax = plt.subplots() ax.plot(x, x**2)
ax.plot(x, x**3)
ax.set_title('Dwie funkcje na jednym wykresie')
Legendę możemy dodać na dwa sposoby. Pierwszy z nich, to metoda legend obiektu axis, składnią przypominająca funkcję z pylaba. Jako argument podajemy krotkę etykiet dla wcześniej zdefiniowanych krzywych (w porządku definiowania):
Out[39]:
<matplotlib.text.Text at 0x7ff75758ef28>
fig, ax = plt.subplots() ax.plot(x, x**2)
ax.plot(x, x**3) ax.set_xlabel('x') ax.set_ylabel('y')
ax.set_title('Dwie funkcje na jednym wykresie')
Lepszy sposób to użycie argumentu label="label text" w funkcji plot:
/usr/local/lib/python3.5/dist-packages/matplotlib/axes/_axes.py:531:
UserWarning: No labelled objects found. Use label='...' kwarg on indi vidual plots.
warnings.warn("No labelled objects found. "
fig, ax = plt.subplots() ax.plot(x, x**2)
ax.plot(x, x**3)
ax.legend(loc=2); # lewy górny róg ax.set_xlabel('x')
ax.set_ylabel('y') ax.set_title('title');
ax.legend(("y = x**2","y = x**3"));
Zaletą tej drugiej metody jest automatyczna aktualizacja legendy w przypadku dodawania lub usuwania krzywych z wykresu.
Funkcja legend przyjmuje opcjonalny argument określający położenie legendy na wykresie:
Out[41]:
<matplotlib.legend.Legend at 0x7ff757b524e0>
fig, ax = plt.subplots()
ax.plot(x, x**2, label="y = x**2") ax.plot(x, x**3, label="y = x**3") ax.set_xlabel('x')
ax.set_ylabel('y') ax.set_title('title');
ax.legend() #tego ciągle potrzebujemy
Więcej na temat legendy można znaleźć pod adresem http://matplotlib.org/users/legend_guide.html#legend- location (http://matplotlib.org/users/legend_guide.html#legend-location).
Formatowanie tekstu
Kilka prostych przykładów formatowania tekstu przy pomocy $\LaTeX$-a zostało już omówionych powyżej.
Formatowanie to jest w zasadzie bezproblemowe, z jednym wyjątkiem - stosowany często w $\LaTeX$-u znak
\ ma również znaczenie w Pythonie (stanowi znak ucieczki. Problem rozwiążemy, wskazując Pythonowi, że wyrażenie tekstowe ma interpretować dosłownie:
x = linspace(0,5,10)
fig, axes = plt.subplots(nrows=4,ncols=3,figsize=(16,12), dpi=100) lok = 0 #lokalizacja legendy
for ax in axes.ravel(): #musimy spłaszczyć tablicę osi ax.plot(x, x**2, label="y = x**2")
ax.plot(x, x**3, label="y = x**3") ax.set_xlabel('x')
ax.set_ylabel('y')
ax.set_title('loc=%d'%lok);
ax.legend(loc=lok) lok = lok + 1
if lok > 10: break fig.tight_layout()
Rozmiar czcionki możemy zmienić globalnie:
In [44]:
fig, ax = plt.subplots()
ax.plot(x, x**2, label=r"$y = \alpha^2$") # r jak "raw"
ax.plot(x, x**3, label=r"$y = \alpha^3$") ax.legend(loc=2) # lewy górny róg
ax.set_xlabel(r'$\alpha$', fontsize=18) ax.set_ylabel(r'$y$', fontsize=18)
ax.set_title('Backslash w wyrażeniach LaTeX-a');
# zmiana parametrów konfiguracji matplotlib
matplotlib.rcParams.update({'font.size': 16, 'font.family': 'serif'})
W pracach naukowych bardzo dobrze prezentują się czcionki STIX:
In [46]:
fig, ax = plt.subplots()
ax.plot(x, x**2, label=r"$y = \alpha^2$") # r jak "raw"
ax.plot(x, x**3, label=r"$y = \alpha^3$") ax.legend(loc=2) # lewy górny róg
ax.set_xlabel(r'$\alpha$', fontsize=18) ax.set_ylabel(r'$y$', fontsize=18)
ax.set_title('Backslash w wyrażeniach LaTeX-a');
matplotlib.rcParams.update({'font.size': 12, 'font.family': 'STIXGeneral', 'mathtex
Kolor, grubość i typ linii
Jednym ze sposobów zmiany koloru linii albo symbolu jest ten znany z pylab (zgodny z Matlabem).
fig, ax = plt.subplots()
ax.plot(x, x**2, label=r"$y = \alpha^2$") # r jak "raw"
ax.plot(x, x**3, label=r"$y = \alpha^3$") ax.legend(loc=2) # lewy górny róg
ax.set_xlabel(r'$\alpha$', fontsize=18) ax.set_ylabel(r'$y$', fontsize=18)
ax.set_title('Backslash w wyrażeniach LaTeX-a');
Inna możliwość to klucz color. Przy tym, oprócz predefiniowanych kolorów, możemy używać kodów RGB:
In [51]:
Out[51]:
[<matplotlib.lines.Line2D at 0x7ff756dafe10>]
fig, ax = plt.subplots()
ax.plot(x, x**2,'g-', label=r"$y = \alpha^2$") # r jak "raw"
ax.plot(x, x**3,'b-', label=r"$y = \alpha^3$") ax.legend(loc=2) # lewy górny róg
ax.set_xlabel(r'$\alpha$') ax.set_ylabel(r'$y$')
ax.set_title('LaTeX w akcji');
fig, ax = plt.subplots()
ax.plot(x, x+1, color="red", alpha=0.5) # półprzezroczysty czerwony ax.plot(x, x+2, color="#1155dd") # (jakiś) niebieski
ax.plot(x, x+3, color="#15cc55") # (jakiś) zielony
In [52]:
Formatowanie osi
Jednym z elementów osi układu współrzędnych, który możemy modyfikować, jest zakres wartości zmiennej:
fig, ax = plt.subplots(figsize=(12,6))
ax.plot(x, x+1, color="blue", linewidth=0.25) ax.plot(x, x+2, color="blue", linewidth=0.50) ax.plot(x, x+3, color="blue", linewidth=1.00) ax.plot(x, x+4, color="blue", linewidth=2.00)
# możliwe typy linii to ‘-‘, ‘–’, ‘-.’, ‘:’, ‘steps’
ax.plot(x, x+5, color="red", lw=2, linestyle='-') ax.plot(x, x+6, color="red", lw=2, ls='-.')
ax.plot(x, x+7, color="red", lw=2, ls=':')
# własna linia przerywana
line, = ax.plot(x, x+8, color="black", lw=1.50)
line.set_dashes([5, 10, 15, 10]) # format: długość kreski, długość przerwy, ...
# możliwe typy symboli: marker = '+', 'o', '*', 's', ',', '.', '1', '2', '3', '4', ax.plot(x, x+ 9, color="green", lw=2, ls='', marker='+')
ax.plot(x, x+10, color="green", lw=2, ls='', marker='o') ax.plot(x, x+11, color="green", lw=2, ls='', marker='s') ax.plot(x, x+12, color="green", lw=2, ls='', marker='1')
# rozmiar i kolor symbolu
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, markerface ax.plot(x, x+16, color="purple", lw=1, ls='-', marker='s', markersize=8,
markerfacecolor="yellow", markeredgewidth=2, markeredgecolor="blue");
Skalę logarytmiczną włączymy przy pomocy metod set_xscale i set_yscale z wartością log:
In [54]:
fig, axes = plt.subplots(1, 3, figsize=(12, 4)) axes[0].plot(x, x**2, x, x**3)
axes[0].set_title(u"zakresy domyślne") axes[1].plot(x, x**2, x, x**3)
axes[1].axis('tight') #ciasne upakowanie axes[1].set_title("ciasne upakowanie") axes[2].plot(x, x**2, x, x**3)
axes[2].set_ylim([0, 60]) #zakres y axes[2].set_xlim([2, 5]) #zakres x
axes[2].set_title(u"zakresy użytkownika");
fig, axes = plt.subplots(1, 2, figsize=(10,4))
axes[0].plot(x, x**2, x, exp(x)) axes[0].set_title("Skala liniowa") axes[1].plot(x, x**2, x, exp(x)) axes[1].set_yscale("log")
axes[1].set_title("Skala logarytmiczna (y)");
In [49]:
Więcej na temat podziałki pod adresem http://matplotlib.org/api/ticker_api.html (http://matplotlib.org/api/ticker_api.html).
Jeżeli na osiach mamy do przedstawienia duże wartości liczbowe, warto jest skorzystać z notacji naukowej:
fig, ax = plt.subplots(figsize=(10, 4)) ax.plot(x, x**2, x, x**3, lw=2)
ax.set_xticks([1, 2, 3, 4, 5]) #wartości podziałki na osi
#i odpowiadające im etykiety
ax.set_xticklabels([r'$\alpha$', r'$\beta$', r'$\gamma$', r'$\delta$', r'$\epsilon$
yticks = [0, 50, 100, 150]
ax.set_yticks(yticks)
ax.set_yticklabels(["$%.1f$" % y for y in yticks], fontsize=18); #wartości sformato
Odległość między osiami, a etykietami podziałki możemy również zmieniać:
fig, ax = plt.subplots(1, 1)
ax.plot(x, x**2, x, exp(x)) ax.set_title("Notacja naukowa")
ax.set_yticks([0, 50, 100, 150]) #wartości na osi y
from matplotlib import ticker #zaawansowane api do kontroli podziałki formatter = ticker.ScalarFormatter(useMathText=True)
formatter.set_scientific(True) #włączamy notację naukową formatter.set_powerlimits((-1,1))
ax.yaxis.set_major_formatter(formatter)
In [52]:
Metoda grid pozwoli włączyć siatkę na wykresie. Jej wygląd możemy zmieniać tymi samymi argumentami kluczowymi, które stosowaliśmy w metodzie plot:
# odległość wartości od osi rcParams['xtick.major.pad'] = 15 rcParams['ytick.major.pad'] = 15 fig, ax = plt.subplots(1, 1)
ax.plot(x, x**2, x, exp(x)) ax.set_yticks([0, 50, 100, 150])
ax.set_title(u"Odległość wartości od osi")
# padding between axis label and axis numbers ax.xaxis.labelpad = 5
ax.yaxis.labelpad = 5 ax.set_xlabel("x") ax.set_ylabel("y");
# powrót do ustawień domyślnych rcParams['xtick.major.pad'] = 3 rcParams['ytick.major.pad'] = 3
Wygląd osi również można zmieniać:
In [54]:
Czasami warto jest mieć 2 różne osie x lub y:
fig, axes = plt.subplots(1, 2, figsize=(10,3))
# siatka domyślna
axes[0].plot(x, x**2, x, x**3, lw=2) axes[0].grid(True)
# siatka po zmianach
axes[1].plot(x, x**2, x, x**3, lw=2)
axes[1].grid(color='b', alpha=0.5, linestyle='dashed', linewidth=1.5)
fig, ax = plt.subplots(figsize=(6,2))
ax.spines['bottom'].set_color('blue') # dolna oś niebieska ax.spines['top'].set_color('blue') # górna oś niebieska ax.spines['left'].set_color('red') # lewa oś czerwona ax.spines['left'].set_linewidth(2) # i o grubości 2
# wyłączamy prawą oś
ax.spines['right'].set_color("none")
ax.yaxis.tick_left() # podziałka tylko po lewej stronie
Poniższy przykład pozwoli nam przedstawić układ współrzędnych w formie znanej z kursu matematyki:
fig, ax1 = plt.subplots()
ax1.plot(x, x**2, lw=2, color="blue")
ax1.set_ylabel(r"area $(m^2)$", fontsize=18, color="blue") for label in ax1.get_yticklabels():
label.set_color("blue")
ax2 = ax1.twinx() #oś x jest taka sama dla obu krzywych ax2.plot(x, x**3, lw=2, color="red")
ax2.set_ylabel(r"volume $(m^3)$", fontsize=18, color="red") for label in ax2.get_yticklabels():
label.set_color("red")
Wybrane typy wykresów 2D
In [57]:
fig, ax = plt.subplots()
#wyłączamy oś prawą i górną
ax.spines['right'].set_color('none') ax.spines['top'].set_color('none') ax.xaxis.set_ticks_position('bottom')
ax.spines['bottom'].set_position(('data',0)) # ax.yaxis.set_ticks_position('left')
ax.spines['left'].set_position(('data',0)) # xx = np.linspace(-0.75, 1., 100)
ax.plot(xx, xx**3);
xx = np.linspace(-0.75, 1., 100) n = array([0,1,2,3,4,5])
In [59]:
fig, axes = plt.subplots(1, 4, figsize=(12,3)) axes[0].scatter(xx, xx + 0.25*randn(len(xx))) axes[0].set_title("scatter")
axes[1].step(n, n**2, lw=2) axes[1].set_title("step")
axes[2].bar(n, n**2, align="center", width=0.5, alpha=0.5) axes[2].set_title("bar")
axes[3].fill_between(x, x**2, x**3, color="green", alpha=0.5);
axes[3].set_title("fill_between");
# wykres we współrzędnych biegunowych fig = plt.figure()
ax = fig.add_axes([0.0, 0.0, .6, .6], polar=True) t = linspace(0, 2 * pi, 100)
ax.plot(t, t, color='blue', lw=3);
Jeszcze o układzie wykresów
subplots
# histogram
n = np.random.randn(100000)
fig, axes = plt.subplots(1, 2, figsize=(12,4)) axes[0].hist(n)
axes[0].set_title(u"Domyślny histogram") axes[0].set_xlim((min(n), max(n)))
axes[1].hist(n, cumulative=True, bins=50) axes[1].set_title(u"Wartości skumulowane") axes[1].set_xlim((min(n), max(n)));
subplot2grid
fig, ax = plt.subplots(2, 3) fig.tight_layout()
gridspec In [63]:
fig = plt.figure()
ax1 = plt.subplot2grid((3,3), (0,0), colspan=3) ax2 = plt.subplot2grid((3,3), (1,0), colspan=2) ax3 = plt.subplot2grid((3,3), (1,2), rowspan=2) ax4 = plt.subplot2grid((3,3), (2,0))
ax5 = plt.subplot2grid((3,3), (2,1)) fig.tight_layout()
import matplotlib.gridspec as gridspec
add_axes
Ręczne układanie wykresów ma w zasadzie sens jedynie w przypadku wstawiania jednego wykresu w drugi:
fig = plt.figure()
gs = gridspec.GridSpec(2, 3, height_ratios=[2,1], width_ratios=[1,2,1]) for g in gs:
ax = fig.add_subplot(g)
fig.tight_layout()
Mapy kolorów i wykresy konturowe
Mapy kolorów oraz wykresy konturowe to jeden z użytecznych sposobów na przedstawienie funkcji dwóch zmiennych.
Rozważmy następujący przykład:
fig, ax = plt.subplots() ax.plot(xx, xx**2, xx, xx**3) fig.tight_layout()
# mniejszy wykres
inset_ax = fig.add_axes([0.2, 0.55, 0.35, 0.35]) # X, Y, szer, wys inset_ax.plot(xx, xx**2, xx, xx**3)
inset_ax.set_title(u'W pobliżu (0,0)')
# zakres
inset_ax.set_xlim(-.2, .2) inset_ax.set_ylim(-.005, .01)
# podziałka
inset_ax.set_yticks([0, 0.005, 0.01]) inset_ax.set_xticks([-0.1,0,.1]);
Przed sporządzeniem wykresu funkcji dwóch zmiennych musimy najpierw wygenerować siatkę (polecenie meshgrid), w punktach której wyznaczymy wartości funkcji:
In [67]:
pcolor In [68]:
imshow alpha = 0.7
phi_ext = 2 * pi * 0.5
def flux_qubit_potential(phi_m, phi_p):
return 2 + alpha - 2 * cos(phi_p)*cos(phi_m) - alpha * cos(phi_ext - 2*phi_p)
phi_m = linspace(0, 2*pi, 100) phi_p = linspace(0, 2*pi, 100) X,Y = meshgrid(phi_p, phi_m) Z = flux_qubit_potential(X, Y).T
fig, ax = plt.subplots()
#argument cmap to mapa kolorów
#więcej na ten temat w dokumentacji matplotlib
p = ax.pcolor(X/(2*pi), Y/(2*pi), Z, cmap=cm.RdBu, vmin=abs(Z).min(), vmax=abs(Z).m cb = fig.colorbar(p, ax=ax) #wyjaśnienie znaczenia kolorów
contour
fig, ax = plt.subplots()
im = ax.imshow(Z, cmap=cm.RdBu, vmin=abs(Z).min(), vmax=abs(Z).max(), extent=[0, 1, im.set_interpolation('bilinear')
cb = fig.colorbar(im, ax=ax)
Wykresy 3D
Wykresy 3D wymagają stworzenia instancji klasy Axes3D:
In [71]:
Wykresy powierzchniowe:
fig, ax = plt.subplots()
cnt = ax.contour(Z, cmap=cm.RdBu, vmin=abs(Z).min(), vmax=abs(Z).max(), extent=[0,
from mpl_toolkits.mplot3d.axes3d import Axes3D
Wykres szkieletowy
fig = plt.figure(figsize=(14,6))
# projection='3d' powoduje, że ax jest układem współrzędnych 3D ax = fig.add_subplot(1, 2, 1, projection='3d')
p = ax.plot_surface(X, Y, Z, rstride=4, cstride=4, linewidth=0)
# wykres powierzchniowy z gradientem kolorów i legendą ax = fig.add_subplot(1, 2, 2, projection='3d')
p = ax.plot_surface(X, Y, Z, rstride=1, cstride=1, cmap=cm.coolwarm, linewidth=0, a cb = fig.colorbar(p, shrink=0.5)
Wykres powierzchniowy z rzutami fig = plt.figure(figsize=(8,6))
ax = fig.add_subplot(1, 1, 1, projection='3d')
p = ax.plot_wireframe(X, Y, Z, rstride=4, cstride=4)
Zmiana perspektywy
fig = plt.figure(figsize=(8,6))
ax = fig.add_subplot(1,1,1, projection='3d')
ax.plot_surface(X, Y, Z, rstride=4, cstride=4, alpha=0.25)
cset = ax.contour(X, Y, Z, zdir='z', offset=-pi, cmap=cm.coolwarm) cset = ax.contour(X, Y, Z, zdir='x', offset=-pi, cmap=cm.coolwarm) cset = ax.contour(X, Y, Z, zdir='y', offset=3*pi, cmap=cm.coolwarm) ax.set_xlim3d(-pi, 2*pi);
ax.set_ylim3d(0, 3*pi);
ax.set_zlim3d(-pi, 2*pi);
Animacje
W bibliotece matplotlib znajdziemy również narzędzia do tworzenia prostych animacji złożonych z sekwencji rysunków. Funkcja FuncAnimation pozwala np. na stworzenie filmu złożonego z klatech stanowiących
poszczególne wykresy. Funkcja ta wymaga następujących argumentów na wejściu: fig, czyli kanwę rysunku, func - funkcję do aktualizacji rysunku, init_func - funkcję inicjalizującą rysunek, frame - liczbę klatek, i blit - aktualizowanie tylko tych części rysunku, które uległy zmianie:
def init():
# sporządź rysunek def update(frame_counter):
# zaktualizuj rysunek do następnej klatki
anim = animation.FuncAnimation(fig, update, init_func=init, frames=200, bli t=True)
anim.save('animation.mp4', fps=30) # fps = klatki na sekundę Narzędzia do animacji znajdują w module matplotlib.animation:
In [76]:
fig = plt.figure(figsize=(12,6))
ax = fig.add_subplot(1,2,1, projection='3d')
ax.plot_surface(X, Y, Z, rstride=4, cstride=4, alpha=0.25) ax.view_init(30, 45)
ax = fig.add_subplot(1,2,2, projection='3d')
ax.plot_surface(X, Y, Z, rstride=4, cstride=4, alpha=0.25) ax.view_init(70, 30) # wysokość i azymut (w kątach)
fig.tight_layout()
Tworzymy animację ruchu wahadła:
In [78]:
Zapisany plik możemy podglądnąć bezpośrednio w IPythonie:
# wahadło podwójne (patrz wcześniejsze wykłady) from scipy.integrate import odeint
g = 9.82; L = 0.5; m = 0.1 def dx(x, t):
x1, x2, x3, x4 = x[0], x[1], x[2], x[3]
dx1 = 6.0/(m*L**2) * (2 * x3 - 3 * cos(x1-x2) * x4)/(16 - 9 * cos(x1-x2)**2) dx2 = 6.0/(m*L**2) * (8 * x4 - 3 * cos(x1-x2) * x3)/(16 - 9 * cos(x1-x2)**2) dx3 = -0.5 * m * L**2 * ( dx1 * dx2 * sin(x1-x2) + 3 * (g/L) * sin(x1)) dx4 = -0.5 * m * L**2 * (-dx1 * dx2 * sin(x1-x2) + (g/L) * sin(x2)) return [dx1, dx2, dx3, dx4]
x0 = [pi/2, pi/2, 0, 0] # stan początkowy t = linspace(0, 10, 250) # czas ruchu
x = odeint(dx, x0, t) # rozwiązanie równania
fig, ax = plt.subplots(figsize=(5,5)) ax.set_ylim([-1.5, 0.5])
ax.set_xlim([1, -1])
pendulum1, = ax.plot([], [], color="red", lw=2) pendulum2, = ax.plot([], [], color="blue", lw=2) def init():
pendulum1.set_data([], []) pendulum2.set_data([], []) def update(n):
# n = numer klatki # położenia wahadła x1 = + L * sin(x[n, 0]) y1 = - L * cos(x[n, 0]) x2 = x1 + L * sin(x[n, 1]) y2 = y1 - L * cos(x[n, 1])
# aktualizuj położenie
pendulum1.set_data([0 ,x1], [0 ,y1]) pendulum2.set_data([x1,x2], [y1,y2])
anim = animation.FuncAnimation(fig, update, init_func=init, frames=len(t), blit=Fal
#zapisujemy animację do pliku (wymaga zainstalowanego programu avconv) anim.save('animation.mp4', fps=20, writer="avconv", codec="libx264") plt.close(fig)
In [ ]:
Out[79]:
0:00
import base64
from IPython.display import HTML
video = open("animation.mp4", "r+b").read() video_encoded = base64.b64encode(video) HTML(data='''<video alt="test" controls>
<source src="data:video/mp4;base64,{0}" type="video/mp4" />
</video>'''.format(video_encoded.decode('ascii')))