Введение в научный Python-1. Введение в научный Python
Скачать 6.2 Mb.
|
3. Массивы и линейная алгебра В «научном» Python основу всех математических вычислений составляет пакет NumPy . Он представляет библиотеку типов и функций для работы с массивами и матрицами, и является мультимодульным пакетом. Перечислим некоторые из содержащихся в нем модулей: random (случайный), linalg (линейная алгебра), fft (Fast Fourier Transform – быстрое преобразование Фурье), polynomial (работа с полиномами) и многие другие. Вначале модуль NumPy следует загрузить. При этом для него можно создать более короткое имя. >>> import numpy as np После этого к любому объекту модуля можно обращаться, используя это короткое имя. 66 Важно также знать версию установленной у вас библиотеки. Это можно сделать командой >>> np.__version__ '1.10.4' 3.1 Массивы Создание массивов. Основным объектом NumPy является массив элементов, как правило, чисел. Существует несколько способов создать массив. Один из них состоит в создании массива из списка с помощью функции array(). >>> a=np.array([0.0,2.5,5.2]) >>> a array([0.,2.5,5.2]) Функция print(...) из ядра Python, печатает массив в виде списка >>> print(a) [0. 2.5 5.2] Вот как создается двумерный массив >>> b=np.array([[1,2,3],[4,5,6]]) >>> b array([[1, 2, 3], [4, 5, 6]]) Для доступа к элементам массива используются индексы. Нумерация индексов начинается с нуля. Обе следующие записи допустимы. >>> b[0,1] 2 >>> b[1][2] 6 Массивы – это объекты со своими атрибутами и методами. Атрибут ndim содержит размерность массива. >>> b.ndim 2 Количество элементов по каждой из координат возвращает атрибут shape. >>> b.shape (2, 3) Атрибут size содержит количество элементов в массиве, а функция len вычисляет размер массива по первой координате. >>> b.size 6 >>> len(b) 2 Перебирать элементы массива можно в цикле, например, так: >>> for i in a: print(i) 0.0 2.5 5.2 67 Модуль NumPy предоставляет несколько различных числовых типов: int16, int32, int64, float32, float64 . Массивы могут содержать элементы любого из них, но все элементы массива будут одного типа. Тип элементов массива можно узнать, используя атрибут dtype. >>> b.dtype dtype('int32') >>> a.dtype dtype('float64') В модуле имеются большое количество команд/функций создания массивов. Приведем здесь некоторые из них. Функция zeros() генерирует массив со всеми нулями, а функция ones() генерирует массив со всеми единицами. Размерность и количество элементов задается в аргументах этих функций. >>> c=np.zeros(5) >>> print(c) [0. 0. 0. 0. 0.] >>> A=5*np.ones([2,3]) >>> print(A) [[ 5. 5. 5.] [ 5. 5. 5.]] Здесь умножение числа на массив соответствует умножению каждого элемента массива на число. При генерировании массива можно задать тип его элементов. >>> d=np.ones(3,dtype=np.int16) ) >>> print(d) [1 1 1] Функция eye() генерирует единичную матрицу, размер которой передается через аргумент. >>> I=np.eye(3); print(I) [[ 1. 0. 0.] [ 0. 1. 0.] [ 0. 0. 1.]] Для создания одномерного массива последовательно идущих чисел в модуле numpy имеются функции arange() и linspace(). Функция arange(a,b,шаг) использует шаг для создания массива чисел, начинающихся значением a, и не превосходящих значение b. >>> a=np.arange(0.5,9.3,2.5) >>> print(a) [0.5 3.0 5.5 8.0] Массив N чисел, равномерно распределенных на отрезке [a,b], создается функцией linspace(a,b,N). Начальная и конечная точки включаются. >>> np.linspace(0,5,6) array([ 0., 1., 2., 3., 4., 5.]) 68 Квадратный единичный массив можно создать функцией identity(N), где N размер массива N x N. >>> np.identity(3) array([[ 1., 0., 0.], [ 0., 1., 0.], [ 0., 0., 1.]]) Функция diag(v[,k=0]) создает квадратный массив (матрицу), на диагонали которого стоят элементы вектора v (одномерного массива), а остальные элементы равны нулю. Заметим, что когда мы говорим о векторе или матрице, то на самом деле подразумеваем одномерный или двумерный массив (как типы Python понятия массива и матрицы различаются), или списки соответствующих размеров. >>> np.diag([1,2,3]) array([[1, 0, 0], [0, 2, 0], [0, 0, 3]]) Необязательный аргумент k представляет номер (побочной) диагонали, на которую будут поставлены элементы вектора v. Аргумент k может быть отрицательным, тогда побочная диагональ располагается снизу от главной. >>> np.diag([1,2,3],1) array([[0, 1, 0, 0], [0, 0, 2, 0], [0, 0, 0, 3], [0, 0, 0, 0]]) Построить массив, используя функцию над его индексами для вычисления элементов, можно следующим образом: >>> def g(i): return i**2 >>> a=np.fromfunction(g,(5,),dtype=np.int32) # запятая после пятерки >>> print(a) [ 0 1 4 9 16] У функции fromfunction(...) обычно три аргумента. Первый – это идентификатор функции–генератора, которая в качестве аргументов принимает индексы элементов. Второй аргумент – кортеж размеров массива, в третьем необязательном аргументе указывается тип элементов. Для создания квадратного массива функция–генератор должна принимать два аргумента. >>> def f(i,j): return i**2+j**2 >>> np.fromfunction(f, (3, 3), dtype=int) array([[0, 1, 4], [1, 2, 5], [4, 5, 8]]) 69 В Python можно создавать неименованные lambda функции. При генерировании массивов их удобно использовать в качестве первого аргумента. >>> np.fromfunction(lambda i, j: i + j, (3, 3)) array([[ 0., 1., 2.], [ 1., 2., 3.], [ 2., 3., 4.]]) Нам часто придется использовать двумерные массивы с идентичными строками или столбцами. Для создания таких массивов используется функция meshgrid(). >>> x=np.array([1,2,3]) >>> y=np.array([ -1,1]) >>> X, Y = np.meshgrid(x, y) >>> print(X) [[1 2 3] [1 2 3]] >>> print(Y) [[-1 -1 -1] [ 1 1 1]] Здесь генерируются две матрицы/массива. Строки массива X состоят из элементов вектора x , а количество строк определяется длиной вектора y Наоборот, столбцы массива Y состоят из элементов вектора y , а количество столбцов определяется длиной вектора x Функция meshgrid() полезна для вычисления значения функций в узлах двумерной сетки, координаты узлов которой задаются парой векторов x и y. Для примера построим двумерный массив, элементы которого вычисляются в узлах сетки по формуле 2 2 j i j i y x z при 2 , 1 , 0 , 1 , 2 i x и 2 , 1 , 0 , 1 , 2 j y >>> x=np.linspace(-2,2,5) >>> y=np.linspace(-2,2,5) >>> print(y) [-2. -1. 0. 1. 2.] >>> X,Y=np.meshgrid(x,y) >>> print(X) [[-2. -1. 0. 1. 2.] [-2. -1. 0. 1. 2.] [-2. -1. 0. 1. 2.] [-2. -1. 0. 1. 2.] [-2. -1. 0. 1. 2.]] 70 >>> print(Y) [[-2. -2. -2. -2. -2.] [-1. -1. -1. -1. -1.] [ 0. 0. 0. 0. 0.] [ 1. 1. 1. 1. 1.] [ 2. 2. 2. 2. 2.]] >>> Z=X**2+Y**2 >>> print(Z) [[ 8. 5. 4. 5. 8.] [ 5. 2. 1. 2. 5.] [ 4. 1. 0. 1. 4.] [ 5. 2. 1. 2. 5.] [ 8. 5. 4. 5. 8.]] Имея массивы координат вершин X,Y,Z, можно построить поверхность многогранника. >>> from matplotlib.pyplot import * >>> from mpl_toolkits.m plot3d import Axes3D >>> fig=figure() # открывает/создает графическое окно >>> ax=Axes3D(fig) # рисует 3–мерные координатные оси >>> ax.plot_surface(X,Y,Z,rstride=1,cstride=1) # рисует поверхность Графические функции мы будем обсуждать в следующей главе. Не вдаваясь в подробности, скажем, что здесь подключаются два графических модуля и затем вызываются функции, которые строят график многогранной поверхности по массивам X, Y, Z координат ее вершин. В модуле numpy имеется оператор mgrid[...], близкий по смыслу функции meshgrid(...) (его аргументы заключаются в квадратные скобки). >>> x1, y1 = np.mgrid[0:8:2, -10:0:4] >>> print(x1) [[0 0 0] [2 2 2] [4 4 4] [6 6 6]] 71 >>> print(y1) [[-10 -6 -2] [-10 -6 -2] [-10 -6 -2] [-10 -6 -2]] >>> x2, y2 = np.meshgrid(np.arange(0, 8, 2), np.arange( -10, 0, 4)) >>> print(x2) [[0 2 4 6] [0 2 4 6] [0 2 4 6]] >>> print(y2) [[-10 -10 -10 -10] [ -6 -6 -6 -6] [ -2 -2 -2 -2]] Как видите, функция meshgrid(...) создает транспонированные массивы по сравнению с теми, которые создает оператор mgrid[...]. Если результат действия последнего оператора mgrid[...] присвоить одной переменной >>> g = np.mgrid[0:8:2, -10:0:4] то она будет представлять массив из 2D массивов. Т.е. g[0] будет содержать массив x1, а g[1] – массив y1. Если в команде np.mgrid[start:stop:step] параметр step содержит мнимую единицу, т.е. имеет вид Nj, то N представляет количество точек на отрезке [start,stop] (а не шаг!), и начальная и конечная точки включаются в массив. >>> x3, y3 = np.mgrid[0:6:4j, -10:-2:3j] Массивы x3, y3 содержат те же значения, что и массивы x1, y1. В данном случае они отличаются типом элементов (вещественые и целые). >>> type(x1[0][0]) numpy.int32 >>> type(x3[0][0]) numpy.float64 Случайные последовательности и массивы. Часто приходится использовать объекты, созданные случайным образом. В Python имеется модуль random (не путать с модулем numpy.random ), который содержит функции, предназначенные для генерирования случайных чисел, символов, и т.д., а также последовательностей из них. Приведем краткое описание наиболее часто используемых функций этого модуля. >>> import random as rnd Функция rnd.seed() инициализирует генератор случайных чисел, используя системное время. Функция rnd.randrange(start,stop,step) возвращает случайно выбранное целое число X из диапазона start ≤ X < stop, где start,stop и step должны быть целыми числами. 72 >>> rnd.seed() >>> rnd.randrange(5,25,5) 15 Функция N=rnd.randint(A,B) возвращает случайное целое число N из диапазона A ≤ N ≤ B. >>> rnd.randint(5, 25) 11 Функция rnd.choice(последовательность) возвращает случайный элемент из непустой последовательности, которая может быть списком, кортежем, массивом и т.д. >>> rnd.choice([1,'x',"str",(21,3),[1,2,3]]) 'str' >>> rnd.choice(np.array([ -1,3,2,6])) 3 Функция rnd.shuffle(последовательность) случайным образом перемешивает последовательность. Функция не работает для неизменяемых объектов. >>> z=[1,'x',"str",(21,3),[1,2,3]] >>> rnd.shuffle(z);z [[1, 2, 3], 1, (21, 3), 'x', 'str'] Функция rnd.random() возвращает одно случайное число из диапазона [0,1]. >>> rnd.random() 0.30184417938671937 Функция rnd.sample(последовательность,k) возвращает список длины k случайно выбранных элементов из последовательности. >>> z=rnd.sample((1,'x',"str",(21,3),[1,2,3]), 3); z ['str',(21,3),[1, 2, 3]] Функция X=rnd.uniform(X0,X1) возвращает случайное число с плавающей точкой из интервала X0 < X < X1 (или X0 ≤ X ≤ X1, зависит от настроек округления). >>> rnd.uniform(1.3, 2.4) 2.3132945207171103 Для генерирования массивов случайных чисел предназначены функции модуля numpy.random. Они имеют сходный синтаксис с одноименными функциями модуля random,. Приведем несколько примеров. >>> import numpy as np Функция numpy.random.random(k) генерирует массив k случайных вещественных чисел из полуинтервала [0,1). >>> np.random.random(4) # массив 4–х вещественных чисел от 0 до 1 array([0.1501402, 0.68299761, 0.5332606, 0.59519775]) Имеется несколько похожих функций, отличающихся друг от друга в деталях. >>> np.random.rand(3) # массив 3–х вещественных чисел от 0 до 1 array([0.16960323, 0.59456467, 0.34565601]) np.random.rand(2,3) # 2x3 массив вещественных чисел от 0 до 1 73 array([[ 0.04864568, 0.24654989, 0.56020694], [ 0.38628115, 0.86663464, 0.09863981]]) >>> np.random.sample() # одно вещественное число от 0 до 1 0.812962885304781 >>> np.random.sample(3) # массив 3–х вещественных чисел от 0 до 1 array([ 0.32872632, 0.21954113, 0.88342433]) >>> np.random.sample((2, 3)) # 2x3 массив вещественных чисел от 0 до 1 array([[0.13162862, 0.65688322, 0.6176331 ], [0.6012843 , 0.16264842, 0.19002568]]) >>> np.random.randint(3, 8, 5) # масcив 5–ти целых чисел 3≤N<8 array([3, 4, 4, 3, 7]) >>> np.random.random_integers(3, 8, 5) # маcсив 5–ти целых чисел 3≤N≤8 array([8, 7, 3, 8, 3]) >>> np.random.randint(3, 8, (2, 4)) # 2x4 масcив целых чисел 3≤N<8 array([[4, 5, 6, 7], [5, 6, 5, 3]]) Функция numpy.random.uniform(x0,x1,k) генерирует массив вещественных чисел, равномерно распределенных на интервале (x0,x1). >>> np.random.uniform(3, 8, (2, 4)) # 2x4 массив вещественных чисел array([[ 5.51213273, 4.95171824, 7.73454383, 3.37876069], [7.24894408, 7.70482718, 6.56001367, 4.54157435]]) Функция numpy.random.shuffle(массив) выполняет случайное перемешивание элементов массива. >>> z = np.arange(8) >>> np.random.shuffle(z);z array([5, 3, 7, 6, 4, 2, 0, 1]) Функция numpy.random. permutation(N) возвращает массив перемешанных случайным образом целых чисел 0≤n В модуле numpy.random. имеются и другие, реже используемые функции. Операции между массивом и скаляром. Операции +,–,*,/ между массивом и числом означает прибавление (вычитание) числа к каждому элементу массива, умножение (деление) на число каждого элемента массива. >>> a=np.array([1.0,2.0,3.0]) >>> a+10 array([11., 12., 13.]) >>> print(a*5) [ 5. 10. 15.] Можно возвести в «числовую» степень каждый элемент массива >>> a**2 array([ 1., 4., 9.]) Допустимы операции составного присваивания типа массив+=число. >>> a+=10 74 >>> print(a) [ 11. 12. 13.] >>> b=np.array([[1,2],[3,4]]) >>> b+=10 >>> print(b) [[11 12] [13 14]] >>> b*=10 >>> b array([[110, 120], [130, 140]]) Допустимы и многие другие поэлементные операции между массивами и числами, например, побитовые. >>> b=np.array([[1,2],[3,4]]) >>> print(b | 1) [[1 3] [3 5]] Операции между массивами. С массивами можно выполнять поэлементные арифметические операции. Размерности массивов должны быть одинаковыми, или один из массивов должен быть одноэлементным. Во втором случае операция выполняется как между массивом и скаляром. Если операнды имеют разный тип элементов, то результат приводится к «старшему» типу. Например, можно сложить поэлементно два массива. >>> a=np.array([1.0,2.0,3.0]) >>> b=np.array([10,20,30]) >>> print(a+b) [11. 22. 33.] Можно умножить поэлементно два массива. Это умножение не является скалярным (или матричным) произведением. >>> print(a*b) [10. 40. 90.] Можно разделить поэлементно два массива. >>> print(a/b) [ 0.1 0.1 0.1] Допустимо сложение массивов различной размерности. >>> a=np.array([1,2,3]) >>> b=np.array([[1,2,3],[4,5,6]]) >>> a+b array([[ 2., 4., 6.], [ 5., 7., 9.]]) Каждый элемент первого массива складывается с соответствующими элементами внутренних массивов второго слагаемого. Это работает так же, как и сложение скаляра с массивом. Схематически это выглядит, например, так: 2 1 2 1 obj obj obj obj obj obj obj , 75 где объектами obj могут быть скаляры, массивы и другие объекты, для которых определена операция „+‟, например, строки. Здесь квадратные скобки обозначают массив, а не список. Сложение массивов a и b, приведенное выше, действует следующим образом: 9 , 7 , 5 6 , 4 , 2 6 , 5 , 4 3 , 2 , 1 3 , 2 , 1 3 , 2 , 1 6 , 5 , 4 3 , 2 , 1 3 , 2 , 1 Вот как выглядит схема сложения вектора–строки (одномерного массива) и вектора–столбца (двумерного массива с одним столбцом). 9 , 8 , 7 8 , 7 , 6 7 , 6 , 5 6 3 , 2 , 1 5 3 , 2 , 1 4 3 , 2 , 1 6 5 4 3 , 2 , 1 Реализацией последней операции сложения являются следующие инструкции. >>> x=np.array([1,2,3]);x array([1, 2, 3]) >>> y=np.array([[4],[5],[6]]);y array([[4], [5], [6]]) >>> x+y array([[5, 6, 7], [6, 7, 8], [7, 8, 9]]) Заметим, что существует простой способ преобразования вектора–строки y (одномерного массива) в вектор–столбец (двумерный массив с одним столбцом). Он реализуется командой y[:,None]. >>> y = np.linspace(-1.,1.,3); y array([-1., 0., 1.]) >>> z=y[:,None]; z array([[-1.], [ 0.], [ 1.]]) Тогда допустимо следующее сложение: >>> x=np.array([1,2,3]) >>> x+y[:,None] array([[ 0., 1., 2.], [ 1., 2., 3.], [ 2., 3., 4.]]) Вот примеры других операций с массивами. >>> a=np.array([10,20,30]) >>> b=np.array([2,4,6]) >>> print(a**b) # поэлементное возведение в степень [ 100 160000 729000000] >>> b*=a # составное поэлементное умножение 76 >>> b array([ 20, 80, 180]) >>> a+=b # составное поэлементное сложение >>> a array([ 30, 100, 210]) >>> a=np.array([10.,20.,30.]) >>> b=np.array([2,4,6]) >>> a/=b # составное поэлементное деление >>> a array([ 5., 5., 5.]) >>> a=np.array([2,3,5]) >>> b=np.array([11,14,19]) >>> print(b%a) # поэлементное вычисление остатка деления [1 2 4] Массивы можно поэлементно сравнивать. Результатом являются булевы массивы. >>> a=np.array([1,2,3]) >>> c=np.array([0,3,2]) >>> print(a |