Чисельні методи. Лабораторний практикум/Коротка довідка з NumPy

Чисельні методи. Лабораторний практикум ред.

NumPy - бібліотека мови Python для роботи з гомогенними багатовимірними масивами даних, які індексуються додатніми цілими числами. Гомогенність даних дозволяє значно оптимізувати роботу в порівнянні з стандартними списками мови.

Основний тип даних відповідно - array.

На базі NumPy написано майже усе науково-технічне програмне забеспечення мовою Python, зокрема

  • українське ПЗ OpenOpt (чисельна оптимізація, автоматичне диференціювання, розв’язування систем рівнянь)
  • SciPy (інтеграція, інтерполяція, статистика і т.і.)
  • науково-інженерні Python-дистрибутиви PythonXY, SAGE (вільні аналоги до MATLAB, Maple, MathCad, Mathematica і т.і.)
  • багато іншого софта, що можна подивитись зокрема тут і тут

Огляд ред.

import numpy as np

Тут і далі всі об'єкти які беруться з numpy будуть починатись з "np". Щоб було ясно, що звідки.
Якщо навіть "np" вам писати занадто довго, ви можете використовувати "from numpy import func1, func2, ..." або "from numpy import *" (тобто усе).
Тепер спробуємо створити різні масиви:

>>> a=np.array([3,4,5]) # зі списку
>>> a
array([3, 4, 5])
>>> b=np.arange(4) # цілі числа від 0 включно до n невключно
>>> b
array([0, 1, 2, 3])
>>> c=np.linspace(-np.pi,np.pi,5) # 5 рівномірно розміщених на проміжку [-pi,pi] чисел
>>> c
array([-3.14159265, -1.57079633,  0.        ,  1.57079633,  3.14159265])

Як і списки, їх можна обрізати, індексувати, та ітерувати крізь них:

>>> b=b[:-1]
>>> b
array([0, 1, 2])
>>> for x in b:
...     print x
... 
0
1
2
>>> b[1]
1

Як і над математичними векторами, над масивами можна виконувати різні операції:

>>> d=a**2+b
>>> d
array([ 9, 17, 27])

Крім одновимірних масивів бувають і двовимірні, і скільки завгодно вимірні. Розмірність задається кортежем shape:

>>> a=np.arange(8)
>>> a
array([0, 1, 2, 3, 4, 5, 6, 7])
>>> a.shape=2,2,2
>>> a
array([[[0, 1],
        [2, 3]],

       [[4, 5],
        [6, 7]]])
>>> a=np.arange(9)
>>> a.shape=3,3
>>> a
array([[0, 1, 2],
       [3, 4, 5],
       [6, 7, 8]])
>>> np.arange(100).shape
(100,)

Можна виконувати операції з масивами різних розмірностей, якщо тільки в них співпадають відповідні розміри:

>>> a
array([[0, 1, 2],
       [3, 4, 5],
       [6, 7, 8]])
>>> d
array([ 9, 17, 27])
>>> a+d
array([[ 9, 18, 29],
       [12, 21, 32],
       [15, 24, 35]])

>>> a=np.arange(12)
>>> a.shape=3,4
>>> a
array([[ 0,  1,  2,  3],
       [ 4,  5,  6,  7],
       [ 8,  9, 10, 11]])
>>> b
array([0, 1, 2])
>>> a+b
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: shape mismatch: objects cannot be broadcast to a single shape
>>> b.shape=3,1
>>> b
array([[0],
       [1],
       [2]])
>>> a+b
array([[ 0,  1,  2,  3],
       [ 5,  6,  7,  8],
       [10, 11, 12, 13]])

При індексуванні багатовимірного масиву індекси розділяють комами:

>>> a[1,2]
6

Основи ред.

Клас в якому зберігаються масиви називається ndarray, і він має наступні поля:

ndarray.ndim
кількість вимірів масиву. (кількість елементів в полі shape)
ndarray.shape
розміри масиву. Кортеж що зберігає розмір вздовж кожного виміру.
ndarray.size
кількість елементів в масиві. ( дорівнює добутку всіх чисел в shape )
ndarray.dtype
об'єкт що описує тип елементів масиву. Можна задати якийсь з стандартних типів, чи заданий в NumPy, як наприклад: bool_, character, int_, int8, int16, int32, int64, float_, float8, float16, float32, float64, complex_, complex64, object_.
ndarray.itemsize
розмір кожного елементу масиву в байтах. Наприклад розмір елемента типу float64 має розмір 8 (=64/8). Еквівалентне до ndarray.dtype.itemsize.
ndarray.data
Власне дані що зберігаються в масиві. Чіпати руками це поле нам не прийдеться.

