Matematikai Algoritmusok és Felfedezések I.

7. Előadás: Numpy

2021 március 22.

Tudományos csomagok (Python Scientific stack)

csomag
NumPy Hatékony N-dimenziós tömb
SciPy Numerikus számítások
Matplotlib Grafikonok és rajzok
IPython (Jupyter) Interaktív notebook
SymPy Szimbolikus számítások
Pandas Adatbányászat

Numpy

A Python Scientific stack (és sok más) alap modulja.

https://numpy.org/

  • lineáris algebra, Fourier transzformáció, random számok,
  • könnyen használható mátrixok, tömbök
  • erősen opitmalizált
  • C/C++/Fortran integráció.

Mandelbrot halmaz

A Mandelbrot-halmaz azon $c$ komplex számokból áll, melyekre az alábbi $x_{n}$ rekurzív sorozat:

$x_{1}=c$

$x_{n+1}:=(x_{n})^{2}+c$

nem tart végtelenbe, azaz abszolút értékben (hosszára nézve) korlátos.

In [2]:
 

Importálási konvenció: import numpy as np

In [1]:
import numpy as np

N-dimenziós tömbök

A numpy központi objektuma az ndarray ($n$-dimenziós tömb).

In [2]:
A = np.array([[1, 2], [3, 4], [5, 6]])     
A
Out[2]:
array([[1, 2],
       [3, 4],
       [5, 6]])
In [3]:
type(A)
Out[3]:
numpy.ndarray

A.shape egy tuple a tömb dimenzióival, alakjával.

In [4]:
A.shape
Out[4]:
(3, 2)

Többdimenzió

Többdimenziós tömbökkel sok helyen találkozhatunk. Egyik tipikus megjelenésük a képek tárolása. Minden tömb egy háromdimenziós tömb, ahol az első két dimenzió az x és y koordinátának felel meg, a harmadik mentén pedig felsoroljuk az RGB értékeket. Az RGB értékek 0 és 255 közötti egészek.

In [5]:
import imageio                                  #kép beolvasáshoz
import matplotlib.pyplot as plt                 #kép megjelenítéshez

fig_size = plt.rcParams["figure.figsize"]       #kép méret növelése
fig_size[0] = 10
fig_size[1] = 8
plt.rcParams["figure.figsize"] = fig_size

dragon = imageio.imread('dragon.jpg')
print(type(dragon))

dragon.shape, dragon.dtype
<class 'imageio.core.util.Array'>
Out[5]:
((453, 640, 3), dtype('uint8'))
In [7]:
dragon[0,0]
Out[7]:
Array([255, 255, 255], dtype=uint8)
In [6]:
plt.imshow(dragon)
plt.show()

dtype

A listákkal ellentétben nem tárolhatunk benne bármit! A dtype megadja a tárolt elemek típusát

In [8]:
A.dtype
Out[8]:
dtype('int32')
In [11]:
a=10**30            #Ez sima ügy
#A[0,0]=10**30      #Ez túl nagy lenne
In [12]:
A = np.array([1.5, 2])
A.dtype
Out[12]:
dtype('float64')

Megadhatjuk a dtype-ot amikor létrehozzuk a tömböt.

In [13]:
np.array(['10', '20'], dtype="float32")
Out[13]:
array([10., 20.], dtype=float32)

String tömbök

In [14]:
T = np.array(['körte', 'alma'])
print(T)
print(T.shape, T.dtype, type(T))
['körte' 'alma']
(2,) <U5 <class 'numpy.ndarray'>

Fix hosszúságú karakter tömböket tárol!!

In [15]:
T[1] = "banana"
T
Out[15]:
array(['körte', 'banan'], dtype='<U5')

Az elemek elérése

Egy sor:

In [18]:
A = np.array([[1, 2], [3, 4], [5, 6]])
A
Out[18]:
array([[1, 2],
       [3, 4],
       [5, 6]])
In [19]:
A[0], A[1]
Out[19]:
(array([1, 2]), array([3, 4]))

Egy oszlop:

In [20]:
A[:, 0]
Out[20]:
array([1, 3, 5])

egy elem:

In [21]:
A[2][1], A[2, 1], type(A[2, 1])
Out[21]:
(6, 6, numpy.int32)

néhány sor vagy oszlop

In [22]:
A = np.array([[1, 2], [3, 4], [5, 6]])
A
Out[22]:
array([[1, 2],
       [3, 4],
       [5, 6]])
In [28]:
A[:2]  # vagy A[:2, :]
Out[28]:
array([[1, 2],
       [3, 4]])
In [25]:
A[:, 1:]
Out[25]:
array([[2],
       [4],
       [6]])
In [24]:
A[::2]
Out[24]:
array([[1, 2],
       [5, 6]])
In [29]:
B = np.array([[[1, 2, 3],[4, 5, 6]]])
B.shape,  B.ndim
Out[29]:
((1, 2, 3), 3)
In [30]:
B
Out[30]:
array([[[1, 2, 3],
        [4, 5, 6]]])
In [32]:
B[0].shape
Out[32]:
(2, 3)
In [33]:
B[0, 1], B[0, 1].shape
Out[33]:
(array([4, 5, 6]), (3,))
In [34]:
B[0, 1, 2]
Out[34]:
6

Operációk

Elemenkénti műveletek

A leggyakrabban használt matematikai fügvények is elérhetőek és elemenként hatnak.

In [35]:
A = np.array([[1, 2], [3, 4], [5, 6]])
print(A)
A + A
[[1 2]
 [3 4]
 [5 6]]
Out[35]:
array([[ 2,  4],
       [ 6,  8],
       [10, 12]])
In [36]:
A * A
Out[36]:
array([[ 1,  4],
       [ 9, 16],
       [25, 36]])
In [38]:
print(np.exp(A))
print(np.sin(A*np.pi/6))
print(2**A)
print(1/A)
[[  2.71828183   7.3890561 ]
 [ 20.08553692  54.59815003]
 [148.4131591  403.42879349]]
[[5.00000000e-01 8.66025404e-01]
 [1.00000000e+00 8.66025404e-01]
 [5.00000000e-01 1.22464680e-16]]
[[ 2  4]
 [ 8 16]
 [32 64]]
[[1.         0.5       ]
 [0.33333333 0.25      ]
 [0.2        0.16666667]]

Logikai műveletek

In [40]:
A=np.array([1,1,1,0]) & np.array([1,0,1,1]) 
A
Out[40]:
array([1, 0, 1, 0], dtype=int32)
In [41]:
A=np.array([1,1,1,0]) | np.array([1,0,1,1]) 
A
Out[41]:
array([1, 1, 1, 1], dtype=int32)
In [42]:
A = np.array([[1, 3], [2, 4]])
P = (A >= 2)
print(P)
print(P.dtype)
[[False  True]
 [ True  True]]
bool

Indexelés listákkal (Advanced indexing)

Egy listával vagy tömbbel fogjuk megadni, hogy hanyadik elemeket szeretnénk.

In [43]:
B = np.array(["a","b","c","d","e","f","g"])
In [44]:
np.array([B[0],B[3],B[4],B[6]])
Out[44]:
array(['a', 'd', 'e', 'g'], dtype='<U1')
In [46]:
B[[0,3,4,6]]            #gyorsabban ugyanaz
Out[46]:
array(['a', 'd', 'e', 'g'], dtype='<U1')
In [47]:
B[np.array([0,3,4,6])]  #tömbbel ugyanaz
Out[47]:
array(['a', 'd', 'e', 'g'], dtype='<U1')

Több dimenzióban komplikálódnak a dolgok.

Ha egyetlen index tömbbel indexelünk, annak elemei megfelelnek M első kordináta szerint vett elemeinek.

In [48]:
M = np.arange(12).reshape(3,4)
M
Out[48]:
array([[ 0,  1,  2,  3],
       [ 4,  5,  6,  7],
       [ 8,  9, 10, 11]])
