12 января 2020

Иллюстрированное руководство по изменению формы массивов NumPy

Пишу, перевожу и иллюстрирую IT-статьи. На proglib написал 140 материалов. Увлекаюсь Python, вебом и Data Science. Открыт к диалогу – ссылки на соцсети и мессенджеры: https://matyushkin.github.io/links/ Если понравился стиль изложения, упорядоченный список публикаций — https://github.com/matyushkin/lessons
Рассказываем, как перекраивать массивы NumPy в трех измерениях. Шпаргалка в конце руководства обобщает приемы работы с методами reshape, stack и ravel.
Иллюстрированное руководство по изменению формы массивов NumPy

Трудность NumPy в том, что операции над многомерными массивами бывает сложно представить. Хаус Лин подготовил шпаргалки, которые сделают преобразование массивов нагляднее. Руководство подойдёт и для знакомства с NumPy.

1. Создаем массив NumPy

Аналог range для массивов. Чтобы создать простой массив, используем функцию np.arange(). Удобно взять небольшое составное число, например, 12.

        import numpy as np

a1 = np.arange(1, 13)  # числа от 1 до 12

print(a1.shape)
> (12,)

print(a1)
> [ 1  2  3  4  5  6  7  8  9 10 11 12]
    
Создан одномерный массив из 12 чисел.
Создан одномерный массив из 12 чисел.

2. Изменяем форму с помощью метода reshape()

Оси и измерения. Чтобы изменить форму массива a1 , используем метод reshape(). Преобразуем одномерный массив из 12 чисел в двумерную таблицу размером 3×4. Первое число – количество строк, второе – столбцов. Строки соответствуют оси (англ. axis) 0, столбцы – оси 1. Ещё их называют измерениями (англ. dimensions).

        a1_2d = a1.reshape(3, 4)

print(a1_2d.shape)
> (3, 4)

print(a1_2d)
> [[ 1  2  3  4]
   [ 5  6  7  8]
   [ 9 10 11 12]]
    
Иллюстрированное руководство по изменению формы массивов NumPy

Автоматическое вычисление размерности. Если нужно, чтобы NumPy сам определил размер незаданного измерения, передайте на этой позиции значение -1:

        a1.reshape(3, 4)
a1.reshape(-1, 4)  # то же самое, что a1.reshape(3, 4)

a1.reshape(3, 4)
a1.reshape(3, -1)  # то же самое, что a1.reshape(3, 4)

a1.reshape(2, 6)
a1.reshape(2, -1)  # то же самое, что a1.reshape(2, 6)
    

3. Изменяем форму по разным направлениям

Порядок переноса С. По умолчанию reshape() перестраивает исходный массив по оси 0. То есть в нашем примере отдельные числа из одномерного массива «переносятся» в двумерный построчно. Поведение можно изменить, передав значение параметру order. Дефолтный метод похож на то, как преобразование реализовано в C. Параметр order по умолчанию имеет значение 'C':

        a1.reshape(3, 4)
a1.reshape(3, 4, order='C') # даст идентичный результат

    

Порядок переноса Fortran. Если вы привыкли к тому, как преобразуются массивы в MATLAB, то это делается так же, как в Fortran. Поэтому используйте значение 'F':

        a1.reshape(3, 4, order='F')  # столбец за столбцом
> [[ 1  4  7 10]
   [ 2  5  8 11]
   [ 3  6  9 12]]
    
Иллюстрированное руководство по изменению формы массивов NumPy

Какую размерность имеет исходный массив a1? Может показаться, что массив a1 имеет размерность (1, 12). Но это одномерный массив с размерностью(12, ). Чтобы преобразовать одномерный массив к двумерному, используем метод reshape():

        print(a1)  # какая форма?
> [ 1  2  3  4  5  6  7  8  9 10 11 12]

print(a1.shape)
> (12,)

a1_1_by_12 = a1.reshape(1, -1)  # преобразуем к 1x12

print(a1_1_by_12)  # обратите внимание на двойные скобки
> [[ 1  2  3  4  5  6  7  8  9 10 11 12]]

print(a1_1_by_12.shape)  # двумерный массив
> (1, 12)
    

4. Схлопываем массив до одномерного

Из 2D в 1D. Метод ravel() преобразует многомерные массивы в одномерные. Тот же параметр order определяет, «схлопнется» ли массив построчно или столбец за столбцом:

        print(a1_2d)
> [[ 1  2  3  4]
   [ 5  6  7  8]
   [ 9 10 11 12]]

print(a1_2d.ravel())  # строка за строкой
> [ 1  2  3  4  5  6  7  8  9 10 11 12]

print(a1_2d.ravel(order='F'))  # столбец за столбцом
> [ 1  5  9  2  6 10  3  7 11  4  8 12]
    

5. Повышаем размерность, сшивая массивы друг с другом

Иллюстрированное руководство по изменению формы массивов NumPy

Создадим два массива:

        a1 = np.arange(1, 13)
print(a1)
> [ 1  2  3  4  5  6  7  8  9 10 11 12]

a2 = np.arange(13, 25)
print(a2)
> [13 14 15 16 17 18 19 20 21 22 23 24]
    

Чтобы соединить два одномерных масива в двумерный, используем метод np.stack(). Параметр axis указывает индекс оси в результирующем массиве, по которой соединяются входные. По умолчанию axis = 0, и массивы стыкуются строка к строке:

        stack0 = np.stack((a1, a1, a2, a2))
print(stack0.shape)
> (4, 12)