Створення ред.

Окрім списків масиви можуть створюватись і з складніших структур:

>>> a = np.array( ([1,2,3] , [4,5,6]) )
>>> a
array([[1, 2, 3],
       [4, 5, 6]])

Також можна явно задати тип списку:

>>> np.array( ([1,2,3] , [4,5,6]) , dtype=complex)
array([[ 1.+0.j,  2.+0.j,  3.+0.j],
       [ 4.+0.j,  5.+0.j,  6.+0.j]])

Є функції, які ствоюють нові масиви з нічого:

>>> np.zeros( (2,2) ) #Створити масив заповнений нулями
array([[ 0.,  0.],
       [ 0.,  0.]])
>>> np.ones( (4,1) ) #Створити масив заповнений одиничками
array([[ 1.],
       [ 1.],
       [ 1.],
       [ 1.]])
>>> np.empty( (2,3) ) #Створити масив. (Він буде забитий всяким сміттям з пам'яті)
array([[  3.05135778e-267,   6.36598737e-314,   1.01855798e-312],
       [  1.27319747e-313,   1.27319747e-313,   1.27319747e-313]])

Також можна створити масив з функції. Наприклад красиву табличку множення:

>>> def f(x,y):
...     return (x+1)*(y+1)
... 
>>> a=np.fromfunction(f,(9,9),dtype=int)
>>> a
array([[ 1,  2,  3,  4,  5,  6,  7,  8,  9],
       [ 2,  4,  6,  8, 10, 12, 14, 16, 18],
       [ 3,  6,  9, 12, 15, 18, 21, 24, 27],
       [ 4,  8, 12, 16, 20, 24, 28, 32, 36],
       [ 5, 10, 15, 20, 25, 30, 35, 40, 45],
       [ 6, 12, 18, 24, 30, 36, 42, 48, 54],
       [ 7, 14, 21, 28, 35, 42, 49, 56, 63],
       [ 8, 16, 24, 32, 40, 48, 56, 64, 72],
       [ 9, 18, 27, 36, 45, 54, 63, 72, 81]])

Базові операції ред.

Базові операції з масивами виконуються поелементно. Створюється новий масив, в який і записується результат:

>>> a=np.arange(4)*10
>>> a
array([ 0, 10, 20, 30])
>>> a=20-a
>>> a
array([ 20,  10,   0, -10])
>>> b=np.arange(4)**2
>>> b
array([0, 1, 4, 9])
>>> 10*np.sin(a)
array([ 9.12945251, -5.44021111,  0.        ,  5.44021111])
>>> a<10
array([False, False,  True,  True], dtype=bool)

Як вже було сказано, всі операції виконуються поелементно. Це ж стосується і множення. Якщо нам потрібно перемноження матриць, то використовують функцію dot:

>>> A
array([[ 1.,  0.],
       [ 0.,  2.]])
>>> B
array([[ 1.,  1.],
       [ 1.,  1.]])
>>> A*B
array([[ 1.,  0.],
       [ 0.,  2.]])
>>> np.dot(A,B)
array([[ 1.,  1.],
       [ 2.,  2.]])

Щоб не створювати нових масивів, операції можна об'єднувати з присвоєнням:

>>> a
array([0, 1, 2])
>>> a+=10
>>> a
array([10, 11, 12])

При здійсненні операцій з різними типами даних, результат приводиться до ширшого. Це називається upcasting.

Такі операції як сума, мінімум та максимум є методами класу масиву:

>>> a
array([10, 11, 12])
>>> a.sum()
33
>>> a.min()
10
>>> a.max()
12

Порівняння ред.

>>> a = np.arange(10)
>>> b = np.arange(10)
>>> a == b
array([ True,  True,  True,  True,  True,  True,  True,  True,  True,  True], dtype=bool)
>>> b[5]=10
>>> a == b
array([ True,  True,  True,  True,  True, False,  True,  True,  True,  True], dtype=bool) 
# тепер масиви відрізняються одним елементом
>>> (a == b).all() # чи всі елементи True?
False
>>> (a == b).any() # чи є хоч один True?
True

Вирізання, індексування, ітерації ред.

З одновимірними масивами поводяться зовсім так само як і зі списками. З багатовимірними не набагато складніше:

>>> a                          # Масив, в якому перша цифра означає рядок, друга стовпець.
array([[ 0,  1,  2,  3,  4,  5],
       [10, 11, 12, 13, 14, 15],
       [20, 21, 22, 23, 24, 25],
       [30, 31, 32, 33, 34, 35],
       [40, 41, 42, 43, 44, 45],
       [50, 51, 52, 53, 54, 55]])
