Основы работы в Python. Учебно-методическое пособие
.pdfa2[1] = 0 |
|
|
|
|
|
|
|
|
print(a1, a2) |
|
|
|
|
|
|
||
Результат работы программы имеет вид: |
[ 0. |
0.1 |
0.2] |
|||||
[0. |
0.1 |
0.2 |
0.3 |
0.4 |
0.5] |
|||
[10. |
0.1 |
0.2 |
0.3 |
0.4 |
3.5] |
[10. |
0.1 |
0.2] |
[10. |
0. |
0.2 |
0.3 |
0.4 |
3.5] |
[10. |
0. |
0.2] |
В приведённом примере поменяли нулевой и последний элементы
массива a1, в результате в массиве a2 нулевой элемент тоже изменил-
ся, затем поменяли первый элемент a2, и первый элемент a1 также изменился.
Теперь изменим этот пример с использованием функции copy: a1 = np.arange(0, 0.6, 0.1)
a2 = np.copy(a1[:len(a1)/2]) print(a1, a2)
a1[0] = 10
a1[-1] = a1[-1] + 3 print(a1, a2)
a2[1] = 0 print(a1, a2)
В новой редакции программы изменение одного из массивов не
приводит к изменению другого:
[0. 0.1 0.2 0.3 0.4 0.5] [0. 0.1 0.2] [10. 0.1 0.2 0.3 0.4 3.5] [0. 0.1 0.2] [10. 0.1 0.2 0.3 0.4 3.5] [0. 0. 0.2]
В данных примерах показано ещё одно свойство индексации: если номер начального элемента опустить, то автоматически подставляется ноль, если опустить номер конечного – то длина массива.
Основные методы работы с одномерными массивами
Массивы NumPy, будучи объектами, имеют множество встроенных методов (реализованных в виде функций), выполняющих часто используемые операции над массивами: поиск минимального и максимального элемента, вычисление суммы, среднего арифметического и других. Формат вызова метода соответствует правилам ООП: «имя объекта».метод( ). Ниже в таблице приведены некоторые методы, их описания и результаты работы для массива a, созданного командой:
import numpy as np
a = np.array([0.1, 0.25, -1.75, 0.4, -0.9])
51
Метод |
|
Описание |
|
|
|
a.min() |
Минимальный элемент массива: −1.75 |
|
a.argmin() |
Индекс минимального элемента: 2 |
|
a.max() |
Максимальный элемент массива: 0.4 |
|
a.argmax() |
Индекс максимального элемента: 3 |
|
a.sum() |
Сумма элементов массива: −1.9 |
|
a.mean() |
Среднее арифметическое всех элементов: −0.38 |
|
a.var() |
Дисперсия элементов массива: 0.6766 |
|
a.std() |
Среднеквадратичное отклонение от среднего |
|
Отметим, для сравнения, что для вычисления медианы использу- |
||
ют функцию median(a). |
|
|
Фрагмент кода |
результат |
|
|
|
|
print(a.sum()) |
-1.9 |
|
print(a.mean()) |
-0.38 |
|
print(a.min()) |
-1.75 |
|
print(a.max()) |
0.4 |
|
print(a.var()) |
0.6766 |
|
print(a.std()) |
0.822556988907 |
|
print(np.median(a)) |
0.1 |
|
Обратите внимание на круглые скобки после имени каждой функции – они указывают, что это именно функция. Без скобок никаких вычислений не будет произведено, а вместо этого будут напечатаны текстовые строки описания соответствующей функции. Полный перечень методов массивов приведен в официальной документации на сайте: https:// numpy.org/doc/stable/reference/arrays.ndarray.html#array-methods
Действия над массивами. Векторизация вычислений
Над массивами NumPy можно выполнять такие операции как: сложение и вычитание, умножение и деление (массивы должны быть одинакового размера и формы). К массиву можно добавлять скаляр или вычитать его из массива, а также умножать или делить на скаляр. При этом все операции происходят поэлементно. Вот пример про-
граммы сложения двух массивов, умножения массива на число и т. д.
52
import numpy as np
a = np.array([0.1, 0.25, -1.75, 0.4, -0.9]) b = np.array([0.9, 1.75, -0.15, 0.4, 0.4])
c = a + b |
|
|
|
|
print(c) |
|
|
|
|
print(2*a) |
; |
print(d) |
||
d = a + 1.5 |
||||
Результат работы программы имеет вид: |
||||
[ 1. |
2. |
-1.9 |
0.8 |
-0.5] |
[ 0.2 |
0.5 |
-3.5 |
0.8 |
-1.8] |
[ 1.6 |
1.75 -0.25 1.9 |
0.6 ] |
||
Такие операции называются векторизованными в том смысле, что
одинаковые действия производятся сразу над целым набором данных
(вектором или массивом). По сути, за каждой из таких операций скры-
вается «невидимый» цикл, который последовательно перебирает все
элементы массива и выполняет определенные действия над каждым из
них. Это позволяет внешне существенно упростить запись программ,
избежав множества циклов, которые пришлось бы писать на большин-
стве языков программирования (например, C++, Pascal, Java). Приведем ещё две полезные функции dot(a,b) и cross(a,b)
для скалярного и векторного произведения векторов соответственно:
a |
= np.array([1,1,0]); print(a) |
|
[1 1 |
0] |
|
||||
b |
= np.array([1,2,0]); print(b) |
|
[1 2 |
0] |
print(np.cross(a,b)) |
|
[0 0 |
1] |
|
print(np.dot(a,b) ) |
|
3 |
|
|
Кроме арифметических операций над массивами можно также векторизовано вычислять все заложенные в NumPy функции, напри-
мер, взять синус или логарифм от массива (поэлементно). import numpy as np
t = np.arange(0, np.pi , np.pi/6) X = np.sin(t)
Y = np.log(t) print(t) print(X) print(Y)
В результате получим:
[0. 0.52359878 1.04719755 1.57079633 2.0943951
2.61799388]
53
[0. 0.5 0.8660254 1. 0.8660254 0.5] [-inf -0.64702958 0.0461176 0.45158271
0.73926478 0.96240833]
Здесь мы табулировали две функции: sin(t) и ln(t) на полуинтер-
вале [0; π) с шагом π/6. Так как натуральный логарифм нуля равен (–∞), то для него получился соответствующий ответ: -inf.
Массивы NumPy можно обходить циклом, как списки. Чтобы вы-
вод результатов табулирования был более удобным и привычным для
чтения, модифицируем предыдущую программу, чтобы она выдавала
результат своей работы в три столбца. Для этого заменим строки, где
стоят операторы print, на строку:
for i in range(len(t)): print(t[i],X[i],Y[i])
Вывод программы при этом становится более наглядным:
0.0 0.0 –inf |
0.5 |
-0.647029583379 |
|
0.523598775598 |
|||
1.0471975512 |
0.866025403784 |
0.0461175971813 |
|
1.57079632679 |
1.0 |
0.451582705289 |
|
2.09439510239 |
0.866025403784 |
0.739264777741 |
|
2.61799387799 |
0.5 |
0.962408329055 |
|
К сожалению, записи выглядят неровно (они имеют разное число знаков после точки). Для получения четких колонок можно явно отформатировать вывод данных, изменив оператор print:
print("{:12.8f}\t {:12.8f}\t {:12.8f}"
.format(t[i],X[i], Y[i]))
Здесь символ \t означает табуляцию, впереди идёт строка форматирования, где :12.8f означает, что выводится действительное чис-
ло с фиксированной точкой (на это указывает f) с 12 знаками, из них
8 знаков после десятичной точки. |
Три подставляемых числа взяты |
|
в скобки. Вывод программы: |
-inf |
|
0.00000000 |
0.00000000 |
|
0.52359878 |
0.50000000 |
-0.64702958 |
1.04719755 |
0.86602540 |
0.04611760 |
1.57079633 |
1.00000000 |
0.45158271 |
2.09439510 |
0.86602540 |
0.73926478 |
2.61799388 |
0.50000000 |
0.96240833 |
Двумерные и многомерные массивы
Модуль NumPy позволяет работать с массивами произвольной размерности: одномерными, двумерными, трёхмерными и т. д. Для
54
многомерных массивов важным свойством является их форма (shape),
которая определяется совокупностью длин массива по каждому изме-
рению. Для одномерных массивов форма – это просто их длина.
Обычно, форма задаётся в виде кортежа из нескольких чисел: двух –
для двумерного, n – для n-мерного. Форму массива можно определить
при его создании [6]. Рассмотрим несколько примеров создания мас-
сивов.
Пример создания массивов |
Результаты работы программы: |
||
|
|
||
x = np.zeros((2, 3)) |
[[ 0. 0. 0.] |
||
print(x) |
[ 0. 0. |
0.]] |
|
y = np.eye(3) |
[[ 1. 0. 0.] |
||
print(y) |
[ 0. |
1. |
0.] |
print(x.shape) |
[ 0. |
0. |
1.]] |
(2, 3) |
|
||
print(len(x)) |
2 |
|
|
print(y.shape) |
(3, 3) |
|
|
Вначале создается матрица размера 2×3 из нулей с помощью
функции zeros с аргументом (2, 3) в виде кортежа; аналогично создается матрица любого размера. Функция eye имеет единственный
аргумент – число N и создаёт единичную квадратную матрицу N×N.
Видно, что длина массива (len) – это его первая размерность (в ранних версиях len соответствовало общему числу элементов).
Функция fromfunction() позволяет задать явное выражение для каждой комбинации индексов. Её параметры – форма массива.
>>>def f_2(i, j): return 3*i+j
>>>np.fromfunction(f_2, (2, 3))
array([[ |
0., |
1., |
2.], |
[ |
3., |
4., |
5.], |
Векторные операции и итерирование двумерных массивов
К двумерным массивам можно применять операции (методы) sum, mean, max и т. д. По умолчанию, эти операции применяются массиву, как если бы он был линейным списком всех чисел, независимо от его формы. Однако, указав параметр axis, можно применить операцию для каждой вертикальной или горизонтальной оси массива, например:
Фрагмент кода |
Результат |
|
|
55
a = np.array([[1, 2, |
3], [4, 5, 6], |
|
[0, 5, |
1]]) |
27 |
print(a.sum()) |
|
|
print(a.sum(axis=0)) |
|
[ 5 12 10] |
print(a.sum(axis=1)) |
|
[ 6 15 6] |
print(a.min()) |
|
0 |
print(a.min(axis=0)) |
|
[0 2 1] |
print(a.min(axis=1)) |
|
[1 4 0] |
Видно, что axis=0 работает отдельно с каждым столбцом, а axis=1
– отдельно с каждой строкой.
Итерирование многомерных массивов начинается с нулевой оси:
A = np.array([[1, 2, 3], [4, 5, 6]]) for e in A:
print( e )
Вывод:
[1 2 3]
[4 5 6]
Однако если нужно перебрать поэлементно весь массив, как если бы он был одномерным, для этого можно использовать атрибут flat:
Фрагмент кода |
Вывод: |
|
1 |
for element in A.flat: |
2 |
print( element ) |
3 |
|
4 |
|
5 |
|
6 |
Объединение массивов
Несколько массивов могут быть объединены вместе вдоль разных
осей с помощью функций hstack и vstack: причем hstack() объ-
единяет массивы по начальному индексу, а vstack()– по последнему. Рассмотрим результаты применения этих двух разных объединя-
ющих процедур на примере двух массивов a и b:
a = np.array([[1 , 2, 3], [4, 5, 6]]) b = np.array([[5 , 6, 7], [8, 9, 10]])
Два соответствующих варианта приведены в таблице ниже.
print(np.vstack((a,b))) print(np.hstack((a,b)))
[[ 1 2 3]
56
|
|
|
|
|
|
|
|
|
|
[ 4 |
5 |
6] |
[[ 1 |
2 |
3 |
5 |
6 |
7] |
|
[ |
5 |
6 |
7] |
[ 4 |
5 |
6 |
8 |
9 |
10]] |
[ |
8 |
9 |
10]] |
|
|
|
|
|
|
Матричное умножение. Функция dot
Модуль NumPy предоставляет множество различных функций для
работы с многомерными массивами, одна из самых популярных –
функция dot. Для матриц и одномерных массивов-векторов эта функ-
ция соответствует функции матричного умножения (скалярное произ-
ведение для двух одномерных массивов) [6]:
Код программы |
Результат |
|
|
|
|
import numpy |
as np |
|
a = np.array([[1 , 0], [0, 1]]) |
|
|
b = np.array([[4 , 1], [2, 2]]) |
[[4 1] |
|
print(dot(a, |
b)) |
|
x = array([-4, 1, 3, 2]) |
[2 2]] |
|
y = array([-1, 8, 1, -2]) |
11 |
|
print(dot(x, |
y)) |
|
z = array([5 |
, -2]) |
[16 1] |
print(dot(z, |
b)) |
|
print(dot(b, |
z)) |
[18 6] |
Вывод программы показывает, что одномерный массив интерпретируется как вектор-столбец или вектор-строка в зависимости от контекста (об этом не нужно заботиться дополнительно):
В версиях Python, начиная с 3.5 и новее для обозначения матрич-
ного умножения вместо функции dot можно использовать символ @: a = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
a2 = a @ a print(a2)
Вывод:
[[ 30, 36, 42], [ 66, 81, 96],
[102 , 126 , 150]]
Запись и чтение данных из файла при работе с массивами
Существуют две простые и чрезвычайно полезные функции в numpy, которые позволяют перейти от задач чисто учебных к зада-
57
чам практически важным. Понятно, что данные удобно хранить в фай-
лах, проще всего в текстовых. Функция loadtxt позволяет загрузить
данные из текстового файла в двумерный или одномерный массив.
Функция savetxt в свою очередь позволяет записать массив в тексто-
вый файл [6]. Пример работы этих функций приведён ниже:
import numpy as np a = np.eye(3)
b = np.arange(0, 10, 1) np.savetxt('eye3.txt', a) np.savetxt('vector.txt', b) a2 = np.loadtxt('eye3.txt') print (a2)
b2 = np.loadtxt('vector.txt') print(b2)
Заполнение массива данными, вводимыми с клавиатуры
Такой ввод используется, как правило, в тех случаях, когда количество данных невелико. Ниже приведены два примера заполнения одномерного и двумерного массивов данными, считываемыми с клавиатуры, а также вывод элементов массива на экран.
from numpy import *
print('Ввод элементов одномерного массива') n = int(input('Введите размер массива: '))
A = zeros(n,dtype='int') for i in range(n):
print('Введите A[', i ,']: ', end='')
A[i] = int(input()) print('Введенный массив: ', A)
print('Ввод элементов двумерного массива.') n = int(input('Введите количество строк: '))
m = int(input('Введите количество столбцов:'))
A = zeros((n,m),dtype='int') for i in range(n):
for j in range(m):
print('Введите A[',i,',',j,']:',end='') A[i,j] = int(input())
print('Введенный массив: \n', A)
58
Заполнение массива случайными числами
Эта процедура используется для создания наборов данных при
моделировании случайных процессов, а также для заполнения масси-
вов при отладке программ. Такие процедуры базируются на специаль-
ных алгоритмах, называемых генераторами случайных (псевдослучай-
ных) чисел. Они содержатся, например, в рассмотренном ранее модуле random, а также включены в модуль с аналогичным названием из биб-
лиотеки NumPy. В нем содержатся функции для генерации случайных
чисел с разными характеристиками и заполнения ими создаваемых
массивов.
numpy.random.random(). Эта функция генерирует массив задан-
ной формы и размера, заполненный случайными вещественными чис-
лами, с равномерным распределением в интервале [0, 1]. В зависимо-
сти от параметров она возвращает число или массив. Например: numpy.random.random() – возвращает одно число; numpy.random.random(n) – возвращает «вектор» из n чисел; numpy.random.random((n,m)) – возвращает двумерный массив n × m (параметром является кортеж).
numpy.random.rand(). Данная функция аналогична функции numpy.random.random(), но не требует использования кортежа (форма и размеры массива просто перечисляются через запятую): numpy.random.rand(n,m) – возвращает двумерный массив n × m.
Замечание. Для того чтобы получить случайные значения из произвольного интервала [a, b) достаточно написать
x = a + (b-a)*random(...).
Рассмотрим примеры применения этих функций.
>>>import numpy as np
>>>np.random.random()
0.8406827668872718
>>>np.random.random(3)
array([0.66324717, 0.13062014, 0.36554664])
>>> np.random.random((2,3)) array([[0.97128135, 0.48616795, 0.22506738],
[0.15358423, 0.21736019, 0.1411523 ]])
Получим числа из интервала [−5, 5):
>>> -5+10*np.random.random((3)) array([-4.42631282, 2.56597598, -1.98089115])
numpy.random.randint() – генерирует случайные целые числа: random.randint(a) – возвращает одно число из интервала [0, a);
59
random.randint(a, b) – возвращает число из интервала [a, b);
random.randint(a, |
b, |
n) – возвращает одномерный массив n |
|
чисел из интервала [a, b); |
b, |
(n, m)) – возвращает двумерный мас- |
|
random.randint(a, |
|||
сив размером |
с числами из интервала [a, b). |
||
В модуле numpy.random предусмотрена возможность генерации
случайных чисел с заданной функцией распределения. Например, ге-
нерация чисел с нормальным распределением осуществляется функци-
ей random.normal(M, d, size= n) , где M – математическое
ожидание, d – стандартное отклонение, n – количество чисел:
>>> np.random.normal(10, 0.5, size=3)
array([ 9.7382444 , 10.50138829, 9.36232793])
Перечень других функций содержится в описании модуля numpy.
Функции библиотеки linalg
Модуль numpy.linalg реализует базовые методы линейной алгебры: вычисление детерминантов, диагонализация матриц, решение линейных алгебраических уравнений и т. д. Пример: вычисление определителя квадратной матрицы M.
Код программы |
Результат |
||
|
[1 2 |
3] |
|
import numpy as np |
|||
M=np.array([[1,2,3],[0,3,6],[1,0,5]]) |
[0 |
3 |
6] |
for x in M: print(x) |
[1 |
0 |
5] |
print(np.linalg.det(M)) |
18.0 |
|
|
Далее представлен список некоторых методов модуля linalg.
linalg.det(A) |
определитель матрицы |
A |
|
linalg.matrix_power(A,n) |
возводит матрицу A в степень n. |
||
linalg.norm(x[,ord,axis]) |
норма вектора или оператора |
||
linalg.solve(A, y) |
решение |
системы |
линейных |
|
уравнений Ax = y. |
|
|
linalg.inv(A) |
результат – обратная матрица A−1 |
||
linalg.eig(A) |
возвращает |
собственные значе- |
|
|
ния и собственные векторы |
||
linalg.lstsq(a,b[,rcond]) |
метод наименьших квадратов |
||
60 |
|
|
|
