Dodatek: NumPy¶
na podstawie materiałów Matthew Terry z SoftwareCarpentry
Wszystkie poniższe przykłady zakładają posiadanie zainstalowanej biblioteki numpy
.
Zakładamy że polecenie
import numpy
jest już wykonane.
Podstawy NumPy¶
Biblioteka NumPy
¶
NumPy
jest modułem implementującym dodatkowe typy danych - głównie numeryczne.
Implementacja jest bardzo wydajna (napisana w języku C).
Dodatkowe typy danych podobne są do zwykłych tablic (ale posiadają o wiele więcej możliwości).
Tworzenie tablic Numpy¶
Najprostszą techniką jest wysłanie sekwencji do konstruktora numpy.array
.
Dodatkowo możemy podać typ danych jeśli domyślnie przyjęty będzie miał np. niewystarczającą dokładność.
>>> A = numpy.array([1, 2.3, 4])
>>> A.dtype
dtype('float64')
>>> B= numpy.array([1, 2.3, 4], dtype int)
>>> B.dtype
dtype('int32')
Ten sposób tworzenia jest dosyć powolny, więc lepiej ręcznie stworzyć tablicę o pożądanych rozmiarach. Można w tym celu użyć funkcji: numpy.identity
, numpy.zeros
, numpy.zeros_like
.
numpy.ndarray((2, 3, 4), dtype=complex)
# new 2x3x4 array of complex numbers
W wielu przykładach będziemy stosować funkcję numpy.arange
, która działa podobnie do wbudowanej funkcji range
.
>>> numpy.arange(2, 5)
array([2, 3, 4])
>>> numpy.arange(1, 5, 2)
array([1, 3])
>>> numpy.arange(1, 10, 2)
array([1, 3, 5, 7, 9])
Typy danych¶
Jednym z parametrów podawanych podczas tworzenia tablic NumPy jest tzw. dtype (data type). W obecnej wersji NumPy obsługuje 21 typów danych. Oprócz typu możemy podać jeszcze tzw. byte order (little- lub big- endian), lub kilka typów aby utworzyć typ podobny do krotki (podobną do struktury w języku C).
Podstawowe typy danych to:
int |
Python-compatible int (zwykle jest to C long) |
intc |
C int |
float |
Python-compatible float (zwykle jest to C double) |
single |
C float |
double |
C double |
complex |
Python-compatible complex |
Operacje na tablicach¶
Dostęp do tablic NumPy odbywa się podobnie jak do zwykły list Pythona.
A więc można używać nawiasów kwadratowych aby uzyskać dostęp do pojedynczych elementów, funkcji len()
, itd.
>>> A = numpy.arange(5)
>>> A
array([0, 1, 2, 3, 4])
>>> A[3]
3
>>> A[3] = 42
>>> A
array([ 0, 1, 2, 42, 4])
>>> len(A)
5
Proste działania¶
Większość podstawowych działań matematycznych została zaimplementowana dla tablic NumPy. Operacje te są elementowe (element-wise).
>>> A = numpy.arange(5)
>>> B = numpy.arange(5, 10)
>>> A
array([0, 1, 2, 3, 4])
>>> B
array([5, 6, 7, 8, 9])
>>> A+B
array([ 5, 7, 9, 11, 13])
>>> B-A
array([5, 5, 5, 5, 5])
>>> A*B
array([ 0, 6, 14, 24, 36])
Jeśli w dodawaniu jednym z elementów działania będzie skalar, to jego wartość zostanie dodana do każdego elementu macierzy.
>>> A = numpy.arange(5)
>>> 2*A
array([0, 2, 4, 6, 8])
>>> A**2
array([ 0, 1, 4, 9, 16])
>>> A+10
array([10, 11, 12, 13, 14])
Porównywanie¶
Tak jak podstawowe działania arytmetyczne, porównywanie jest również elementowe.
W rezultacie porównania otrzymujemy tablicę wartości logicznych, jeśli rozmiary porównywanych tablic są identyczne. W innym przypadku dostaniemy wartość False
.
>>> A = numpy.array([1, 2, 3, 4, 5])
>>> B = numpy.array([1, 1, 3, 3, 5])
>>> A == B
array([ True, False, True, False, True], dtype=bool)
Aby otrzymać pojedynczą wartość logiczną, należy użyć metod .any()
, lub .all()
, które odpowiednio podają czy jakakolwiek, lub każda wartość w tablicy to True
.
Zaawansowane indeksowanie¶
Poza zwykłymi metodami indeksowania za pomocą liczb całkowitych i dwukropka, NumPy pozwala na różne inne sposoby dostępu do tablic.
Indeksowanie wielowymiarowe¶
W przeciwieństwie do zwykłych list i krotek, tablice NumPy mogą być wielowymiarowe. Aby dostać się do elementu w takiej tablicy, należy podać rozdzieloną przecinkami listę indeksów.
Poza tym, aby np. dostać elementy w pierwszym rzędzie takiej tablicy, w NumPy wystarczy:
>>> A = numpy.arange(16).reshape(4, 4)
>>> A
array([[ 0, 1, 2, 3],
[ 4, 5, 6, 7],
[ 8, 9, 10, 11],
[12, 13, 14, 15]])
>>> A[1]
array([4, 5, 6, 7])
A co z dostępem do pierwszej kolumny?. Należy użyć rozbudowanych wycinków (slices), jak w poniższym przykładzie:
>>> A[:, 1]
array([ 1, 5, 9, 13])
Z rosnącą liczbą wymiarów rośnie stopień komplikacji składni wycinków.
Indeksowanie przy pomocy tablic¶
Tablice mogą być indeksowane przy pomocy innych tablic, zarówno przy pomocy tablic indeksów, jak i tablic wartości logicznych (boolean) o tej samej długości.
NumPy zwraca widok (view) tablicy dla danych wskaźników jaką nową tablicę.
W przypadku tablic logicznych zwracany widok zawiera elementy dla których tablica logiczna miała wartość True
.
Indeksowanie przy pomocy tablic:
>>> A = numpy.arange(5, 10)
>>> A
array([5, 6, 7, 8, 9])
>>> A[[0, 2, 3]]
array([5, 7, 8])
>>> A[[0, 2, 3]] = 0
>>> A
array([0, 6, 0, 0, 9])
Indeksowanie przy pomocy tablic wartości logicznych:
>>> import random
>>> A = numpy.array([random.randint(0, 10) for i in range(10)])
>>> A
array([10, 5, 1, 2, 3, 9, 3, 4, 9, 8])
>>> A[A>5] = 5
>>> A
array([5, 5, 1, 2, 3, 5, 3, 4, 5, 5])
Pułapki¶
NumPy wprowadza pewnie bardzo ważne różnice w traktowaniu tablic. Użytkownicy list i krotek mogą być tym zdezorientowani. Poniżej przedstawiono najważniejsze różnice.
Mnożenie i dzielenie¶
Mnożenie i dzielenie przez skalar są działaniami elementowymi. W listach operator mnożenia powoduje powtórzenie listy n razy.
>>> numpy.arange(5)*2
array([0, 2, 4, 6, 8])
>>> range(5)*2
[0, 1, 2, 3, 4, 0, 1, 2, 3, 4]
Podobna różnica dotyczy operatora dodawania.
>>> numpy.arange(5) + numpy.arange(5)
array([0, 2, 4, 6, 8])
>>> range(5) + range(5)
[0, 1, 2, 3, 4, 0, 1, 2, 3, 4]
Różnice między widokami (views) a kopiami (copies)¶
Z uwagi na wymaganą wydajność, biblioteka NumPy używa widoków zamiast kopii tam gdzie tylko to możliwe. Oznacza to że tablica powstała z innej tablicy w iększości przypadków będzie odnosić się do tych samych danych co tablica macierzysta. Konsekwencją tego faktu jest możliwość niechcianej modyfikacji pierwotnej tablicy poprzez tablicę pochodną - jak w poprzednim przykładzie.
W szczególności slices (przekroje) są zawsze widokami - w przeciwieństwie do przekrojów zwykłych list w Pythonie.
>>> A = numpy.arange(5)
>>> B = A[0:1]
>>> B[0] = 42
>>> A
array([42, 1, 2, 3, 4])
>>> >>> A = range(5)
>>> B = A[0:1]
>>> B[0] = 42
>>> A
[0, 1, 2, 3, 4]
Funkcje matematyczne¶
NumPy zostało zaprojektowane do obliczeń naukowych, zawiera więc również wiele funkcji matematycznych, włączając funkcje z zakresu algebry liniowej, transformat Fouriera, oraz funkcji statystycznych i probabilistycznych. Pełnego opisu możliwości należy szukać w dokumentacji, poniżej przedstawione zostaną wybranie, podstawowe zagadnienia.
Podstawy¶
Wszystkie tablice mają wbudowane podstawowe operacje działające na poszczególnych osiach tablic. W poniższych przykładach używane są tylko tablice jednowymiarowe.
>>> import random
>>> A = numpy.array([random.randint(0, 10) for i in range(10)])
>>> A
array([6, 9, 9, 4, 9, 8, 7, 9, 0, 3])
>>> A.min()
0
>>> A.max()
9
>>> A.mean()
6.4000000000000004
>>> A.std() # standard deviation
2.9732137494637012
>>> A.sum()
64
Dla tablic dwu- i więcej- wymiarowych pojawiają się dodatkowe operacje:
>>> A = numpy.arange(16).reshape(4, 4)
>>> A
array([[ 0, 1, 2, 3],
[ 4, 5, 6, 7],
[ 8, 9, 10, 11],
[12, 13, 14, 15]])
>>> A.T # transpose
array([[ 0, 4, 8, 12],
[ 1, 5, 9, 13],
[ 2, 6, 10, 14],
[ 3, 7, 11, 15]])
>>> A.trace()
30
Macierze¶
Dotychczas używaliśmy dwuwymiarowych tablic aby utworzyć obiekty-macierze.
Biblioteka NumPy dostarcza w tym celu wyspecjalizowanej klasy.
Klasa matrix
(macierz) zachowuje się tak jak dwuwymiarowa tablica NumPy, ale wprowadzone zostały pewne zmiany w interfejsie klasy.
Zmiany te mają na celu uproszczenie operacji z zakresu algebry linowej.
Te zmiany to:
operator
*
działa jak mnożenie macierzyoperator
**
działa jak podnoszenie macierzy do potęgiwłaściwość
.I
(metoda.getI()
) zwraca macierz odwróconąwłaściwość
.H
(metodagetH()
) zwraca sprzężenie hermitowskie macierzy
Przykład - rozwiązanie układu równań linowych:
>>> import numpy.linalg
>>> A = numpy.matrix([[3, 2, -1], [2, -2, 4], [-1, .5, -1]])
>>> B = numpy.array([1, -2, 0])
>>> numpy.linalg.solve(A, B)
array([ 1., -2., -2.])
Funkcje uniwersalne¶
Funkcje uniwersalne (universal functions a.k.a. „ufuncs”) to szybkie, działające element po elemencie operacje na tablicach NumPy. To właśnie one pozwalają na efektywne wykorzystywanie tablic. Istnieje bardzo duża ilość istniejących funkcji uniwersalnych, realizujących większość podstawowych operacji, takich jak dodawanie, odejmowanie, logarytmowanie itd. Ich wywołanie jest bardzo proste:
>>> A = numpy.arange(1,10)
>>> numpy.log10(A)
array([ 0. , 0.30103 , 0.47712125, 0.60205999, 0.69897 ,
0.77815125, 0.84509804, 0.90308999, 0.95424251])
Oczywiście istnieją bardziej skomplikowane funkcje, działające np. na dwóch macierzach.