Зарегистрируйтесь для доступа к 15+ бесплатным курсам по программированию с тренажером

Переформатирование, изменение формы Python: Numpy-массивы

Numpy

Часто разработчикам приходится изменять размеры массивов. Например, переформатировать исходные данные, чтобы разделить их на подмассивы. В некоторых случаях требуется еще и объединять многомерные данные в единый массив значений. Чтобы решать такие задачи, массивы numpy.ndarray предоставляют набор методов, самым популярным из которых является метод reshape().

В этом уроке разберем, как работать с размерами массивов numpy.ndarray и как получать их производные. Еще поговорим об ограничениях размерности и узнаем, как они помогают оптимизировать работу.

Как изменить размер массива

Представим, что нам нужно увеличить размер массива numpy.ndarray. Для этого будем идти по следующим шагам:

  1. Узнаем размер массива и индексы вдоль оси
  2. Изменим размер массива

Рассмотрим каждый этап подробнее.

Как узнать размер массива и индексы вдоль оси

Чтобы изменить размер numpy.ndarray, нужно узнать его значение. Для этого используют атрибут shape:

import numpy as np

one_dimension_array = np.array(
    [0,1,2,3,4,5,6,7,8,9,10,11]
)

print(one_dimension_array.shape)
# => (12,)

two_dimensions_array = np.array(
    [
        [0,1,2],
        [3,4,5],
        [6,7,8],
        [9,10,11]
    ]
)
print(two_dimensions_array.shape)
# => (4, 3)

three_dimensions_array = np.array(
    [
        [
            [0,1],
            [2,3],
        ],
        [
            [4,5],
            [6,7],
        ],
        [
            [8,9],
            [10,11]
        ]
    ]
)
print(three_dimensions_array.shape)
# => (3, 2, 2)

В примере выше атрибут shape возвращает кортеж целых чисел. Длина кортежа указывает на размерность массива:

  • (12,) — одномерный массив
  • (4, 3) — двумерный массив
  • (3, 2, 2) — трехмерный массив

Числа в кортеже означают количество элементов по конкретной оси индексов:

  • (12,) — 12 значений
  • (4, 3) — четыре блока значений по три значения в каждом
  • (3, 2, 2) — три блока значений, каждый из которых состоит из двух блоков по два значения

Название ось индексов отсылает к декартовой системе координат. Вспомним ее основные правила:

  • Чтобы построить отрезок или другой одномерный объект, достаточно одной координатной оси
  • Чтобы построить квадрат или другой двумерный объект, необходима координатная плоскость из двух перпендикулярных осей
  • Чтобы построить куб или другой трехмерный объект, нужно три ортогональные оси координат

Теперь, когда мы знаем размер исходного массива, можно изменять его форму. Для этого используем метод reshape().

Как изменить размер массива с помощью метода reshape()

В Python используется метод reshape(), с помощью которого можно получить двухмерный и трехмерный массив из одномерного. Этот обязательный параметр ожидает новый размер данных, к которому нужно переформатировать исходный массив.

Попробуем получить двумерный массив two_dimensions_array из одномерного массива one_dimension_array. Для этого используем метод reshape() с новым размером данных (4, 3):

print(one_dimension_array.reshape((4, 3)))
# => [[ 0  1  2]
#  [ 3  4  5]
#  [ 6  7  8]
#  [ 9 10 11]]

Чтобы получить трехмерный массив three_dimensions_array, достаточно также указать нужный размер:

print(one_dimension_array.reshape((3, 2, 2)))
# => [[[ 0  1]
#   [ 2  3]]
#  [[ 4  5]
#   [ 6  7]]
#  [[ 8  9]
#   [10 11]]]

Изменять форму массива можно не только от данных меньшей размерности к данным большей размерности. Это можно делать и в обратную сторону.

Попробуем получить исходный одномерный массив one_dimension_array из двумерного массива two_dimensions_array:

print(two_dimensions_array.reshape((12,)))
# => [ 0  1  2  3  4  5  6  7  8  9 10 11]

А тут переформатируем three_dimensions_array в two_dimensions_array:

print(three_dimensions_array.reshape((4,3)))
# => [[ 0  1  2]
#  [ 3  4  5]
#  [ 6  7  8]
#  [ 9 10 11]]

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

