Pakiet NumPy - tutorial

Wstep

Czym jest pakiet numpy? Pakiet NumPy jest pakietam wykorzystywanym do operacji numerycznych przeprowadzanych na wielowymiarowych obiektach zwanych arrayami. Pakiet ten jest bardzo wydajny i często wykorzystywany - zarówno przez początkujących jak i tych bardziej zaawansowanych. Dobrze współpracuje z pakietami takimi jak pandas, scipy, matplotlib, scikit-learn i wieloma innymi pakietami bazującymi na danych numerycznych.

Czym jest array? Array jest obiektem, na którym można wywoływać złożone operacje numeryczne. Z uwagi na wydajność obliczeń array’e sa często wykorzystywane. Swoją strukturą przypominają listę, lecz między tymi obiektami są znaczące różnice. Niektóre z nich zostały przedstawione poniżej.

Różnica między działaniami na listach a numpy array

Standardowo Python nie potrafi robić obliczeń na listach:

height = [1.82, 2.01, 1.68]
weight = [100, 89, 53]
weight / height ** 2 # to nie zadziala

Jest na to sposób, który też jest powszechnie stosowany - List Comprehension.

Należy jednak pamiętać, że nie w każdym przypadku List Comprehension zastąpi nam funckje wbudowane w pakiet numpy. Ponadto nadmiar tej struktury wpływa negatywnie na wydajność kodu.

[weight[i] / height[i] ** 2 for i in range(len(height))]
[30.189590629151066, 22.02915769411649, 18.77834467120182]

Szybszym (i wydaje się, że bardziej przyjaznym) rozwiązaniem jest użycie pakietu Numpy. Dzięki temu obiektowi możemy definiować działania na całej array’i.

import numpy as np
np_height = np.array(height)
np_height
array([1.82, 2.01, 1.68])
np_weight = np.array(weight)
np_weight
array([100,  89,  53])
bmi = np_weight / np_height ** 2
bmi
array([30.18959063, 22.02915769, 18.77834467])
Uwaga!

W numpy array można przechowywać dane tylko jednego typu (w listach można mieszać typy danych).

[0.2, 'll', True]
[0.2, 'll', True]
np.array([0.2, 'll', True])
array(['0.2', 'll', 'True'], dtype='<U32')
Uwaga!

Uwaga! Dodajac standardowe listy w Pythonie uzyskamy listę elementów wartości z obu list. Robiąc to samo na arrayach dodajemy odpowiednie elementy do siebie. Trzeba więc uważać na to co się robi.

python_list = [1, 2, 3]
numpy_array = np.array(python_list)
python_list + python_list
[1, 2, 3, 1, 2, 3]
numpy_array + numpy_array
array([2, 4, 6])
# Chcac uzyskac efekt powiekszenia listy (jak na standardowych listach) wystarczy uzyc funkcji np.append
np.append(numpy_array, numpy_array)
array([1, 2, 3, 1, 2, 3])

Operacje na arrayach

Tworzenie Numpy Array

# Definiowanie array'a na podstawie listy
np.array([1,2,3,4,5])
array([1, 2, 3, 4, 5])
# Funkcja np.ones tworzy array skladajacy sie z samych 1
np.ones(16)
array([1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.])
# Funkcja np.ones tworzy array skladajacy sie z samych 0
np.zeros(5)
array([0., 0., 0., 0., 0.])
# Tworzenie pustej arrayi. Powinna dzialac najszybciej bo nie wymaga zmieniania wartosci liczb
# (wartosci sa wybierane na podstawie miejsca w pamieci gdzie sie znajduja)
np.empty(15, dtype=int)
array([      0,       0,       0,       0,       0,       0,       0,
             0,       0,       0,    1540,       0,       0, 7077999,
             0])
# Tworzenie macierzy jednostkowej
np.eye(3)
array([[1., 0., 0.],
       [0., 1., 0.],
       [0., 0., 1.]])
