Open in Colab

© Семён Лукашевский сайт автора

Главный объект NumPy - это однородный многомерный массив.

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

Что бы перейти к примерам, сначала выполним импорт пакета:

Импортирование numpy под псевдонимом np уже стало общепринятой, негласной договоренностью, можно сказать, традицией.

Теперь мы може приступить к примерам. Способов создания массивов NumPy довольно много, но мы начнем с самого тривиального - создание массива из заполненного вручную списка Python:

Теперь у нас есть одномерный массив, т.е. у него всего одна ось вдоль которой происходит индексирование его элементов.

image

В общем-то, можно подумать, что ничего интересного и нет в этих массивах, но на самом деле это только начало кроличьей норы.

Оцените:

Вместо одного индекса, указан целый список индексов. А вот еще любопытный пример, теперь вместо индекса укажем логическое выражение:

Цель этих двух примеров - не устраивать головоломку, а продемонстрировать расширенные возможности индексирования массивов NumPy.

Что еще интересного можно продемонстрировать? Векторизованные вычисления:

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

Давайте перейдем к двумерным массивам:

Сейчас мы создали массив с помощью функции np.arange(), которая во многом аналогична функции range() языка Python.

Затем, мы изменили форму массива с помощью метода reshape(), т.е. на самом деле создать этот массив мы могли бы и одной командой:

Визуально, данный массив выглядит следующим образом:

image

Глядя на картинку, становится понятно, что первая ось (и индекс соответственно) - это строки, вторая ось - это столбцы. Т.е. получить элемент 9 можно простой командой:

Снова можно подумать, что ничего нового - все как в стандартном Python. Да, так и есть, и, это круто!

Еще круто, то что NumPy добавляет к удобному и привычному синтаксису Python, весьма удобные трюки, например - транслирование массивов:

В данном примере, без всяких циклов, мы умножили каждый столбец из массива a на соответствующий элемент из массива b.

Т.е. мы как бы транслировали (в какой-то степени можно сказать - растянули) массив b по массиву a.

То же самое мы можем проделать с каждой строкой массива a:

В данном случае мы просто прибавили к массиву a массив-столбец c. И получили, то что хотели.

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

Например, у нас есть двумерный массив и мы хотим узнать его минимальные элементы по строкам и столбцам.

Для начала создадим массив из случайных чисел и пусть, для нашего удобства, эти числа будут целыми:

Минимальный элемент в данном массиве это:

А вот минимальные элементы по столбцам и строкам:

Такое поведение заложено практически во все функции и методы NumPy:

Что насчет вычислений, их скорости и занимаемой памяти?

Для примера, создадим трехмерный массив:

Почему именно трехмерный?

На самом деле реальный мир вовсе не ограничивается таблицами, векторами и матрицами.

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

image

Визуализация (и хорошее воображение) позволяет сразу догадаться, как устроена индексация трехмерных массивов. Например, если нам нужно вытащить из данного массива число 31, то достаточно выполнить:

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

Массив a действительно трехмерный.

Но иногда становится интересно, а на сколько же большой массив перед нами. Например, какой он формы, т.е. сколько элементов расположено вдоль каждой оси? Ответить позволяет метод ndarray.shape:

Метод ndarray.size просто возвращает общее количество элементов массива:

Еще может встать такой вопрос - сколько памяти занимает наш массив?

Иногда даже возникает такой вопрос - влезет ли результирующий массив после всех вычислений в оперативную память?

Что бы на него ответить надо знать, сколько "весит" один элемент массива:

ndarray.itemsize возвращает размер элемента в байтах.

Теперь мы можем узнать сколько "весит" наш массив:

Итого - 384 байта. На самом деле, размер занимаемой массивом памяти, зависит не только от количества элементов в нем, но и от испльзуемого типа данных:

dtype('int64') - означает, что используется целочисленный тип данных, в котором для хранения одного числа выделяется 64 бита памяти.

Но если мы выполним какие-нибудь вычисления с массивом, то тип данных может измениться:

Теперь у нас есть еще один массив - массив b и его тип данных 'float64' - вещественные числа (числа с плавающей точкой) длинной 64 бита.

А его размер:

Создание массивов в NumPy

Большой обзор функций для создания массивов

И так, массив может быть создан из обычного списка или кортежа Python с использованием функции array().

Причем тип полученного массива зависит от типа элементов последовательности:

Функция array() преобразует последовательности последовательностей в двумерные массивы, а последовательности последовательностей, которые тоже состоят из последовательностей в трехмерные массивы.

То есть уровень вложенности исходной последовательности определяет размерность получаемого массива:

Очень часто возникает задача создания массива определенного размера, причем, чем заполнен массив абсолютно неважно.

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

Функция zeros заполняет массив нулями, функция ones - единицами, а функция empty - случайными числами, которые зависят от состояния памяти.

По умолчанию, тип создаваемого массива - float64.

Для создания последовательностей чисел NumPy предоставляет функцию arange, которая возвращает одномерные массивы:

Если функция arange используется с аргументами типа float, то предсказать количество элементов в возвращаемом массиве не так-то просто.

Гораздо чаще возникает необходимость указания не шага изменения чисел в диапазоне, а количества чисел в заданном диапазоне.

Функция linspace, так же как и arange принимает три аргумента, но третий аргумент, как раз и указывает количество чисел в диапазоне.

Функция linspace удобна еще и тем, что может быть использована для вычисления значений функций на заданном множестве точек:

Вывод массивов на экран

Чтобы быстрее разобраться с примерами печати массивов воспользуемся методом ndarray.reshape(), который позволяет изменять размеры массивов.

Одномерные массивы в NumPy печатаются в виде строк:

Двумерные массивы печатаются в виде матриц:

Трехмерные массивы печатаются в виде списка матриц, которые разделены пустой строкой:

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

В случае, если массив очень большой (больше 1000 элементов), NumPy печатает только начало и конец массива, заменяя его центральную часть многоточием.

Если необходимо выводить весь массив целиком, то такое поведение печати можно изменить с помощью set_printoptions.

np.set_printoptions(threshold=np.nan)

Файловый ввод и вывод массивов

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

Самый надежный способ хранения - это загрузка массивов с результатами в файл, так как их легко хранить и передавать.

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

Двоичные файлы NumPy (.npy, .npz)

NumPy имеет два собственных формата файлов .npy - для хранения массивов без сжатия и .npz - для предварительного сжатия массивов.

Если массивы, которые необходимо сохранить являются небольшими, то можно воспользоваться функцией numpy.save(). В самом простом случае, данная функция принимает всего два аргумента - имя файла в который будет сохранен массив и имя самого сохраняемого массива. Однако следует помнить, что файл будет сохранен, в той директории в которой происходит выполнение скрипта Python или в указанном месте:

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

Файлы .npy удобны для хранения одного массива, если в одном файле нужно сохранить несколько массивов, то необходимо воспользоваться функцией numpy.savez(), которая сохранит их в несжатом виде в файле NumPy с форматом .npz.

После сохранения массивов в файл .npz они могут быть загружены с помощью, уже знакомой нам функции numpy.load(). Однако, имена массивов теперь изменились с a, b и c на arr_0, arr_1 и arr_2 соответственно:

Что бы вместе с массивами сохранялись их оригинальные имена, необходимо в функции numpy.savez() указывать их как ключи словарей Python:

В случае очень больших массивов можно воспользоваться функцией numpy.savez_compressed().

На самом деле, файлы .npz это просто zip-архив который содержит отдельные файлы .npy для каждого массива.

После того как файл был загружен с помощью функции numpy.savez_compressed() его так же легко загрузить с помощью функции numpy.load():