print(stack0)
> [[ 1  2  3  4  5  6  7  8  9 10 11 12]
   [ 1  2  3  4  5  6  7  8  9 10 11 12]
   [13 14 15 16 17 18 19 20 21 22 23 24]
   [13 14 15 16 17 18 19 20 21 22 23 24]]
    

Чтобы соединить массивы столбец к столбцу, явным образом передаем axis = 1:

        stack1 = np.stack((a1, a1, a2, a2), axis=1)
print(stack1.shape)
> (12, 4)
print(stack1)
> [[ 1  1 13 13]
   [ 2  2 14 14]
   [ 3  3 15 15]
   [ 4  4 16 16]
   [ 5  5 17 17]
   [ 6  6 18 18]
   [ 7  7 19 19]
   [ 8  8 20 20]
   [ 9  9 21 21]
   [10 10 22 22]
   [11 11 23 23]
   [12 12 24 24]]
    

Как не повышать число измерений? Функция hstack() соединяет массивы горизонтально без изменения размерности. Если применить ее к одномерным массивам, получится длинный одномерный массив:

        stack_long = np.hstack((a1, a2))

print(stack_long.shape)
> (24,)

print(stack_long)
> [ 1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24]
    

6. Создаем трехмерный массив

Где применяются многомерные массивы? Массивы, живущие в трёх и более измерениях, активно используются в глубоком обучении. Например, для операций с изображениями. В работе с нейросетями преобразование многомерных массивов – обычное дело.

Для начала создадим два двумерных массива размером 3×4:

        a1 = np.arange(1, 13).reshape(3, -1)
a2 = np.arange(13, 25).reshape(3, -1)

print(a1)
> [[ 1  2  3  4]
   [ 5  6  7  8]
   [ 9 10 11 12]]

print(a2)
> [[13 14 15 16]
   [17 18 19 20]
   [21 22 23 24]]
    
Иллюстрированное руководство по изменению формы массивов NumPy

Теперь соберём из них трёхмерный массив:

        a3_0 = np.stack((a1, a2))  # дефолтное axis=0, одна плоскость ложится на другую
a3_1 = np.stack((a1, a2), axis=1)
a3_2 = np.stack((a1, a2), axis=2)

print(a3_0.shape)
> (2, 3, 4)

print(a3_1.shape)
> (3, 2, 4)

print(a3_2.shape)
> (3, 4, 2)
    
Иллюстрированное руководство по изменению формы массивов NumPy

Как выглядят массивы:

        print(a3_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]]]

print(a3_1)
> [[[ 1  2  3  4]
    [13 14 15 16]]

   [[ 5  6  7  8]
    [17 18 19 20]]

   [[ 9 10 11 12]
    [21 22 23 24]]]

print(a3_2)
> [[[ 1 13]
    [ 2 14]
    [ 3 15]
    [ 4 16]]

   [[ 5 17]
    [ 6 18]
    [ 7 19]
    [ 8 20]]

   [[ 9 21]
    [10 22]
    [11 23]
    [12 24]]]
    

Чтобы извлечь плоскость массива a1 из трехмерного, нужно передать 0 по соответствующему индексу оси:

        print(a1)
> [[ 1  2  3  4]
   [ 5  6  7  8]
   [ 9 10 11 12]]

a3_0[0, :, :]
a3_1[:, 0, :]
a3_2[:, :, 0]
    

Чтобы то же самое сделать для двумерного массива a2, на тех же позициях вместо 0 отправляем индекс 1. Символы двоеточия указывают, что нужно взять все элементы соответствующей оси.

7. Схлопываем многомерные массивы

Метод ravel() действует и для массивов с числом размерностей больше 2:

Иллюстрированное руководство по изменению формы массивов NumPy
        print(a3_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]]]

print(a3_0.ravel())
> [ 1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24]

print(a3_0.ravel(order='F'))
> [ 1 13  5 17  9 21  2 14  6 18 10 22  3 15  7 19 11 23  4 16  8 20 12 24]
    

8. Изменяем форму многомерных массивов

Метод reshape() также работает с массивами любой размерности. Главное условие, которое нужно соблюдать – количество элементов в массиве не должно меняться:

        print(a3_0)  # исходный размер 2x3x4
> [[[ 1  2  3  4]
    [ 5  6  7  8]
    [ 9 10 11 12]]

   [[13 14 15 16]
    [17 18 19 20]
    [21 22 23 24]]]

print(a3_0.reshape(4, -1))  # преобразуем к 4x6
> [[ 1  2  3  4  5  6]
   [ 7  8  9 10 11 12]
   [13 14 15 16 17 18]
   [19 20 21 22 23 24]]

print(a3_0.reshape(4, -1, order='F'))  # делаем то же самое, но в F-стиле
> [[ 1  9  6  3 11  8]
   [13 21 18 15 23 20]
   [ 5  2 10  7  4 12]
   [17 14 22 19 16 24]]

print(a3_0.reshape(4, 2, 3))  # преобразуем к трехмерному массиву другой формы
> [[[ 1  2  3]
    [ 4  5  6]]
   [[ 7  8  9]
    [10 11 12]]
   [[13 14 15]
    [16 17 18]]
   [[19 20 21]
    [22 23 24]]]
    

Заключение

Напоследок приведем изображение со всеми элементами рассказа вместе (оригинальный pdf). Напишите нам в комментариях, если что-то осталось непонятным и требует пояснений.

Иллюстрированное руководство по изменению формы массивов NumPy

Источники

Комментарии

ВАКАНСИИ

Добавить вакансию
Разработчик C++
Москва, по итогам собеседования

ЛУЧШИЕ СТАТЬИ ПО ТЕМЕ