На предыдущем
занятии мы с вами сделали небольшое введение в пакет NumPy и далее на
занятиях будем подробнее изучать его возможности. И, конечно же, следует начать
со способов формирования массивов (array), которые лежат
в основе работы этой библиотеки.
Мы уже видели с
вами как создаются массивы с помощью функции
numpy.array(object,
dtype=None, …)
подробное
описание которой можно почитать на странице официальной документации:
https://numpy.org/doc/stable/reference/arrays.ndarray.html
В самом простом варианте
мы можем ее вызвать так:
Будет
сформирован одномерный массив целых чисел. Если же нам явно нужно указать тип
элементов массива, то его можно записать вторым аргументом, например, так:
a = np.array([1,2,3,4], 'float64')
В результате все
величины будут вещественными 64-битными числами. И здесь сразу возникает
вопрос: какие типы данных поддерживает пакет NumPy. Их огромное
количество и посмотреть можно с помощью словаря 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])
можно привести к
комплексному представлению:
При этом, будет
создан новый массив с измененным типом данных:
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])
И, обратно, из
комплексного представления можем перейти в целочисленное:
Вконсолиувидимпредупреждение,
что мнимая информация о числе будет утеряна:
<input>:1:
ComplexWarning: Casting complex values to real discards the imaginary part
Но массив будет
успешно преобразован:
array([-100,
2, 5000, 1000])
Создание массивов с помощью функции array
Теперь, когда мы
знаем основные типы данных, как их указывать и использовать, вернемся к функции
array() и подробнее
рассмотрим ее работу.
Как я уже
отмечал, первым параметром следует указывать итерированный объект в виде списка
или кортежа. Например, так:
Или можно
сформировать список с использованием инструмента listcomprehensions:
def getList():
for i in range(10):
yield i
a = np.array( [x for x in getList()] )
print(a)
Здесь в качестве
итератора используется функция-итератор getList(). Но если
передать строку:
то получим массив
из одного элемента:
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) будут
определять строки и столбцы этих двумерных матриц:
Например, выполнив
в консоли команду
увидим первый
двумерный срез трехмерного массива:
array([[1,
2],
[3, 4]])
Если указать
первые два индекса:
то увидим первую
строку первого среза:
array([1, 2])
Наконец, указав
все три индекса:
получим первый
элемент трехмерной матрицы:
1
Из этих примеров
хорошо видно, что первый индекс отвечает за первую ось, второй – за вторую, ну
а третий – за третью. Что вполне логично. Если размерность массива
увеличивается до четырех, пяти и так далее осей, то принцип индексирования
сохраняется: мы также указываем требуемый индекс элемента в виде кортежа чисел:
(x1, x3, x3, …, xN)
где
местоположение каждого числа определяет ось, по которой берется тот или иной
индекс.
Итак, на этом
занятии мы с вами познакомились с базовыми типами данных
пакета NumPy и узнали как
создавать различные массивы с помощью функции array(). Если вам все
это понятно, значит цель этого занятия достигнута.