# setup
from IPython.core.interactiveshell import InteractiveShell
InteractiveShell.ast_node_interactivity = "all"
Język python dzięki wielu dodatkowym bibliotekom stał się potężnym narzędziem do obliczeń numerycznych, analizy i eksploracji danych, wizualizacji itp. Do tej grupy w pierwszej kolejności wchodzą:
W ramach tej części kursu zapoznamy się z podstawami działania pakietów numpy, scipy i matplotlib. Pozostałe pakiety pojawią si ę w kolejnych wersjach kursu.
Jest to podstawowy pakiet do obliczeń numerycznych koncentrujący się na zarządzaniu macierzami dwuwymiarowymi. Podstawową zaletą numpy jest silna typiczność - to znaczy że tablice numpy mają z góry określony typ danych.
Tablice tworzy się najczęściej na bazie list zawierających dane numeryczne, lub bezpośrednio wczytując wartości binarne.
import numpy as np
# utworzenie tablicy, typ rozpoznany z danych
arr = np.array([1,2,3,4,5,6])
arr
arr.dtype
# utworzenie tablicy z wymuszonym typem danych
arr = np.array([1,2,3,4,5,6],dtype='int8')
arr
arr.dtype
# utworzenie tablicy z wymuszonym typem danych zmiennoprzecinkowych
arr = np.array([1,2,3,4,5,6],dtype='float32')
arr
arr.dtype
Istnieje wiele funkcji pozwalających na definiowanie tablic na podstawie algorytmów
# utworzenie tablicy o określonym zasięgu wartości i kroku
np.arange(10)
np.arange(10,30,2)
# utworzenie tablicy o określonym zasięgu wartości i liczbie elementów
np.linspace(10,20,3)
# utworzenie tablicy wypełnionej określoną liczbą (0,1,NaN, dowolna)
np.zeros(10)
np.full(10,np.nan)
np.full(10,3,dtype='int8') # wymuszenie typu
# pusta tablica, zawiera losowe wartości
np.empty(10,dtype='int16')
Można również generować tablice liczb losowych. Więcej na https://docs.scipy.org/doc/numpy-1.14.0/reference/routines.random.html
np.random.random((5,2))
np.random.randint(10,40,(3,4)) # w przedziale
Aby zmienić wymiary macierzy należy zmodyfikować jej atrybut shape (to nie jest funkcja, tylko atrybut!), przy pomocy krotki zawierającej tyle wymiarów ile ma liczyć nowa wersja tablicy. W przypadku tablicy dwuwymiarowej jako pierwszy podaje się liczbę wierszy a jako drugi liczbę kolumn.
arr = np.arange(12)
arr.shape = (3,4) # krotka definiująca tablicę dwuwymiarową
arr
arr.ndim
arr.data # miejsce gdzie znajdują się dnae
Można też utworzyć macierz bezwymiarową lub z jednym wymiarem zerowym. Takie macierze są przydatne jeżeli zamierzamy je rozszerzać w kolejnych krokach. Dla przykładu utworzymy macierz która ma 10 kolumn i 0 wierszy:
barr = np.empty((0,10))
barr
Tablice numpy pozwalają na stosowanie rachunku wektorowego, tj modyfikacji każdego elementu tablicy bez konieczności stosowania pętli. Dzięki temu operacje na tablicach wykonuje się tak jak na pojedynczych wartościach skalarnych
arr = np.arange(15)
arr.shape = (3,5)
arr
# mnożenie przez skalar
arr*3
# dodanie dwóch macierzy
narr = np.arange(2,17)
narr.shape = (3,5)
print("B przed dodaniem")
narr
print("A+B po dodaniu")
arr+narr
Transpozycja albo inaczej przestawianie macierzy to operacja obrotu wymiarów, tak, że wiersze tablicy stają się kolumnami a kolumny wierszami. Jest to jedna z najważniejszych operacji macierzowych, stosuje się ją wszędzie tam gdzie są ściśle zdefiniowane wymogi co do roli wierszy i kolumn. Do transpozycji macierzy służy funkcja transpose()
, która pozwala dodatkowo wybrać osie transpozycji natomiast w praktyce stosuje się atrybut T, do wykonania prostego przekształcenia w dwóch wymiarach.
# transpozycja macierzy (obrót wymiarów)
arr
tarr=arr.transpose()
tarr
ttarr=arr.T
ttarr
Mnożenie macierzy przez skalar jest proste do zrozumienia, o tyle mnożenie macierzy przez wektor jest mniej zrozumiałe i polega na przemnożeniu pól wektora przez kolumny. Może się okazać że niezbędne jest wcześniejsze odwrócenie macierzy, tak aby liczba kolumn była zgodna z długością wektora.
# mnożenie macierzy przez wektor
tarr.shape
vect = np.array([10,20,30]) # wektor musi mieć długość 3
np.multiply(tarr,vect)
Niektóre operacje mają charakter metod obiektu - najczęściej wtedy gdy nie są symetryczne. Na przykład przecięcie (dot product) może być zastosowane tylko do macierzy przyjmując wektor jako argument. Przecięcie macierzy i wektora to suma wszystkich wierszy macierzy przemnożonej przez wektor.
# przecięcie wektora z macierzą (dot product)
tarr.dot(vect)
Argument axis jest bardzo ważny w operacjach macierzowych jeżeli operacje wektorowe mają być wykonywane wzdłóż tylko jednej osi (wymiaru). Kontynuując poprzedni przykład poznamy dwie nowe możliwości operacji numpy. W poniższym przykładzie złożyliśmy wykonanie dwóch funkcji: przemnożenia macierzy i wektora i wynik tego przemnożenia - też macierz - zostanie zsumowany wzdłuż kolumn - dlatego używamy wymiaru 1. Sumowanie wzdłóż wierszy byłby to wymiar 0.
W przypadku, gdy nie podamy żadnej osi, należy sprawdzić jak dana funkcja/metoda działa. Niektóre przyjmują jako domyślą oś 0, inne (w tym sum()
) wykonują działanie wzdłuż wszystkich osi - a dokładnie na wersji spłaszczonej.
np.multiply(tarr,vect).sum(axis=1)
np.multiply(tarr,vect).sum(axis=0)
np.multiply(tarr,vect).sum()
Macierz to tylko sposób uporządkowania wartości zdefiniowany przez wymiary. W praktyce jest to ciąg liczb. Każdą macierz można spłaszczyć funkcją flatten()
do takiego ciągu. W zależności czy macierz poddamy wcześniej transpozycji kolejność wartości w spłaszczonym wektorze będzie różna. Spłaszczona macierz posiada tylko jeden wymiar.
tarr.flatten()
tarr.T.flatten()
tarr.T.flatten().shape=(5,3)
tarr
tarr.flatten().shape
Do łączenia macierzy służy grupa funkcji np.*stack
. Do łączenia wzdłóż osi pionowej vstack
, poziomej hstack
na stosie wzdłuż nowego wskazanego wymiaru stack
.
arr = np.arange(15)
arr.shape = (3,5)
arr
np.vstack((arr,arr))
np.hstack((arr,arr))
np.stack((arr,arr))
Funkcjami odwrotnymi dzielącymi macierze wzdłuż określonego wymiaru są odpowiednie funkcje split
oraz vsplit
i hsplit
np.split(arr,indices_or_sections=3)
np.split(arr,indices_or_sections=5,axis=1)
arr = np.arange(15)
arr.shape = (3,5)
arr
Dostęp elementów macierzy definiujemy poprzez indeksy. Kolejność indeksów jest taka sama jak wymiarów tablicy. Indeksowanie rozpoczynamy od 0. Możemy również wyznaczać zasięgi indeksów. Jeżeli omijamy zasiegi indeksów, oznacza do że domyślnie jest to początek lub koniec zasięu. Taki fragment macierzy nazywamy wycinkiem
arr[1,3]
arr[1:3,2:4]
arr[0:2,0:3]
print('to samo co powyżej')
arr[:2,:3]
print('trzecia kolumna')
arr[:,1]
W tablicy możemy wybierać również wartości na podtawie testu logicznego. Test logiczny zwraca tablicę wartości True/False a następnie jeżeli taką tablicę zastosujemy jako selekor, ograniczymy liczęb zwracanych wartości tylko do tych które spełniają test logiczny
art = np.random.random(20)
art
art<0.5
art[art>0.7]
Stosując selekcję można modyfikować wybrane elementy a nawet całe zasięgi
arr[1,4]=100
arr
arr[1:3,4]=[200,300]
arr
W przypadku, gdy chcemy wyciąć nieciągły fragment macierzy na podstawie dowolnej tablicy numpy należy użyć funkcji np.ix_
do konwersji wymiarów, odpowiednio dla osi 0 i 1. W przeciwnym wypadku przycięcie nie będzie skuteczne.
arr = np.arange(100)
arr.shape = (10,10)
arr
selector=np.array([3,5,8])
arr[np.ix_(selector,selector)]
Dane tabelaryczne można wczytać bezpośrednio do tablicy
arr = np.loadtxt("dane.txt", dtype='uint8')
arr
Jest to bardzo rozbudowana biblioteka do wizualizacji danych numerycznych. Jest rozszerzeniem biblioteki numpy. Biblioteka ma wiele wbudowanych funkcji pozwalających na wykonanie praktycznie dowolnego wykresu oraz jego modyfikację. Niestety składania stosowana w matplotlib nie jest przejrzysta, i ma raczej charakter niskopoziomowy co oznacza że jest raczej przeznaczona dla programistów piszących narzędzia wizualizacyjne, niż do codziennego użytku.
Podstawowy wykres opiera się na wykorzystaniu dwóch tablic numpy. Polecenie plot utworzy linię na podstawie danych.
import matplotlib.pyplot as plt
from numpy.random import normal,rand
x = np.linspace(0,10,100)
y = np.exp(-x)
plt.plot(x,y)
Dla danych jednowymiarowych (zawierających tylko jeden wymiar) można utwoyć histogram poprzedz podział ciągłych danych na przedziały
x = normal(size=200)
plt.hist(x,bins=10)
plt.show()
Pobiera wspórzędne z dwóch kolumn i wyświetla je w postaci punktów o współrzędych zdefiniowanych przez punkty
x = rand(100)
x = rand(100)
plt.scatter(x,y)
plt.show()
Elementy wykresu można modyfikować. Wymaga to jednak wyjaśnienia na starcie kilku terminów:
Istnieje wiele innych elementów wykresu, o których można przeczytać: [https://matplotlib.org/examples/showcase/anatomy.html]
W poniższym przykładzie uzupełnimy powyższy wykres o kilka dodatkowych elementów:
plt.scatter(x,y)
plt.axis([-1, 2, -2, 3]) # zakres osi (xmin,xmax,ymin,ymax)
plt.xlabel('Oś X')
plt.ylabel('Oś Y')
plt.title('Wykres rozproszony z przesunietym zakresem osi')
Matplotlib pozwala również na tworzenie wykresów składających się z pod-wykresów (axes). W tym celu należy utworzyć Figure (tu: fig) zawierającą macierz podwykresów z jednym wierszem i dwoma kolumami. Parametr "sharey=True" oznacza, że oba wykresy dzielą zasięg osi y. Funkcja plt.subplots() zwraca obiekt Figure oraz macierz obietów Axes pod nazwą ax. Dostęp do poszczególnych pod-wykresów mamy przez indeksowanie. Dla pierwszego pod wykresu wykonamy histogram liczący 12 przedziałów typu schodkowego a dla drugiego wypełniony, czerwony o 8 przedziałach. Następnie dla każdego sub-wykresu ustawimy tytuły. Ustawianie parametrów dla sub-wykresów rozpoczyna się z reguły przedrostkiem set_
. W ostatnim kroku ustawimy tytuł główny sup(er)title dla całej figury, o określonym rozmiarze tekstu. W ostatniej części ustawimy szerokość Figure na 9 cali.
x = normal(size=200)
fig, ax = plt.subplots(1,2,sharey=True)
ax[0].hist(x,bins=12,histtype='step')
ax[1].hist(x,bins=8,color='#FF1100')
ax[0].set_title("wykres schodkowy")
ax[1].set_title("wykres pełny")
fig.suptitle("Dwa wykresy", fontsize=14)
fig.set_figwidth(9)