print(three_dimensions_array.reshape((12,)))
# => [ 0  1  2  3  4  5  6  7  8  9 10 11]

С помощью атрибута shape можно узнать размерность массива numpy.ndarray. А метод reshape поможет ее уменьшить или увеличить. Однако у этого массива есть ограничения по размеру данных — его нужно соблюдать, чтобы оптимизировать выполнения методов над массивами.

Какие размеры массива допустимы

У массива numpy.ndarray есть ограничения по размеру данных — по осям индексов должны быть данные одного размера. Это ограничение позволяет оптимизировать выполнения методов над массивами. Рассмотрим на примере.

Допустим, нам нужно сконвертировать список из списков длиной три и два:

np.array(
    [
        [0,1,2],
        [3,4,],
    ]
)
# => [list([0, 1, 2]) list([3, 4])]

На первый взгляд у нас получился массив numpy.ndarray. Но если внимательно посмотреть на элементы, мы увидим, что получились списки, а не ожидаемые целочисленные массивы. Это ограничит дальнейшую работу с данными, потому что поведение многих методов меняется.

Попробуем найти в данном массиве максимальный элемент 4. Это приведет к такому результату:

print(np.array(
    [
        [0,1,2],
        [3,4,],
    ]
).max())
# => [3, 4]

В этом примере мы получили не тот результат, которого ожидали.

Numpy старается предотвращать некорректные действия — для этого в нем есть система предупреждений и подсказок. Но это не значит, что не нужно следить за размером массива. Он играет важную роль в реализации методов библиотеки Numpy, поэтому рекомендуем обращать внимание на этот момент.

В случае с методом reshape() Numpy вообще не дает совершить некорректную конвертацию массива из 12 элементов в массив из 15 элементов — три блока по пять значений. В этом случае он вызывает исключение:

one_dimension_array.reshape(3,5)
# => ValueError: cannot reshape array of size 12 into shape (3,5)

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

Как сделать автоматический расчет размера массива

Ограничения на размер массива позволяют не указывать некоторые размеры в методе reshape(). Это можно оставить на автоматический расчет. Для этого нужное значение размерности поменяем на -1:

print(one_dimension_array.reshape((4,3)))
print(one_dimension_array.reshape(((4, -1))))
print(one_dimension_array.reshape(((-1, 3))))
# => [[ 0  1  2]
#  [ 3  4  5]
#  [ 6  7  8]
#  [ 9 10 11]]

Все преобразования в примере выше дают одинаковый результат. Необходимый размер рассчитывается автоматически, исходя из количества элементов.

Для массивов большей размерности это работает по такому же принципу:

print(one_dimension_array.reshape((3, 2, 2)))
print(one_dimension_array.reshape((-1, 2, 2)))
print(one_dimension_array.reshape((3, -1, 2)))
print(one_dimension_array.reshape((3, 2, -1)))
# => [[[ 0  1]
#   [ 2  3]]
#  [[ 4  5]
#   [ 6  7]]
#  [[ 8  9]
#   [10 11]]]

Чтобы получить одномерный массив и использовать автоматический расчет, не нужно находить количество элементов. Строки в примере ниже дают одинаковый результат:

print(three_dimensions_array.reshape((12,)))
print(three_dimensions_array.reshape((-1,)))
# => [ 0  1  2  3  4  5  6  7  8  9 10 11]

Теперь вы знаете, как определять размерность массива. Вы умеете изменять размер и рассчитывать его автоматически. Чтобы закрепить знания на практике, рассмотрим еще один пример.

Как размер массива меняется на практике

Изменение формы массива помогает подготовить исходные данные — после такой обработки их будет удобнее анализировать и преобразовывать.

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

orders = [7, 1, 7, 8, 4, 2, 4, 5, 3, 5, 2, 3, 8, 12, 8, 7, 15, 11, 13, 9, 21, 18, 17, 21, 25, 16, 25, 17,]
shops_number = 4

orders_matrix = np.array(orders)
orders_matrix = orders_matrix.reshape(-1, shops_number)
print(orders_matrix)
# => [[ 7  1  7  8]
#  [ 4  2  4  5]
#  [ 3  5  2  3]
#  [ 8 12  8  7]
#  [15 11 13  9]
#  [21 18 17 21]
#  [25 16 25 17]]