# Definiowanie array'a z zadanego zakresu o okreslonej dlugosci
np.linspace(start=0, stop=5, num=21)
array([0.  , 0.25, 0.5 , 0.75, 1.  , 1.25, 1.5 , 1.75, 2.  , 2.25, 2.5 ,
       2.75, 3.  , 3.25, 3.5 , 3.75, 4.  , 4.25, 4.5 , 4.75, 5.  ])
# Definiowanie arraya z zadanego zakresu o okreslonej roznicy miedzy wyrazami
# Zauwaz ze wyraz 5.25 nie zostal juz dopisany
np.arange(start=0, stop=5.25, step=0.25)
array([0.  , 0.25, 0.5 , 0.75, 1.  , 1.25, 1.5 , 1.75, 2.  , 2.25, 2.5 ,
       2.75, 3.  , 3.25, 3.5 , 3.75, 4.  , 4.25, 4.5 , 4.75, 5.  ])
# Jesli nie poda sie parametru stop to array budowany jest od 0 do podanej liczby z zadanym krokiem
np.arange(5.25, step=0.25)
array([0.  , 0.25, 0.5 , 0.75, 1.  , 1.25, 1.5 , 1.75, 2.  , 2.25, 2.5 ,
       2.75, 3.  , 3.25, 3.5 , 3.75, 4.  , 4.25, 4.5 , 4.75, 5.  ])
# Mozna zdefiniowac typ danych przechowywanych w array'u
# Zwroc uwage ze nie definiujac parametru step jest on zdefiniowany jako 1
np.arange(10, dtype=complex)
array([0.+0.j, 1.+0.j, 2.+0.j, 3.+0.j, 4.+0.j, 5.+0.j, 6.+0.j, 7.+0.j,
       8.+0.j, 9.+0.j])

Filtrowanie elementow

# Chcac stworzyc dwuwymiarowy array tworzymy array od zagniezdzonych list
np_2d = np.array([[1.1, 5, 3],
                  [4, 5, 6]])
np_2d
array([[1.1, 5. , 3. ],
       [4. , 5. , 6. ]])
# Wybieranie pierwszego wiersza
np_2d[0]
array([1.1, 5. , 3. ])
# Wybieranie drugiej kolumny
np_2d[:, 1]
array([5., 5.])
# Wybranie wszystkich kolumn dla pierwszego wiersza (od 0 do 1 (bez 1))
np_2d[0:1, :]
array([[1.1, 5. , 3. ]])
# pierwsza i druga kolumna od drugiego wiersza wlacznie
np_2d[1:,[0,1]]
array([[4., 5.]])
# Wybranie elementow spelniajacych okreslony warunek
np_2d[np_2d<5]
array([1.1, 3. , 4. ])
# Czy ktorykolwiek element array'a jest rowny 5
np.any(np_2d==5)
True
# Czy wszystkie elementy array'a sa rowne 5
np.all(np_2d==5)
False
np_2d
array([[1.1, 5. , 3. ],
       [4. , 5. , 6. ]])
np_2d[np.nonzero(np_2d)]
array([1.1, 5. , 3. , 4. , 5. , 6. ])
np_2d[np.where(np_2d<5)]
array([1.1, 3. , 4. ])

Wywoływanie funkcji opisujących array

W celu lepszego poznania obiektu, z którym pracujemy warto wywołać podstawowe funkcje mówiące nam o ilości danych, liczbie wymiarów, itp.

funkcja

opis

ndim

zwraca liczbę wymiarów array’a

shape

zwraca wymiar array’a

size

zwraca liczbę elementów w array’u

dtype

określenie typu danych w array’u

unique

zwraca unikalne elementy w array’u

# Utworzmy 16elementowy array. Zauwaz, ze w pakiecie numpy mamy zdefiniowana liczbe pi.
pi_array = np.linspace(start=-2*np.pi, stop=2*np.pi, num=16)
pi_array
array([-6.28318531, -5.44542727, -4.60766923, -3.76991118, -2.93215314,
       -2.0943951 , -1.25663706, -0.41887902,  0.41887902,  1.25663706,
        2.0943951 ,  2.93215314,  3.76991118,  4.60766923,  5.44542727,
        6.28318531])
