03 - Pakiet `NumPy` cz. 1
Pakiet NumPy
jest powszechnie stosowany w obliczeniach naukowych i nie tylko. Jego strona domowa znajduje się pod adresem numpy.org. Powinien być zainstalowany wraz ze środowiskiem Anaconda.
Poznajmy kilka użytecznych elementów pakietu. Zacznijmy od ndarray
, który jest wielowymiarowym, tablicowym (ang. array) obiektem umożliwiającym przechowywanie i szybką obróbkę dużej ilości danych określonego typu.
Tworzenie tablic
Zacznijmy od poznania kilku sposobów utworzenia jednowymiarowych tablic z liczbami.
# import pakietu numpy
import numpy as np
# Utworzenie tablic z listy
tab_1 = np.array([1, 2, 3, 4])
print(f'tab_1 {tab_1}, typ: {type(tab_1)}')
lis = [5, 6, 7, 8]
tab_2 = np.array(lis)
print(f'tab_2 {tab_2}, typ: {type(tab_2)}')
tab_1 [1 2 3 4], typ: <class 'numpy.ndarray'>
tab_2 [5 6 7 8], typ: <class 'numpy.ndarray'>
# z krotki
tab_3 = np.array((9, 10, 11, 12))
print(f'tab_3 {tab_3}, typ: {type(tab_3)}')
tab_3 [ 9 10 11 12], typ: <class 'numpy.ndarray'>
# Tablica z wygenerowanej sekwencji kolejnych liczb
# analogicznie do funkcji range()
tab_4 = np.arange(10)
print(f'tab_4 {tab_4}, typ: {type(tab_4)}')
tab_4 [0 1 2 3 4 5 6 7 8 9], typ: <class 'numpy.ndarray'>
# (Pseudo)losowe liczby ze standardowego rozkłady normalnego
tab_5 = np.random.randn(5)
print(f'tab_5 {tab_5}, typ: {type(tab_5)}')
tab_5 [ 0.15194022 -1.7265475 -0.57213547 0.31769404 0.80205681], typ: <class 'numpy.ndarray'>
# 10-elementowa tablica wypełniona zerami
tab_6 = np.zeros(10)
print(f'tab_6 {tab_6}, typ: {type(tab_6)}')
tab_6 [0. 0. 0. 0. 0. 0. 0. 0. 0. 0.], typ: <class 'numpy.ndarray'>
# 10-elementowa tablica wypełniona jedynkami
tab_7 = np.ones(10)
print(f'tab_7 {tab_7}, typ: {type(tab_7)}')
tab_7 [1. 1. 1. 1. 1. 1. 1. 1. 1. 1.], typ: <class 'numpy.ndarray'>
# 10-elementowa tablica wypełniona siódemkami
tab_8 = np.full(10, 7)
print(f'tab_8 {tab_8}, typ: {type(tab_8)}')
tab_8 [7 7 7 7 7 7 7 7 7 7], typ: <class 'numpy.ndarray'>
Utwórzmy kolejną tablicę (typu ndarray
) i porównajmy ją z listą:
# Import modułu
import numpy as np
# Utworzenie listy z kolejnymi wartościami od 0 do 9
lis = list(range(10))
# Utworzenie tablicy z kolejnymi wartościami od 0 do 9
tab = np.arange(10)
# Drukowanie zawartości
print(f'Lista: {lis}')
print(f'Tablica: {tab}')
# Sprawdzenie typu obiektów
print(f'Typ listy: {type(lis)}\nTyp tablicy: {type(tab)}')
# Mnożenie
print(f"lis * 2: {lis * 2}")
print(f"tab * 2: {tab * 2}")
Lista: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
Tablica: [0 1 2 3 4 5 6 7 8 9]
Typ listy: <class 'numpy.ndarray'>
Typ tablicy: <class 'list'>
lis * 2: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
tab * 2: [ 0 2 4 6 8 10 12 14 16 18]
Zwróć uwagę, że o ile w przypadku listy operator *
spowodował wygenerowanie listy z powtórzoną serią danych, dane w tablicy zostały pomnożone i została zwrócona tablica ze zmodyfikowanymi liczbami. Aby uzyskać analogiczny wynik dla listy należałoby użyć np. takiego kodu:
lis2 = [i * 2 for i in lis]
print(lis2)
[0, 2, 4, 6, 8, 10, 12, 14, 16, 18]
Jak widać, mimo zastosowania listy składanej, operacja na liście była bardziej skomplikowana. Co więcej, nie będziemy tego sprawdzać, ale obiekty udostępniane przez pakiet numpy
umożliwiają nawet kilkudziesięciokrotnie szybszą pracę z danymi niż w przypadku struktur, które poznaliśmy wcześniej (jak listy).
Działania na tablicach jednowymiarowych
Sprawdźmy kilka innych możliwości:
import numpy as np
tab_1 = np.arange(10)
print(f'tab_1: {tab_1}')
# Długość tablicy
print(f'Długość: {tab_1.size}')
# Proste działania arytmetyczne
print(f'Dodawanie: {tab_1 + 2}')
print(f'Odejmowanie: {tab_1 - 2}')
print(f'Mnożenie: {tab_1 * 2}')
print(f'Dzielenie: {tab_1 / 2}')
print(f'Potęgowanie: {tab_1 ** 2}')
tab_1: [0 1 2 3 4 5 6 7 8 9]
Długość: 10
Dodawanie: [ 2 3 4 5 6 7 8 9 10 11]
Odejmowanie: [-2 -1 0 1 2 3 4 5 6 7]
Mnożenie: [ 0 2 4 6 8 10 12 14 16 18]
Dzielenie: [0. 0.5 1. 1.5 2. 2.5 3. 3.5 4. 4.5]
Potęgowanie: [ 0 1 4 9 16 25 36 49 64 81]
W działaniach można też zastosować dwie (lub więcej) tablice o takiej samej długości:
# Bezpośredni sposób tworzenia tablic
tab_2 = np.arange(10, 101, 10)
print(f'tab_2: {tab_2}')
print(f'Dodawanie tablic: {tab_1 + tab_2}')
print(f'Odejmowanie tablic: {tab_1 - tab_2}')
print(f'Mnożenie tablic: {tab_1 * tab_2}')
print(f'Dzielenie tablic: {tab_1 / tab_2}')
print(f'Potęgowanie tablic: {tab_1 ** tab_2}')
tab_2: [ 10 20 30 40 50 60 70 80 90 100]
Dodawanie tablic: [ 10 21 32 43 54 65 76 87 98 109]
Odejmowanie tablic: [-10 -19 -28 -37 -46 -55 -64 -73 -82 -91]
Mnożenie tablic: [ 0 20 60 120 200 300 420 560 720 900]
Dzielenie tablic: [0. 0.05 0.06666667 0.075 0.08 0.08333333
0.08571429 0.0875 0.08888889 0.09 ]
Potęgowanie tablic: [ 0 1 1073741824
-6289078614652622815 0 8512967443501092241
0 -1156583747620310143 0
6627890308811632801]
Przy okazji (potęgowanie) zauważ, że przy dużych liczbach pojawił się problem z dużymi liczbami. Wrócimy do tego później.
Dość łatwo można porównać zawartość dwu tablic, otrzymując tablicę z wynikiem porównania (True
/False
) kolejnych elementów:
tab_3 = np.array([1, 2, 3, 4])
tab_4 = np.array([1, 3, 3, 5])
porownanie = tab_3 == tab_4
print(f'Porównanie: {porownanie}, typ: {type(porownanie)}')
Porównanie: [ True False True False], typ: <class 'numpy.ndarray'>
Obiekty ndarray
mają zdefiniowane metody m. in. przydatne w szybkim przeprowadzaniu prostych operacji matematycznych czy obliczaniu prostych wartości statystycznych. Na przykład:
dane = np.array([2.3, 2.4, 1.9, 2.0, 2.5, 1.8, 2.1, 1.8])
print(dane)
# Suma
print(f'Suma: {dane.sum()}')
# Wartość największa
print(f'Największa: {dane.max()}')
# Najmniejsza
print(f'Najmniejsza: {dane.min()}')
# Średnia arytmetyczna
print(f'Średnia: {dane.mean()}')
# Wariancja
print(f'Wariancja: {dane.var()}')
# Odchylenie standardowe
print(f'Odch. stand: {dane.std()}')
[2.3 2.4 1.9 2. 2.5 1.8 2.1 1.8]
Suma: 16.799999999999997
Największa: 2.5
Najmniejsza: 1.8
Średnia: 2.0999999999999996
Wariancja: 0.06499999999999997
Odch. stand: 0.2549509756796392
Można też posortować elementy w tablicy, należy jednak pamiętać, że nie jest zwracana kopia tablicy z posortowanymi wartościami, ale zmienia się ich kolejność w tablicy, na której wywołujemy metodę:
# Wartości posortowane
dane.sort()
print(f'Posortowane: {dane}')
Posortowane: [1.8 1.8 1.9 2. 2.1 2.3 2.4 2.5]
Jeśli chcemy otrzymać posortowaną kopię, nalezy użyć funkcji sort()
z pakietu numpy
:
dane_sort = np.sort(dane)
print(f"dane: {dane}")
print(f"dane sort.: {dane_sort}")
dane: [2.3 2.4 1.9 2. 2.5 1.8 2.1 1.8]
dane sort.: [1.8 1.8 1.9 2. 2.1 2.3 2.4 2.5]
Odwołanie do elementów tablic jednowymiarowych
Odwoływanie się do poszczególnych elementów tablic jednowymiarowych, wygląda podobnie jak w przypadku list:
import numpy as np
tab = np.arange(10)
print(tab)
# Element pod indeksem 1
print(tab[1])
[0 1 2 3 4 5 6 7 8 9]
1
# Ostatni element
print(tab[-1])
9
# Elementy do indeksu 4 (4 pierwsze)
print(tab[:4])
[0 1 2 3]
# Cztery ostatnie elementy
print(tab[-4:])
[6 7 8 9]
# Elementy między indeksami 2 i 7-1
print(tab[2:7])
[2 3 4 5 6]
# Elementy między indeksami 2 i 7-1, co drugi
print(tab[2:7:2])
[2 4 6]
# Wszystkie elementy odwrotnie
print(tab[::-1])
[9 8 7 6 5 4 3 2 1 0]
# Elementy między indeksami 7 i 2+1 (odwrotnie)
print(tab[7:2:-1])
[7 6 5 4 3]
# Zmiana elementu pod indeksem 3
tab[3] = 99
print(tab)
[ 0 1 2 99 4 5 6 7 8 9]
Tablice wielowymiarowe
Jak wspomniałem na początku, można tworzyć wielowymiarowe tablice ndarray
. Zacznijmy od dwu wymiarów. Tablicę dwuwymiarową można utworzyć np. tak:
tab_2d = np.array([[0, 1, 2],
[3, 4, 5],
[6, 7, 8]])
print(tab_2d)
[[0 1 2]
[3 4 5]
[6 7 8]]
Jak widać otrzymaliśmy tablicę, zawierającą tablice jednowymiarowe, które możemy interpretować jako rzędy. Możemy się odwołać do poszczególnych elementów na dwa sposoby:
print(tab_2d[2][1])
print(tab_2d[2, 1])
7
7
Rzędy i kolumny pobieramy tak:
# Drugi rząd:
print(tab_2d[1,])
# Druga kolumna:
print(tab_2d[:,1])
[3 4 5]
[1 4 7]
Można też wybrać odpowiednie wycinki tablicy:
print(tab_2d[2,1:])
print(tab_2d[:2,:])
print(tab_2d[:2,:2])
[7 8]
[[0 1 2]
[3 4 5]]
[[0 1]
[3 4]]
Zauważ, że przy tworzeniu tablicy liczba kolumn musi się zgadzać w każdym rzędzie:
tab_2d = np.array([[0, 1, 2],
[3, 4, 5],
[6, 7, 8, 9]])
<ipython-input-93-b10b20179f94>:1: VisibleDeprecationWarning: Creating an ndarray from ragged nested sequences (which is a list-or-tuple of lists-or-tuples-or ndarrays with different lengths or shapes) is deprecated. If you meant to do this, you must specify 'dtype=object' when creating the ndarray
tab_2d = np.array([[0, 1, 2],
Analogicznie do dwuwymiarowych, można utworzyć tablice o większej liczbie wymiarów, zobaczmy to na przykładzie tablicy trójwymiarowej:
tab_3d = np.array([[[1, 2, 3],[4, 5, 6]],[[7, 8, 9],[10, 11, 12]]])
print(tab_3d)
[[[ 1 2 3]
[ 4 5 6]]
[[ 7 8 9]
[10 11 12]]]
Do wartości w takiej tablicy odwołujemy się analogicznie do dwuwymiarowej, dodając indeks(-y) dodatkowego wymiaru:
print(tab_3d[1,0,2])
9
print(tab_3d[1,:2,2])
[ 9 12]
print(tab_3d[0:,:2,2])
[[ 3 6]
[ 9 12]]
Takich funkcji jak zeros()
można używać także przy tworzeniu tablic o wielu wymiarach, w takim przypadku, należy odpowiednie parametry podać w krotce, jako argument:
tab_3d_0 = np.zeros((2, 5, 10))
print(tab_3d_0)
[[[0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
[0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
[0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
[0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
[0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]]
[[0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
[0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
[0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
[0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
[0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]]]
Informację o liczbie wymiarów i kształcie tablicy można uzyskać z odpowiednich atrybutów:
print(f'Liczba wymiarów: {tab_3d_0.ndim}')
print(f'Kształt: {tab_3d_0.shape}')
Liczba wymiarów: 3
Kształt: (2, 5, 10)
Zmiana wartości tablicy
Skoro wiemy jak odwołać się do komórek, sprawdźmy jak wygląda modyfikacja ich zawartości. Przy okazji poznamy dwie nowe funkcje ułatwiających tworzenie tablic z określoną zawartością (jest ich znacznie więcej).
Wartości w tablicy jednowymiarowej można zmieniać przypisując nowe wartości do określonej komórki, lub komórek:
import numpy as np
# Generowanie tablicy z równo rozłożonymi 11 wartościami
# (zmiennoprzecinkowymi) między 0 i 20
tab_1d = np.linspace(0, 20, 11)
print(tab_1d)
[ 0. 2. 4. 6. 8. 10. 12. 14. 16. 18. 20.]
# Zmiana pojedynczej wartości
tab_1d[0] = 77
print(tab_1d)
[77. 2. 4. 6. 8. 10. 12. 14. 16. 18. 20.]
#Zmiana zakresu wartości
tab_1d[5:8] = 0
print(tab_1d)
[77. 2. 4. 6. 8. 0. 0. 0. 16. 18. 20.]
#Zmiana wartości w całej tablicy
tab_1d[:] = 1
print(tab_1d)
[1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1.]
Analogicznie postępujemy z tablicami wielowymiarowymi, podając komórkę, lub wycinek, który chcemy zmienić:
# Generowanie tablicy 5x5 wypełnionej l. całkowitymi:
# 1 na przekątnej i 0 w pozostałych miejscach
tab_2d = np.identity(5, int)
print(tab_2d)
[[1 0 0 0 0]
[0 1 0 0 0]
[0 0 1 0 0]
[0 0 0 1 0]
[0 0 0 0 1]]
#Zmiana pojedynczej wartości
tab_2d[0, 2] = 7
print(tab_2d)
[[1 0 7 0 0]
[0 1 0 0 0]
[0 0 1 0 0]
[0 0 0 1 0]
[0 0 0 0 1]]
#Zmiana w kolumnie
tab_2d[:, 1] = 6
print(tab_2d)
[[1 6 7 0 0]
[0 6 0 0 0]
[0 6 1 0 0]
[0 6 0 1 0]
[0 6 0 0 1]]
#Zmiana w rzędzie
tab_2d[2, :] = 4
print(tab_2d)
[[1 6 7 0 0]
[0 6 0 0 0]
[4 4 4 4 4]
[0 6 0 1 0]
[0 6 0 0 1]]
#Zmiana w wycinku tabeli
tab_2d[3:5, 2:] = 8
print(tab_2d)
[[1 6 7 0 0]
[0 6 0 0 0]
[4 4 4 4 4]
[0 6 8 8 8]
[0 6 8 8 8]]
Zadania
Zadanie 1
Stwórz tabelę dwuwymiarową, która po wydrukowaniu będzie przypominała szachownicę:
[[0 1 0 1 0 1 0 1]
[1 0 1 0 1 0 1 0]
[0 1 0 1 0 1 0 1]
[1 0 1 0 1 0 1 0]
[0 1 0 1 0 1 0 1]
[1 0 1 0 1 0 1 0]
[0 1 0 1 0 1 0 1]
[1 0 1 0 1 0 1 0]]
Zadanie 2
Napisz program, który stworzy i wydrukuje tablicę przypominającą kwadratową mozaikę ze spiralą. Dodatkowe wymogi:
- Znaki tworzące tło i spiralę podaje użytkownik, ale jeśli użytkownik nie wprowadzi znaku ale wciśnie Enter to przyjmowana jest wartość domyślna.
- Domyślnie “tło” powinno składać się ze spacji, a spirala ze znaków “0”.
- Program powinien odebrać od użytkownika żądany rozmiar (liczba rzędów i kolumn).
- Ponieważ spirala powinna rozpoczynać się w środku mozaiki, jeśli użytkownik poda liczbę parzystą, należy ustalić rozmiar na liczbę o 1 większą niż podana.
Poniżej dwie przykładowe sesje:
Podaj rozmiar mozaiki: 10
Podaj znak tła (domyślnie spacja):
Podaj znak rysunku (domyślnie '0'): 8
Rozmiar mozaiki: 11 x 11
[[' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ']
[' ' '8' '8' '8' '8' '8' '8' '8' '8' '8' ' ']
[' ' '8' ' ' ' ' ' ' ' ' ' ' ' ' ' ' '8' ' ']
[' ' '8' ' ' '8' '8' '8' '8' '8' ' ' '8' ' ']
[' ' '8' ' ' '8' ' ' ' ' ' ' '8' ' ' '8' ' ']
[' ' '8' ' ' '8' ' ' '8' ' ' '8' ' ' '8' ' ']
[' ' '8' ' ' '8' ' ' '8' '8' '8' ' ' '8' ' ']
[' ' '8' ' ' '8' ' ' ' ' ' ' ' ' ' ' '8' ' ']
[' ' '8' ' ' '8' '8' '8' '8' '8' '8' '8' ' ']
[' ' '8' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ']
[' ' '8' '8' '8' '8' '8' '8' '8' '8' '8' '8']]
Podaj rozmiar mozaiki: 13
Podaj znak tła (domyślnie spacja): #
Podaj znak rysunku (domyślnie '0'): .
Rozmiar mozaiki: 13 x 13
[['.' '.' '.' '.' '.' '.' '.' '.' '.' '.' '.' '.' '.']
['.' '#' '#' '#' '#' '#' '#' '#' '#' '#' '#' '#' '.']
['.' '#' '.' '.' '.' '.' '.' '.' '.' '.' '.' '#' '.']
['.' '#' '.' '#' '#' '#' '#' '#' '#' '#' '.' '#' '.']
['.' '#' '.' '#' '.' '.' '.' '.' '.' '#' '.' '#' '.']
['.' '#' '.' '#' '.' '#' '#' '#' '.' '#' '.' '#' '.']
['.' '#' '.' '#' '.' '#' '.' '#' '.' '#' '.' '#' '.']
['.' '#' '.' '#' '.' '#' '.' '.' '.' '#' '.' '#' '.']
['.' '#' '.' '#' '.' '#' '#' '#' '#' '#' '.' '#' '.']
['.' '#' '.' '#' '.' '.' '.' '.' '.' '.' '.' '#' '.']
['.' '#' '.' '#' '#' '#' '#' '#' '#' '#' '#' '#' '.']
['.' '#' '.' '.' '.' '.' '.' '.' '.' '.' '.' '.' '.']
['.' '#' '#' '#' '#' '#' '#' '#' '#' '#' '#' '#' '#']]
Przykładowe rozwiązania
Zadanie 1
import numpy as np
szachownica = np.zeros((8,8), dtype=int)
for i in range(8):
for j in range(8):
# nieparzyste
if i%2 == 0 or i == 0:
if j%2 != 0:
szachownica[i,j] = 1
else:
if j%2 == 0:
szachownica[i,j] = 1
print(szachownica)
Zadanie 2
import numpy as np
rozmiar = int(input("Podaj rozmiar mozaiki: "))
tlo = input("Podaj znak tła (domyślnie spacja): ")
if tlo == '':
tlo = ' '
rys = input("Podaj znak rysunku (domyślnie '0'): ")
if rys == '':
rys = '0'
x = int(rozmiar/2)
if rozmiar%2 == 0:
rozmiar += 1
print(f'Rozmiar mozaiki: {rozmiar} x {rozmiar}')
mozaika = np.full((rozmiar,rozmiar), tlo)
y = x
mozaika[x,y] = 0
krok = 1
gora = True
prawo = True
while x>=0 and x<rozmiar and y>=0 and y<rozmiar and krok<=rozmiar:
if gora:
mozaika[x:x+krok, y] = rys
x = x+krok
gora = False
else:
mozaika[x-krok:x, y] = rys
x = x-krok
gora = True
krok += 1
if krok > rozmiar:
break
if prawo:
mozaika[x, y:y+krok+1] = rys
y = y+krok
prawo = False
else:
mozaika[x, y-krok:y] = rys
y = y-krok
prawo = True
krok += 1
print()
print(mozaika)