Python - wprowadzenie

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 rzędu 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}')
Last updated on 16 Mar 2021
Published on 16 Mar 2021