pi_array.ndim
1
# Sprawdzanie wymiaru arraya
pi_array.shape
(16,)
# Sprawdzanie liczby elementow w arrayu
pi_array.size
16
pi_array.dtype.name
'float64'
np.unique(pi_array)
array([-6.28318531, -5.44542727, -4.60766923, -3.76991118, -2.93215314,
       -2.0943951 , -1.25663706, -0.41887902,  0.41887902,  1.25663706,
        2.0943951 ,  2.93215314,  3.76991118,  4.60766923,  5.44542727,
        6.28318531])

Wywoływanie określonych operacji matematycznych na arrayach

funkcja

opis

reshape

zmienia wymiar array’a

ravel

spłaszczanie array’a

T

transponuje array

@ lub dot

mnożenie macierzowe arrayi

* , / , +,-

mnożenie/dzielenie/dodawanie/odejmowanie poszczególnych elementów między sobą

sin (lub inne funkcje trygonometryczne)/exp/sqrt/log

działanie na array odpowiednia funkcja

Stałe matematyczne:

funkcja

stała

np.pi

liczba pi

np.e

liczba e (zobacz także funkcję np.exp())

np.inf

nieskonczoność

np.nan

not a number

np.ninf

minus nieskonczoność

np.log()

funkcja logarytmiczna, np np.log(0)==-inf

# Wywolywanie funkcji na arrayu. Zauwaz, ze w pakiecie numpy wystepuja funkcje trygonometryczne
np.sin(pi_array)
array([ 2.44929360e-16,  7.43144825e-01,  9.94521895e-01,  5.87785252e-01,
       -2.07911691e-01, -8.66025404e-01, -9.51056516e-01, -4.06736643e-01,
        4.06736643e-01,  9.51056516e-01,  8.66025404e-01,  2.07911691e-01,
       -5.87785252e-01, -9.94521895e-01, -7.43144825e-01, -2.44929360e-16])
np.arcsin(np.sin(pi_array))
array([ 2.44929360e-16,  8.37758041e-01,  1.46607657e+00,  6.28318531e-01,
       -2.09439510e-01, -1.04719755e+00, -1.25663706e+00, -4.18879020e-01,
        4.18879020e-01,  1.25663706e+00,  1.04719755e+00,  2.09439510e-01,
       -6.28318531e-01, -1.46607657e+00, -8.37758041e-01, -2.44929360e-16])
# Funkcja reshape przeksztalca wymiar array'a do zadanego
# W tym przypadku array o wymiarze (16,) przeksztalcamy na array o wymiarze 4,4
A = np.ones(16).reshape(4,4)
A
array([[1., 1., 1., 1.],
       [1., 1., 1., 1.],
       [1., 1., 1., 1.],
       [1., 1., 1., 1.]])
B = np.arange(16).reshape(4,4)
B
array([[ 0,  1,  2,  3],
       [ 4,  5,  6,  7],
       [ 8,  9, 10, 11],
       [12, 13, 14, 15]])
# Funkcja splaszczajaca (zauwaz ze B nie zostalo nadpisane)
B.ravel()
array([ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14, 15])
B.ndim
2
# Porownywanie poszczegolnych elementow w arrayach
A==B
array([[False,  True, False, False],
       [False, False, False, False],
       [False, False, False, False],
       [False, False, False, False]])
# Mnozenie odpowiadajacych sobie wyrazow
A*B
array([[ 0.,  1.,  2.,  3.],
       [ 4.,  5.,  6.,  7.],
       [ 8.,  9., 10., 11.],
       [12., 13., 14., 15.]])
# Mnozenie macierzowe
A.dot(B)
array([[24., 28., 32., 36.],
       [24., 28., 32., 36.],
       [24., 28., 32., 36.],
       [24., 28., 32., 36.]])
