Числовые данные в Python

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

NumPy

Ключевой компонент numpy — высокопроизводительные многомерные статические числовые массивы np.array. Также в NumPy имеются многочисленные методы которые позволяют избежать накладных расходов при преобразовании np.array к типам Python и обратно.

Некоторые свойства np.array:

Свойство Значение
a.ndim Число измерений массива a
a.shape Размер по осям (кортеж) массива a
a.size Общее число элементов в a
a.dtype Тип данных массива a

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

import numpy as np

# Из списка Python
a = np.array([1,2,3,4,5])
# a == array([1, 2, 3, 4, 5])

# Заполнение нулями
a = np.zeros(5)
# a == array([ 0.,  0.,  0.,  0.,  0.])

# 5 точек от 0 до 5 включительно
a = np.linspace(0,5,5)
# a == array([ 0.  ,  1.25,  2.5 ,  3.75,  5.  ])

# точки от 0 до 5 с шагом 1
a = np.arange(0,5,1)
# a == array([0, 1, 2, 3, 4])

Многомерные массивы

Класс np.array также позволяет работать с многомерными массивами. При этом задается параметр shape определяющий размерность.

import numpy as np

# Массив 2x3
a = np.zeros((2,3))
# a == array([[ 0.,  0.,  0.],
#             [ 0.,  0.,  0.]])

a = np.linspace(0,5,6)

# Изменение формы массива
b = a.reshape((3,2))
# b == array([[ 0.,  1.],
#             [ 2.,  3.],
#             [ 4.,  5.]])

# Изменение порядка осей
b = np.moveaxis(b,0,-1)
# b == array([[0., 2., 4.],
#       [1., 3., 5.]])

Индексация в массиве

При индексации сохраняется порядок индексов аналогичный фигуре

import numpy as np

b = np.linspace(0,5,6).reshape((3,2))
# b == array([[ 0.,  1.],
#             [ 2.,  3.],
#             [ 4.,  5.]])

# Один элемент
v = b[1,1]
# v == 3.0

# Строка
v = b[1,:]
# v == array([ 2.,  3.])

# Столбец
v = b[:,1]
# v == array([ 1.,  3.,  5.])

# Выборка и использованием срезов
v = b[1:-1,1]
# v == array([ 3.])

В целях оптимизации срезы создают не копии np.array, а так называемые представления (view), поэтому меняя данные в срезе они будут меняться и в исходном массиве.

Операции

В отличии от обычных списков Python операции над массивами производятся поэлементно.

import numpy as np

b = np.linspace(0,5,6).reshape((3,2))
# b == array([[ 0.,  1.],
#             [ 2.,  3.],
#             [ 4.,  5.]])

# Операция со скаляром
c = b + 2
# c == array([[ 2.,  3.],
#             [ 4.,  5.],
#             [ 6.,  7.]])

# Операция с массивом того-же размера
c = b * b
# c == array([[  0.,   1.],
#             [  4.,   9.],
#             [ 16.,  25.]])

В отличии от обычных списков Python операции над массивами производятся поэлементно.

Математические функции из math работать с np.array не умеют, но в модуле numpy есть их аналоги (np.cos, np.log10, …).

Конкатенация

b = np.linspace(0,5,6).reshape((3,2))
# b == array([[ 0.,  1.],
#             [ 2.,  3.],
#             [ 4.,  5.]])

# Конкатенация по строкам
d = np.vstack((b,b))

# d ==  array([[0., 1.],
#       [2., 3.],
#       [4., 5.],
#       [0., 1.],
#       [2., 3.],
#       [4., 5.]])

# Конкатенация по столбцам

d = np.hstack((b,b))
#d == array([[0., 1., 0., 1.],
#       [2., 3., 2., 3.],
#       [4., 5., 4., 5.]])

Итерация

NumPy предлагает альтернативный подход к итерации по всем элементам многомерного массива по сравнению с обычными вложенными циклами.

# Обычный обход. Получаем строки.
# Нужен вложенный цикл для доступа к элементам. Только чтение.
for x in b:
    print(x, end=";")
# [ 0.  1.]; [ 2.  3.]; [ 4.  5.];

# Обход через np.nditer
for x in np.nditer(b):
     print(x, end=" ")
# 0.0 1.0 2.0 3.0 4.0 5.0

# Обход через np.nditer с возможностью записи
for x in np.nditer(b, op_flags=['readwrite']):
    x[...] = 2 * x + 2
# b == array([[  2.,   4.],
#             [  6.,   8.],
#             [ 10.,  12.]])

# Многомерный аналог enumerate
for index, x in np.ndenumerate(b):
    print(index, x, end="; ")
# (0, 0) 2.0; (0, 1) 4.0; (1, 0) 6.0; (1, 1) 8.0; (2, 0) 10.0; (2, 1) 12.0;

Операции сравнения

Применение операций сравнения (>, <, >=, , ==, !=) к массивам np.array вызывает создание массива значений типа bool соответствующего размера.

b = np.linspace(0,5,6).reshape((3,2))
# b == array([[0., 1.],
#             [2., 3.],
#             [4., 5.]])

t = b>3
# t == array([[False, False],
#             [False, False],
#             [ True,  True]])

Этот массив можно использовать далее для различных операций в качестве индекса.

q = b[b>3]
# q == array([4., 5.])

b[b>3] = 0
# b == array([[0., 1.],
#             [2., 3.],
#             [0., 0.]])

Маску можно использовать для любых массивов имеющих одинаковый shape. Их можно объединить в логические выражение с использованием операторов побитового «и» & и «или» |, а также использовать кванторы np.any и np.all.

Чтение и запись

Пример Назначение Справка
np.save('file.npy', M) Сохранение данных в формат Numpy save
M = np.load('file.npy') Чтение данных из формата Numpy load
np.savetxt('file.txt',M) Запись данных в текстовый файл savetxt
M = np.loadtxt('file.txt') Чтение данных из текстового файла loadtxt
M.tofile('file.dat') Сохранение данных в бинарный файл tofile
M = np.fromfile('file.dat') Чтение данных из бинарного файла fromfile

Что еще нужно знать о NumPy

  • По NumPy есть подробная официальная документация: https://docs.scipy.org/doc/numpy/reference/index.html
  • Для работы с данными содержащими инвалидные значения существует специальный класс numpy.ma.masked_array
  • Два числа с плавающий запятой могут быть равны только чудом, поэтому следует использовать numpy.isclose и явно указывать точность.
  • Классы np.array переопределяют вывод на печать. Поэтому сделав print вы увидите только часть элементов.