print(orders_matrix.shape)
# => (7, 4)

Полученный массив данных можно визуализировать в виде такой таблицы:

День Магазин №1 Магазин №2 Магазин №3 Магазин №4
0 7 1 7 8
1 4 2 4 5
2 3 5 2 3
3 8 12 8 7
4 15 11 13 9
5 21 18 17 21
6 25 16 25 17

Выводы

Метод shape — важный атрибут для структурного описания массива numpy.ndarray. Он помогает узнать размер вдоль каждой оси.

Также стоит пользоваться методом reshape(). Он форматирует исходный массив под нужный размер — при это можно настроить параметры как вручную, так и автоматически.

Меняя размер массива, можно получить новые массивы, которые упрощают анализ исходных данных. Главное — соблюдать правила размерности, иначе не получится оптимизировать выполнения методов.


Самостоятельная работа

Нажмите, чтобы увидеть тестовые данные
import random
random.seed(42)
min_num = 1200
max_num = 2500

input_click_numbers = [random.randrange(min_num, max_num) for _ in range(280)]

image_mnist = [
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 17, 17, 17, 17, 81, 180, 180, 35, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 139, 253, 253, 253, 253, 253, 253, 253, 48, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0, 0, 60, 228, 253, 253, 253, 253, 253, 253, 253, 207, 197, 46, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0, 0, 213, 253, 253, 253, 253, 253, 253, 253, 253, 253, 253, 223, 52, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0, 0, 66, 231, 253, 253, 253, 108, 40, 40, 115, 244, 253, 253, 134, 3, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 63, 114, 114, 114, 37, 0, 0, 0, 205, 253, 253, 253, 15, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 57, 253, 253, 253, 15, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 42, 253, 253, 253, 15, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 95, 253, 253, 253, 15, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 205, 253, 253, 253, 15, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 61, 99, 96, 0, 0, 45, 224, 253, 253, 195, 10, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 11, 25, 105, 83, 189, 189, 228, 253, 251, 189, 189, 218, 253, 253, 210, 27, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 42, 116, 173, 253, 253, 253, 253, 253, 253, 253, 253, 253, 253, 253, 253, 253, 221, 116, 7, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 118, 253, 253, 253, 253, 245, 212, 222, 253, 253, 253, 253, 253, 253, 253, 253, 253, 253, 160, 15, 0, 0, 0, 0,
    0, 0, 0, 0, 254, 253, 253, 253, 189, 99, 0, 32, 202, 253, 253, 253, 240, 122, 122, 190, 253, 253, 253, 174, 0, 0, 0, 0,
    0, 0, 0, 0, 255, 253, 253, 253, 238, 222, 222, 222, 241, 253, 253, 230, 70, 0, 0, 17, 175, 229, 253, 253, 0, 0, 0, 0,
    0, 0, 0, 0, 158, 253, 253, 253, 253, 253, 253, 253, 253, 205, 106, 65, 0, 0, 0, 0, 0, 62, 244, 157, 0, 0, 0, 0,
    0, 0, 0, 0, 6, 26, 179, 179, 179, 179, 179, 30, 15, 10, 0, 0, 0, 0, 0, 0, 0, 0, 14, 6, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
]

code_array = [1, 3, 0, 0, 2, 1, 0, 2, 1, 1, 1, 2, 1, 3, 0, 0, 2, 1]

Библиотека Numpy разрабатывалась для работы с таблицами и быстрых вычислений над большими массивами данных, но он нашел применение и в других задачах. Давайте попробуем взглянуть на привычные нам текстовые данные и картинки, как на массивы данных. Снова пройдем по шагам:

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

Напишите функцию create_weekly_clicks_table(), которая получает на вход список значений и возвращает массив, состоящий из недельных кликов (таблиц).

Нажмите, чтобы увидеть ответ
    def test(create_weekly_clicks_table, input_click_numbers):
        # shape
        assert create_weekly_clicks_table(input_click_numbers).shape == (4,7,10)
        # type
        assert create_weekly_clicks_table(input_click_numbers).dtype == int

    def create_weekly_clicks_table(input_click_numbers):
        return np.array(input_click_numbers).reshape(-1,7,10)

    test(create_weekly_clicks_table, input_click_numbers)