# Inny sposob mnozenia macierzowego
A@B
array([[24., 28., 32., 36.],
       [24., 28., 32., 36.],
       [24., 28., 32., 36.],
       [24., 28., 32., 36.]])
# Transponowanie
B.T
array([[ 0,  4,  8, 12],
       [ 1,  5,  9, 13],
       [ 2,  6, 10, 14],
       [ 3,  7, 11, 15]])
# Funkcja e^x. Chac uzyskac liczbe e wystarczy podac np.exp(1)
np.exp(B)
array([[1.00000000e+00, 2.71828183e+00, 7.38905610e+00, 2.00855369e+01],
       [5.45981500e+01, 1.48413159e+02, 4.03428793e+02, 1.09663316e+03],
       [2.98095799e+03, 8.10308393e+03, 2.20264658e+04, 5.98741417e+04],
       [1.62754791e+05, 4.42413392e+05, 1.20260428e+06, 3.26901737e+06]])
# Pierwiastkowanie
np.sqrt(B)
array([[0.        , 1.        , 1.41421356, 1.73205081],
       [2.        , 2.23606798, 2.44948974, 2.64575131],
       [2.82842712, 3.        , 3.16227766, 3.31662479],
       [3.46410162, 3.60555128, 3.74165739, 3.87298335]])

Sortowanie array’a

# Stworzenie arrayi z 10 losowych liczb calkowitych z przedzialu [1,10]
to_sort = np.random.randint(1, 10, 10)
to_sort
array([4, 3, 9, 1, 3, 7, 7, 5, 9, 2])
np.sort(to_sort)
array([1, 2, 3, 3, 4, 5, 7, 7, 9, 9])

Sortowanie malejaco

-np.sort(-to_sort)
array([9, 9, 7, 7, 5, 4, 3, 3, 2, 1])
# Funckja flip odwraca kolejnosc elementow w arrayi
np.flip(np.sort(to_sort))
array([9, 9, 7, 7, 5, 4, 3, 3, 2, 1])

Łączenie i dzielenie array’i, dodawanie i usuwanie elementów

funkcja

opis

vstack/hstack

wierszowe/kolumnowe skladanie dwoch arrayi

vspli/hsplit

wierszowe/kolumnowe rozdzielanie dwoch arrayi

append

dodawanie elementu na koniec arrayi

insert

umieszczanie nowego elementu w arrayi w okreslonym miejscu

delete

usuwanie elementow arrayi

# Wertykalne laczanie arrayi
np.vstack((A, B))
array([[ 1.,  1.,  1.,  1.],
       [ 1.,  1.,  1.,  1.],
       [ 1.,  1.,  1.,  1.],
       [ 1.,  1.,  1.,  1.],
       [ 0.,  1.,  2.,  3.],
       [ 4.,  5.,  6.,  7.],
       [ 8.,  9., 10., 11.],
       [12., 13., 14., 15.]])
# Horyzontalne laczanie array'i
np.hstack((A, B))
array([[ 1.,  1.,  1.,  1.,  0.,  1.,  2.,  3.],
       [ 1.,  1.,  1.,  1.,  4.,  5.,  6.,  7.],
       [ 1.,  1.,  1.,  1.,  8.,  9., 10., 11.],
       [ 1.,  1.,  1.,  1., 12., 13., 14., 15.]])
# Podzial arrayi B na dwie mniejsze
np.hsplit(B, 2)
[array([[ 0,  1],
        [ 4,  5],
        [ 8,  9],
        [12, 13]]),
 array([[ 2,  3],
        [ 6,  7],
        [10, 11],
        [14, 15]])]
