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