04 - Pakiet `NumPy` cz. 2
Typy wartości w tablicy
Tablice ndarray
przechowują dane określonego typu. Mogą to być m. in. liczby całkowite i zmiennoprzecinkowe, wartości boolowskie, łańcuchy znaków. Liczby całkowite (int
- ze znakiem, uint
- bez znaku) oraz zmiennoprzecinkowe (float
) mogą być przechowywane w kilku typach danych zajmujących różną ilość bitów. Na przykład int16
służący do przechowywania liczb całkowitych ze znakiem zajmuje 16 bitów. Oczywiście im więcej bitów, tym większy zakres liczb może być przechowywany w pamięci. Informację na jego temat można uzyskać odwołując się do obiektu dtype
:
import numpy as np
# Liczby zmiennoprzecinkowe
tab_1 = np.linspace(0, 20, 11)
print(tab_1)
print(tab_1.dtype)
[ 0. 2. 4. 6. 8. 10. 12. 14. 16. 18. 20.]
float64
# Liczby całkowite
tab_2 = np.arange(0, 10)
print(tab_2)
print(tab_2.dtype)
[0 1 2 3 4 5 6 7 8 9]
int64
# Tworzenie tablicy z wartościami boolowskimi,
# będącymi wynikami porównaia
tab_3 = tab_2 > 4
print(tab_3)
print(tab_3.dtype)
[False False False False False True True True True True]
bool
# Łańcuchy znaków
tab_4 = np.array(['neuron', 'akson', 'dendryt', 'synapsa'])
print(tab_4)
print(tab_4.dtype)
['neuron' 'akson' 'dendryt' 'synapsa']
<U7
U
oznacza, łańcuch znaków Unicode, w tym wypadku o długości 7 znaków (długość najdłuższego ciągu)
Przy tworzeniu tablic, Python stara się dobrać właściwy rodzaj typu danych na podstawie otrzymanych wartości, ale można także go określić:
tab_5 = np.array([0, 1, 2, 3, 4])
print(tab_5)
print(tab_5.dtype)
tab_6 = np.array([0, 1, 2, 3, 4], dtype=np.float64)
print(tab_6)
print(tab_6.dtype)
tab_7 = np.array([0, 1, 2, 3, 4], dtype=np.bool)
print(tab_7)
print(tab_7.dtype)
[0 1 2 3 4]
int64
[0. 1. 2. 3. 4.]
float64
[False True True True True]
bool
Jak widać w powyższych przypadkach dokonuje się konwersja do odpowiedniego typu danych. Można też ją przeprowadzić na gotowej tablicy:
tab_8 = np.array([0, 1, 2, 3, 4])
print(tab_8)
print(tab_8.dtype)
tab_9 = tab_8.astype(np.float16)
print(tab_9)
print(tab_9.dtype)
[0 1 2 3 4]
int64
[0. 1. 2. 3. 4.]
float16
,,Przekręcenie licznika''
W jednym z przykładów działań matematycznych z udziałem tablic (potęgowanie) pojawiły się niespodziewanie wartości ujemne. Wynikają one z efektu ,,przekręcenia licznika’’. Wykonaj poniższy kod:
import numpy as np
a = np.array([9223372036854775807])
print(a)
print(a.dtype)
a = a + 1
print(a)
print(a.dtype)
[9223372036854775807]
int64
[-9223372036854775808]
int64
Największą liczbą jaką można przechować używając typu int64
jest 9223372036854775807
. Zwiększenie jej o 1 powoduje, przyjmuje najniższą, możliwą wartość, podobnie jak po osiągnięciu maksymalnej wartości licznik samochodowy się ,,zeruje’’. Jak zatem sobie radzić, jeśli w tablicy chcemy przechować większe liczby? Jeśli wiemy, że wartości nie będą ujemne, można użyć typu uint64
, ale jak widać tu też istnieją limity:
a = np.array([9223372036854775807], dtype='uint64')
print(a)
print(a.dtype)
a = a + 10
print(a)
print(a.dtype)
a = a * 2
print(a)
print(a.dtype)
[9223372036854775807]
uint64
[9223372036854775817]
uint64
[18]
uint64
Jeśli chcemy przechować liczby całkowite przekraczające limit int64/uint64
to można ustawić typ object
w takim przypadku tracimy jednak na szybkości działania tablic ndarray
.
a = np.array([9223372036854775807], dtype='object')
print(a)
print(a.dtype)
a = a + 10
print(a)
print(a.dtype)
a = a * 2
print(a)
print(a.dtype)
a = a ** 2
print(a)
print(a.dtype)
[9223372036854775807]
object
[9223372036854775817]
object
[18446744073709551634]
object
[340282366920938464127457394085312069956]
object
Wartości boolowskie i selekcja danych
Przeanalizuj poniższy kod:
import numpy as np
probki = np.array(['A', 'A', 'A', 'B', 'B', 'B'])
pomiary = np.array([1.2, 0.8, 1.3, 0.9, 0.95, 1.2])
print(probki)
print(pomiary)
['A' 'A' 'A' 'B' 'B' 'B']
[1.2 0.8 1.3 0.9 0.95 1.2 ]
probki_A = probki == 'A'
print(probki_A)
[ True True True False False False]
dane_A = pomiary[probki_A]
print(dane_A)
[1.2 0.8 1.3]
dane_A = pomiary[probki == 'A']
print(dane_A)
[1.2 0.8 1.3]
pomiary_0 = pomiary < 1
print(pomiary_0)
[False True False True True False]
pomiary_0 = pomiary[pomiary < 1]
print(pomiary_0)
[0.8 0.9 0.95]
probki_0 = probki[pomiary < 1]
print(probki_0)
['A' 'B' 'B']
Jak widać, możemy dokonywać selekcji danych używając wartości boolowskich (logicznych) uzyskanych w wyniku różnego rodzaju testów, zwracających wartości True/False
, przy czym testy i późniejsza selekcja mogą dotyczyć różnych tablic, muszą jednak mieć one taki sam rozmiar.
Do podobnych operacji można wykorzystać funkcję where()
:
import numpy as np
probki = np.array(['A', 'A', 'A', 'B', 'B', 'B'])
pomiary = np.array([1.2, 0.8, 1.3, 0.9, 0.95, 1.2])
alternatywa = np.array([-1, -2, -3, -4, -5, -6])
print(f'{probki = }')
print(f'{pomiary = }')
print(f'{alternatywa = }')
probki = array(['A', 'A', 'A', 'B', 'B', 'B'], dtype='<U1')
pomiary = array([1.2 , 0.8 , 1.3 , 0.9 , 0.95, 1.2 ])
alternatywa = array([-1, -2, -3, -4, -5, -6])
print(f'Wartości > 1: {pomiary[np.where(pomiary > 1)]}')
Wartości > 1: [1.2 1.3 1.2]
# Zwraca wartości 1 dla wartości w tablicy > 1, zwraca 0 dla <= 1
print(f'1 dla > 1, 0 dla <= 1: {np.where(pomiary > 1, 1, 0)}')
1 dla > 1, 0 dla <= 1: [1 0 1 0 0 1]
# Zwraca te wartości, które znajdują się pod takim samym indeksem
# jak 'A'w tablicy 'probki'
print(f'Pomiary dla próbek A: {pomiary[np.where(probki == "A")]}')
Pomiary dla próbek A: [1.2 0.8 1.3]
# Zwraca wartości z tab. 'pomiary' dla wartości > 1,
# zwraca wartości z tab. 'alternatywa' dla <= 1
print(f'Tab. "pomiary" dla > 1, "alternatywa" dla <= 1: \
{np.where(pomiary > 1, pomiary, alternatywa)}')
Tab. "pomiary" dla > 1, "alternatywa" dla <= 1: [ 1.2 -2. 1.3 -4. -5. 1.2]
Modyfikacje tabel
Pakiet numpy
oferuje szereg funkcji i metod umożliwiających modyfikację utworzonych tabel. Poznamy teraz niektóre z nich.
Zmiana kształtu
Zacznijmy od funkcji reshape()
, która pozwala na zmianę kształtu tabeli:
import numpy as np
tab_a = np.arange(36)
print(tab_a)
[ 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
24 25 26 27 28 29 30 31 32 33 34 35]
tab_b = tab_a.reshape(6,6)
print(tab_b)
[[ 0 1 2 3 4 5]
[ 6 7 8 9 10 11]
[12 13 14 15 16 17]
[18 19 20 21 22 23]
[24 25 26 27 28 29]
[30 31 32 33 34 35]]
tab_c = tab_a.reshape(3, 12)
print(tab_c)
[[ 0 1 2 3 4 5 6 7 8 9 10 11]
[12 13 14 15 16 17 18 19 20 21 22 23]
[24 25 26 27 28 29 30 31 32 33 34 35]]
tab_d = tab_c.reshape(3, 2, 6)
print(tab_d)
[[[ 0 1 2 3 4 5]
[ 6 7 8 9 10 11]]
[[12 13 14 15 16 17]
[18 19 20 21 22 23]]
[[24 25 26 27 28 29]
[30 31 32 33 34 35]]]
tab_e = np.arange(25).reshape(5, 5)
print(tab_e)
[[ 0 1 2 3 4]
[ 5 6 7 8 9]
[10 11 12 13 14]
[15 16 17 18 19]
[20 21 22 23 24]]
Dodawanie danych
Funkcja append()
umożliwia dodawanie nowych danych do tabeli:
import numpy as np
tab_1 = np.array([[1, 2, 3], [4, 5, 6]])
print(tab_1)
print('Dodanie rzędu')
tab_2 = np.append(tab_1, [[7, 8, 9]], axis=0)
print(tab_2)
print('Dodanie kolumny')
tab_3 = np.append(tab_1, [[7], [8]], axis=1)
print(tab_3)
print('Brak wskazanej osi')
tab_4 = np.append(tab_1, [[7, 8, 9]])
print(tab_4)
[[1 2 3]
[4 5 6]]
Dodanie rzędu
[[1 2 3]
[4 5 6]
[7 8 9]]
Dodanie kolumny
[[1 2 3 7]
[4 5 6 8]]
Brak wskazanej osi
[1 2 3 4 5 6 7 8 9]
Parametr axis
wskazuje oś dodanych elementów. Porównaj powyższe sposoby dodania danych do tablicy powyżej i ich wynik.
Funkcja insert()
pozwala na dodanie danych z uwzględnieniem indeksu miejsca, w którym chcemy je umieścić.
print('Dodanie rzędu przed rzędem o indeksie 1')
tab_5 = np.insert(tab_1, 1, [[7, 8, 9]], axis=0)
print(tab_5)
print('Dodanie kolumny przed kolumną o indeksie 1')
tab_6 = np.insert(tab_1, 1, [[7, 8]], axis=1)
print(tab_6)
Dodanie rzędu przed rzędem o indeksie 1
[[1 2 3]
[7 8 9]
[4 5 6]]
Dodanie rzędu przed kolumną o indeksie 1
[[1 7 2 3]
[4 8 5 6]]
Usuwanie danych
Usuwanie rzędów i kolumn z tabeli można osiągnąć za pomocą funkcji delete()
:
import numpy as np
print('Tablica 5x5')
tab_a = np.arange(25).reshape(5, 5)
print(tab_a)
[[ 0 1 2 3 4]
[ 5 6 7 8 9]
[10 11 12 13 14]
[15 16 17 18 19]
[20 21 22 23 24]]
print('Usuwanie rzędu o indeksie 2')
tab_b = np.delete(tab_a, 2, axis=0)
print(tab_b)
[[ 0 1 2 3 4]
[ 5 6 7 8 9]
[15 16 17 18 19]
[20 21 22 23 24]]
print('Usuwanie kolumny o indeksie 2')
tab_c = np.delete(tab_a, 2, axis=1)
print(tab_c)
[[ 0 1 3 4]
[ 5 6 8 9]
[10 11 13 14]
[15 16 18 19]
[20 21 23 24]]
# Trójwymiarowa kolumna
tab_d = np.arange(50).reshape(2, 5, 5)
print(tab_d)
[[[ 0 1 2 3 4]
[ 5 6 7 8 9]
[10 11 12 13 14]
[15 16 17 18 19]
[20 21 22 23 24]]
[[25 26 27 28 29]
[30 31 32 33 34]
[35 36 37 38 39]
[40 41 42 43 44]
[45 46 47 48 49]]]
print('Usuwanie kolumn o indeksie 0')
tab_e = np.delete(tab_d, 0, axis=2)
print(tab_e)
Usuwanie kolumn o indeksie 0
[[[ 1 2 3 4]
[ 6 7 8 9]
[11 12 13 14]
[16 17 18 19]
[21 22 23 24]]
[[26 27 28 29]
[31 32 33 34]
[36 37 38 39]
[41 42 43 44]
[46 47 48 49]]]
Iteracja tablicy
W celu uzyskania poszczególnych elementów tablicy można wykorzystać pętlę for
:
tab_1d = np.arange(10)
print(tab_1d)
i = 1
for e in tab_1d:
print(f'{i}: {e}')
i += 1
[0 1 2 3 4 5 6 7 8 9]
1: 0
2: 1
3: 2
4: 3
5: 4
6: 5
7: 6
8: 7
9: 8
10: 9
tab_2d = np.arange(12).reshape(3,4)
print(tab_2d)
i = 1
for t_1d in tab_2d:
print(f'{i}: {t_1d}')
i += 1
[[ 0 1 2 3]
[ 4 5 6 7]
[ 8 9 10 11]]
1: [0 1 2 3]
2: [4 5 6 7]
3: [ 8 9 10 11]
Jak widać, w wyniku iteracji tablicy dwuwymiarowej, otrzymujemy tablice jednowymiarowe. Zatem, chcąc otrzymać poszczególne elementy, musimy zagnieździć pętlę:
tab_2d = np.arange(12).reshape(3,4)
print(tab_2d)
i = 1
j = 1
for t_1d in tab_2d:
print(f'*{i}: {t_1d}')
for e in t_1d:
print(f'\t{j}: {e}')
j += 1
i += 1
[[ 0 1 2 3]
[ 4 5 6 7]
[ 8 9 10 11]]
*1: [0 1 2 3]
1: 0
2: 1
3: 2
4: 3
*2: [4 5 6 7]
5: 4
6: 5
7: 6
8: 7
*3: [ 8 9 10 11]
9: 8
10: 9
11: 10
12: 11
Należy jednak pamiętać, że pakiet numpy
umożliwia przeprowadzenie wielu operacji na rzędach, czy kolumnach tablic bez użycia pętli. Na przykład poznane wcześniej takie metody jak sum()
, mean()
czy std()
przyjmują opcjonalny argument axis
, co pozwala na uzyskanie tablicy o jeden wymiar mniejszy, czyli np. kolumny lub rzędu z tablicy dwuwymiarowej i wykonanie na pobranych danych odpowiednich obliczeń:
import numpy as np
tab = np.arange(25).reshape(5, 5)
print(tab)
# Sumy wartości w kolumnach:
print(f'Sumy kolumn: {tab.sum(axis = 0)}')
# Sumy wartości w wierszach:
print(f'Sumy wierszy: {tab.sum(axis = 1)}')
[[ 0 1 2 3 4]
[ 5 6 7 8 9]
[10 11 12 13 14]
[15 16 17 18 19]
[20 21 22 23 24]]
Sumy kolumn: [50 55 60 65 70]
Sumy wierszy: [ 10 35 60 85 110]
Zadania
Zadanie
Napisz kod, który dla dowolnej tablicy dwuwymiarowej, np. takiej:
1, 2, 2, 1, 3
2, 3, 3, 2, 1
2, 3, 2, 2, 2
2, 4, 2, 2, 3
- dla każdego rzędu obliczy średnią arytmetyczną, a następnie policzy dla nich średnią
- dla każdej kolumny zsumuje wartości większe niż obliczona wcześniej średnia dla wszystkich danych
Przykładowe rozwiązanie
import numpy as np
dane = np.array([[1, 2, 2, 1, 3],
[2, 3, 3, 2, 1],
[2, 3, 2, 2, 2],
[2, 4, 2, 2, 3]])
# Lista w której będziemy gromadzić średnie dla rzędów
srednie = []
# Iteracja po rzędach, oblicznie średnich
for rzad in dane:
srednie.append(rzad.mean())
print(f'Średnie: {srednie}')
# Obliczenie średniej ze średnich
srednia = np.array(srednie).mean()
print(f'Średnia średnich: {srednia}')
# Pobierane liczby kolumn
l_kolumn = dane.shape[1]
# Iteracja kolumn
for kolumna in range(l_kolumn):
# Wartości w kolumnie
dane_kolumna = dane[:,kolumna]
# Które wartości są większe niż średnia średnich
wieksze = dane_kolumna > srednia
# Suma wartości większych niż średnia średnich
suma_kolumna = dane_kolumna[wieksze].sum()
print(f'Suma dla kolumny {kolumna}: {suma_kolumna}')