- Библиотека numpy ¶
- 1. Одномерные массивы¶
- 1.1 Типы массивов, атрибуты¶
- 1.2 Индексация¶
- 1.3 Создание массивов¶
- 2. Операции над одномерными массивами¶
- 2.1 Математические операции¶
- 2.2 Сортировка, изменение массивов¶
- 2.3 Способы индексации массивов¶
- 3. Двумерные массивы¶
- 3.1 Создание, простые операции¶
- 3.2 Работа с матрицами¶
- 4. Тензоры (многомерные массивы)¶
- 4.1 Создание, простые операции¶
- 4.2. Broadcasting¶
Библиотека numpy ¶
Пакет numpy предоставляет $n$-мерные однородные массивы (все элементы одного типа); в них нельзя вставить или удалить элемент в произвольном месте. В numpy реализовано много операций над массивами в целом. Если задачу можно решить, произведя некоторую последовательность операций над массивами, то это будет столь же эффективно, как в C или matlab — львиная доля времени тратится в библиотечных функциях, написанных на C . Замечание. Модуль numpy.random не рассматривается целенаправленно. Вместо него рассмотри модуль scipy.stats , который больше подходит под вероятностно-статистические задачи.
1. Одномерные массивы¶
1.1 Типы массивов, атрибуты¶
size — это полное число элементов в массиве; len — размер по первой координате (в 1-мерном случае это то же самое).
numpy предоставляет несколько типов для целых ( int16 , int32 , int64 ) и чисел с плавающей точкой ( float32 , float64 ).
a.dtype, a.dtype.name, a.itemsize
c = np.array([0, 2, 1], dtype=np.float64) print(c)
print(c.dtype) print(c.astype(int)) print(c.astype(str))
1.2 Индексация¶
Массивы, разумеется, можно использовать в for циклах. Но при этом теряется главное преимущество numpy — быстродействие. Всегда, когда это возможно, лучше использовать операции над массивами как едиными целыми.
Упражнение: создайте numpy-массив, состоящий из первых пяти простых чисел, выведите его тип и размер. Решение:
arr = np.array([2, 3, 5, 7, 11]) print(arr) print(arr.shape) print(arr.dtype)
1.3 Создание массивов¶
Массивы, заполненные нулями или единицами. Часто лучше сначала создать такой массив, а потом присваивать значения его элементам.
a = np.zeros(3) b = np.ones(3, dtype=np.int64) print(a) print(b)
Если нужно создать массив, заполненный нулями, длины и типа другого массива, то можно использовать конструкцию
Функция arange подобна range . Аргументы могут быть с плавающей точкой. Следует избегать ситуаций, когда (конец-начало)/шаг — целое число, потому что в этом случае включение последнего элемента зависит от ошибок округления. Лучше, чтобы конец диапазона был где-то посредине шага.
Последовательности чисел с постоянным шагом можно также создавать функцией linspace . Начало и конец диапазона включаются; последний аргумент — число точек.
a = np.linspace(0, 8, 5) print(a)
Упражнение: создайте и выведите последовательность чисел от 10 до 20 с постоянным шагом, длина последовательности — 21. Решение:
arr = np.linspace(10, 20, 21) print(arr)
[10. 10.5 11. 11.5 12. 12.5 13. 13.5 14. 14.5 15. 15.5 16. 16.5 17. 17.5 18. 18.5 19. 19.5 20. ]
b = np.logspace(0, 1, 5) print(b)
[ 1. 1.77827941 3.16227766 5.62341325 10. ]
2. Операции над одномерными массивами¶
2.1 Математические операции¶
array([ 1. , 1.77827941, 3.16227766, 5.62341325, 10. ])
[ 1. 3.77827941 7.16227766 11.62341325 18. ]
[-1. 0.22172059 0.83772234 0.37658675 -2. ]
[ 0. 3.55655882 12.64911064 33.74047951 80. ]
[0. 1.12468265 1.26491106 1.06696765 0.8 ]
i = np.ones(5, dtype=np.int64) print(a + i)
Библиотека numpy содержит элементарные функции, которые тоже применяются к массивам поэлементно. Они называются универсальными функциями ( ufunc ).
[ 0. 0.90929743 -0.7568025 -0.2794155 0.98935825]
[False True True True False]
[False False False False False]
[False False False True True]
array([ 1. , 1.77827941, 3.16227766, 5.62341325, 10. ])
[ 2. 3.55655882 6.32455532 11.2468265 20. ]
[2. 1.18551961 1.26491106 1.6066895 2.22222222]
При выполнении операций над массивами деление на 0 не возбуждает исключения, а даёт значения np.nan или np.inf .
print(np.array([0.0, 0.0, 1.0, -1.0]) / np.array([1.0, 0.0, 0.0, 0.0]))
/usr/local/lib/python3.6/dist-packages/ipykernel_launcher.py:1: RuntimeWarning: divide by zero encountered in true_divide """Entry point for launching an IPython kernel. /usr/local/lib/python3.6/dist-packages/ipykernel_launcher.py:1: RuntimeWarning: invalid value encountered in true_divide """Entry point for launching an IPython kernel.
np.nan + 1, np.inf + 1, np.inf * 0, 1. / np.inf
Сумма и произведение всех элементов массива; максимальный и минимальный элемент; среднее и среднеквадратичное отклонение.
array([2. , 1.18551961, 1.26491106, 1.6066895 , 2.22222222])
b.sum(), b.prod(), b.max(), b.min(), b.mean(), b.std()
(8.279342393526044, 10.708241812210389, 2.2222222222222223, 1.1855196066926152, 1.6558684787052087, 0.4039003342660745)
print(np.sqrt(b)) print(np.exp(b)) print(np.log(b)) print(np.sin(b)) print(np.e, np.pi)
[1.41421356 1.08881569 1.12468265 1.26755256 1.49071198] [7.3890561 3.27238673 3.54277764 4.98627681 9.22781435] [0.69314718 0.17018117 0.23500181 0.47417585 0.7985077 ] [0.90929743 0.92669447 0.95358074 0.99935591 0.79522006] 2.718281828459045 3.141592653589793
Иногда бывает нужно использовать частичные (кумулятивные) суммы. В наших курсах такое может пригодится.
[2. 3.18551961 4.45043067 6.05712017 8.27934239]
2.2 Сортировка, изменение массивов¶
array([2. , 1.18551961, 1.26491106, 1.6066895 , 2.22222222])
[1.18551961 1.26491106 1.6066895 2. 2.22222222] [2. 1.18551961 1.26491106 1.6066895 2.22222222]
[1.18551961 1.26491106 1.6066895 2. 2.22222222]
array([1.18551961, 1.26491106, 1.6066895 , 2. , 2.22222222])
[1. 3. 5. 7. 9. 1.18551961 1.26491106 1.6066895 2. 2.22222222]
[array([1., 3., 5.]), array([7. , 9. , 1.18551961]), array([1.26491106, 1.6066895 , 2. , 2.22222222])]
Функции delete , insert и append не меняют массив на месте, а возвращают новый массив, в котором удалены, вставлены в середину или добавлены в конец какие-то элементы.
[1. 3. 5. 7. 9. 1.26491106 2. 2.22222222]
a = np.insert(a, 2, [0, 0]) print(a)
[1. 3. 0. 0. 5. 7. 9. 1.26491106 2. 2.22222222]
a = np.append(a, [1, 2, 3]) print(a)
[1. 3. 0. 0. 5. 7. 9. 1.26491106 2. 2.22222222 1. 2. 3. ]
2.3 Способы индексации массивов¶
a = np.linspace(0, 1, 11) print(a)
[0. 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 1. ]
Диапазон индексов. Создаётся новый заголовок массива, указывающий на те же данные. Изменения, сделанные через такой массив, видны и в исходном массиве.
[ 0. 0.1 -0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 1. ]
[ 0. -0.1 -0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 1. ]
[ 1. 0.9 0.8 0.7 0.6 0.5 0.4 0.3 -0.2 -0.1 0. ]
[ 0. 0. -0.2 0.3 0. 0.5 0.6 0. 0.8 0.9 1. ]
[ 0. 0.1 -0.2 0.3 0. 0.5 0.6 0. 0.8 0.9 1. ]
b = a.copy() b[2] = 0 print(b) print(a)
[0. 0.1 0. 0.3 0. 0.5 0.6 0. 0.8 0.9 1. ] [ 0. 0.1 -0.2 0.3 0. 0.5 0.6 0. 0.8 0.9 1. ]
[False True False True False True True False True True True]
array([ 0. , 0.1, -0.2, 0.3, 0. , 0.5, 0.6, 0. , 0.8, 0.9, 1. ])
array([False, True, False, True, False, True, True, False, True, True, True])
Упражнение:
1). Создайте массив чисел от $-2\pi$ до $2\pi$. 2). Посчитайте сумму поэлементных квадратов синуса и косинуса для данного массива. 3). С помощью np.all проверьте, что в ответе только единицы. Решение:
x = np.linspace(-2 * np.pi, 2 * np.pi, 20) np.all((np.sin(x)**2 + np.cos(x)**2).round() == 1)
3. Двумерные массивы¶
3.1 Создание, простые операции¶
a = np.array([[0.0, 1.0], [-1.0, 0.0]]) print(a)
Атрибуту shape можно присвоить новое значение — кортеж размеров по всем координатам. Получится новый заголовок массива; его данные не изменятся.
b = np.linspace(0, 3, 4) print(b)
print(a + 1) print(a * 2) print(a + [0, 1]) # второе слагаемое дополняется до матрицы копированием строк print(a + np.array([[0, 2]]).T) # .T - транспонирование print(a + b)
[[1. 2.] [0. 1.]] [[ 0. 2.] [-2. 0.]] [[ 0. 2.] [-1. 1.]] [[0. 1.] [1. 2.]] [[0. 2.] [1. 3.]]
3.2 Работа с матрицами¶
Упражнение: создайте матрицы $\begin -3 & 4 \\ 4 & 3 \end$ и $\begin 2 & 1 \\ 1 & 2 \end$. Посчитайте их поэлементное и матричное произведения. Решение:
a = np.array([[-3, 4], [4, 3]]) b = np.array([[2, 1], [1, 2]]) print(a * b) print(b * a) print(a @ b) print(b @ a)
[[-6 4] [ 4 6]] [[-6 4] [ 4 6]] [[-2 5] [11 10]] [[-2 11] [ 5 10]]
v = np.array([1, -1], dtype=np.float64) print(b @ v)
Если у вас Питон более ранней версии, то для работы с матрицами можно использовать класс np.matrix , в котором операция умножения реализуется как матричное умножение.
u = np.linspace(1, 2, 2) v = np.linspace(2, 4, 3) print(u) print(v)
x, y = np.meshgrid(u, v) print(x) print(y)
[[1. 0. 0. 0.] [0. 1. 0. 0.] [0. 0. 1. 0.] [0. 0. 0. 1.]]
[1. 0. 0. 0. 0. 1. 0. 0. 0. 0. 1. 0. 0. 0. 0. 1.]
[[1. 0. 0. 0. 0. 1. 0. 0.] [0. 0. 1. 0. 0. 0. 0. 1.]]
[1. 0. 0. 0.] [0. 1. 0. 0.] [0. 0. 1. 0.] [0. 0. 0. 1.]
def f(i, j): print(i) print(j) return 10 * i + j print(np.fromfunction(f, (4, 4), dtype=np.int64))
[[0 0 0 0] [1 1 1 1] [2 2 2 2] [3 3 3 3]] [[0 1 2 3] [0 1 2 3] [0 1 2 3] [0 1 2 3]] [[ 0 1 2 3] [10 11 12 13] [20 21 22 23] [30 31 32 33]]
a = np.array([[0, 1], [2, 3]]) b = np.array([[4, 5, 6], [7, 8, 9]]) c = np.array([[4, 5], [6, 7], [8, 9]]) print(a) print(b) print(c)
[[0 1] [2 3]] [[4 5 6] [7 8 9]] [[4 5] [6 7] [8 9]]
print(b.sum()) print(b.sum(axis=0)) print(b.sum(axis=1))
print(b.max()) print(b.max(axis=0)) print(b.min(axis=1))
Упражнение: в статистике и машинном обучении часто приходится иметь с функцией $RSS$, которая вычисляется по формуле $\sum_^ (y_i — a_i)^2$, где $y_i$ — координаты одномерного вектора $y$, $a_i$ — координаты одномерного вектора $a$. Посчитайте $RSS$ для $y = (1, 2, 3, 4, 5), a = (3, 2, 1, 0, -1)$. Решение:
# решение y = np.arange(1, 6) a = np.arange(3, -2, -1) rss = np.sum((y - a)**2)
4. Тензоры (многомерные массивы)¶
4.1 Создание, простые операции¶
X = np.arange(24).reshape(2, 3, 4) print(X)
[[[ 0 1 2 3] [ 4 5 6 7] [ 8 9 10 11]] [[12 13 14 15] [16 17 18 19] [20 21 22 23]]]
# суммируем только по нулевой оси, то есть для фиксированных j и k # суммируем только элементы с индексами (*, j, k) print(X.sum(axis=0))
[[12 14 16 18] [20 22 24 26] [28 30 32 34]]
# суммируем сразу по двум осям, то есть для фиксированной i # суммируем только элементы с индексами (i, *, *) print(X.sum(axis=(1, 2)))
4.2. Broadcasting¶
Выше при арифметических операциях с массивами, например, при сложении и умножении, мы перемножали массивы одинаковой формы. В самом простом случае операндами были одномерные массивы одинаковой длины.
# Самый простой случай a = np.array([1, 2, 3]) b = np.array([2, 2, 2]) print(a * b)
Произошло поэлементное умножение, все элементы массива $a$ умножились на $2$. Но мы знаем, что это можно сделать проще, просто умножив массив на $2$.
# Умножение массива на число print(a * 2)
# Умножение массивов разных длин print(a * [2])
В этом случае работает так называемый broadcasting. Один массив «растягивается», чтобы повторить форму другого.
Такой же эффект работает и для многомерных массивов. Если по какому-то измерению размер у одного массива равен $1$, а у другого — произвольный, то по этому измерению может произойти «рястяжение». Таким образом, массивы можно умножать друг на друга, если в измерениях, где они по размеру не совпадают, хотя бы у одного размер $1$. Для других поэлементных операций правило аналогично. Важно отметить, что размерности сопоставляются справа налево. Если их количество не совпадает, что массивы меньшей размерности сначала дополняются слева размерностями 1. Например, при сложении массива размера $4 \times 3$ с массивом размера $3$ последний сначала преобразуется в массив размера $1 \times 3$.
a = np.array([[ 0, 0, 0], [10, 10, 10], [20, 20, 20], [30, 30, 30]]) b = np.array([0, 1, 2]) print(a + b)