# Dodawanie liczby do arraya
np.append(to_sort, 10)
array([ 4,  3,  9,  1,  3,  7,  7,  5,  9,  2, 10])
# Dodawanie listy do arraya
np.append(to_sort, [10, 15, 13])
array([ 4,  3,  9,  1,  3,  7,  7,  5,  9,  2, 10, 15, 13])
# Dodawanie arraya do arraya
np.append(to_sort, np.array([10, 15, 13]))
array([ 4,  3,  9,  1,  3,  7,  7,  5,  9,  2, 10, 15, 13])
# Umieszczenie na 5 miejscu w arrayu listy
np.insert(to_sort, 4, [1, 23])
array([ 4,  3,  9,  1,  1, 23,  3,  7,  7,  5,  9,  2])
# Usuniecie drugiej kolumny w arrayu
np.delete(B, 1, axis=1)
array([[ 0,  2,  3],
       [ 4,  6,  7],
       [ 8, 10, 11],
       [12, 14, 15]])
# Usuniecie drugiego i trzecicego wiersza
np.delete(B, [1, 2], axis=0)
array([[ 0,  1,  2,  3],
       [12, 13, 14, 15]])

Podstawowe statystyki

funkcja

opis

sum

zsumowanie elementow

max

element maksymalny

min

element minimalny

mean

srednia

sum

zsumowanie elementow

std

odchylenie standardowe

var

wariancja

cov

macierz kowariancji

cumsum

suma skumulowana

argmax

zwraca indeks najwiekszego elementu

argmin

zwraca indeks najmniejszego elementu

# Zsumowanie wszystkich elementow
B.sum()
120
# Suma poszczegolnych kolumn
B.sum(axis=0)
array([24, 28, 32, 36])
# Suma poszczegolnych wierszy
B.sum(axis=1)
array([ 6, 22, 38, 54])
np.cumsum(B)
array([  0,   1,   3,   6,  10,  15,  21,  28,  36,  45,  55,  66,  78,
        91, 105, 120], dtype=int32)
to_sort
array([4, 3, 9, 1, 3, 7, 7, 5, 9, 2])
np.argmax(to_sort)
2

Zdjęcie jako wielowymiarowy array

# Wczytajmy przykladowe zdjecie i sprawdzmy jego typ
from scipy import misc
raccoon = misc.face()
type(raccoon)
numpy.ndarray
Info!

Wczytując kolorowe zdjęcie (kolory w RGB) otrzymamy array wielkości (X, Y, 3). X odpowiada za wysokość obrazu (liczba wierszy w array’i), Y odpowiada za szerokość (liczba kolumn w array’i), a 3 odpowiada za składowe RGB.

# Sprawdzmy liczbe wymiarow
raccoon.ndim
3
# Sprawdzmy rozmiary (zdjecie ma rozmiar 768x1024 pixeli)
raccoon.shape
(768, 1024, 3)
# Liczba elementow (768*1024*3)
raccoon.size
2359296
raccoon.dtype
dtype('uint8')
# Wyswietlenie pierwszego pixela
raccoon[0][0]
array([121, 112, 131], dtype=uint8)
# Wybranie drugiego wiersza.
# Analogicznie: raccoon[1, :, :] lub raccoon[1]
raccoon[1, ...]  
array([[ 89,  82, 100],
       [110, 103, 121],
       [130, 122, 143],
       ...,
       [118, 125,  71],
       [134, 141,  87],
       [146, 153,  99]], dtype=uint8)
# Iterowanie po pikselach
for column in raccoon[:5]:
    for rgb in column[:5]:
        print(rgb)
[121 112 131]
[138 129 148]
[153 144 165]
[155 146 167]
[155 146 167]
[ 89  82 100]
[110 103 121]
[130 122 143]
[137 129 150]
[141 133 154]
[73 66 84]
[ 94  87 105]
[115 108 126]
[123 115 136]
[127 119 140]
[81 77 94]
[ 97  93 110]
[113 109 126]
[120 115 135]
[125 120 140]
[103  99 114]
[113 109 126]
[123 119 136]
[132 127 147]
[142 137 157]
# Sprawdzenie poprawnosci petli (wyciagniecie 5 pierwszych wierszy i 5 pierwszych kolumn)
raccoon[:5,:5]
array([[[121, 112, 131],
        [138, 129, 148],
        [153, 144, 165],
        [155, 146, 167],
        [155, 146, 167]],

       [[ 89,  82, 100],
        [110, 103, 121],
        [130, 122, 143],
        [137, 129, 150],
        [141, 133, 154]],

       [[ 73,  66,  84],
        [ 94,  87, 105],
        [115, 108, 126],
        [123, 115, 136],
        [127, 119, 140]],

       [[ 81,  77,  94],
        [ 97,  93, 110],
        [113, 109, 126],
        [120, 115, 135],
        [125, 120, 140]],

       [[103,  99, 114],
        [113, 109, 126],
        [123, 119, 136],
        [132, 127, 147],
        [142, 137, 157]]], dtype=uint8)
