import numpy as np
a = np.arange(12) ** 2
aarray([ 0, 1, 4, 9, 16, 25, 36, 49, 64, 81, 100, 121])
Одномерные массивы очень похожи на простые списки Python:
import numpy as np
a = np.arange(12) ** 2
aarray([ 0, 1, 4, 9, 16, 25, 36, 49, 64, 81, 100, 121])
# Обратиться к элементу можно по его индексу
a[0]0
a[-1]121
# Извлечение срезов по двум индексам массива
a[4:8]array([16, 25, 36, 49])
a[:6], a[6:](array([ 0, 1, 4, 9, 16, 25]), array([ 36, 49, 64, 81, 100, 121]))
# Извлечение элементов с определенным шагом
a[::3]array([ 0, 9, 36, 81])
a[::-1] # элементы массива в обратном порядкеarray([121, 100, 81, 64, 49, 36, 25, 16, 9, 4, 1, 0])
# Каждому второму элементу можно присвоить значение
a[:12:2] = -1aarray([ -1, 1, -1, 9, -1, 25, -1, 49, -1, 81, -1, 121])
Многомерные массивы имеют один индекс на одну ось, поэтому обращение к элементам и извлечение срезов производится не по одному, а по нескольким индексам:
a = np.arange(27).reshape(3, 9)
aarray([[ 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]])
a[0, 0]0
# В качестве срезов можно извлекать как столбцы так и строки
a[:, 0] # Извлекаем столбец, равносильно a[0:3, 0]array([ 0, 9, 18])
a[:, 1]array([ 1, 10, 19])
a[1:3, 2] # Можно извлечь часть столбцаarray([11, 20])
a[0] # Извлекаем строку, равносильно a[0, :]array([0, 1, 2, 3, 4, 5, 6, 7, 8])
a[0, 2:7] # Можно извлечь часть строкиarray([2, 3, 4, 5, 6])
a[2, 4:5]array([22])
Те же правила действуют и для многомерных массивов.
Работая с последовательностями: строками, списками или кортежами, мы привыкли только к одному единственному способу индексирования, который предлагает нам Python.
Неужели это можно сделать как-то по другому?
Оказывается можно, и, иногда это оказывается гораздо удобнее, чем может показаться.
Помимо индексирования целыми числами и срезами, к которому мы так сильно привыкли, NumPy предлагает еще два необычных метода индексирования.
Первый - это индексация с помощью массивов целых чисел, а второй - это индексация с помощью массивов булевых значений.
Что такое массив индексов?
Массив индексов - это обычный массив (последовательность) целых чисел, причем каждое число соответствует некоторому индексу определенного элемента в другом массиве, например:
a = np.arange(10, 20)
aarray([10, 11, 12, 13, 14, 15, 16, 17, 18, 19])
i = np.array([1, 1, 1, 9, 8, 5])
iarray([1, 1, 1, 9, 8, 5])
b = a[i]
barray([11, 11, 11, 19, 18, 15])
Но на практике гораздо удобнее использовать в качестве массивов индексов списки Python. Относительно вышеприведенного примера это будет выглядеть следующим образом:
aarray([10, 11, 12, 13, 14, 15, 16, 17, 18, 19])
a[[1, 1, 1, 5, 8, 9]]array([11, 11, 11, 15, 18, 19])
Булевых значений всего два, это False и True.
Возникает весьма интересный вопрос, как с помощью таких массивов выполняется индексация, ведь они состоят из одних лишь логических значиний.
И снова, оказывается не только возможно, но и крайне удобно в некоторых случаях:
a = np.arange(5)
aarray([0, 1, 2, 3, 4])
a[np.array([False, True, True, False, True])]array([1, 2, 4])
a = np.arange(18).reshape(3, 6)
aarray([[ 0, 1, 2, 3, 4, 5],
[ 6, 7, 8, 9, 10, 11],
[12, 13, 14, 15, 16, 17]])
a[a % 2 == 0] # Создадим булев массивarray([ 0, 2, 4, 6, 8, 10, 12, 14, 16])
a[a % 2 == 0] = -1 # Заменим все четные числа на -1aarray([[-1, 1, -1, 3, -1, 5],
[-1, 7, -1, 9, -1, 11],
[-1, 13, -1, 15, -1, 17]])
a[a > 1]array([ 3, 5, 7, 9, 11, 13, 15, 17])
aarray([[-1, 1, -1, 3, -1, 5],
[-1, 7, -1, 9, -1, 11],
[-1, 13, -1, 15, -1, 17]])
a[(0 < a) & (a < 20)]array([ 1, 3, 5, 7, 9, 11, 13, 15, 17])
Форму массива определяет количество элементов вдоль каждой оси:
a = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9], [10, 11, 12]])
aarray([[ 1, 2, 3],
[ 4, 5, 6],
[ 7, 8, 9],
[10, 11, 12]])
a.shape # Узнаем форму массива(4, 3)
a.ravel() # "Сплющивает" массив до одной осиarray([ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12])
Изменить форму массива можно разными способами:
a.reshape(2, 6) # Возвращает массив с измененной формойarray([[ 1, 2, 3, 4, 5, 6],
[ 7, 8, 9, 10, 11, 12]])
a.reshape(3, 4)array([[ 1, 2, 3, 4],
[ 5, 6, 7, 8],
[ 9, 10, 11, 12]])
a.T # Возвращает транспонированный массивarray([[ 1, 4, 7, 10],
[ 2, 5, 8, 11],
[ 3, 6, 9, 12]])
aarray([[ 1, 2, 3],
[ 4, 5, 6],
[ 7, 8, 9],
[10, 11, 12]])
Важно понимать, что функция reshape не изменяет исходный массив.
Если же вам необходимо изменить форму существующего массива, то можно воспользоваться методом resize.
aarray([[ 1, 2, 3],
[ 4, 5, 6],
[ 7, 8, 9],
[10, 11, 12]])
a.resize((2, 6))aarray([[ 1, 2, 3, 4, 5, 6],
[ 7, 8, 9, 10, 11, 12]])
Если в функции reshape один из размеров задать равным -1, то этот размер будет вычислен автоматически:
aarray([[ 1, 2, 3, 4, 5, 6],
[ 7, 8, 9, 10, 11, 12]])
a.reshape(3, -1)array([[ 1, 2, 3, 4],
[ 5, 6, 7, 8],
[ 9, 10, 11, 12]])
a.reshape(4, -1)array([[ 1, 2, 3],
[ 4, 5, 6],
[ 7, 8, 9],
[10, 11, 12]])
Объединение массивов возможно по разным осям:
a = np.arange(4).reshape(2, 2)
aarray([[0, 1],
[2, 3]])
b = np.arange(6, 10).reshape(2, 2)
barray([[6, 7],
[8, 9]])
np.vstack((a, b))array([[0, 1],
[2, 3],
[6, 7],
[8, 9]])
np.hstack((a, b))array([[0, 1, 6, 7],
[2, 3, 8, 9]])
Как видим функция vstack объединяет массивы по вертикали, а hstack - по горизонтали. Причем количество элементов вдоль объединяемых осей должно быть одинаковым.
Функция column_stack представляет одномерные массивы в виде столбцов после чего объединяет их.
Для двумерных массивов column_stack эквивалентна hstack:
a = np.arange(2)
aarray([0, 1])
b = np.arange(2, 4)
barray([2, 3])
np.column_stack((a, b))array([[0, 2],
[1, 3]])
Функция row_stack эквивалентна vstack для любых входных массивов:
a = np.arange(4)
aarray([0, 1, 2, 3])
b = np.arange(4, 8)
barray([4, 5, 6, 7])
np.row_stack((a, b))array([[0, 1, 2, 3],
[4, 5, 6, 7]])
Есть еще один способ быстро объединять и создавать массивы - это объекты r_ и c_.
Они позволяют объединить массивы вдоль одной оси или позволяют создать новый массив с использованием операций среза.
np.r_[0:5] # Создает массив из срезаarray([0, 1, 2, 3, 4])
np.r_[0:5, 3, 2, 1, 0] # Создает массив из среза и последовательностиarray([0, 1, 2, 3, 4, 3, 2, 1, 0])
np.c_[0:5]array([[0],
[1],
[2],
[3],
[4]])
Разделить массив вдоль горизонтальной оси можно с помощью функции hsplit, а вдоль вертикальной vsplit:
a = np.arange(10)
aarray([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
np.hsplit(a, 2)[array([0, 1, 2, 3, 4]), array([5, 6, 7, 8, 9])]
Вы, может быть уже заметили, что некоторые функции, что-то делают с массивом, возвращают какой-то результат, но при этом с исходным массивом ничего не происходит.
Например:
a = np.arange(18).reshape(3, 6)
aarray([[ 0, 1, 2, 3, 4, 5],
[ 6, 7, 8, 9, 10, 11],
[12, 13, 14, 15, 16, 17]])
a.Tarray([[ 0, 6, 12],
[ 1, 7, 13],
[ 2, 8, 14],
[ 3, 9, 15],
[ 4, 10, 16],
[ 5, 11, 17]])
aarray([[ 0, 1, 2, 3, 4, 5],
[ 6, 7, 8, 9, 10, 11],
[12, 13, 14, 15, 16, 17]])
Дело в том, что в NumPy существует два понятия: копия массива и представление массива.
Попробуем разобраться на примерах.
a = np.arange(18).reshape(3, 6)
aarray([[ 0, 1, 2, 3, 4, 5],
[ 6, 7, 8, 9, 10, 11],
[12, 13, 14, 15, 16, 17]])
b = a
c = a.Tbarray([[ 0, 1, 2, 3, 4, 5],
[ 6, 7, 8, 9, 10, 11],
[12, 13, 14, 15, 16, 17]])
carray([[ 0, 6, 12],
[ 1, 7, 13],
[ 2, 8, 14],
[ 3, 9, 15],
[ 4, 10, 16],
[ 5, 11, 17]])
a[0, 0] = -1aarray([[-1, 1, 2, 3, 4, 5],
[ 6, 7, 8, 9, 10, 11],
[12, 13, 14, 15, 16, 17]])
barray([[-1, 1, 2, 3, 4, 5],
[ 6, 7, 8, 9, 10, 11],
[12, 13, 14, 15, 16, 17]])
carray([[-1, 6, 12],
[ 1, 7, 13],
[ 2, 8, 14],
[ 3, 9, 15],
[ 4, 10, 16],
[ 5, 11, 17]])
Хорошо, a и b - это адреса (указатели) одного и того же массива.
Как быть с переменной с?
По сути это тоже указатель, который ссылается на ту же самую область памяти с данными, на которую ссылаются a и b, но представлены эти данные в другой форме.
Поэтому в NumPy и существует понятие - представление массива.
Действительно, одни и те же данные могут быть представлены в разной форме:
a = np.arange(18).reshape(3, 6)
aarray([[ 0, 1, 2, 3, 4, 5],
[ 6, 7, 8, 9, 10, 11],
[12, 13, 14, 15, 16, 17]])
b = a.reshape(2, 9)
barray([[ 0, 1, 2, 3, 4, 5, 6, 7, 8],
[ 9, 10, 11, 12, 13, 14, 15, 16, 17]])
c = a.reshape(2, 3, 3)
carray([[[ 0, 1, 2],
[ 3, 4, 5],
[ 6, 7, 8]],
[[ 9, 10, 11],
[12, 13, 14],
[15, 16, 17]]])
# Но если изменить один элемент через а
# то это отразится во всех представлениях
a[0, 0] = 7777barray([[7777, 1, 2, 3, 4, 5, 6, 7, 8],
[ 9, 10, 11, 12, 13, 14, 15, 16, 17]])
carray([[[7777, 1, 2],
[ 3, 4, 5],
[ 6, 7, 8]],
[[ 9, 10, 11],
[ 12, 13, 14],
[ 15, 16, 17]]])
Простое присваивание не делает никаких копий массива - это первое, о чем нужно помнить.
Поэтому отсутствие автоматического копирования при присваивании - небольшая плата за простоту и легкость Python.
a = np.arange(12)
aarray([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11])
b = a
b is aTrue
id(a), id(b)(140497073808928, 140497073808928)
Довольно часто, необходимо сделать полную копию массива и метод copy позволяет сделать это, причем скопировать не только данные массива, но и все его свойства.
a = np.arange(12).reshape(2,6)
aarray([[ 0, 1, 2, 3, 4, 5],
[ 6, 7, 8, 9, 10, 11]])
c = ab = a.copy()a[0, 0] = 79
aarray([[79, 1, 2, 3, 4, 5],
[ 6, 7, 8, 9, 10, 11]])
carray([[79, 1, 2, 3, 4, 5],
[ 6, 7, 8, 9, 10, 11]])
barray([[ 0, 1, 2, 3, 4, 5],
[ 6, 7, 8, 9, 10, 11]])
Теперь мы знаем, что простое присваивание не выполняет копирования массивов.
Если нам нужна копия, то мы можем легко сделать ее с помощью метода copy.
Однако, бывают случаи когда копия массива не нужна, а нужен тот же самый массив, но с другими размерами - другое представление исходного массива.
Для таких нужд NumPy предоставляет метод ndarray.view().
Этот метод создает новый объект массива, который просматривает данные исходного массива, но изменение размеров одного массива не повлечет изменение размеров другого.
a = np.arange(12)
aarray([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11])
b = aa.shape = 3, -1 # Меняем размеры массива "а"barray([[ 0, 1, 2, 3],
[ 4, 5, 6, 7],
[ 8, 9, 10, 11]])
# Теперь создадим представление массива "а"
c = a.view()a.shape = 2, -1 # Изменим размеры массива "а"carray([[ 0, 1, 2, 3],
[ 4, 5, 6, 7],
[ 8, 9, 10, 11]])
aarray([[ 0, 1, 2, 3, 4, 5],
[ 6, 7, 8, 9, 10, 11]])
barray([[ 0, 1, 2, 3, 4, 5],
[ 6, 7, 8, 9, 10, 11]])
a[0,0] = 3242carray([[3242, 1, 2, 3],
[ 4, 5, 6, 7],
[ 8, 9, 10, 11]])
Как правило, функции меняющие форму и порядок элементов в массивах возвращают именно представление, а не копию массива:
a = np.arange(8)
aarray([0, 1, 2, 3, 4, 5, 6, 7])
b = a.reshape(2, 4) # Массив "b" - это представление массива "а"barray([[0, 1, 2, 3],
[4, 5, 6, 7]])
c = b.T # А вот массив "с" - это представление массива "b"carray([[0, 4],
[1, 5],
[2, 6],
[3, 7]])
c[0,0] = 888aarray([888, 1, 2, 3, 4, 5, 6, 7])
barray([[888, 1, 2, 3],
[ 4, 5, 6, 7]])
Срезы массивов - это тоже представления массивов:
a = np.arange(12)
aarray([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11])
b = a[0:12:2]
barray([ 0, 2, 4, 6, 8, 10])
a[:] = 0barray([0, 0, 0, 0, 0, 0])
Если мы говорим, что массив b - это представление массива a, то подразумевается, что независимо от формы и вида массива b он состоит из тех же данных в памяти, что и массив a.
Поэтому изменение элементов в одном из них повлечет изменение соответствующих элементов в другом.