На предыдущем
занятии мы немного познакомились со способами изменения формы массивов пакета NumPy, то есть
изменением их размерностей. На этом занятии поближе рассмотрим эту тему и
познакомимся с наиболее употребительными функциями и свойствами, влияющих на
представление массивов.
Изменение размерности массивов
Предположим, у
нас имеется массив, состоящий из десяти чисел:
a = np.arange(10) # [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
Мы уже знаем,
что для изменения формы этого массива, достаточно указать свойству shape кортеж с новыми
размерами, например, так:
a.shape = 2, 5 # массив размерностью 2x5
В результате
изменится представление массива, на которое ссылается переменная a. Если же
требуется создать новое представление массива, сохранив прежнее, то следует
воспользоваться методом reshape():
b = a.reshape(10) # массив [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
И, как мы с вами
говорили на предыдущем занятии, ссылки b и a будут использовать
одни и те же данные, то есть, изменение массива через b:
приведет к
изменению соответствующего элемента массива a:
array([[-1, 1, 2, 3, 4],
[ 5, 6,
7, 8, 9]])
Об этом всегда
следует помнить. Также следует помнить, что у свойства shape и метода reshape() размерность
должна охватывать все элементы массива. Например, вот такая команда:
приведет к
ошибке, т.к. размерность 3x3 = 9 элементов, а в массиве 10
элементов. Здесь всегда должно выполняться равенство:
n1 x n2 x … x nN = число
элементов массива
Но допускается
делать так:
a.shape = -1, 2 # размерность 5x2
Отрицательное
значение -1 означает автоматическое вычисление размерности по первой оси. По
второй берется значение 2. В этом случае получим размерность 5x2.
То же самое
можно делать и в методе reshape():
b.reshape(-1, 1) # размерность 10x1
b.reshape(1, -1) # размерность 1x10
Обратите
внимание, в последних двух случаях мы получаем представления двумерных
массивов, преобразуя одномерный. Это важный момент, так как на выходе метода reshape() получается
матрица с двумя осями (строки и столбцы), тогда как изначально массив b имел только
одну ось. Не случайно последнее представление отображается с двумя квадратными
скобками:
array([[-1, 1, 2,
3, 4, 5, 6, 7, 8, 9]])
Первая скобка –
это первая ось (строка), а вторая скобка (вторая ось) описывает столбцы. Одномерный
же массив b отображается с
одной квадратной скобкой:
array([-1, 1,
2, 3, 4, 5, 6, 7, 8, 9])
Используя
отрицательный индекс, можно делать и такие преобразования:
b.reshape(2, -1) # размерность 2x5
b.reshape(-1, 2) # размерность 5x2
Первое
представление (2x5) отображается следующим образом:
array([[-1,
1, 2, 3, 4],
[ 5, 6,
7, 8, 9]])
Здесь снова мы
видим две квадратные скобки (значит, массив двумерный). Первая описывает ось axis0, отвечающую за
строки, а вложенные скобки описывают вторую ось axis1, отвечающую за
столбцы.
Если нам
требуется многомерный массив преобразовать в обычный одномерный, то можно
воспользоваться методом ravel(), который возвращает новое
представление, не меняя текущего:
c = b.ravel() # с ссылается на одномерное представление массива
Если же нам
нужно текущий массив преобразовать в одномерный, то это можно сделать так:
Помимо свойства shape можно
использовать метод resize, который выполняет подобную операцию с
текущим массивом. Например:
a.resize(2, 5) # массив 2x5
Но, как мы уже
говорили, вот такая строчка приведет к ошибке:
a.resize(3, 3) # ошибка: 3x3 != 10
Однако, мы
все-таки можем выполнить такую операцию, указав дополнительно флаг refcheck=False:
a.resize(3, 3, refcheck=False) # массив 3x3
или так:
a.resize(4, 5, refcheck=False) # массив 4x5
В первом случае
происходит удаление одного 10-го элемента, а во втором случае – добавление
4∙5 - 3∙3 = 11 нулей. Это очень удобно, когда нам нужно менять не
только форму, но и размер самого массива.
Транспонирование матриц и векторов
Очень часто в
математических операциях требуется выполнять транспонирование матриц и
векторов, то есть, заменять строки на столбцы. Например, если имеется матрица
(двумерный массив):
a = np.array([(1, 2, 3), (1, 4, 9), (1, 8, 27)])
то операция
транспонирования может быть реализована так:
Обратите
внимание, мы здесь создаем лишь новое представление тех же самых данных массива
a. И изменение
элементов в массиве b:
приведет к
соответствующему изменению значения элемента и массива a. Это следует
помнить, используя операцию транспонирования.
Транспонирование
векторов работает несколько иначе. Предположим, имеется одномерный массив:
и мы выполняем
операцию транспонирования:
В результате
ничего не изменилось: вектор как был строкой, так строкой и остался. Почему?
Дело в том, что массив x имеет только
одну размерность, поэтому здесь нет понятия строк и столбцов. Соответственно,
операция транспонирования ни к чему не приводит. Чтобы получить ожидаемый
эффект, нужно добавить к массиву еще одну ось, например, так:
И теперь, при
транспонировании получим вектор-столбец:
Добавление и удаление осей
Часто при работе
с массивами NumPy требуется
добавлять новые оси измерений и удалять существующие. Есть множество способов
выполнять эти операции, но мы рассмотрим два наиболее распространенных с
помощью функций:
-
np.expand_dims(a,
axis) – добавление
новой оси;
-
np.squeeze(a[, axis]) – удаление
оси (без удаления элементов).
Давайте
предположим, что у нас имеется некий многомерный массив:
x_test = np.arange(32).reshape(8, 2, 2) # массив 8x2x2
И нам
потребовалось добавить еще одно измерение (ось), причем, в самое начало, то
есть, ось axis0. Сейчас на
этой оси 8 элементов – матриц 2x2, но мы хотим сделать четырехмерный
массив, сохранив остальные три оси и их данные без изменений. Как раз это
достаточно просто сделать с помощью функции expand_dims, следующим
образом:
x_test4 = np.expand_dims(x_test, axis=0)
Обращаясь к
свойству shape:
x_test4.shape # (1, 8, 2, 2)
Видим, что
массив стал четырехмерным и первая добавленная ось axis0 содержит один
элемент – трехмерный массив 8x2x2. При
необходимости, мы всегда можем добавить новый элемент на эту ось:
a = np.append(x_test4, x_test4, axis=0) # размерность (2, 8, 2, 2)
или удалить
ненужные элементы:
b = np.delete(a, 0, axis=0) # размерность (1, 8, 2, 2)
Здесь второй
параметр 0 – индекс удаляемого элемента на оси axis0.
Если нам нужно
добавить последнюю ось в массиве, то для этого можно записать такую команду:
b = np.expand_dims(x_test4, axis=-1) # размерность (1, 8, 2, 2, 1)
Отрицательный
индекс -1 – это следующая с конца ось. Если указать индекс -2, то добавится
предпоследняя ось и так далее. Отрицательные индексы очень удобно использовать
при работе с массивами произвольных размерностей.
Следующая
функция squeeze позволяет
удалить все оси с одним элементом. Например, строчка:
c = np.squeeze(b) # размерность (8, 2, 2)
превращает
массив размерностью (1, 8, 2, 2) в массив размерностью (8, 2, 2). При
необходимости, дополнительно мы можем самостоятельно указать оси, которые
следует удалять, например, так:
c = np.squeeze(b, axis=0) # удалит только ось axis0, не затронув другие
Но, если указать
ось с числом элементов больше 1, то возникнет ошибка:
c = np.squeeze(b, axis=1) # ошибка, на оси axis1 8 элементов
Объект newaxis
В NumPy добавлять новые
оси иногда удобнее с помощью специального объекта np.newaxis. Например,
пусть у нас есть одномерный массив:
a = np.arange(1, 10) # array([1, 2, 3, 4, 5, 6, 7, 8, 9])
У него одна ось
– одно измерение. Добавим еще одну ось, допустим, в начало. С помощью объекта np.newaxis это можно
сделать так:
b = a[np.newaxis, :] # добавление оси axis0
b.shape # (1, 9)
Или, можно
прописать сразу две оси:
c = a[np.newaxis, :, np.newaxis]
c.shape # (1, 9, 1)
Как видите, это
достаточно удобная операция.