Spec-Zone.ru › NumPy 2.0

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

Предварительные условия

Вам нужно немного знать Python. Для освежения, см. туториал по Python.

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

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

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

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

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

  • Понимать разницу между одномерными, двумерными и многомерными массивами в NumPy;
  • Понимать, как применять некоторые операции линейной алгебры к многомерным массивам без использования циклов for;
  • Понимать свойства оси и формы многомерных массивов.

Основы

Основной объект 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)
<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, random.Generator.random, random.Generator.normal, 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  # 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, ...].

Точки (...) представляют столько двоеточий, сколько необходимо для создания полного кортежа индексирования. Например, если 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

См. также

Индексирование nd-массивов, Индексирующие процедуры (справочник), 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.]])

См. также

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 объединяет одномерные массивы в столбцы в двумерный массив. Она эквивалентна 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.]])

В общем случае для массивов с более чем двумя измерениями 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

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

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

Основные линейные алгебраические операции

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

Укладка векторов

Как создать двумерный массив из списка векторов строк одинаковой длины? В 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 для пользователей MATLAB

Гистограммы

Функция 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).

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

  • Руководство по Python
  • Справочник NumPy
  • Руководство по SciPy
  • Заметки по лекциям SciPy
  • Словарь MATLAB, R, IDL, NumPy/SciPy
  • tutorial-svd

© 2005–2024 NumPy Developers
Licensed under the 3-clause BSD License.
https://numpy.org/doc/2.0/user/quickstart.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API