In [49]:
np.array([[M[0],M[1]],[M[2],M[2]]])
Out[49]:
array([[[ 0,  1,  2,  3],
        [ 4,  5,  6,  7]],

       [[ 8,  9, 10, 11],
        [ 8,  9, 10, 11]]])
In [50]:
M[np.array([[0,1],[2,2]])] #gyorsabban ugyanaz
Out[50]:
array([[[ 0,  1,  2,  3],
        [ 4,  5,  6,  7]],

       [[ 8,  9, 10, 11],
        [ 8,  9, 10, 11]]])

Ha több index tömbbel indexelünk, akkor az első az első koordinátnak a második a másodiknak, ... felel meg.

In [51]:
np.array([M[0][2],M[1][2],M[1][0]])
Out[51]:
array([2, 6, 4])
In [52]:
np.array([M[0,2],M[1,2],M[1,0]])
Out[52]:
array([2, 6, 4])
In [54]:
M[np.array([0,1,1]),np.array([2,2,0])] #gyorsabban ugyanaz
Out[54]:
array([2, 6, 4])
In [55]:
np.array([[M[0,1],M[1,0]],[M[2,1],M[2,3]]])
Out[55]:
array([[ 1,  4],
       [ 9, 11]])
In [56]:
M[np.array([[0,1],[2,2]]),np.array([[1,0],[1,3]])]  #gyorsabban ugyanaz
Out[56]:
array([[ 1,  4],
       [ 9, 11]])
In [57]:
M[:,[1,3]]
Out[57]:
array([[ 1,  3],
       [ 5,  7],
       [ 9, 11]])
In [58]:
M[[1,2],:]
Out[58]:
array([[ 4,  5,  6,  7],
       [ 8,  9, 10, 11]])

Többször is kiválaszthatjuk ugyanazt a sort vagy oszlopot:

In [59]:
M[[2,1,2],:]
Out[59]:
array([[ 8,  9, 10, 11],
       [ 4,  5,  6,  7],
       [ 8,  9, 10, 11]])

Többdimenzióban még komplikáltabba a helyzet. https://docs.scipy.org/doc/numpy/reference/arrays.indexing.html

A tömb alakjának megváltoztatása

A reshape függvénnyel megváltoztathatjuk a tömb alakját anélkül, hogy a tartalmazott elemek megváltoznának, vagy akár lemásolódnának a hattérben.

In [60]:
B = np.array([[[1, 2, 3], [4, 5, 6]]])
B.reshape((2, 3))
Out[60]:
array([[1, 2, 3],
       [4, 5, 6]])
In [61]:
B.reshape((3, 2))
Out[61]:
array([[1, 2],
       [3, 4],
       [5, 6]])

ValueError hibát kapunk ha rosz alakot adunk meg.

In [64]:
#B.reshape(7)  # raises ValueError
In [62]:
np.array(range(6)).reshape((1, 2, 3))
Out[62]:
array([[[0, 1, 2],
        [3, 4, 5]]])

Ha -1-et adunk meg, megpróbálja kitalálni, hogy annak a dimenziónak mekkorának kell lennie az elemek száma alapján.

In [65]:
X = np.array(range(12)).reshape((2, -1, 2))
print("X.shape:", X.shape)
print(X)
X.shape: (2, 3, 2)
[[[ 0  1]
  [ 2  3]
  [ 4  5]]

 [[ 6  7]
  [ 8  9]
  [10 11]]]

A resize akkor is működik, ha az elemek száma nem passzol. Ilyenkor töröl, vagy feltölt nullákkal. Magát a tömböt változtatja meg.

In [67]:
X = np.array([[1, 2], [3, 4]])
X.resize((5, 3))
X.resize((1, 2))
X
Out[67]:
array([[1, 2]])

Tömbök létrehozása

Ritkán akarjuk egyesével megadni az elemeket, ezért van egy csomó gyorsabb módszer:

  • arange: range megfelelője csak nem listát hoz létre, hanem tömböt.
  • linspace: egyenletesen felosztott intervallum
  • ones, ones_like, csupa egyes
  • zeros, zeros_like, csupa nulla
  • eye: indetitás mátrix 2 dimenzióban
  • fromfunction függvény alapján

