Быстрый старт NumPy
Предварительные требования
Вам необходимо немного знать Python. Для освежения, см. учебник по Python.
Для работы с примерами вам потребуется matplotlib помимо NumPy.
Профиль обучающегося
Это краткий обзор массивов в NumPy. Он демонстрирует, как представлены и могут быть обработаны n-мерные (\(n>=2\)) массивы. В частности, если вы не знаете, как применять общие функции к n-мерным массивам (без использования циклов for), или если хотите понять свойства осей и формы для n-мерных массивов, эта статья может быть полезной.
Цели обучения
После прочтения вы сможете:
- Понять разницу между одномерными, двумерными и n-мерными массивами в NumPy;
- Понять, как применять некоторые операции линейной алгебры к n-мерным массивам без использования циклов for;
- Понять свойства осей и формы для n-мерных массивов.
Основы
Основным объектом NumPy является однородный многомерный массив. Это таблица элементов (обычно чисел), все одного типа, индексируемая кортежем неотрицательных целых чисел. В NumPy размерности называются осями.
Например, координаты точки в 3D-пространстве [1, 2, 1] имеют одну ось. Эта ось содержит 3 элемента, поэтому мы говорим, что ее длина равна 3. В примере, изображенном ниже, массив имеет 2 оси. Первая ось имеет длину 2, вторая ось — длину 3.
[[1., 0., 0.], [0., 1., 2.]]
Класс массивов NumPy называется ndarray. Он также известен под псевдонимом array. Обратите внимание, что numpy.array не эквивалентен классу Стандартной библиотеки Python array.array, который обрабатывает только одномерные массивы и предлагает меньше функциональности. Более важные атрибуты объекта ndarray:
- ndarray.ndim
-
количество осей (измерений) массива.
- ndarray.shape
-
размерности массива. Это кортеж целых чисел, указывающий размер массива в каждом измерении. Для матрицы с n строками и m столбцами,
shapeбудет(n,m). Длина кортежаshape, следовательно, равна количеству осей,ndim. - ndarray.size
-
общее количество элементов массива. Это равно произведению элементов
shape. - ndarray.dtype
-
объект, описывающий тип элементов в массиве. Можно создавать или указывать dtype, используя стандартные типы Python. Кроме того, NumPy предоставляет собственные типы. numpy.int32, numpy.int16 и numpy.float64 — некоторые примеры.
- ndarray.itemsize
-
размер в байтах каждого элемента массива. Например, массив элементов типа
float64имеетitemsize8 (=64/8), а массив типаcomplex32имеетitemsize4 (=32/8). Это эквивалентноndarray.dtype.itemsize. - ndarray.data
-
буфер, содержащий фактические элементы массива. Обычно нам не нужно использовать этот атрибут, потому что мы будем обращаться к элементам массива с помощью средств индексирования.
Пример
>>> import numpy as np
>>> a = np.arange(15).reshape(3, 5)
>>> a
array([[ 0, 1, 2, 3, 4],
[ 5, 6, 7, 8, 9],
[10, 11, 12, 13, 14]])
>>> a.shape
(3, 5)
>>> a.ndim
2
>>> a.dtype.name
'int64'
>>> a.itemsize
8
>>> a.size
15
>>> type(a)
<class 'numpy.ndarray'>
>>> b = np.array([6, 7, 8])
>>> b
array([6, 7, 8])
>>> type(b)
<class 'numpy.ndarray'>
Создание массивов
Существует несколько способов создания массивов.
Например, вы можете создать массив из обычного списка или кортежа Python, используя функцию array. Тип результирующего массива определяется типом элементов в последовательностях.
>>> import numpy as np
>>> a = np.array([2, 3, 4])
>>> a
array([2, 3, 4])
>>> a.dtype
dtype('int64')
>>> b = np.array([1.2, 3.5, 5.1])
>>> b.dtype
dtype('float64')
Частая ошибка заключается в вызове array с несколькими аргументами вместо предоставления одной последовательности в качестве аргумента.
>>> a = np.array(1, 2, 3, 4) # WRONG Traceback (most recent call last): ... TypeError: array() takes from 1 to 2 positional arguments but 4 were given >>> a = np.array([1, 2, 3, 4]) # RIGHT
array преобразует последовательности последовательностей в двумерные массивы, последовательности последовательностей последовательностей в трехмерные массивы и так далее.
>>> b = np.array([(1.5, 2, 3), (4, 5, 6)])
>>> b
array([[1.5, 2. , 3. ],
[4. , 5. , 6. ]])
Тип массива также можно явно указать при создании:
>>> c = np.array([[1, 2], [3, 4]], dtype=complex)
>>> c
array([[1.+0.j, 2.+0.j],
[3.+0.j, 4.+0.j]])
Часто элементы массива изначально неизвестны, но известен его размер. Поэтому NumPy предлагает несколько функций для создания массивов с начальным содержимым-заполнителем. Они минимизируют необходимость увеличения массивов, что является дорогостоящей операцией.
Функция zeros создает массив, заполненный нулями, функция ones создает массив, заполненный единицами, а функция empty создает массив, начальное содержимое которого случайное и зависит от состояния памяти. По умолчанию dtype создаваемого массива — float64, но его можно указать с помощью ключевого аргумента dtype.
>>> np.zeros((3, 4))
array([[0., 0., 0., 0.],
[0., 0., 0., 0.],
[0., 0., 0., 0.]])
>>> np.ones((2, 3, 4), dtype=np.int16)
array([[[1, 1, 1, 1],
[1, 1, 1, 1],
[1, 1, 1, 1]],
[[1, 1, 1, 1],
[1, 1, 1, 1],
[1, 1, 1, 1]]], dtype=int16)
>>> np.empty((2, 3))
array([[3.73603959e-262, 6.02658058e-154, 6.55490914e-260], # may vary
[5.30498948e-313, 3.14673309e-307, 1.00000000e+000]])
Для создания последовательностей чисел NumPy предоставляет функцию arange, которая аналогична встроенной функции Python range, но возвращает массив.
>>> np.arange(10, 30, 5) array([10, 15, 20, 25]) >>> np.arange(0, 2, 0.3) # it accepts float arguments array([0. , 0.3, 0.6, 0.9, 1.2, 1.5, 1.8])
Когда arange используется с аргументами с плавающей запятой, обычно невозможно предсказать количество полученных элементов из-за конечной точности чисел с плавающей запятой. По этой причине обычно лучше использовать функцию linspace , которая получает в качестве аргумента количество элементов, которые мы хотим, вместо шага:
>>> from numpy import pi >>> np.linspace(0, 2, 9) # 9 numbers from 0 to 2 array([0. , 0.25, 0.5 , 0.75, 1. , 1.25, 1.5 , 1.75, 2. ]) >>> x = np.linspace(0, 2 * pi, 100) # useful to evaluate function at lots of points >>> f = np.sin(x)
См. также
array, zeros, zeros_like, ones, ones_like, empty, empty_like, arange, linspace, numpy.random.Generator.rand, numpy.random.Generator.randn, fromfunction, fromfile
Вывод массивов
При выводе массива NumPy отображает его аналогично вложенным спискам, но с указанным расположением:
- последняя ось выводится слева направо,
- предпоследняя — сверху вниз,
- остальные также выводятся сверху вниз, с каждой частью, разделенной пустой строкой.
Одномерные массивы выводятся как строки, двумерные — как матрицы, трехмерные — как списки матриц.
>>> a = np.arange(6) # 1d array >>> print(a) [0 1 2 3 4 5] >>> >>> b = np.arange(12).reshape(4, 3) # 2d array >>> print(b) [[ 0 1 2] [ 3 4 5] [ 6 7 8] [ 9 10 11]] >>> >>> c = np.arange(24).reshape(2, 3, 4) # 3d array >>> print(c) [[[ 0 1 2 3] [ 4 5 6 7] [ 8 9 10 11]] [[12 13 14 15] [16 17 18 19] [20 21 22 23]]]
См. ниже, чтобы узнать больше о reshape.
Если массив слишком велик для вывода, NumPy автоматически пропускает центральную часть массива и выводит только углы:
>>> print(np.arange(10000)) [ 0 1 2 ... 9997 9998 9999] >>> >>> print(np.arange(10000).reshape(100, 100)) [[ 0 1 2 ... 97 98 99] [ 100 101 102 ... 197 198 199] [ 200 201 202 ... 297 298 299] ... [9700 9701 9702 ... 9797 9798 9799] [9800 9801 9802 ... 9897 9898 9899] [9900 9901 9902 ... 9997 9998 9999]]
Чтобы отключить это поведение и принудить NumPy выводить весь массив, можно изменить параметры вывода с помощью set_printoptions.
>>> np.set_printoptions(threshold=sys.maxsize) # sys module should be imported
Основные операции
Арифметические операции с массивами применяются элементно. Создается и заполняется новый массив результатом.
>>> a = np.array([20, 30, 40, 50]) >>> b = np.arange(4) >>> b array([0, 1, 2, 3]) >>> c = a - b >>> c array([20, 29, 38, 47]) >>> b**2 array([0, 1, 4, 9]) >>> 10 * np.sin(a) array([ 9.12945251, -9.88031624, 7.4511316 , -2.62374854]) >>> a < 35 array([ True, True, False, False])
В отличие от многих языков матриц, оператор умножения * в массивах NumPy работает поэлементно. Умножение матриц можно выполнить с помощью оператора @ (в Python >=3.5) или функции dot или метода:
>>> A = np.array([[1, 1],
... [0, 1]])
>>> B = np.array([[2, 0],
... [3, 4]])
>>> A * B # elementwise product
array([[2, 0],
[0, 4]])
>>> A @ B # matrix product
array([[5, 4],
[3, 4]])
>>> A.dot(B) # another matrix product
array([[5, 4],
[3, 4]])
Некоторые операции, такие как += и *= , изменяют существующий массив на месте, а не создают новый.
>>> rg = np.random.default_rng(1) # create instance of default random number generator
>>> a = np.ones((2, 3), dtype=int)
>>> b = rg.random((2, 3))
>>> a *= 3
>>> a
array([[3, 3, 3],
[3, 3, 3]])
>>> b += a
>>> b
array([[3.51182162, 3.9504637 , 3.14415961],
[3.94864945, 3.31183145, 3.42332645]])
>>> a += b # b is not automatically converted to integer type
Traceback (most recent call last):
...
numpy.core._exceptions._UFuncOutputCastingError: Cannot cast ufunc 'add' output from dtype('float64') to dtype('int64') with casting rule 'same_kind'
При работе с массивами разных типов тип результирующего массива соответствует более общему или точному типу (поведение, известное как расширение типов).
>>> a = np.ones(3, dtype=np.int32)
>>> b = np.linspace(0, pi, 3)
>>> b.dtype.name
'float64'
>>> c = a + b
>>> c
array([1. , 2.57079633, 4.14159265])
>>> c.dtype.name
'float64'
>>> d = np.exp(c * 1j)
>>> d
array([ 0.54030231+0.84147098j, -0.84147098+0.54030231j,
-0.54030231-0.84147098j])
>>> d.dtype.name
'complex128'
Многие унарные операции, такие как вычисление суммы всех элементов в массиве, реализуются как методы класса ndarray.
>>> a = rg.random((2, 3))
>>> a
array([[0.82770259, 0.40919914, 0.54959369],
[0.02755911, 0.75351311, 0.53814331]])
>>> a.sum()
3.1057109529998157
>>> a.min()
0.027559113243068367
>>> a.max()
0.8277025938204418
По умолчанию эти операции применяются к массиву так, как будто он был списком чисел, независимо от его формы. Однако, указав параметр axis, вы можете применить операцию вдоль указанной оси массива:
>>> b = np.arange(12).reshape(3, 4)
>>> b
array([[ 0, 1, 2, 3],
[ 4, 5, 6, 7],
[ 8, 9, 10, 11]])
>>>
>>> b.sum(axis=0) # sum of each column
array([12, 15, 18, 21])
>>>
>>> b.min(axis=1) # min of each row
array([0, 4, 8])
>>>
>>> b.cumsum(axis=1) # cumulative sum along each row
array([[ 0, 1, 3, 6],
[ 4, 9, 15, 22],
[ 8, 17, 27, 38]])
Универсальные функции
NumPy предоставляет знакомые математические функции, такие как sin, cos и exp. В NumPy они называются «универсальными функциями» (ufunc). В NumPy эти функции работают поэлементно с массивом, производя массив в качестве результата.
>>> B = np.arange(3) >>> B array([0, 1, 2]) >>> np.exp(B) array([1. , 2.71828183, 7.3890561 ]) >>> np.sqrt(B) array([0. , 1. , 1.41421356]) >>> C = np.array([2., -1., 4.]) >>> np.add(B, C) array([2., 0., 6.])
См. также
all, any, apply_along_axis, argmax, argmin, argsort, average, bincount, ceil, clip, conj, corrcoef, cov, cross, cumprod, cumsum, diff, dot, floor, inner, invert, lexsort, max, maximum, mean, median, min, minimum, nonzero, outer, prod, re, round, sort, std, sum, trace, transpose, var, vdot, vectorize, where
Индексирование, срезы и итерация
Одномерные массивы можно индексировать, извлекать срезы и итерироваться по ним, как и по спискам и другим последовательностям Python.
>>> a = np.arange(10)**3 >>> a array([ 0, 1, 8, 27, 64, 125, 216, 343, 512, 729]) >>> a[2] 8 >>> a[2:5] array([ 8, 27, 64]) >>> # equivalent to a[0:6:2] = 1000; >>> # from start to position 6, exclusive, set every 2nd element to 1000 >>> a[:6:2] = 1000 >>> a array([1000, 1, 1000, 27, 1000, 125, 216, 343, 512, 729]) >>> a[::-1] # reversed a array([ 729, 512, 343, 216, 125, 1000, 27, 1000, 1, 1000]) >>> for i in a: ... print(i**(1 / 3.)) ... 9.999999999999998 1.0 9.999999999999998 3.0 9.999999999999998 4.999999999999999 5.999999999999999 6.999999999999999 7.999999999999999 8.999999999999998
Многомерные массивы могут иметь по одному индексу на каждую ось. Эти индексы задаются в кортеже, разделённом запятыми:
>>> def f(x, y):
... return 10 * x + y
...
>>> b = np.fromfunction(f, (5, 4), dtype=int)
>>> b
array([[ 0, 1, 2, 3],
[10, 11, 12, 13],
[20, 21, 22, 23],
[30, 31, 32, 33],
[40, 41, 42, 43]])
>>> b[2, 3]
23
>>> b[0:5, 1] # each row in the second column of b
array([ 1, 11, 21, 31, 41])
>>> b[:, 1] # equivalent to the previous example
array([ 1, 11, 21, 31, 41])
>>> b[1:3, :] # each column in the second and third row of b
array([[10, 11, 12, 13],
[20, 21, 22, 23]])
Когда предоставлено меньше индексов, чем осей, пропущенные индексы рассматриваются как полные срезы:
>>> b[-1] # the last row. Equivalent to b[-1, :] array([40, 41, 42, 43])
Выражение в квадратных скобках в b[i] рассматривается как i , за которым следуют столько же экземпляров : , сколько нужно для представления оставшихся осей. NumPy также позволяет записать это, используя точки как b[i, ...].
Точки (...) представляют столько двоеточий, сколько нужно для создания полного кортежа индексирования. Например, если x - это массив с 5 осями, то
-
x[1, 2, ...]эквивалентноx[1, 2, :, :, :], -
x[..., 3]эквивалентноx[:, :, :, :, 3]и -
x[4, ..., 5, :]эквивалентноx[4, :, :, 5, :].
>>> c = np.array([[[ 0, 1, 2], # a 3D array (two stacked 2D arrays)
... [ 10, 12, 13]],
... [[100, 101, 102],
... [110, 112, 113]]])
>>> c.shape
(2, 2, 3)
>>> c[1, ...] # same as c[1, :, :] or c[1]
array([[100, 101, 102],
[110, 112, 113]])
>>> c[..., 2] # same as c[:, :, 2]
array([[ 2, 13],
[102, 113]])
Итерация по многомерным массивам выполняется относительно первой оси:
>>> for row in b: ... print(row) ... [0 1 2 3] [10 11 12 13] [20 21 22 23] [30 31 32 33] [40 41 42 43]
Однако, если нужно выполнить операцию над каждым элементом в массиве, можно использовать атрибут flat, который является итератором по всем элементам массива:
>>> for element in b.flat: ... print(element) ... 0 1 2 3 10 11 12 13 20 21 22 23 30 31 32 33 40 41 42 43
См. также
Индексирование, Индексирование (справочник), newaxis, ndenumerate, indices
Изменение формы массива
Изменение формы массива
Форма массива определяется количеством элементов вдоль каждой оси:
>>> a = np.floor(10 * rg.random((3, 4)))
>>> a
array([[3., 7., 3., 4.],
[1., 4., 2., 2.],
[7., 2., 4., 9.]])
>>> a.shape
(3, 4)
Форму массива можно изменить с помощью различных команд. Обратите внимание, что следующие три команды возвращают изменённый массив, но не изменяют исходный массив:
>>> a.ravel() # returns the array, flattened
array([3., 7., 3., 4., 1., 4., 2., 2., 7., 2., 4., 9.])
>>> a.reshape(6, 2) # returns the array with a modified shape
array([[3., 7.],
[3., 4.],
[1., 4.],
[2., 2.],
[7., 2.],
[4., 9.]])
>>> a.T # returns the array, transposed
array([[3., 1., 7.],
[7., 4., 2.],
[3., 2., 4.],
[4., 2., 9.]])
>>> a.T.shape
(4, 3)
>>> a.shape
(3, 4)
Порядок элементов в массиве, полученном в результате ravel, обычно «C-стиль», то есть самый правый индекс «меняется быстрее», поэтому элемент после a[0, 0] - это a[0, 1]. Если массив преобразуется в другую форму, снова массив обрабатывается как «C-стиль». NumPy обычно создаёт массивы, хранящиеся в этом порядке, поэтому ravel обычно не нужно копировать свой аргумент, но если массив был создан путём взятия срезей из другого массива или создан с необычными параметрами, возможно, потребуется копирование. Функции ravel и reshape также могут быть настроены с помощью необязательного аргумента, чтобы использовать массивы в стиле FORTRAN, в котором самый левый индекс меняется быстрее.
Функция reshape возвращает аргумент с изменённой формой, в то время как метод ndarray.resize изменяет сам массив:
>>> a
array([[3., 7., 3., 4.],
[1., 4., 2., 2.],
[7., 2., 4., 9.]])
>>> a.resize((2, 6))
>>> a
array([[3., 7., 3., 4., 1., 4.],
[2., 2., 7., 2., 4., 9.]])
Если размерность задана как -1 в операции преобразования формы, другие размерности автоматически вычисляются:
>>> a.reshape(3, -1)
array([[3., 7., 3., 4.],
[1., 4., 2., 2.],
[7., 2., 4., 9.]])
См. также
Объединение массивов
Несколько массивов можно объединить вдоль разных осей:
>>> a = np.floor(10 * rg.random((2, 2)))
>>> a
array([[9., 7.],
[5., 2.]])
>>> b = np.floor(10 * rg.random((2, 2)))
>>> b
array([[1., 9.],
[5., 1.]])
>>> np.vstack((a, b))
array([[9., 7.],
[5., 2.],
[1., 9.],
[5., 1.]])
>>> np.hstack((a, b))
array([[9., 7., 1., 9.],
[5., 2., 5., 1.]])
Функция column_stack объединяет одномерные массивы в виде столбцов в двумерный массив. Она эквивалентна hstack только для двумерных массивов:
>>> from numpy import newaxis
>>> np.column_stack((a, b)) # with 2D arrays
array([[9., 7., 1., 9.],
[5., 2., 5., 1.]])
>>> a = np.array([4., 2.])
>>> b = np.array([3., 8.])
>>> np.column_stack((a, b)) # returns a 2D array
array([[4., 3.],
[2., 8.]])
>>> np.hstack((a, b)) # the result is different
array([4., 2., 3., 8.])
>>> a[:, newaxis] # view `a` as a 2D column vector
array([[4.],
[2.]])
>>> np.column_stack((a[:, newaxis], b[:, newaxis]))
array([[4., 3.],
[2., 8.]])
>>> np.hstack((a[:, newaxis], b[:, newaxis])) # the result is the same
array([[4., 3.],
[2., 8.]])
С другой стороны, функция row_stack эквивалентна vstack для любых входных массивов. Фактически, row_stack является псевдонимом для vstack:
>>> np.column_stack is np.hstack False >>> np.row_stack is np.vstack True
В общем случае, для массивов с более чем двумя измерениями, hstack склеивает по их второму осям, vstack склеивает по их первым осям, а concatenate позволяет использовать необязательные аргументы, указывающие номер оси, по которой должно произойти конкатенация.
Примечание
В сложных случаях r_ и c_ полезны для создания массивов путём склеивания чисел вдоль одной оси. Они позволяют использовать литералы диапазонов :.
>>> np.r_[1:4, 0, 4] array([1, 2, 3, 0, 4])
При использовании с массивами в качестве аргументов, r_ и c_ аналогичны vstack и hstack в своём поведении по умолчанию, но позволяют использовать необязательный аргумент, указывающий номер оси, по которой должна происходить конкатенация.
См. также
hstack, vstack, column_stack, concatenate, c_, r_
Разбиение одного массива на несколько меньших
Используя hsplit, вы можете разбить массив вдоль его горизонтальной оси, либо указав количество массивов одинаковой формы, которые нужно вернуть, либо указав столбцы, после которых должно произойти разделение:
>>> a = np.floor(10 * rg.random((2, 12)))
>>> a
array([[6., 7., 6., 9., 0., 5., 4., 0., 6., 8., 5., 2.],
[8., 5., 5., 7., 1., 8., 6., 7., 1., 8., 1., 0.]])
>>> # Split `a` into 3
>>> np.hsplit(a, 3)
[array([[6., 7., 6., 9.],
[8., 5., 5., 7.]]), array([[0., 5., 4., 0.],
[1., 8., 6., 7.]]), array([[6., 8., 5., 2.],
[1., 8., 1., 0.]])]
>>> # Split `a` after the third and the fourth column
>>> np.hsplit(a, (3, 4))
[array([[6., 7., 6.],
[8., 5., 5.]]), array([[9.],
[7.]]), array([[0., 5., 4., 0., 6., 8., 5., 2.],
[1., 8., 6., 7., 1., 8., 1., 0.]])]
vsplit разбивает вдоль вертикальной оси, а array_split позволяет указать, вдоль какой оси нужно разделить.
Копии и представления
При работе и манипулировании массивами данные иногда копируются в новый массив, а иногда нет. Это часто является источником путаницы для начинающих. Существует три случая:
Отсутствует копирование
Простые присваивания не создают копий объектов или их данных.
>>> a = np.array([[ 0, 1, 2, 3], ... [ 4, 5, 6, 7], ... [ 8, 9, 10, 11]]) >>> b = a # no new object is created >>> b is a # a and b are two names for the same ndarray object True
Python передает изменяемые объекты по ссылке, поэтому вызовы функций не создают копий.
>>> def f(x): ... print(id(x)) ... >>> id(a) # id is a unique identifier of an object 148293216 # may vary >>> f(a) 148293216 # may vary
Представление или поверхностная копия
Разные объекты массивов могут использовать одни и те же данные. Метод view создаёт новый объект массива, который смотрит на те же данные.
>>> c = a.view()
>>> c is a
False
>>> c.base is a # c is a view of the data owned by a
True
>>> c.flags.owndata
False
>>>
>>> c = c.reshape((2, 6)) # a's shape doesn't change
>>> a.shape
(3, 4)
>>> c[0, 4] = 1234 # a's data changes
>>> a
array([[ 0, 1, 2, 3],
[1234, 5, 6, 7],
[ 8, 9, 10, 11]])
Вырезка массива возвращает представление его данных:
>>> s = a[:, 1:3]
>>> s[:] = 10 # s[:] is a view of s. Note the difference between s = 10 and s[:] = 10
>>> a
array([[ 0, 10, 10, 3],
[1234, 10, 10, 7],
[ 8, 10, 10, 11]])
Глубокая копия
Метод copy создаёт полную копию массива и его данных.
>>> d = a.copy() # a new array object with new data is created
>>> d is a
False
>>> d.base is a # d doesn't share anything with a
False
>>> d[0, 0] = 9999
>>> a
array([[ 0, 10, 10, 3],
[1234, 10, 10, 7],
[ 8, 10, 10, 11]])
Иногда copy необходимо вызывать после среза, если исходный массив больше не требуется. Например, предположим, что a является огромным промежуточным результатом, а конечный результат b содержит лишь небольшую часть a, глубокая копия должна быть сделана при построении b с использованием среза:
>>> a = np.arange(int(1e8)) >>> b = a[:100].copy() >>> del a # the memory of ``a`` can be released.
Если вместо этого использовать b = a[:100], a будет ссылаться на b и сохранится в памяти, даже если del a будет выполнен.
Обзор функций и методов
Вот список некоторых полезных функций и методов NumPy, упорядоченных по категориям. См. Процедуры для получения полного списка.
- Создание массивов
-
arange,array,copy,empty,empty_like,eye,fromfile,fromfunction,identity,linspace,logspace,mgrid,ogrid,ones,ones_like,r_,zeros,zeros_like - Преобразования
- Манипуляции
-
array_split,column_stack,concatenate,diagonal,dsplit,dstack,hsplit,hstack,ndarray.item,newaxis,ravel,repeat,reshape,resize,squeeze,swapaxes,take,transpose,vsplit,vstack - Вопросы
- Сортировка
- Операции
-
choose,compress,cumprod,cumsum,inner,ndarray.fill,imag,prod,put,putmask,real,sum - Основные статистические показатели
- Основы линейной алгебры
-
cross,dot,outer,linalg.svd,vdot
Менее базовые
Правила вещания
Вещание позволяет универсальным функциям осмысленно обрабатывать входные данные, которые не имеют точно одинаковой формы.
Первое правило вещания заключается в том, что если все входные массивы не имеют одинакового числа измерений, то «1» будет многократно добавляться к формам меньших массивов до тех пор, пока все массивы не будут иметь одинаковое количество измерений.
Второе правило вещания гарантирует, что массивы с размером 1 вдоль конкретного измерения ведут себя так, как если бы они имели размер массива с наибольшей формой вдоль этого измерения. Значение элемента массива предполагается одинаковым вдоль этого измерения для массива «вещания».
После применения правил трансляции размеры всех массивов должны совпадать. Более подробную информацию можно найти в Трансляции.
Расширенное индексирование и трюки с индексами
NumPy предлагает больше возможностей индексирования, чем обычные последовательности Python. Помимо индексирования целыми числами и срезами, как мы видели ранее, массивы могут индексироваться массивами целых чисел и массивами булевых значений.
Индексирование массивами индексов
>>> a = np.arange(12)**2 # the first 12 square numbers
>>> i = np.array([1, 1, 3, 8, 5]) # an array of indices
>>> a[i] # the elements of `a` at the positions `i`
array([ 1, 1, 9, 64, 25])
>>>
>>> j = np.array([[3, 4], [9, 7]]) # a bidimensional array of indices
>>> a[j] # the same shape as `j`
array([[ 9, 16],
[81, 49]])
Когда индексируемый массив a является многомерным, один массив индексов относится к первому измерению a. Следующий пример демонстрирует это поведение, преобразуя изображение меток в цветное изображение с использованием палитры.
>>> palette = np.array([[0, 0, 0], # black
... [255, 0, 0], # red
... [0, 255, 0], # green
... [0, 0, 255], # blue
... [255, 255, 255]]) # white
>>> image = np.array([[0, 1, 2, 0], # each value corresponds to a color in the palette
... [0, 3, 4, 0]])
>>> palette[image] # the (2, 4, 3) color image
array([[[ 0, 0, 0],
[255, 0, 0],
[ 0, 255, 0],
[ 0, 0, 0]],
[[ 0, 0, 0],
[ 0, 0, 255],
[255, 255, 255],
[ 0, 0, 0]]])
Мы также можем указать индексы для нескольких измерений. Массивы индексов для каждого измерения должны иметь одинаковую форму.
>>> a = np.arange(12).reshape(3, 4)
>>> a
array([[ 0, 1, 2, 3],
[ 4, 5, 6, 7],
[ 8, 9, 10, 11]])
>>> i = np.array([[0, 1], # indices for the first dim of `a`
... [1, 2]])
>>> j = np.array([[2, 1], # indices for the second dim
... [3, 3]])
>>>
>>> a[i, j] # i and j must have equal shape
array([[ 2, 5],
[ 7, 11]])
>>>
>>> a[i, 2]
array([[ 2, 6],
[ 6, 10]])
>>>
>>> a[:, j]
array([[[ 2, 1],
[ 3, 3]],
[[ 6, 5],
[ 7, 7]],
[[10, 9],
[11, 11]]])
В Python, arr[i, j] точно такое же, как arr[(i, j)] — поэтому мы можем поместить i и j в tuple и затем выполнить индексирование с помощью этого.
>>> l = (i, j)
>>> # equivalent to a[i, j]
>>> a[l]
array([[ 2, 5],
[ 7, 11]])
Однако, мы не можем сделать это, поместив i и j в массив, потому что этот массив будет интерпретирован как индексирование первого измерения a.
>>> s = np.array([i, j])
>>> # not what we want
>>> a[s]
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
IndexError: index 3 is out of bounds for axis 0 with size 3
>>> # same as `a[i, j]`
>>> a[tuple(s)]
array([[ 2, 5],
[ 7, 11]])
Другое распространённое использование индексирования массивами — поиск максимального значения временных рядов:
>>> time = np.linspace(20, 145, 5) # time scale
>>> data = np.sin(np.arange(20)).reshape(5, 4) # 4 time-dependent series
>>> time
array([ 20. , 51.25, 82.5 , 113.75, 145. ])
>>> data
array([[ 0. , 0.84147098, 0.90929743, 0.14112001],
[-0.7568025 , -0.95892427, -0.2794155 , 0.6569866 ],
[ 0.98935825, 0.41211849, -0.54402111, -0.99999021],
[-0.53657292, 0.42016704, 0.99060736, 0.65028784],
[-0.28790332, -0.96139749, -0.75098725, 0.14987721]])
>>> # index of the maxima for each series
>>> ind = data.argmax(axis=0)
>>> ind
array([2, 0, 3, 1])
>>> # times corresponding to the maxima
>>> time_max = time[ind]
>>>
>>> data_max = data[ind, range(data.shape[1])] # => data[ind[0], 0], data[ind[1], 1]...
>>> time_max
array([ 82.5 , 20. , 113.75, 51.25])
>>> data_max
array([0.98935825, 0.84147098, 0.99060736, 0.6569866 ])
>>> np.all(data_max == data.max(axis=0))
True
Вы также можете использовать индексирование массивами как цель для присваивания:
>>> a = np.arange(5) >>> a array([0, 1, 2, 3, 4]) >>> a[[1, 3, 4]] = 0 >>> a array([0, 0, 2, 0, 0])
Однако, когда список индексов содержит повторения, присваивание выполняется несколько раз, оставляя последнюю записанную величину:
>>> a = np.arange(5) >>> a[[0, 0, 2]] = [1, 2, 3] >>> a array([2, 1, 3, 3, 4])
Это вполне разумно, но будьте осторожны, если вы хотите использовать конструкцию Python +=, так как она может не делать того, чего вы ожидаете:
>>> a = np.arange(5) >>> a[[0, 0, 2]] += 1 >>> a array([1, 1, 3, 3, 4])
Несмотря на то, что 0 встречается дважды в списке индексов, элемент с индексом 0 увеличивается только один раз. Это происходит потому, что Python требует, чтобы a += 1 было эквивалентно a = a + 1.
Индексирование массивами булевых значений
Когда мы индексируем массивы массивами (целых) индексов, мы предоставляем список индексов для выбора. С булевыми индексами подход другой; мы явно выбираем, какие элементы массива мы хотим, а какие нет.
Наиболее естественный способ для булевого индексирования — использовать булевы массивы, имеющие такую же форму, как исходный массив:
>>> a = np.arange(12).reshape(3, 4)
>>> b = a > 4
>>> b # `b` is a boolean with `a`'s shape
array([[False, False, False, False],
[False, True, True, True],
[ True, True, True, True]])
>>> a[b] # 1d array with the selected elements
array([ 5, 6, 7, 8, 9, 10, 11])
Это свойство может быть очень полезно при присваивании:
>>> a[b] = 0 # All elements of `a` higher than 4 become 0
>>> a
array([[0, 1, 2, 3],
[4, 0, 0, 0],
[0, 0, 0, 0]])
Вы можете посмотреть на следующий пример, чтобы увидеть, как использовать булевое индексирование для генерации изображения множества Мандельброта:
>>> import numpy as np >>> import matplotlib.pyplot as plt >>> def mandelbrot(h, w, maxit=20, r=2): ... """Returns an image of the Mandelbrot fractal of size (h,w).""" ... x = np.linspace(-2.5, 1.5, 4*h+1) ... y = np.linspace(-1.5, 1.5, 3*w+1) ... A, B = np.meshgrid(x, y) ... C = A + B*1j ... z = np.zeros_like(C) ... divtime = maxit + np.zeros(z.shape, dtype=int) ... ... for i in range(maxit): ... z = z**2 + C ... diverge = abs(z) > r # who is diverging ... div_now = diverge & (divtime == maxit) # who is diverging now ... divtime[div_now] = i # note when ... z[diverge] = r # avoid diverging too much ... ... return divtime >>> plt.imshow(mandelbrot(400, 400))
Второй способ индексирования булевыми значениями более похож на индексирование целыми числами; для каждого измерения массива мы даём одномерный булев массив, выбирающий нужные нам срезы:
>>> a = np.arange(12).reshape(3, 4)
>>> b1 = np.array([False, True, True]) # first dim selection
>>> b2 = np.array([True, False, True, False]) # second dim selection
>>>
>>> a[b1, :] # selecting rows
array([[ 4, 5, 6, 7],
[ 8, 9, 10, 11]])
>>>
>>> a[b1] # same thing
array([[ 4, 5, 6, 7],
[ 8, 9, 10, 11]])
>>>
>>> a[:, b2] # selecting columns
array([[ 0, 2],
[ 4, 6],
[ 8, 10]])
>>>
>>> a[b1, b2] # a weird thing to do
array([ 4, 10])
Обратите внимание, что длина одномерного булева массива должна совпадать с длиной измерения (или оси), которое вы хотите нарезать. В предыдущем примере b1 имеет длину 3 (количество строк в a), а b2 (длины 4) подходит для индексирования второй оси (столбцов) a.
Функция ix_()
Функция ix_ может использоваться для объединения различных векторов, чтобы получить результат для каждого n-кортежа. Например, если вы хотите вычислить все a+b*c для всех троек, взятых из каждого из векторов a, b и c:
>>> a = np.array([2, 3, 4, 5])
>>> b = np.array([8, 5, 4])
>>> c = np.array([5, 4, 6, 8, 3])
>>> ax, bx, cx = np.ix_(a, b, c)
>>> ax
array([[[2]],
[[3]],
[[4]],
[[5]]])
>>> bx
array([[[8],
[5],
[4]]])
>>> cx
array([[[5, 4, 6, 8, 3]]])
>>> ax.shape, bx.shape, cx.shape
((4, 1, 1), (1, 3, 1), (1, 1, 5))
>>> result = ax + bx * cx
>>> result
array([[[42, 34, 50, 66, 26],
[27, 22, 32, 42, 17],
[22, 18, 26, 34, 14]],
[[43, 35, 51, 67, 27],
[28, 23, 33, 43, 18],
[23, 19, 27, 35, 15]],
[[44, 36, 52, 68, 28],
[29, 24, 34, 44, 19],
[24, 20, 28, 36, 16]],
[[45, 37, 53, 69, 29],
[30, 25, 35, 45, 20],
[25, 21, 29, 37, 17]]])
>>> result[3, 2, 4]
17
>>> a[3] + b[2] * c[4]
17
Вы также можете реализовать reduce следующим образом:
>>> def ufunc_reduce(ufct, *vectors): ... vs = np.ix_(*vectors) ... r = ufct.identity ... for v in vs: ... r = ufct(r, v) ... return r
и затем использовать его как:
>>> ufunc_reduce(np.add, a, b, c)
array([[[15, 14, 16, 18, 13],
[12, 11, 13, 15, 10],
[11, 10, 12, 14, 9]],
[[16, 15, 17, 19, 14],
[13, 12, 14, 16, 11],
[12, 11, 13, 15, 10]],
[[17, 16, 18, 20, 15],
[14, 13, 15, 17, 12],
[13, 12, 14, 16, 11]],
[[18, 17, 19, 21, 16],
[15, 14, 16, 18, 13],
[14, 13, 15, 17, 12]]])
Преимущество этой версии reduce по сравнению с обычным ufunc.reduce заключается в том, что она использует правила трансляции, чтобы избежать создания массива аргументов размером с выходные данные умноженным на количество векторов.
Индексирование строками
См. Структурированные массивы.
Трюки и советы
Здесь представлен список коротких и полезных советов.
“Автоматическое” изменение формы
Для изменения размеров массива вы можете опустить один из размеров, который затем будет вычислен автоматически:
>>> a = np.arange(30)
>>> b = a.reshape((2, -1, 3)) # -1 means "whatever is needed"
>>> b.shape
(2, 5, 3)
>>> b
array([[[ 0, 1, 2],
[ 3, 4, 5],
[ 6, 7, 8],
[ 9, 10, 11],
[12, 13, 14]],
[[15, 16, 17],
[18, 19, 20],
[21, 22, 23],
[24, 25, 26],
[27, 28, 29]]])
Укладка векторов
Как мы создаём 2D массив из списка векторов строк одинаковой длины? В MATLAB это довольно просто: если x и y — это два вектора одной длины, вам нужно только m=[x;y]. В NumPy это работает с помощью функций column_stack, dstack, hstack и vstack, в зависимости от измерения, в котором необходимо осуществить укладку. Например:
>>> x = np.arange(0, 10, 2)
>>> y = np.arange(5)
>>> m = np.vstack([x, y])
>>> m
array([[0, 2, 4, 6, 8],
[0, 1, 2, 3, 4]])
>>> xy = np.hstack([x, y])
>>> xy
array([0, 2, 4, 6, 8, 0, 1, 2, 3, 4])
Логика работы этих функций в более чем двух измерениях может быть необычной.
См. также
Гистограммы
Функция NumPy histogram, применённая к массиву, возвращает пару векторов: гистограмму массива и вектор границ бинов. Осторожно: matplotlib также имеет функцию для построения гистограмм (называемую hist, как в MATLAB), которая отличается от функции в NumPy. Основное отличие в том, что pylab.hist строит гистограмму автоматически, в то время как numpy.histogram только генерирует данные.
>>> import numpy as np >>> rg = np.random.default_rng(1) >>> import matplotlib.pyplot as plt >>> # Build a vector of 10000 normal deviates with variance 0.5^2 and mean 2 >>> mu, sigma = 2, 0.5 >>> v = rg.normal(mu, sigma, 10000) >>> # Plot a normalized histogram with 50 bins >>> plt.hist(v, bins=50, density=True) # matplotlib version (plot) >>> # Compute the histogram with numpy and then plot it >>> (n, bins) = np.histogram(v, bins=50, density=True) # NumPy version (no plot) >>> plt.plot(.5 * (bins[1:] + bins[:-1]), n)
С Matplotlib >=3.4 вы также можете использовать plt.stairs(n, bins).
Дополнительное чтение
- Учебник Python Python tutorial
- Справочник команд
- Учебник SciPy
- Заметки по курсу SciPy
- Словарь matlab, R, IDL, NumPy/SciPy
- Учебник: Линейная алгебра на n-мерных массивах
© 2005–2022 NumPy Developers
Licensed under the 3-clause BSD License.
https://numpy.org/doc/1.21/user/quickstart.html