3. diel - NumPy - Tvorba polí
V predchádzajúcej lekcii, NumPy - Dátové typy, sme sa pozreli na základné dátové typy knižnice NumPy.
V dnešnom tutoriáli knižnice NumPy v Pythone sa pozrieme na polia v knižnici NumPy a naučíme sa ich vytvárať. Vysvetlíme si, aké je dôležité udržiavať pole homogénne.
Úvod do polí ndarray v
NumPy
Polia, ktoré poskytuje knižnica NumPy, sa do značnej miery správajú podobne ako bežné zoznamy v Pythone. Je možné do nich ukladať najrôznejšie typy premenných – čísla, reťazce i všeobecné objekty a potom k prvkom pristupovať, prípadne ich meniť. Oproti bežným zoznamom v Pythone ale nie je možné do nich prvky pridávať a odoberať. Polia v NumPy majú pevne daný počet prvkov, podobne ako to majú polia v jazykoch C alebo Java.
Rozdiel medzi poľom a zoznamom
Ešte raz si zopakujme veľmi dôležitý poznatok, ktorý sme spomenuli v lekcii NumPy - Predstavenie knižnice:
- Zoznamy v Pythone sú dynamické.
Zodpovedajú tomu, čo v iných jazykoch označujeme ako
list- zoznam. - Oproti zoznamom sa v NumPy jedná o skutočné polia -
array, ako ich poznáme z jazykov C či Java. V NumPy ich označujeme aj ich technickým názvom akondarray.
Natívne zoznamy Pythonu a polí NumPy budeme v tutoriáli striktne odlišovať.
Ukážme si ešte pre istotu kód zoznamu a poľa:
my_list = [1, 2, 3] # list print(type(my_list)) my_array = np.array([1, 2, 3]) # ndarray print(type(my_array))
Vo výstupe konzoly dostaneme:
Difference between list and array
<class 'list'>
<class 'numpy.ndarray'>
Kedy použiť pole, kedy zoznam?
Prečo by sme mali používať NumPy ndarray, keď nám oproti
pythonovskému zoznamu neumožňuje pridávať a odoberať prvky? Odpovede sú
dve: čas a priestor v pamäti. S poľom sa
počítaču zaobchádza jednoduchšie. Akonáhle sa v pamäti pole vytvorí,
jeho dĺžka zostáva až do zmazania Garbage Collectorom konštantná.
Samozrejme sa občas nejaká hodnota v ňom zmení, ale to je všetko.
Preto ak pracujeme s veľkým množstvom hodnôt alebo
operácií, je všeobecne rýchlejšie aj úspornejšie použiť
ndarray. Inak samozrejme môžeme použiť aj základný Python
zoznam.
Tvorba ndarray polí v
NumPy
Existuje viacero spôsobov, ako vytvoriť ndarray.
Najčastejšie použijeme "premenu" zoznamu pomocou metódy
np.array(). NumPy však obsahuje mnoho ďalších zaujímavých
funkcií a metód, ktoré poskytujú na výstupe ndarray. Pozrime
sa na niektoré z nich.
Vytvorenie NumPy poľa z Python zoznamu
Najprv si ukážeme už spomínanú metódu np.array():
natural_numbers_array = np.array([1, 2, 3]) animals_array = np.array(['dog', 'cat', 'turtle']) date_array = np.array([np.datetime64('2023-07-20T17:23:10.42'), np.datetime64('2023-07-20'), np.datetime64('2023-07')])
Pri vytváraní polí z textov (np.array([...])) používa NumPy
dátový typ np.str_ s pevnou dĺžkou, v
predchádzajúcej ukážke je pole animals_array typu
dtype='<U6'. Ak do neho budeme chcieť neskôr vložiť dlhší
reťazec, bude automaticky skrátený na šesť znakov.
Aby sme tomu predišli, môžeme pri vytváraní poľa vopred definovať dostatočnú dĺžku:
animals_array = np.array(['dog', 'cat', 'turtle'], dtype='<U20') animals_array[1] = "Bengal cat" print(animals_array[1])
Existuje tiež možnosť vytvoriť pole s parametrom
dtype=object, kde sú prvky bežné Python reťazce
(str). Také pole síce umožňuje ukladať reťazce
ľubovoľnej dĺžky, ale stráca sa rýchlosť a
pamäťová efektivita NumPy. Pracuje sa s ním podobne pomaly
ako s bežným Python zoznamom. Preto sa dtype=object používa len
vtedy, keď je nutné uchovať reťazce rôznej dĺžky a nechceme riskovať
orezanie.
Všeobecne je nevhodné miešať rôzne typy v jednom poli. Dátový typ NumPy poľa pri jeho vytvorení buď explicitne špecifikujeme alebo je automaticky určený na základe hodnôt, ktoré do poľa vložíme. Ak vložíme do jedného poľa rôzne typy dát, NumPy ich všetky prevedie na jednotný typ. Vyberie pritom taký, ktorý dokáže reprezentovať všetky vložené hodnoty.
Uveďme si príklad. Ak vytvoríme pole s celými číslami a jedno z nich bude desatinné, všetky čísla budú prevedené na desatinné. Podobne, ak pridáme reťazec do poľa s celými číslami, všetky čísla budú prevedené na reťazce. Je zrejmé, ako nepekne toto dokáže ovplyvniť výsledky matematických operácií.
Pretypovanie v poli je nákladné z hľadiska výkonu a ide o veľmi jednoduchý spôsob, ako si prirobiť naozaj nepríjemné problémy s neočakávaným správaním programu. Preto sa snažíme pole udržiavať homogénne.
Ak budeme naozaj kreatívni a do jedného poľa namixujeme toľko typov, že
NumPy nedokáže nájsť jeden, ktorým by ich zastrešil, vznikne nám pole
prvkov object:
mix_array = np.array([1, 'dog', np.datetime64('2023-07-20T17:23:10.42')]) print(mix_array.dtype)
Vo výstupe uvidíme:
Array element type
object
Tvorba viacrozmerných polí
Poďme si ukázať, ako s pomocou metódy np.array() vytvoríme
viacrozmerné polia:
array_2D = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) array_3D = np.array([[[1, 2, 3], [4, 5, 6]], [[7, 8, 9], [10, 11, 12]]])
Prístup k prvkom v 3D poli
Naše array_3D má formát 2 x (2 x 3). Predstavme
si ho ako kinosálu. Formát 2 x (2 x 3) nám hovorí, že máme
prízemie a poschodie a v každom z nich dva rady po troch sedadlách. Rozložme
si zápis poľa tak, aby bol intuitívnejší:
array_3D = np.array([ [[1, 2, 3], # ground floor, first row [4, 5, 6]], # ground floor, second row [[7, 8, 9], # floor, first row [10, 11, 12]] # floor, second row ]) # Access to seat number 12 on the floor: seat = array_3D[1, 1, 2] # in the variable seat there will be the number 12
Zaujímavosťou je, že môžeme vytvoriť aj polia s "dimenziou 0":
array_0D = np.array(10)
V podstate tým do poľa uložíme iba jednu hodnotu. Ak vytlačíme obsah
premennej array_0D, dostaneme hodnotu 10. Ak však
zistíme typ premennej, dostaneme ndarray, nie
integer:
print(array_0D) print(type(array_0D))
V konzole uvidíme:
Arrays with dimension 0
10
<class 'numpy.ndarray'>
Náhodné celé číslo
np.random.randint()
Metóda randint() v module random knižnice NumPy
je základná funkcia na generovanie náhodných čísel typu
integer. Ako pre vygenerovanie jednej náhodnej hodnoty, tak aj
celého (aj viacrozmerného) poľa náhodných hodnôt.
Funkcie a metódy v module random máme k dispozícii buď
priamo z knižnice np.random, alebo si modul pridáme priamo ako
premennú. To je spôsob, aký budeme používať v tutoriáli kvôli
zjednodušeniu kódu:
import numpy.random as random
Teraz už môžeme premennú random používať priamo.
Metódu randint() už poznáme z kurzu Základné konštrukcie jazyka Python. Nechajme si
teda vygenerovať náhodné číslo od nuly do devätnásť:
random_number = random.randint(20)
Je potrebné si uvedomiť, že ako mnoho ďalších knižníc a
funkcií v Pythone, aj NumPy používa spodnú definovanú hranicu intervalu
"vrátane" (inkluzívne) a hornú hranicu "bez" (exkluzívne). V našom prípade
kód random.randint(20) znamená v reči matematiky interval
<0; 20).
Pole náhod 
Ak chceme vytvoriť pole náhodných hodnôt, vyplníme metóde argument
požadovanej dĺžky poľa size:
random_number_array = random.randint(3, 20, size=(5)) # fills the array of 5 numbers with numbers from 3 to 19 print(random_number_array) print() random_number_2Darray = random.randint(3, 20, size=(5, 4)) # fills the array of 5 rows and 4 columns print(random_number_2Darray)
Výstup v konzole:
Arrays with random numbers
[ 8 7 7 5 17]
[[19 8 14 8]
[14 5 5 3]
[ 4 6 17 10]
[16 14 5 6]
[19 18 8 9]]
Náhodný float -
np.random.rand()
Metóda rand() má trochu odlišnú syntax. Táto metóda
generuje náhodné číslo medzi 0 a 1. Opäť v
half-open intervale, avšak tentoraz <0; 1). Ak chceme získať
desatinné číslo v inom intervale, musíme funkciu rand()
prenásobiť a posunúť. Povedzme, že chceme náhodné číslo v intervale
<10, 30):
random_decimal_number = random.rand() * 20 + 10
Teraz sa presunieme na vytváranie polí. NumPy ndarray pomocou
metódy rand() vytvoríme tak, že do argumentu dáme číslo
označujúce rozmer poľa, ktoré chceme:
# Array of random numbers of length 3 random_1D_array = random.rand(3)
Získali sme polia troch čísel z intervalu <0,1). Podobne
vytvoríme aj viacrozmerné polia – jednoducho len pridáme číslo
označujúce ďalší rozmer:
# Random number matrix with dimensions 3×4 random_2D_array = random.rand(3, 4)
Pre čísla v inom intervale ako <0,1) opäť
použijeme posunutie násobením a pripočítaním čísla.
Moderná tvorba náhodných čísel
Od verzie NumPy 1.17 knižnica odporúča používať nové rozhranie
na generovanie náhodných čísel. Využíva sa objekt
Generator, ktorý nahrádza staršie funkcie z modulu
np.random. Nový prístup je bezpečnejší, lepšie
reprodukovateľný a prehľadnejší.
Vytvorenie nového generátora
Najprv vytvoríme inštanciu generátora pomocou funkcie
default_rng():
from numpy.random import default_rng rng = default_rng()
Generovanie náhodných čísel
Generator ponúka mnoho metód pre rôzne typy náhodných
hodnôt. Najčastejšie používame metódy random() a
integers().
Metóda random()
Metóda random() vracia desatinné čísla v intervale
<0,1):
random_float_number = rng.random() print("Random number (float) <0,1): ", random_float_number)
Ak ju zavoláme bez argumentu, získame jedno číslo. Ak jej odovzdáme tvar
výsledného poľa (napríklad (3, 4)), vráti viac náhodných
čísel usporiadaných do poľa daného rozmeru.
Ak chceme čísla v inom intervale ako <0,1), opäť ich
jednoducho vynásobíme a posunieme. Takto napríklad získame číslo v
intervale <10,30):
random_float_number = rng.random() * 20 + 10 print("Random number in an interval <10,30): ", random_float_number)
Metóda integers()
Metóda integers() generuje náhodné celé čísla. Ak zadáme
iba hornú hranicu, získame jedno náhodné číslo od 0 do zadanej hodnoty
mínus jedna:
random_int_number = rng.integers(10) print("Random integer <0,10): ", random_int_number)
Keď zadáme aj spodnú hranicu a parameter size, získame viac
čísel v poli zvoleného rozmeru. Ak chceme vytvoriť 2D pole náhodných
čísel od 3 do 19 s rozmermi 5×4, použijeme zápis:
int_array = rng.integers(3, 20, size=(5, 4)) print("2D array of random integers in the interval <3,20):\n", int_array)
V nasledujúcej lekcii, NumPy - Operácie s poľami, si vysvetlíme základné operácie s NumPy poľami. Konkrétne si ukážeme indexáciu a prechádzanie polí.