Az np.ones_like() és np.zeros_like() függvények megtartják a formát és a dtype-ot!

In [68]:
np.arange(10), np.arange(10).shape
Out[68]:
(array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]), (10,))
In [69]:
np.arange(1, 21, 2).reshape(5, -1)
Out[69]:
array([[ 1,  3],
       [ 5,  7],
       [ 9, 11],
       [13, 15],
       [17, 19]])
In [70]:
np.linspace(0, 4, 11)
Out[70]:
array([0. , 0.4, 0.8, 1.2, 1.6, 2. , 2.4, 2.8, 3.2, 3.6, 4. ])
In [71]:
np.ones((3, 2)) 
Out[71]:
array([[1., 1.],
       [1., 1.],
       [1., 1.]])
In [72]:
np.zeros((2, 3, 2))
Out[72]:
array([[[0., 0.],
        [0., 0.],
        [0., 0.]],

       [[0., 0.],
        [0., 0.],
        [0., 0.]]])
In [74]:
A = np.arange(6).reshape(3, -1)
np.zeros_like(A)
Out[74]:
array([[0, 0],
       [0, 0],
       [0, 0]])
In [76]:
np.eye(5)
Out[76]:
array([[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.]])
In [77]:
np.eye(4, dtype=bool)
Out[77]:
array([[ True, False, False, False],
       [False,  True, False, False],
       [False, False,  True, False],
       [False, False, False,  True]])

Az egyik leghasznosabb, hogy az indexek függvényében is definiálhatjuk a tömböt:

In [79]:
np.fromfunction(lambda i, j: i == j, (3, 3))
Out[79]:
array([[ True, False, False],
       [False,  True, False],
       [False, False,  True]])
In [80]:
np.fromfunction(lambda i,j: 100*i+j, (5,5))
Out[80]:
array([[  0.,   1.,   2.,   3.,   4.],
       [100., 101., 102., 103., 104.],
       [200., 201., 202., 203., 204.],
       [300., 301., 302., 303., 304.],
       [400., 401., 402., 403., 404.]])

Tömbök összeragasztása

Bármelyik dimenzió szerint ragaszthatunk, ha megfelelőek a dimenziók.

In [90]:
A = np.arange(8).reshape(2, -1)
B = np.arange(8).reshape(2, -1)

np.concatenate((A, B), axis=0)
Out[90]:
array([[0, 1, 2, 3],
       [4, 5, 6, 7],
       [0, 1, 2, 3],
       [4, 5, 6, 7]])
In [85]:
np.concatenate((A, B), axis=-1)  # utolsó dimenzió dimension
Out[85]:
array([[0, 1, 2, 3, 0, 1, 2, 3],
       [4, 5, 6, 7, 4, 5, 6, 7]])

Mivel általában az első vagy második koordináta szerint ragasztunk, ezekre vannak rövidítések:

In [86]:
A = np.arange(6).reshape(2, -1)
B = np.arange(8).reshape(2, -1)
In [87]:
np.hstack((A, B))
Out[87]:
array([[0, 1, 2, 0, 1, 2, 3],
       [3, 4, 5, 4, 5, 6, 7]])
In [91]:
A = np.arange(6).reshape(-1, 2)
B = np.arange(8).reshape(-1, 2)
print(A.shape, B.shape)

np.vstack((A, B))
(3, 2) (4, 2)
Out[91]:
array([[0, 1],
       [2, 3],
       [4, 5],
       [0, 1],
       [2, 3],
       [4, 5],
       [6, 7]])
In [89]:
plt.imshow(np.vstack((dragon,dragon)))
plt.show()
plt.imshow(np.hstack((dragon,dragon)))
plt.show()

np.stack egy új dimenzió szerint egymás mellé rakja a tömböket

In [92]:
plt.imshow(np.stack((dragon[:,:,0],dragon[:,:,0],dragon[:,:,0]  ),axis=2 ))
plt.show()