Obsah
2. Natočení popisek a další triky
4. Použití funkce numpy.meshgrid()
5. Graf s konturami funkce z=f(x,y)
6. Přidání popisků k jednotlivým „vrstevnicím“
7. Další vylepšení grafu s konturami – legenda s výškami
8. Jednoduchý trojrozměrný graf funkce z=f(x,y) – drátový model
9. Od drátového modelu k vyplněné ploše
10. Zobrazení legendy – colorbaru
11. Promítnutí grafu na plochy kolmé na osy
12. Zobrazení 3D grafu funkce typu [x,y,z]=f(t)
15. Animace grafu jedné funkce
16. Animace průběhů dvou funkcí
17. Postupná změna kontury grafu
18. Obsah následujícího článku
19. Repositář s demonstračními příklady (diáři)
1. Popisky os grafu
Na předchozí článek s popisem některých možností nabízených knihovnou Matplotlib dnes navážeme, protože si ukážeme některé další funkce a grafy touto knihovnou nabízené. Nejdříve si vysvětlíme, jakým způsobem lze do grafů s průběhy funkcí, popř. do sloupcových grafů přidat další potřebné informace. Titulek se přidá snadno:
# titulek grafu plt.title("Sloupcový graf se dvěma skupinami sloupců")
Dále můžeme přidat popisky vodorovné i svislé osy (popř. více svislých os, což knihovna Matplotlib taktéž umožňuje):
# popisek vodorovné osy plt.xlabel("Měsíc") # popisek svislé osy plt.ylabel("Zisk pobočky")
A konečně do grafu lze přidat i legendu a zvolit si, do které oblasti grafu má být umístěna:
# přidání legendy plt.legend(loc="upper right")
V prvním diáři je ukázán způsob vykreslení grafu s titulkem, popiskem u obou os i s legendou:
Obrázek 1: Diář s kódem pro vykreslení grafu.
A takto vypadá výsledný graf vykreslený kódem v diáři:
Obrázek 2: Výsledný graf.
Úplný skript pro vykreslení grafu nalezneme na adrese https://github.com/tisnik/jupyter-notebook-examples/blob/master/matplotlib/example19.py:
# Jupyter Notebook # # Devatenáctý demonstrační příklad: # - sloupcový graf se dvěma skupinami sloupců # - přidání popisů os, titulku aj. import numpy as np import matplotlib.pyplot as plt # první pole hodnot vals1 = [10, 15, 20, 12, 14, 8] # druhé pole hodnot vals2 = [19, 18, 6, 11, 6, 14] # počet prvků N = len(vals1) # indexy prvků indexes = np.arange(N) # šířka sloupců width = 0.30 # titulek grafu plt.title("Sloupcový graf se dvěma skupinami sloupců") # sloupcový graf se dvěma skupinami sloupců plt.bar(indexes, vals1, width, color='gray', edgecolor='black', label='Pobočka 1') # posunuté sloupce plt.bar(indexes+width, vals2, width, color='red', edgecolor='black', label='Pobočka 2') # povolení zobrazení mřížky plt.grid(True) # popisek vodorovné osy plt.xlabel("Měsíc") # popisek svislé osy plt.ylabel("Zisk pobočky") # přidání legendy plt.legend(loc="upper right") # zobrazení grafu plt.show()
2. Natočení popisek a další triky
Předchozí graf nebyl ještě dokonalý, protože v něm například chybí správné popisky na vodorovné ose. Ty lze ovšem velmi snadno doplnit a to konkrétně následujícím způsobem:
# popisky plt.xticks([0, 1, 2, 3, 4, 5], ['Leden', 'Únor', 'Březen', 'Duben', 'Květen', 'Červen'])
Zajistit lze, pokud je to pochopitelně nutné, natočení těchto popisek o libovolný úhel:
# popisky plt.xticks([0, 1, 2, 3, 4, 5], ['Leden', 'Únor', 'Březen', 'Duben', 'Květen', 'Červen'], rotation=20)
Při natočení popisek (zejména o 90°) se ovšem může stát, že se samotné popisky do plochy grafu již nevlezou. Tento problém se většinou může vyřešit změnou pozic jednotlivých prvků, popř. změnou velikosti plátna:
# změna pozic jednotlivých prvků grafu plt.tight_layout()
Diář se skriptem, který zajistí výše uvedené kroky:
Obrázek 3: Diář s kódem pro vykreslení grafu.
Nový tvar grafu bude vypadat takto:
Obrázek 4: Výsledný graf.
Úplný skript pro vykreslení grafu lze najít na adrese https://github.com/tisnik/jupyter-notebook-examples/blob/master/matplotlib/example20.py:
# Jupyter Notebook # # Dvacátý demonstrační příklad: # - sloupcový graf se dvěma skupinami sloupců # - přidání popisů os, titulku aj. import numpy as np import matplotlib.pyplot as plt # první pole hodnot vals1 = [10, 15, 20, 12, 14, 8] # druhé pole hodnot vals2 = [19, 18, 6, 11, 6, 14] # počet prvků N = len(vals1) # indexy prvků indexes = np.arange(N) # šířka sloupců width = 0.30 # titulek grafu plt.title("Sloupcový graf se dvěma skupinami sloupců") # sloupcový graf se dvěma skupinami sloupců plt.bar(indexes, vals1, width, color='gray', edgecolor='black', label='Pobočka 1') # posunuté sloupce plt.bar(indexes+width, vals2, width, color='red', edgecolor='black', label='Pobočka 2') # povolení zobrazení mřížky plt.grid(True) # popisek vodorovné osy plt.xlabel("Měsíc") # popisek svislé osy plt.ylabel("Zisk pobočky") # přidání legendy plt.legend(loc="upper right") # popisky plt.xticks([0, 1, 2, 3, 4, 5], ['Leden', 'Únor', 'Březen', 'Duben', 'Květen', 'Červen'], rotation=20) # změna pozic jednotlivých prvků grafu plt.tight_layout() # zobrazení grafu plt.show()
3. Změna velikosti grafu
Nyní si ukažme, jak lze změnit velikost grafu, resp. rozlišení rastrového obrázku, do kterého se graf vykreslí. Nejprve původní diář bez změny velikosti grafu:
Obrázek 5: Diář s kódem pro vykreslení grafu.
Samotný obrázek s grafem je relativně malý:
Obrázek 6: Výsledný graf.
Lze ho však zvětšit, konkrétně vypnutím tzv. bounding boxů a nastavením velikosti v palcích:
plt.figure(1, figsize=(8,6), dpi=100)
Obrázek 7: Diář s kódem pro vykreslení grafu.
Obrázek 8: Výsledný graf.
4. Použití funkce numpy.meshgrid()
Před popisem grafů zobrazujících drátové modely a popř. i kontury funkcí typu z=f(x,y) se musíme seznámit s jednou velmi užitečnou funkcí z knihovny Numpy nazvanou numpy.meshgrid(). Tato funkce má sice poměrně univerzální možnosti použití, my se však v dnešním článku spokojíme s tím, že pokud se funkci numpy.meshgrid() předá dvojice jednorozměrných polí (vektorů) představujících hodnoty nezávislých x-ových a y-ových souřadnic, vytvoří se jako výsledek dvě dvourozměrné matice, které dohromady tvoří mřížku souřadnic [xi, yi] (první matice obsahuje x-ové souřadnice, druhá souřadnice y-ové). Počet řádků těchto 2D matic odpovídá délce druhého pole, počet sloupců pak délce pole prvního. Podívejme se nejprve na velmi jednoduchý příklad:
# vytvoření vektoru [1..10] x=np.arange(1, 11, 1) # vytvoření vektoru [101..105] y=np.arange(101, 106, 1) # zobrazení prvního vektoru x array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]) # zobrazení druhého vektoru y array([101, 102, 103, 104, 105]) # zavolání funkce numpy.meshgrid np.meshgrid(x,y) [array([[ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10], [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10], [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10], [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10], [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]]), array([[101, 101, 101, 101, 101, 101, 101, 101, 101, 101], [102, 102, 102, 102, 102, 102, 102, 102, 102, 102], [103, 103, 103, 103, 103, 103, 103, 103, 103, 103], [104, 104, 104, 104, 104, 104, 104, 104, 104, 104], [105, 105, 105, 105, 105, 105, 105, 105, 105, 105]])]
Většinou se první i druhá matice uloží do samostatné proměnné, a to následujícím způsobem (povšimněte si, že výsledné matice jsou uloženy do proměnných označených verzálkami):
# uložení první matice do proměnné X # a současně # uložení druhé matice do proměnné Y X,Y=np.meshgrid(x,y) # zobrazení první matice X array([[ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10], [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10], [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10], [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10], [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]]) # zobrazení druhé matice Y array([[101, 101, 101, 101, 101, 101, 101, 101, 101, 101], [102, 102, 102, 102, 102, 102, 102, 102, 102, 102], [103, 103, 103, 103, 103, 103, 103, 103, 103, 103], [104, 104, 104, 104, 104, 104, 104, 104, 104, 104], [105, 105, 105, 105, 105, 105, 105, 105, 105, 105]])
S maticemi je při použití knihovny Numpy možné mj. provádět i základní operace typu součet matic, rozdíl matic, maticový součin atd. Taktéž je možné na prvky matice aplikovat logaritmické či goniometrické funkce, takže například následující zápis je zcela korektní a plně podporovaný:
# součet matic a následně aplikace goniometrické funkce sin # na každý prvek výsledné matice Z=np.sin(X+Y) # podívejme se na výslednou matici Z array([[ 0.99482679, 0.62298863, -0.3216224 , -0.97053528, -0.7271425 , 0.18478174, 0.92681851, 0.81674261, -0.04424268, -0.86455145], [ 0.62298863, -0.3216224 , -0.97053528, -0.7271425 , 0.18478174, 0.92681851, 0.81674261, -0.04424268, -0.86455145, -0.8899956 ], [-0.3216224 , -0.97053528, -0.7271425 , 0.18478174, 0.92681851, 0.81674261, -0.04424268, -0.86455145, -0.8899956 , -0.09718191], [-0.97053528, -0.7271425 , 0.18478174, 0.92681851, 0.81674261, -0.04424268, -0.86455145, -0.8899956 , -0.09718191, 0.78498039], [-0.7271425 , 0.18478174, 0.92681851, 0.81674261, -0.04424268, -0.86455145, -0.8899956 , -0.09718191, 0.78498039, 0.94543533]])
Funkci numpy.meshgrid využijeme v dalších demonstračních příkladech.
5. Graf s konturami funkce z=f(x,y)
První způsob zobrazení funkce typu z=f(x,y) spočívá ve vykreslení takzvaných kontur, které si pro zjednodušení můžeme představit jako vrstevnice na mapě – body spojené konturou/vrstevnicí mají stejnou hodnotu funkce (tj. stejnou hodnotu z-ové souřadnice; řekněme výšky). Při vyhodnocování a následném vykreslení funkce budeme postupovat následovně:
- Vytvoříme vektor s hodnotami nezávislé proměnné x.
- Vytvoříme vektor s hodnotami nezávislé proměnné y.
- S využitím výše popsané funkce numpy.meshgrid necháme vygenerovat dvojici matic souřadnic.
- Necháme vypočítat body ležící na ploše funkce (z-ové souřadnice se uloží do matice Z).
- Vlastní vykreslení kontur zajistí funkce matplotlib.pyplot.contour(X, Y, Z).
Podívejme se na úplný příklad diáře s jednoduchou konturou:
Obrázek 9: Diář s kódem pro vykreslení grafu.
Obrázek 10: Výsledný graf.
Obrázek 11: Pokud je hodnota „delta“ příliš vysoká, vypočte se menší počet bodů tvořících plochu funkce, takže i kontury budou vykresleny velmi nepřesně (knihovna bude mít k dispozici jen málo bodů, které bude moci spojit).
Úplný skript pro vykreslení grafu vypadá následovně:
# Jupyter Notebook # # Dvacátý druhý demonstrační příklad: # - zobrazení kontur funkce typu z=f(x,y) import matplotlib import numpy as np import matplotlib.cm as cm import matplotlib.mlab as mlab import matplotlib.pyplot as plt delta = 0.1 # průběh nezávislé proměnné x x = np.arange(-10.0, 10.0, delta) # průběh nezávislé proměnné y y = np.arange(-10.0, 10.0, delta) # vytvoření dvou polí se souřadnicemi [x,y] X, Y = np.meshgrid(x, y) # vzdálenost od bodu [0,0] R1 = np.sqrt(X*X+Y*Y) # vzdálenost od bodu [3,3] R2 = np.sqrt((X-3)*(X-3)+(Y-3)*(Y-3)) # výpočet funkce, kterou použijeme při vykreslování grafu Z = np.sin(R1)-np.cos(R2) # povolení zobrazení mřížky plt.grid(True) # vytvoření grafu s konturami funkce z=f(x,y) plt.contour(X, Y, Z) # zobrazení grafu plt.show()
6. Přidání popisků k jednotlivým „vrstevnicím“
Další vylepšení předchozího demonstračního příkladu spočívá v přidání popisků (výšek) jednotlivých kontur/vrstevnic, což je opět způsob, který čtenáři pravděpodobně znají z běžných map. Jakmile je graf funkce vykreslen, postačuje zavolat funkci nazvanou plt.clabel(), které se předá proměnná obsahující všechny informace o právě vytvořeném grafu. Tato funkce taktéž akceptuje množství parametrů popsaných na stránce http://matplotlib.org/api/pyplot_api.html?highlight=contour#matplotlib.pyplot.contour. My nastavíme parametr inline na hodnotu True (pod textem se nezobrazí kontura, to by bylo poměrně nečitelné) a parametr fontsize na požadovanou relativní velikost písma s popisky:
Obrázek 12: Diář s kódem pro vykreslení grafu.
Obrázek 13: Výsledný graf – u jednotlivých „vrstevnic“ je uvedena i jejich hodnota odpovídající hodnotě funkce v daném bodě..
Úplný skript pro vykreslení grafu je následující:
# Jupyter Notebook # # Dvacátý třetí demonstrační příklad: # - zobrazení kontur funkce typu z=f(x,y) # - zobrazení hodnot u jednotlivých "vrstevnic" import matplotlib import numpy as np import matplotlib.cm as cm import matplotlib.mlab as mlab import matplotlib.pyplot as plt delta = 0.1 # průběh nezávislé proměnné x x = np.arange(-10.0, 10.0, delta) # průběh nezávislé proměnné y y = np.arange(-10.0, 10.0, delta) # vytvoření dvou polí se souřadnicemi [x,y] X, Y = np.meshgrid(x, y) # vzdálenost od bodu [0,0] R1 = np.sqrt(X*X+Y*Y) # vzdálenost od bodu [3,3] R2 = np.sqrt((X-3)*(X-3)+(Y-3)*(Y-3)) # výpočet funkce, kterou použijeme při vykreslování grafu Z = np.sin(R1)-np.cos(R2) # povolení zobrazení mřížky plt.grid(True) # vytvoření grafu s konturami funkce z=f(x,y) CS = plt.contour(X, Y, Z) # popisky "vrstevnic" plt.clabel(CS, inline=1, fontsize=10) # zobrazení grafu plt.show()
7. Další vylepšení grafu s konturami – legenda s výškami
Další možné vylepšení, které může být výhodné zejména ve chvíli, kdy jsou plochy s konturami vybarveny (pro vybarvení použijte funkci matplotlib.pyplot.contourf namísto pouhého matplotlib.pyplot.countour – rozdíl spočívá v „f“ na konci příslušné funkce) spočívá v přidání legendy, na níž se zobrazují výšky jednotlivých kontur/vrstevnic. Přidání je jednoduché, stačí pouze použít funkci nazvanou matplotlib.pyplot.colorbar(), které se opět předá datová struktura reprezentující graf funkce a několik nepovinných pojmenovaných parametrů. Z těchto parametrů dnes použijeme pouze parametr nazvaný shrink (relativní velikost popisku) a extend (způsob vykreslení popisků vedle grafu). Podívejme se na úplný zdrojový kód příkladu (diáře):
Obrázek 14: Diář s kódem pro vykreslení grafu.
Obrázek 15: Výsledný graf. Kromě vrstevnic a jejich hodnot se napravo od grafu zobrazila i „mapa výšek“. Relativní velikost mapy vůči celému grafu se řídí hodnotou shrink. Zde konkrétně má celá legenda výšku jen 70% výšky celého grafu.
Úplný skript pro vykreslení grafu z obrázku číslo 14:
# Jupyter Notebook # # Dvacátý čtvrtý demonstrační příklad: # - zobrazení kontur funkce typu z=f(x,y) # - zobrazení hodnot u jednotlivých "vrstevnic" # - přidání legendy import matplotlib import numpy as np import matplotlib.cm as cm import matplotlib.mlab as mlab import matplotlib.pyplot as plt delta = 0.1 # průběh nezávislé proměnné x x = np.arange(-10.0, 10.0, delta) # průběh nezávislé proměnné y y = np.arange(-10.0, 10.0, delta) # vytvoření dvou polí se souřadnicemi [x,y] X, Y = np.meshgrid(x, y) # vzdálenost od bodu [0,0] R1 = np.sqrt(X*X+Y*Y) # vzdálenost od bodu [3,3] R2 = np.sqrt((X-3)*(X-3)+(Y-3)*(Y-3)) # výpočet funkce, kterou použijeme při vykreslování grafu Z = np.sin(R1)-np.cos(R2) # povolení zobrazení mřížky plt.grid(True) # vytvoření grafu s konturami funkce z=f(x,y) CS = plt.contour(X, Y, Z) # přidání legendy (colorbar) CB = plt.colorbar(CS, shrink=0.7, extend='both') # popisky "vrstevnic" plt.clabel(CS, inline=1, fontsize=10) # zobrazení grafu plt.show()
8. Jednoduchý trojrozměrný graf funkce z=f(x,y) – drátový model
Použití grafu s konturami sice může být v mnoha ohledech velmi užitečné (například při zjišťování lokálních minim a maxim i při potřebě nezkresleného pohledu na průběh funkce), v praxi se však spíše setkáme s odlišným typem grafů zobrazujících funkce typu z=f(x,y). Jedná se o trojrozměrné grafy, v nichž se zobrazuje plocha funkce. Nejjednodušším typem tohoto grafu je takzvaný drátový model, který je spíše známý pod svým anglickým názvem wireframe. V tomto typu grafu je zobrazena série křivek či spíše lomených čar. Jedna série je vypočtena takovým způsobem, že x-ová souřadnice se postupně mění v nastaveném intervalu zatímco y-ová souřadnice je konstantní. Druhá série lomených čar se vykresluje kolmo na sérii první, tj. x-ová souřadnice je konstantní a postupně se mění hodnota y-ových souřadnic. Výsledkem je tedy plocha, která má při pohledu z osy z tvar pravidelné mřížky. Pro vykreslení tohoto typu grafu se používá funkce nazvaná plot_wireframe(), které se předá trojice polí odpovídajících x-ovým, y-ovým a z-ovým souřadnicím bodů ležících na ploše představujících obraz funkce:
Obrázek 16: Diář s kódem pro vykreslení grafu.
Pojmenované parametry rstride a cstride lze použít pro řízení hustoty vykreslované mřížky. Tyto parametry představují krok použitý při změně x-ových a y-ových souřadnic, což znamená, že čím menší hodnota se požije, tím bode vykreslená mřížka jemnější:
ax.plot_wireframe(X, Y, Z, rstride=3, cstride=3)
Obrázek 17: Výsledný graf. Drátový model vykreslený s využitím funkce plot_wireframe(X, Y, Z, rstride=7, cstride=7)
Úplný skript pro vykreslení grafu vypadá následovně:
# Jupyter Notebook # # Dvacátý pátý demonstrační příklad: # - zobrazení 3D grafu funkce typu z=f(x,y) from mpl_toolkits.mplot3d import axes3d import matplotlib.pyplot as plt import numpy as np fig = plt.figure() ax = fig.add_subplot(111, projection='3d') delta = 0.1 # průběh nezávislé proměnné x x = np.arange(-10.0, 10.0, delta) # průběh nezávislé proměnné y y = np.arange(-10.0, 10.0, delta) # vytvoření dvou polí se souřadnicemi [x,y] X, Y = np.meshgrid(x, y) # vzdálenost od bodu [0,0] R = np.sqrt(X*X+Y*Y) # výpočet funkce, kterou použijeme při vykreslování grafu Z = np.sin(R)/R # zobrazení 3D grafu ax.plot_wireframe(X, Y, Z, rstride=7, cstride=7) # zobrazení grafu plt.show()
Obrázek 18: Výsledný graf. Drátový model vykreslený s využitím funkce plot_wireframe(X, Y, Z, rstride=3, cstride=3)
9. Od drátového modelu k vyplněné ploše
Drátový model je možné v případě potřeby nahradit vykreslením vyplněné plochy namísto pouhé mřížky. V tomto případě je nutné namísto funkce plot_wireframe() použít funkci pojmenovanou plot_surface(). První tři povinné parametry obou zmíněných funkcí jsou shodné, dokonce lze použít i stejně pojmenované parametry cstride a rstride, o jejichž významu jsme se taktéž zmiňovali v předchozích kapitolách. Kromě toho se však navíc většinou používá i další pojmenovaný parametr cmap, kterému se předá barvová paleta (či barvová mapa), která typicky definuje jeden gradientní přechod i větší množství gradientních přechodů mezi různými odstíny. Pro účely vytváření gradientních přechodů či pro použití již předem připravených barvových map se používá specializovaný modul nazvaný matplotlib.cm (color map). Seznam všech předdefinovaných barvových map naleznete na adrese https://gist.github.com/endolith/2719900#id7, ukázky (palety) pak na adrese http://matplotlib.org/examples/color/colormaps_reference.html. My využijeme barvovou mapu pojmenovanou „coolwarm“:
Obrázek 19: Diář s kódem pro vykreslení grafu.
Obrázek 20: Výsledný graf.
Úplný skript pro vykreslení 3D grafu vypadá takto:
# Jupyter Notebook # # Dvacátý šestý demonstrační příklad: # - zobrazení 3D grafu funkce typu z=f(x,y) from mpl_toolkits.mplot3d import axes3d from matplotlib import cm import matplotlib.pyplot as plt import numpy as np fig = plt.figure() ax = fig.gca(projection='3d') delta = 0.1 # průběh nezávislé proměnné x x = np.arange(-10.0, 10.0, delta) # průběh nezávislé proměnné y y = np.arange(-10.0, 10.0, delta) # vytvoření dvou polí se souřadnicemi [x,y] X, Y = np.meshgrid(x, y) # vzdálenost od bodu [0,0] R = np.sqrt(X*X+Y*Y) # výpočet funkce, kterou použijeme při vykreslování grafu Z = np.sin(R)/R # zobrazení 3D grafu formou plochy ax.plot_surface(X, Y, Z, rstride=2, cstride=2, cmap=cm.coolwarm, linewidth=0, antialiased=False) # zobrazení grafu plt.show()
10. Zobrazení legendy – colorbaru
O způsobu zobrazení takzvaného colorbaru jsme se již zmínili v sedmé kapitole v souvislosti s grafem zobrazujícím kontury nějaké funkce. Ovšem colorbar má svůj význam i v případě trojrozměrných grafů, což ostatně uvidíme po spuštění následujícího demonstračního příkladu, v němž se napravo od grafu s plochou funkce z=f(x,y) zobrazí i „mapa výšek“. Povšimněte si způsobu nastavení měřítka z-ové osy funkcí set_zlim() a taktéž změny způsobu formátování výšek, tj. popisu z-ové osy (set_major_locator() a set_major_formatter()):
Obrázek 21: Diář s kódem pro vykreslení grafu.
Obrázek 22: Výsledný graf. Plocha funkce z=f(x,y) používající barvovou mapu pojmenovanou „coolwarm“.
Opět následuje výpis úplného skriptu pro vykreslení grafu:
# Jupyter Notebook # # Dvacátý sedmý demonstrační příklad: # - zobrazení 3D grafu funkce typu z=f(x,y) # - pomocná legenda - colorbar from mpl_toolkits.mplot3d import axes3d from matplotlib import cm import matplotlib.pyplot as plt from matplotlib.ticker import LinearLocator, FormatStrFormatter import numpy as np fig = plt.figure() ax = fig.gca(projection='3d') delta = 0.1 # průběh nezávislé proměnné x x = np.arange(-10.0, 10.0, delta) # průběh nezávislé proměnné y y = np.arange(-10.0, 10.0, delta) # vytvoření dvou polí se souřadnicemi [x,y] X, Y = np.meshgrid(x, y) # vzdálenost od bodu [0,0] R = np.sqrt(X*X+Y*Y) # výpočet funkce, kterou použijeme při vykreslování grafu Z = np.sin(R)/R # zobrazení 3D grafu formou plochy surface = ax.plot_surface(X, Y, Z, rstride=2, cstride=2, cmap=cm.coolwarm, linewidth=0, antialiased=False) ax.set_zlim(-1.01, 1.01) # styl formátování popisků ax.zaxis.set_major_locator(LinearLocator(10)) ax.zaxis.set_major_formatter(FormatStrFormatter('%.02f')) # přidání pomocné legendy fig.colorbar(surface, shrink=0.7, aspect=5) # zobrazení grafu plt.show()
11. Promítnutí grafu na plochy kolmé na osy
Vzhledem k tomu, že se pro zobrazení trojrozměrného grafu na 2D obrazovce musí používat axonometrické promítání, popř. promítání s perspektivou, nemusí být z výsledného obrázku s grafem na první pohled zřejmé, jak přesně vlastně průběh funkce vypadá. Knihovna Matplotlib nám však nabízí určité řešení tohoto problému – na plochy (které jsou kolmé na osy souřadného systému) se promítnou kontury průběhu funkce. Podívejme se nejdříve na to, jak vypadá výsledek:
Obrázek 23: Výsledný graf. Promítnutí kontur průběhu funkce na plochy.
Samotná plocha představující funkci se vykreslí příkazem (přesněji řečeno funkcí) matplotlib.pyplot.plot_surface(), podobně jako v předchozím příkladu. Dále se metodou ax.contour() mohou vykreslit kontury grafu na jednotlivé plochy, ve skutečnosti je však ještě nutné korektně nastavit přesné umístění těchto kontur do grafu. K tomu slouží explicitní nastavení rozsahů na jednotlivých osách (set_xlim(), set_ylim(), set_zlim()) a vlastní posun reprezentovaný pojmenovaným parametrem offset předaným do metody ax.contour(). Podívejme se na odladěný příklad:
Obrázek 24: Diář s kódem pro vykreslení grafu.
Následně si ukažme celý skript pro vykreslení grafu:
# Jupyter Notebook # # Dvacátý osmý demonstrační příklad: # - zobrazení 3D grafu funkce typu z=f(x,y) # - pomocná legenda - colorbar # - promítnutí grafu na ploch kolmých na osy from mpl_toolkits.mplot3d import axes3d from matplotlib import cm import matplotlib.pyplot as plt import numpy as np fig = plt.figure() ax = fig.gca(projection='3d') delta = 0.1 # průběh nezávislé proměnné x x = np.arange(-10.0, 10.0, delta) # průběh nezávislé proměnné y y = np.arange(-10.0, 10.0, delta) # vytvoření dvou polí se souřadnicemi [x,y] X, Y = np.meshgrid(x, y) # vzdálenost od bodu [0,0] R = np.sqrt(X*X+Y*Y) # výpočet funkce, kterou použijeme při vykreslování grafu Z = np.sin(R)/R # zobrazení 3D grafu formou plochy surface = ax.plot_surface(X, Y, Z, rstride=2, cstride=2, cmap=cm.coolwarm, linewidth=0, antialiased=False) # kontutra: průmět na rovinu x-y cset = ax.contour(X, Y, Z, zdir='z', offset=-5, cmap=cm.coolwarm) # kontutra: průmět na rovinu y-z cset = ax.contour(X, Y, Z, zdir='x', offset=-15, cmap=cm.coolwarm) # kontutra: průmět na rovinu x-z cset = ax.contour(X, Y, Z, zdir='y', offset=15, cmap=cm.coolwarm) # rozměry grafu ve směru osy x ax.set_xlabel('X') ax.set_xlim(-15, 15) # rozměry grafu ve směru osy y ax.set_ylabel('Y') ax.set_ylim(-15, 15) # rozměry grafu ve směru osy z ax.set_zlabel('Z') ax.set_zlim(-5, 5) # zobrazení grafu plt.show()
12. Zobrazení 3D grafu funkce typu [x,y,z]=f(t)
Další typ grafu, s nímž se v dnešním článku seznámíme, je trojrozměrný graf, v němž se zobrazuje nějaká funkce typu [x,y,z]=f(t), popř. složitější funkce [xn, yn, zn]=f(xn-1, yn-1, zn-1). O zobrazení průběhů těchto funkcí se stará matplotlib.pyplot.plot(), s níž jsme se již seznámili v předchozí části tohoto seriálu. Tento příkaz automaticky zjistí potřebné rozsahy na všech třech osách, což je dobře patrné ze screenshotu číslo 28. Podívejme se tedy, jakým způsobem je možné zobrazit trojrozměrnou spirálu (pokud budete potřebovat, aby se spirála nezužovala, postačuje proměnnou r nastavit na konstantní hodnotu a nijak ji v programové smyčce nemodifikovat):
Obrázek 25: Diář s kódem pro vykreslení grafu.
Obrázek 26: Výsledný graf. Spirála vykreslená předchozím demonstračním příkladem.
Úplný skript pro vykreslení grafu je dostupný na adrese https://github.com/tisnik/jupyter-notebook-examples/blob/master/matplotlib/example29.py:
# Jupyter Notebook # # Dvacátý devátý demonstrační příklad: # - zobrazení 3D grafu funkce typu [x,y,z]=f(t) from mpl_toolkits.mplot3d import axes3d import matplotlib.pyplot as plt import numpy as np # nezávislá proměnná t = np.arange(0, 8*np.pi, 0.1) # vzdálenost od osy spirály r = 10.0/(t+4) # výpočet souřadnic [x,y,z]) pro každé t x = r*np.cos(t) y = r*np.sin(t) z = t fig = plt.figure() ax = fig.gca(projection='3d') # vykreslení grafu ax.plot(x, y, z) # zobrazení grafu plt.show()
13. Lorenzův atraktor
Poměrně vděčným příkladem funkce zobrazené v 3D prostoru je dynamický systém s takzvaným podivným atraktorem, který je nazvaný Lorenzův atraktor podle svého objevitele. Tento systém sestávající ze tří dynamických rovnic použil Edward Lorenz v roce 1963 při simulaci vývoje počasí (resp. ve velmi zjednodušeném modelu počasí). Na tomto systému byla také numericky a analyticky ověřena velká citlivost na počáteční podmínky (někdy také nazývaná „motýlí efekt“). Pro upřesnění je však nutné říci, že při simulaci na počítači vlastně získáme atraktor, jenž je periodický. Je to z toho důvodu, že pro zobrazení číselných hodnot je použito konečného počtu bitů, z toho nutně vyplývá, že se po určitém počtu kroků (který je však obrovský, takže tento jev mnohdy nezaregistrujeme) začne dráha Lorenzova atraktoru překrývat. V matematicky přesném modelu však tato situace nenastane, každá smyčka funkce bude mít unikátní tvar a dráhy se nebudou překrývat, pouze protínat. Diferenciální rovnice Lorenzova atraktoru mají po převodu na diferenční tvar následující formát:
dx/dt = σ (y-x) dy/dt = x(ρ - z) - y dz/dt = xy - Βz
Takže pro iterativní (samozřejmě že nepřesný) výpočet můžeme pracovat s následujícími vztahy, které pro dostatečně malé dt vedou k výpočtu bodů ležících na Lorenzově atraktoru:
xn+1=xn+(σ (y-x)) dt yn+1=yn+(x(ρ - z) - y) dt zn+1=zn+(xy - Βz) dt
Podívejme se nyní na způsob implementace této funkce v Pythonu, což je snadné:
def lorenz(x, y, z, s=10, r=28, b=2.667): x_dot = s*(y - x) y_dot = r*x - y - x*z z_dot = x*y - b*z return x_dot, y_dot, z_dot
A výpočtu sekvence bodů ležících na atraktoru:
# prozatím prázdné pole připravené pro výpočet x = np.zeros((n,)) y = np.zeros((n,)) z = np.zeros((n,)) # počáteční hodnoty x[0], y[0], z[0] = (0., 1., 1.05) # vlastní výpočet atraktoru (resp. bodů na něm ležících) for i in range(n-1): x_dot, y_dot, z_dot = lorenz(x[i], y[i], z[i]) x[i+1] = x[i] + x_dot * dt y[i+1] = y[i] + y_dot * dt z[i+1] = z[i] + z_dot * dt
Vlastní zobrazení pak probíhá naprosto stejným způsobem, jako tomu bylo v předchozím demonstračním příkladu:
Obrázek 27: Diář s kódem pro vykreslení grafu.
Obrázek 28: Výsledný graf. Lorenzův atraktor vykreslený předchozím demonstračním příkladem.
Úplný skript pro vykreslení Lorenzova atraktoru:
# Jupyter Notebook # # Třicátý demonstrační příklad: # - Lorenzův atraktor from mpl_toolkits.mplot3d import axes3d import matplotlib.pyplot as plt import numpy as np # funkce pro výpočet dalšího bodu Lorenzova atraktoru def lorenz(x, y, z, s=10, r=28, b=2.667): x_dot = s*(y - x) y_dot = r*x - y - x*z z_dot = x*y - b*z return x_dot, y_dot, z_dot # krok (změna času) dt = 0.01 # celkový počet vypočtených bodů na Lorenzově atraktoru n = 10000 # prozatím prázdné pole připravené pro výpočet x = np.zeros((n,)) y = np.zeros((n,)) z = np.zeros((n,)) # počáteční hodnoty x[0], y[0], z[0] = (0., 1., 1.05) # vlastní výpočet atraktoru for i in range(n-1): x_dot, y_dot, z_dot = lorenz(x[i], y[i], z[i]) x[i+1] = x[i] + x_dot * dt y[i+1] = y[i] + y_dot * dt z[i+1] = z[i] + z_dot * dt fig = plt.figure() ax = fig.gca(projection='3d') # vykreslení grafu ax.plot(x, y, z) # zobrazení grafu plt.show()
14. Animované grafy
V závěrečné části dnešního článku si popíšeme způsob tvorby animovaných grafů, pochopitelně opět s využitím možností nabízených Jupyter Notebookem, knihovnou Numpy a Matplotlib. Animace budou převedeny do formátu vhodného pro zobrazení na HTML stránce (tedy v rámci notebooku), ovšem v případě potřeby je možné například vytvořit MPEG-4 apod. (vyžadován bude v tomto případě nainstalovaný ffmpeg). Animace jsou založeny na modulu matplotlib.animation a zobrazení na modulu IPython.display.
15. Animace grafu jedné funkce
Podívejme se nyní na způsob zajištění animace grafu jedné funkce typu y=f(x). Animovanou funkcí bude sin s proměnným offsetem, který postupně poroste od hodnoty 0 do 2π, takže poslední snímek naváže na snímek první. Celkem vytvoříme sto snímků, které se mohou neustále opakovat. Samotnou animaci zajistí následující kód:
anim = animation.FuncAnimation(fig, animate, init_func=init, frames=100, interval=20, blit=True) HTML(anim.to_html5_video())
Povšimněte si specifikace dvou funkcí nazvaných animate a init. Funkce init provede inicializaci grafu před jeho vykreslením a může vypadat následovně:
# funkce zavolaná při inicializaci animace def init(): function.set_data([], []) return (function,)
function, = ax.plot([], [], lw=2)
Druhá funkce nazvaná animate je již složitější, neboť jejím úkolem je vytvořit graf, z něhož se následně vyrendruje jeden snímek animace. Povšimněte si, jak parametr i (což je číslo snímku) transformujeme na posun při výpočtu vektoru y:
# funkce zavolaná pro vytvoření každého snímku def animate(i): # hodnoty na x-ové ose x = np.linspace(0, 2*np.pi, 100) # hodnoty na y-ové ose y = np.sin(x + np.pi*i/50) function.set_data(x, y) return (function,)
Výsledný diář s celým nastavením animace se již nevlezl do jediného screenshotu:
Obrázek 29: První část diáře.
Obrázek 30: Druhá část diáře i s animací.
Následující skript zajistí vykreslení celé animace a její zobrazení v Jupyter Notebooku:
# Jupyter Notebook # # Třicátý první demonstrační příklad: # - Jednoduchá animace # import všech potřebných knihoven - Numpy a Matplotlibu import numpy as np import matplotlib.pyplot as plt from matplotlib import animation, rc from IPython.display import HTML # příprava grafu fig, ax = plt.subplots() ax.set_xlim(( 0, 2*np.pi)) ax.set_ylim((-2, 2)) function, = ax.plot([], [], lw=2) # funkce zavolaná při inicializaci animace def init(): function.set_data([], []) return (function,) # funkce zavolaná pro vytvoření každého snímku def animate(i): # hodnoty na x-ové ose x = np.linspace(0, 2*np.pi, 100) # hodnoty na y-ové ose y = np.sin(x + np.pi*i/50) function.set_data(x, y) return (function,) anim = animation.FuncAnimation(fig, animate, init_func=init, frames=100, interval=20, blit=True) HTML(anim.to_html5_video())
16. Animace průběhů dvou funkcí
Nepatrnou úpravou funkcí init a animate lze dosáhnout toho, že se v jednom grafu zobrazí dvě animované funkce. V init se vytvoří struktury pro dvě sady dat (dvojice vektorů s x-ovými a y-ovými souřadnicemi ležícími na grafu funkce):
# funkce zavolaná při inicializaci animace def init(): function.set_data([], []) function2.set_data([], []) return (function,function2,)
Ve funkci animate se vytvoří dva průběhy funkce, které nastaví příslušné atributy objektu function a function2:
# funkce zavolaná pro vytvoření každého snímku def animate(i): # hodnoty na x-ové ose x = np.linspace(0, 2*np.pi, 100) # hodnoty na y-ové ose: první funkce y1 = np.sin(x + np.pi*i/50) # hodnoty na y-ové ose: druhá funkce y2 = np.cos(x + np.pi*i/25) function.set_data(x, y1) function2.set_data(x, y2) return (function,function2,)
Obrázek 31: První část diáře.
Obrázek 32: Druhá část diáře i s animací.
Upravený skript vypadá následovně:
# Jupyter Notebook # # Třicátý druhý demonstrační příklad: # - Jednoduchá animace # import všech potřebných knihoven - Numpy a Matplotlibu import numpy as np import matplotlib.pyplot as plt from matplotlib import animation, rc from IPython.display import HTML # příprava grafu fig, ax = plt.subplots() ax.set_xlim(( 0, 2*np.pi)) ax.set_ylim((-2, 2)) function, = ax.plot([], [], lw=2) function2, = ax.plot([], [], lw=2) # funkce zavolaná při inicializaci animace def init(): function.set_data([], []) function2.set_data([], []) return (function,function2,) # funkce zavolaná pro vytvoření každého snímku def animate(i): # hodnoty na x-ové ose x = np.linspace(0, 2*np.pi, 100) # hodnoty na y-ové ose: první funkce y1 = np.sin(x + np.pi*i/50) # hodnoty na y-ové ose: druhá funkce y2 = np.cos(x + np.pi*i/25) function.set_data(x, y1) function2.set_data(x, y2) return (function,function2,) anim = animation.FuncAnimation(fig, animate, init_func=init, frames=100, interval=20, blit=True) HTML(anim.to_html5_video())
17. Postupná změna kontury grafu
Podobným způsobem je možné animovat i postupnou změnu kontury funkce typu z=f(x,y). Jeden krok (resp. přesněji řečeno příprava jednoho snímku animace) může vypadat následovně:
# funkce zavolaná pro vytvoření každého snímku def animate(i): # výpočet funkce, kterou použijeme při vykreslování grafu Z = np.sin(R1)-np.cos(R2+2*np.pi*i/100) # povolení zobrazení mřížky plt.grid(True) # vytvoření grafu s konturami funkce z=f(x,y) cnt = plt.contour(X, Y, Z) return cnt
Kde plt je objekt představující celý graf:
# příprava grafu fig = plt.figure()
Problém ovšem spočívá v tom, že volání plt.contour do grafu přidá další viditelné kontury, ovšem původní kontury nejsou smazány, což vede mj. i k velmi pomalému výpočtu animace. Řešením, resp. přesněji řečeno jedním z možných řešení, je vytvoření nového grafu pro každý snímek.
Obrázek 33: Diář s deklarací celé animace.
Opět si – dnes již naposledy – ukážeme celý skript:
# Jupyter Notebook # # Třicátý třetí demonstrační příklad: # - Animace grafu kontur # import všech potřebných knihoven - Numpy a Matplotlibu import matplotlib import numpy as np import matplotlib.cm as cm import matplotlib.mlab as mlab import matplotlib.pyplot as plt from matplotlib import animation, rc from IPython.display import HTML # příprava grafu fig = plt.figure() delta = 0.1 # průběh nezávislé proměnné x x = np.arange(-10.0, 10.0, delta) # průběh nezávislé proměnné y y = np.arange(-10.0, 10.0, delta) # vytvoření dvou polí se souřadnicemi [x,y] X, Y = np.meshgrid(x, y) # vzdálenost od bodu [0,0] R1 = np.sqrt(X*X+Y*Y) # vzdálenost od bodu [3,3] R2 = np.sqrt((X-3)*(X-3)+(Y-3)*(Y-3)) # funkce zavolaná pro vytvoření každého snímku def animate(i): # výpočet funkce, kterou použijeme při vykreslování grafu Z = np.sin(R1)-np.cos(R2+2*np.pi*i/100) # povolení zobrazení mřížky plt.grid(True) # vytvoření grafu s konturami funkce z=f(x,y) cnt = plt.contour(X, Y, Z) return cnt anim = animation.FuncAnimation(fig, animate, frames=100, interval=20, blit=False) HTML(anim.to_html5_video())
18. Obsah následujícího článku
Další část tohoto seriálu již nebude zaměřena na popis možností knihovny Matplotlib. Namísto toho si ukážeme další důležitou oblast – knihovny Numpy a SciPy, které se v souvislosti s Jupyter Notebookem taktéž používají velmi často.
19. Repositář s demonstračními příklady (diáři)
Všechny demonstrační příklady (resp. přesněji řečeno diáře), s nimiž jsme se seznámili v předchozích kapitolách, byly uloženy do Git repositáře umístěného na GitHubu (https://github.com/tisnik/jupyter-notebook-examples/). Poslední verze souborů s diáři naleznete pod odkazy uvedenými v tabulce pod tímto odstavcem. Diář by se měl otevřít přímo v rámci stránky GitHubu:
Skripty naprogramované v Pythonu pro přímé použití:
20. Odkazy na Internetu
- Notebook interface
https://en.wikipedia.org/wiki/Notebook_interface - Jypyter: open source, interactive data science and scientific computing across over 40 programming languages
https://jupyter.org/ - Matplotlib Home Page
http://matplotlib.org/ - Matplotlib (Wikipedia)
https://en.wikipedia.org/wiki/Matplotlib - Popis barvových map modulu matplotlib.cm
https://gist.github.com/endolith/2719900#id7 - Ukázky (palety) barvových map modulu matplotlib.cm
http://matplotlib.org/examples/color/colormaps_reference.html - Galerie grafů vytvořených v Matplotlibu
https://matplotlib.org/3.2.1/gallery/ - showcase example code: xkcd.py
https://matplotlib.org/xkcd/examples/showcase/xkcd.html - Customising contour plots in matplotlib
https://philbull.wordpress.com/2012/12/27/customising-contour-plots-in-matplotlib/ - Graphics with Matplotlib
http://kestrel.nmt.edu/~raymond/software/python_notes/paper004.html - The IPython Notebook
http://ipython.org/notebook.html - nbviewer: a simple way to share Jupyter Notebooks
https://nbviewer.jupyter.org/ - Back to the Future: Lisp as a Base for a Statistical Computing System
https://www.stat.auckland.ac.nz/~ihaka/downloads/Compstat-2008.pdf - gg4clj: a simple wrapper for using R's ggplot2 in Clojure and Gorilla REPL
https://github.com/JonyEpsilon/gg4clj - Analemma: a Clojure-based SVG DSL and charting library
http://liebke.github.io/analemma/ - Clojupyter: a Jupyter kernel for Clojure
https://github.com/roryk/clojupyter - Incanter is a Clojure-based, R-like platform for statistical computing and graphics.
http://incanter.org/ - Evolution of incanter (Gource Visualization)
https://www.youtube.com/watch?v=TVfL5nPELr4 - Questions tagged [incanter] (na Stack Overflow)
https://stackoverflow.com/questions/tagged/incanter?sort=active - Data Sorcery with Clojure
https://data-sorcery.org/contents/ - What is REPL?
https://pythonprogramminglanguage.com/repl/ - What is a REPL?
https://codewith.mu/en/tutorials/1.0/repl - Programming at the REPL: Introduction
https://clojure.org/guides/repl/introduction - What is REPL? (Quora)
https://www.quora.com/What-is-REPL - Gorilla REPL: interaktivní prostředí pro programovací jazyk Clojure
https://www.root.cz/clanky/gorilla-repl-interaktivni-prostredi-pro-programovaci-jazyk-clojure/ - R Markdown: The Definitive Guide
https://bookdown.org/yihui/rmarkdown/ - Single-page application
https://en.wikipedia.org/wiki/Single-page_application - Video streaming in the Jupyter Notebook
https://towardsdatascience.com/video-streaming-in-the-jupyter-notebook-635bc5809e85 - How IPython and Jupyter Notebook work
https://jupyter.readthedocs.io/en/latest/architecture/how_jupyter_ipython_work.html - Jupyter kernels
https://github.com/jupyter/jupyter/wiki/Jupyter-kernels - Keras: The Python Deep Learning library
https://keras.io/ - TensorFlow
https://www.tensorflow.org/ - PyTorch
https://pytorch.org/ - Seriál Torch: framework pro strojové učení
https://www.root.cz/serialy/torch-framework-pro-strojove-uceni/ - Scikit-learn
https://scikit-learn.org/stable/ - Java Interop (Clojure)
https://clojure.org/reference/java_interop - Obrazy s balíčky Jupyter Notebooku pro Docker
https://hub.docker.com/u/jupyter/#! - Správce balíčků Conda (dokumentace)
https://docs.conda.io/en/latest/ - Lorenzův atraktor
https://www.root.cz/clanky/fraktaly-v-pocitacove-grafice-vi/#k02 - Lorenzův atraktor
https://www.root.cz/clanky/fraktaly-v-pocitacove-grafice-iii/#k03 - Graphics with Matplotlib
http://kestrel.nmt.edu/~raymond/software/python_notes/paper004.html - Embedding Matplotlib Animations in Jupyter Notebooks
http://louistiao.me/posts/notebooks/embedding-matplotlib-animations-in-jupyter-notebooks/