Основные типы данных. Создание массивов функцией array()

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

Мы уже видели с вами как создаются массивы с помощью функции

numpy.array(object, dtype=None, …)

подробное описание которой можно почитать на странице официальной документации:

https://numpy.org/doc/stable/reference/arrays.ndarray.html

В самом простом варианте мы можем ее вызвать так:

a = np.array([1,2,3,4])

Будет сформирован одномерный массив целых чисел. Если же нам явно нужно указать тип элементов массива, то его можно записать вторым аргументом, например, так:

a = np.array([1,2,3,4], 'float64')

В результате все величины будут вещественными 64-битными числами. И здесь сразу возникает вопрос: какие типы данных поддерживает пакет NumPy. Их огромное количество и посмотреть можно с помощью словаря sctypeDict:

np.sctypeDict

Я отмечу лишь основные, которые наиболее часто используются на практике:

Тип

Описание

bool_

Логический тип (истина или ложь), хранящийся в виде байта.

int_

Целочисленный тип установленный по умолчанию (такой же, как C long, как правило это либо int64 либо int32).

intc

Идентичен C int (int32 или int64).

intp

Целочисленный тип, используемый для индексирования (такой же, как C ssize_t, как правило это либо int64 либо int32).

int8

Целые числа в диапазоне от -128 по 127 (числа размером 1 байт).

int16

Целые числа в диапазоне от -32768 по 32767, (числа размером 2 байта).

int32

Целые числа в диапазоне от -2147483648 по 2147483647, (числа размером 4 байта).

int64

Целые числа размером 8 байт.

uint8

Целые числа в диапазоне от 0 по 255 (числа размером 1 байт).

uint16

Целые числа в диапазоне от 0 по 65535 (числа размером 2 байта).

uint32

Целые числа в диапазоне от 0 по 4294967295 (числа размером 4 байта).

uint64

Целые беззнаковыечисла размером 8 байт.

float_

То же самое что и float64.

float16

Вещественные числа половинной точности: 1 бит знака, 5 бит экспоненты, 10 бит мантисы (числа размером 2 байта).

float32

Вещественные числа одинарной точности: 1 бит знака, 8 бит экспоненты, 23 бита мантисы (числа размером 4 байта).

float64

Вещественные числа двойной точности: 1 бит знака, 11 бит экспоненты, 52 бита мантисы (числа размером 8 байт).

complex_

То же самое что и complex128.

complex64

Комплексные числа, в которых действительная и мнимая части представлены двумя вещественными числами типа float32.

complex128

Комплексные числа, в которых действительная и мнимая части представлены двумя вещественными числами типа float64.

str_

Для представления строк

Как видите, массивы NumPy могут оперировать самыми разными типами: от булевых и числовых до комплексных и строковых. Я думаю не нужно объяснять, зачем нужно такое многообразие типов? Когда речь заходит о массивах, хранящих тысячи, а то и сотни тысяч элементов, размер каждого из них влияет на расход памяти устройства. Например, если 1000 элементов целых чисел представлять типом int8, то под данные достаточно зарезервировать 1000 байт. А если взять тип int64 (8 байт), то размер увеличится до 8000 байт. Поэтому для больших по размерам массивов следует заботиться о корректном типе данных их элементов с целью экономии памяти устройства.

Каждый из перечисленных типов можно указывать или в виде строки у параметра dtype (который имеется у многих функций пакета NumPy), например, так:

a = np.array([1,2,3,4], 'uintc')
a = np.array([1,2,3,4], 'str_')

Или использовать как самостоятельный объект:

np.complex64(10)
np.int16(10.5)

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

a = np.array([1, 2, 5000, 1000], dtype='int8')

создаст массив с типом int8 (однобайтовое целое число), но данными:

array([   1,    2, -120,  -24], dtype=int8)

Почему так? Потому что значения 5000 и 1000 не умещаются в однобайтовое целое число, поэтому видим значения -120 и -24.

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

a = np.array([1, 2, 5000, 1000])

можно привести к комплексному представлению:

b = np.complex64(a)

При этом, будет создан новый массив с измененным типом данных:

array([1.e+00+0.j, 2.e+00+0.j, 5.e+03+0.j, 1.e+03+0.j], dtype=complex64)

прежний массив aостается без изменений:

array([   1,    2, 5000, 1000])

И, обратно, из комплексного представления можем перейти в целочисленное:

c = np.int32(b)

Вконсолиувидимпредупреждение, что мнимая информация о числе будет утеряна:

<input>:1: ComplexWarning: Casting complex values to real discards the imaginary part

Но массив будет успешно преобразован:

array([-100,    2, 5000, 1000])

Создание массивов с помощью функции array

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

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

np.array( (1, 2, 3) )

Или можно сформировать список с использованием инструмента listcomprehensions:

def getList():
    for i in range(10):
        yield i
 
a = np.array( [x for x in getList()] )
print(a)

Здесь в качестве итератора используется функция-итератор getList(). Но если передать строку:

np.array( "Hello" )

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

array('Hello', dtype='<U5')

Строки не разбиваются на символы, так как элементами массива выступают только элементы списка или кортежа.

Объявление многомерных массивов

Теперь давайте посмотрим, как можно создавать массивы больших размерностей. И начнем с двумерных. Предположим, требуется определить матрицу, размерностью 3x2 элемента. Это можно сделать так:

a = np.array([[1, 2], [3, 4], [5, 6]])

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

array([[1, 2],
       [3, 4],
      [5, 6]])

Но, если указать не прямоугольный двумерный список, например, так:

a = np.array([[1, 2], [3, 4], [5, 6, 7]])

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

Далее, если требуется объявить трехмерную матрицу, то это будет выглядеть уже так:

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

И при ее выводе в консоли увидим следующее:

array([[[ 1,  2],
[ 3,  4]],
[[ 5,  6],
[ 7,  8]],
[[ 9, 10],
[11, 12]]])

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

Расположение осей многомерного массива

И здесь главный вопрос: как располагаются оси многомерных массивов?Ответ вполне очевиден. Вдоль первой оси (axis0) для трехмерного массива будут располагаться двумерные срезы (матрицы), а остальные две оси (axis1 и axis2) будут определять строки и столбцы этих двумерных матриц:

Например, выполнив в консоли команду

b[0]

увидим первый двумерный срез трехмерного массива:

array([[1, 2],
[3, 4]])

Если указать первые два индекса:

b[0, 0]

то увидим первую строку первого среза:

array([1, 2])

Наконец, указав все три индекса:

b[0, 0, 0]

получим первый элемент трехмерной матрицы:

1

Из этих примеров хорошо видно, что первый индекс отвечает за первую ось, второй – за вторую, ну а третий – за третью. Что вполне логично. Если размерность массива увеличивается до четырех, пяти и так далее осей, то принцип индексирования сохраняется: мы также указываем требуемый индекс элемента в виде кортежа чисел:

(x1, x3, x3, …, xN)

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

Итак, на этом занятии мы с вами познакомились с базовыми типами данных пакета NumPy и узнали как создавать различные массивы с помощью функции array(). Если вам все это понятно, значит цель этого занятия достигнута.