Чисельні методи. Лабораторний практикум/Коротка довідка з 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]])