>>> a[1]                              # Другий рядок
array([10, 11, 12, 13, 14, 15])
>>> a[:,1]                           # Другий стовпець
array([ 1, 11, 21, 31, 41, 51]) 
>>> a[1:-1,1:-1]                 # Викидаємо всі крайні елементи
array([[11, 12, 13, 14],
       [21, 22, 23, 24],
       [31, 32, 33, 34],
       [41, 42, 43, 44]])

Ітерація відбувається починаючи з першого виміру:

>>> for row in a:
...     print row
... 
[0 1 2 3 4 5]
[10 11 12 13 14 15]
[20 21 22 23 24 25]
[30 31 32 33 34 35]
[40 41 42 43 44 45]
[50 51 52 53 54 55]

А також можна проітерувати поелементно, за допомогою об'єкта flat:

>>> for element in a.flat:
...     print element,
... 
0 1 2 3 4 5 10 11 12 13 14 15 20 21 22 23 24 25 30 31 32 33 34 35 40 41 42 43 44 45 50 51 52 53 54 55

Розмірність ред.

Транспозиція матриці робиться за допомогою метода transpose. Розгортанння в одновимірний за допомогою ravel:

>>> a
array([[ 0,  1,  2],
       [10, 11, 12],
       [20, 21, 22]])
>>> a.ravel()
array([ 0,  1,  2, 10, 11, 12, 20, 21, 22])
>>> a.transpose()
array([[ 0, 10, 20],
       [ 1, 11, 21],
       [ 2, 12, 22]])

Міняти розмірність масивів можна методами resize, та reshape. Різниця між ними в тому, що resize змінює сам масив, а reshape повертає новий масив як результат функції. Також resize працює не завжди, а коли йому не доводиться перевиділяти пам'ять ( чи щось подібне. мені він пише ValueError: resize only works on single-segment arrays ).

Хочете приклад? Та відкрийте середовище, і самі спробуйте! Що все за вас мають робити? :)

Конкатенація ред.

Масивами можна конкатенувати вертикально, і горизонтально:

>>> a=np.ones((2,2))
>>> b=np.zeros((2,2))
>>> np.vstack((a,b))
array([[ 1.,  1.],
       [ 1.,  1.],
       [ 0.,  0.],
       [ 0.,  0.]])
>>> np.hstack((a,b))
array([[ 1.,  1.,  0.,  0.],
       [ 1.,  1.,  0.,  0.]])

Корисно, коли треба зліпити розширену матрицю, чи щось подібне.

Розрізання ред.

Матриці можна розрізати. Теж вертикально чи горизонтально. Функціям hsplit чи vsplit передають матрицю для розрізання, і кількість рівних частин на які будуть різати, чи кортеж з набором номерів рядків (стовпців) з яких починається новий масив. Пояснення заплутане, приклад зрозуміліший:

>>> a=np.fromfunction(lambda x,y:y,(2,10),dtype=int)
>>> a
array([[0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
       [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]])
>>> np.hsplit(a,2)
[array([[0, 1, 2, 3, 4],
       [0, 1, 2, 3, 4]]), array([[5, 6, 7, 8, 9],
       [5, 6, 7, 8, 9]])]
>>> np.hsplit(a,(2,4,7))
[array([[0, 1],
       [0, 1]]), array([[2, 3],
       [2, 3]]), array([[4, 5, 6],
       [4, 5, 6]]), array([[7, 8, 9],
       [7, 8, 9]])]

Копії ред.

З копіями треба акуратно. Звичайне присвоєння не створює копії, що може призвести до деяких помилок:

>>> a=np.arange(3) 
>>> b=a
>>> a
array([0, 1, 2]) 
>>> b
array([0, 1, 2]) # Ну прямо точна копія
>>> b[1]=10
>>> b
array([ 0, 10,  2]) # Що і очікувалось
>>> a
array([ 0, 10,  2]) # А ось і приїхали.

Щоб такого не відбувалось копії можна робити явно:

>>> b=np.copy(a)
>>> b
array([ 0, 10,  2])
>>> a
array([ 0, 10,  2])
>>> a[1]=0
>>> a
array([0, 0, 2])
>>> b
array([ 0, 10,  2])

Хоча така фіча може бути й корисна:

>>> a
array([[ 0,  1,  2],
       [10, 10, 10],
       [20, 10, 10]])
>>> b=a[1:,1:]
>>> b
array([[10, 10],
       [10, 10]])
>>> b[:]=0
>>> a
array([[ 0,  1,  2],
       [10,  0,  0],
       [20,  0,  0]])

Джерела ред.