Шаг 2. Привычные нам картинки — не более, чем массивы чисел. Если картинка черно-белая и без сжатия, то число в массиве отражает оттенки серого цвета конкретного пикселя: от 0.0 (белый) до 1.0 (черный). Разберемся в этом на примере датасета MNIST, который содержит набор картинок рукописных цифр:

400

Например, рукописная единица представлена таким образом:

400

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

Нажмите, чтобы увидеть ответ
    def test(image):
        # shape
        assert image.shape == (28, 28)
        # type
        assert image.dtype == int

    def mnist_data_converter(image_mnist):
        image = np.array(image_mnist)
        sq_len = int(np.sqrt(image.shape))
        image = image.reshape(sq_len, sq_len)
        return image

    image = mnist_data_converter(image_mnist)
    test(image)

Шаг 3. Сегодня мы уже не удивляемся автоматическим переводчикам, которым переводят тексты на многих языках мира — все это благодаря глубоким нейронным сетям. Алгоритмы работают с числовыми данными, и поэтому встает резонный вопрос: «Как превратить слово в числовое представление?».

Один из способов — векторное представление слов, то есть сопоставление слов с наборами чисел определенной длины. Фактически, это процесс кодирования. Выглядит это так:

   мама = (0,2,1)
   мыла = (1,3,0)
   раму = (1,1,2)
   мама мыла раму = ['мама','мыла','раму'] = [0,2,1,1,3,0,1,1,2]

Ключ к решению проблем машинного перевода лежит в качественной кодировке слов! Для знакомства с векторным представлением напишите функцию vec2word(), которая получает на вход список чисел, а возвращает список слов, используя правила кодирования, описанные выше. Постарайтесь использовать массивы Numpy с их функциональности по изменению размеров массива.

Нажмите, чтобы увидеть ответ
    def test(words_array):
        expected_words_array = ['мыла', 'мама', 'мама', 'раму', 'мыла', 'мама']
        # shape
        assert len(words_array) == len(expected_words_array)
        # type
        assert words_array == expected_words_array

    def vec2word(code_array):
        code_rules = {
            (0,2,1) : 'мама',
            (1,3,0) : 'мыла',
            (1,1,2) : 'раму'
        }
        code_array = np.array(code_array).reshape(-1,3)
        return [code_rules[tuple(code_array_vec)] for code_array_vec in code_array]

    words_array = vec2word(code_array)
    test(words_array)

Дополнительные материалы

  1. Абсолютные основы для начинающих по NumPy

Аватары экспертов Хекслета

Остались вопросы? Задайте их в разделе «Обсуждение»

Вам ответят команда поддержки Хекслета или другие студенты

Об обучении на Хекслете

Для полного доступа к курсу нужен базовый план

Базовый план откроет полный доступ ко всем курсам, упражнениям и урокам Хекслета, проектам и пожизненный доступ к теории пройденных уроков. Подписку можно отменить в любой момент.

Получить доступ
1000
упражнений
2000+
часов теории
3200
тестов

Открыть доступ

Курсы программирования для новичков и опытных разработчиков. Начните обучение бесплатно

  • 130 курсов, 2000+ часов теории
  • 1000 практических заданий в браузере
  • 360 000 студентов
Отправляя форму, вы принимаете «Соглашение об обработке персональных данных» и условия «Оферты», а также соглашаетесь с «Условиями использования»

Наши выпускники работают в компаниях:

Логотип компании Альфа Банк
Логотип компании Aviasales
Логотип компании Yandex
Логотип компании Tinkoff
Рекомендуемые программы
профессия
от 5 025 ₽ в месяц
новый
Сбор, анализ и интерпретация данных
9 месяцев
с нуля
Старт 2 мая

Используйте Хекслет по-максимуму!

  • Задавайте вопросы по уроку
  • Проверяйте знания в квизах
  • Проходите практику прямо в браузере
  • Отслеживайте свой прогресс

Зарегистрируйтесь или войдите в свой аккаунт

Отправляя форму, вы принимаете «Соглашение об обработке персональных данных» и условия «Оферты», а также соглашаетесь с «Условиями использования»