Быстрый старт NumPy#

Предварительные требования#

Вам потребуются базовые знания Python. Для повторения см. Python tutorial.

Для работы с примерами вам понадобится matplotlib установлен в дополнение к NumPy.

Профиль обучающегося

Это краткий обзор массивов в NumPy. Он демонстрирует, как n-мерные\(n>=2\)) массивы представлены и могут быть обработаны. В частности, если вы не знаете, как применять общие функции к n-мерным массивам (без использования циклов for), или если вы хотите понять свойства оси и формы для n-мерных массивов, эта статья может помочь.

Цели обучения

После прочтения вы должны уметь:

  • Поймите разницу между одномерными, двумерными и n-мерными массивами в NumPy;

  • Понять, как применять некоторые операции линейной алгебры к n-мерным массивам без использования циклов for;

  • Понимание свойств axis и shape для 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 имеет itemsize 8 (=64/8), в то время как один из типа complex32 имеет itemsize 4 (=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)

>>> b = np.array([6, 7, 8])
>>> b
array([6, 7, 8])
>>> type(b)

Создание массивов#

Существует несколько способов создания массивов.

Например, можно создать массив из обычного списка или кортежа 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)

Вывод массивов#

При выводе массива 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.])

Индексирование, срезы и итерация#

Одномерный массивы могут индексироваться, срезаться и итерироваться, почти как списки и другие последовательности 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  # may vary
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, ...].

The точки (...) представляют столько двоеточий, сколько необходимо для создания полного кортежа индексации. Например, если x является массивом с 5 осями, тогда

  • x[1, 2, ...] эквивалентно x[1, 2, :, :, :],

  • x[..., 3] to x[:, :, :, :, 3] и

  • x[4, ..., 5, :] to 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

Манипуляция формой#

Изменение формы массива#

Массив имеет форму, заданную количеством элементов вдоль каждой оси:

>>> 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, в которых самый левый индекс изменяется быстрее всего.

The 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.]])

Смотрите также

ndarray.shape, reshape, resize, ravel

Объединение различных массивов#

Несколько массивов могут быть объединены вместе по разным осям:

>>> 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 укладывает 1D-массивы как столбцы в 2D-массив. Эквивалентно hstack только для 2D массивов:

>>> 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.]])

В общем случае для массивов с более чем двумя измерениями, 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, reassigned c is still a view of a
>>> 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]])

Глубокая копия#

The 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

Преобразования

ndarray.astype, atleast_1d, atleast_2d, atleast_3d, mat

Манипуляции

array_split, column_stack, concatenate, diagonal, dsplit, dstack, hsplit, hstack, ndarray.item, newaxis, ravel, repeat, reshape, resize, squeeze, swapaxes, take, transpose, vsplit, vstack

Вопросы

all, any, nonzero, where

Упорядочивание

argmax, argmin, argsort, max, min, ptp, searchsorted, sort

Операции

choose, compress, cumprod, cumsum, inner, ndarray.fill, imag, prod, put, putmask, real, sum

Основная статистика

cov, mean, std, var

Basic Linear Algebra

cross, dot, outer, linalg.svd, vdot

Менее базовый#

Правила трансляции#

Трансляция позволяет универсальным функциям осмысленно работать с входными данными, которые не имеют точно такой же формы.

Первое правило трансляции заключается в том, что если все входные массивы не имеют одинакового количества измерений, "1" будет неоднократно добавляться в начало форм меньших массивов до тех пор, пока все массивы не будут иметь одинаковое количество измерений.

Второе правило трансляции гарантирует, что массивы размером 1 вдоль определённого измерения действуют так, как если бы они имели размер массива с наибольшей формой вдоль этого измерения. Значение элемента массива предполагается одинаковым вдоль этого измерения для «транслируемого» массива.

После применения правил трансляции размеры всех массивов должны совпадать. Подробнее можно найти в Трансляция (Broadcasting).

Расширенная индексация и трюки с индексами#

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 "", line 1, in 
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.clf()
>>> plt.imshow(mandelbrot(400, 400))
../_images/quickstart-1.png

Второй способ индексации с булевыми значениями более похож на целочисленную индексацию; для каждого измерения массива мы даем одномерный булев массив, выбирающий нужные срезы:

>>> 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) подходит для индексации 2-й оси (столбцов) a.

Функция ix_()#

The 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]]])

Векторное объединение#

Как построить двумерный массив из списка векторов-строк одинакового размера? В 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)
(array...)
>>> # 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) 
../_images/quickstart-2.png

С Matplotlib >=3.4 вы также можете использовать plt.stairs(n, bins).

Дополнительное чтение#