# Wyswietlenie arraya przy pomocy pakietu matplotlib
# Na jednym z kolejnych zajec bedzie wiecej o tym pakiecie i nie tylko :)
import matplotlib.pyplot as plt
plt.imshow(raccoon)
<matplotlib.image.AxesImage at 0x1e65cbe5cd0>
../_images/2_Tutorial numpy_100_1.png
# Znormalizowanie RGB (zmiana z przedzialu [0, 255] na [0, 1])
raccoon = raccoon / 255
# Chac uzyskac zdjecie w skali szarosci trzeba pomnozyc macierzowo nasza macierz przez podana ponizej
raccoon_gray = raccoon @ [0.2126, 0.7152, 0.0722]
raccoon_gray.shape
(768, 1024)
plt.imshow(raccoon_gray, cmap="gray")
<matplotlib.image.AxesImage at 0x1e65e750c10>
../_images/2_Tutorial numpy_104_1.png

Pandas DataFrame a Numpy Array

Chcąc wykonywać obliczenia szybciej warto zamienić DataFrame na Array. Wtedy każdy wiersz arraya będzie odpowiadać za każdy wiersz ramki danych.

import pandas as pd
df = pd.read_csv(filepath_or_buffer=r'mpg.csv', # sciezka do pliku
                sep=',', # separator
                header=0, # naglowek (nazwy kolumn)
                index_col=0 # kolumna z indeksem
                )
df.head()
cylinders displacement horsepower weight acceleration model_year origin name
mpg
18.0 8 307.0 130 3504 12.0 70 1 chevrolet chevelle malibu
15.0 8 350.0 165 3693 11.5 70 1 buick skylark 320
18.0 8 318.0 150 3436 11.0 70 1 plymouth satellite
16.0 8 304.0 150 3433 12.0 70 1 amc rebel sst
17.0 8 302.0 140 3449 10.5 70 1 ford torino
df_array = df.to_numpy()
df_array[:5]
array([[8, 307.0, '130', 3504, 12.0, 70, 1, 'chevrolet chevelle malibu'],
       [8, 350.0, '165', 3693, 11.5, 70, 1, 'buick skylark 320'],
       [8, 318.0, '150', 3436, 11.0, 70, 1, 'plymouth satellite'],
       [8, 304.0, '150', 3433, 12.0, 70, 1, 'amc rebel sst'],
       [8, 302.0, '140', 3449, 10.5, 70, 1, 'ford torino']], dtype=object)
df.shape
(398, 8)
df_array.shape
(398, 8)

Porównajmy czas wykonania działań na trzech kolumnach

from time import time
start = time()
fun = lambda row:row['cylinders']*row['weight']**row['acceleration']
df.apply(fun, axis=1)
print(f'Czas wykonania: {time()-start}')
Czas wykonania: 0.018826961517333984
start = time()
df_array[:,1]*df_array[:,4]**df_array[:,5]
print(f'Czas wykonania: {time()-start}')
Czas wykonania: 0.0

Jak widać już na niewielkim zbiorze danych proste obliczenia na arrayach wykonują się szybciej. Warto o tym pamiętać i to wykorzystywać.

Podsumowanie

Jak widać NumPy jest pakietem, ktory zdecydowanie potrafi ułatwić życie. Jest często wykorzystywany do złożonych obliczeń. Ma zastosowania przy pracy z różnymi danymi - obrazami, ramkami danych, czy po prostu dużymi listami.