Матрицы и массивы NumPy в Python

NumPy хранит матрицу в объекте ndarray и задает ее форму парой чисел — числом строк и числом столбцов. Несовпадение форм (shape) при поэлементных операциях — сложении, умножении * — вызывает ошибку трансляции (broadcasting), и NumPy прямо называет, какие формы разошлись.

В статье — создание двумерного массива NumPy, его форма и broadcasting, сложение, умножение и транспонирование матриц, обратная матрица, доступ к элементам, строкам, столбцам и срезам, а также добавление элементов через np.append. Каждый пример — рабочий код на Python с выводом ровно таким, каким его печатает интерпретатор.

Вадим Дворниковавтор материала

Матрицы в Python

Python не имеет встроенного типа данных для матриц, но список из списков работает как матрица. Например:

A = [[1, 4, 5],
    [-5, 8, 9]]

Такой список задает матрицу на 2 строки и 3 столбца.

Иллюстрация к примеру: список Python как матрица

Справка по спискам Python — в официальной документации.

Давайте посмотрим, как работать с вложенным списком.

A = [[1, 4, 5, 12],
    [-5, 8, 9, 0],
    [-6, 7, 11, 19]]

print("A =", A)
print("A[1] =", A[1])      # вторая строка
print("A[1][2] =", A[1][2])   # третий элемент второй строки
print("A[0][-1] =", A[0][-1])   # последний элемент первой строки

column = []        # пустой список
for row in A:
  column.append(row[2])

print("3-й столбец =", column)

Когда мы запустим эту программу, результат будет следующий:

A = [[1, 4, 5, 12], [-5, 8, 9, 0], [-6, 7, 11, 19]]
A[1] = [-5, 8, 9, 0]
A[1][2] = 9
A[0][-1] = 12
3-й столбец = [5, 9, 11]

Вложенные списки подходят для простых вычислительных задач. Но в Python есть более эффективный способ работы с матрицами — NumPy.

Как установить NumPy в Python и создать первый массив

NumPy — библиотека для научных вычислений на Python с мощным объектом N-мерного массива. Поставьте ее командой:

pip install numpy

Проверьте версию после установки:

import numpy
print(numpy.__version__)

Команда напечатает установленную версию в формате X.Y.Z, например 1.26.0.

  • Подробный разбор способов установки — официальная страница установки NumPy.
  • На Windows подходит и дистрибутив anaconda: он ставит NumPy вместе с другими библиотеками для анализа данных.

После установки NumPy можно импортировать и использовать в коде.

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

NumPy хранит числа в объекте ndarray — многомерном массиве. Рассмотрим пример:

import numpy as np
a = np.array([1, 2, 3])
print(a)               # Вывод: [1 2 3]
print(type(a))         # Вывод: <class 'numpy.ndarray'>

Как видите, класс массива NumPy называется ndarray.

Как создать массив NumPy?

Существует несколько способов создания массивов NumPy.

Массив целых чисел, чисел с плавающей точкой и комплексных чисел

import numpy as np

A = np.array([[1, 2, 3], [3, 4, 5]])
print(A)

A = np.array([[1.1, 2, 3], [3, 4, 5]]) # Массив чисел с плавающей запятой
print(A)

A = np.array([[1, 2, 3], [3, 4, 5]], dtype = complex) # Массив комплексных чисел
print(A)

Когда вы запустите эту программу, результат будет следующий:

[[1 2 3]
 [3 4 5]]

[[1.1 2.  3. ]
 [3.  4.  5. ]]

[[1.+0.j 2.+0.j 3.+0.j]
 [3.+0.j 4.+0.j 5.+0.j]]

Массив нулей и единиц

import numpy as np

zeros_array = np.zeros( (2, 3) )
print(zeros_array)

'''
 Вывод:
 [[0. 0. 0.]
  [0. 0. 0.]]
'''

ones_array = np.ones( (1, 5), dtype=np.int32 )  # указание dtype
print(ones_array)      # Вывод: [[1 1 1 1 1]]

Здесь мы указали dtype — 32 бита (4 байта). Следовательно, этот массив может принимать значения от -231 до 231-1, то есть от -2 147 483 648 до 2 147 483 647.

Единичная матрица NumPy

Единичная матрица — квадратная матрица с единицами на главной диагонали и нулями на остальных местах. Собирают ее функции numpy.eye или numpy.identity:

import numpy as np

I = np.eye(3)
print(I)

# Вывод:
# [[1. 0. 0.]
#  [0. 1. 0.]
#  [0. 0. 1.]]

numpy.identity(3) дает тот же результат. numpy.eye берут, когда матрица не квадратная или единицы нужны не строго по главной диагонали.

Использование arange(), shape и broadcasting

import numpy as np

A = np.arange(4)
print('A =', A)

B = np.arange(12).reshape(2, 6)
print('B =', B)

'''
Вывод:
A = [0 1 2 3]
B = [[ 0  1  2  3  4  5]
 [ 6  7  8  9 10 11]]
'''

У каждого массива есть атрибут shape — кортеж с числом элементов по каждой оси:

print(B.shape)

# Вывод:
# (2, 6)

При арифметике NumPy сравнивает shape операндов и растягивает (транслирует, broadcast) меньший массив под больший, если размеры совпадают или один из них равен 1. Так вектор из трех чисел складывается с каждой строкой матрицы 2×3 без цикла:

import numpy as np

matrix = np.array([[1, 2, 3], [4, 5, 6]])
row = np.array([10, 20, 30])
print(matrix + row)

# Вывод:
# [[11 22 33]
#  [14 25 36]]

Если формы несовместимы, NumPy не подбирает значения сам, а останавливается с ошибкой трансляции:

import numpy as np

matrix = np.array([[1, 2, 3], [4, 5, 6]])
pair = np.array([1, 2])
print(matrix + pair)

# ValueError: operands could not be broadcast together with shapes (2,3) (2,)

Ошибка называет обе формы: (2, 3) у matrix и (2,) у pair. Число строк совпадает, а число столбцов и элементов — нет, поэтому транслировать pair под каждую строку matrix нельзя: форму нужно привести к (3,) или к (2, 1).

Узнайте больше о других способах создания массива NumPy.

Операции с матрицами

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

Сложение двух матриц или сумма элементов массива Python

Мы используем оператор +, чтобы сложить соответствующие элементы двух матриц NumPy.

import numpy as np

A = np.array([[2, 4], [5, -6]])
B = np.array([[9, -3], [3, 6]])
C = A + B      # сложение соответствующих элементов
print(C)

'''
Вывод:
[[11  1]
 [ 8  0]]
 '''

Функция numpy.sum считает сумму элементов массива — всех сразу или по одной оси через параметр axis: axis=0 — сумма по столбцам, axis=1 — сумма по строкам.

import numpy as np

A = np.array([[2, 4], [5, -6]])

print(A.sum())        # сумма всех элементов
print(A.sum(axis=0))   # сумма по столбцам
print(A.sum(axis=1))   # сумма по строкам

# Вывод:
# 5
# [ 7 -2]
# [ 6 -1]

Умножение двух матриц Python

Чтобы умножить две матрицы, мы используем метод dot(). Узнайте больше о том, как работает numpy.dot.

Примечание: * используется для умножения массива (умножения соответствующих элементов двух массивов), а не умножения матрицы.

import numpy as np

A = np.array([[3, 6, 7], [5, -3, 0]])
B = np.array([[1, 1], [2, 1], [3, -3]])
C = A.dot(B)
print(C)

'''
Вывод:
[[ 36 -12]
 [ -1   2]]
'''

Начиная с Python 3.5 то же умножение записывают оператором @ — это более короткая запись A.dot(B):

print(A @ B)

# Вывод:
# [[ 36 -12]
#  [ -1   2]]

Транспонирование матрицы Python

Мы используем numpy.transpose для вычисления транспонирования матрицы.

import numpy as np

A = np.array([[1, 1], [2, 1], [3, -3]])
print(A.transpose())

'''
Вывод:
[[ 1  2  3]
 [ 1  1 -3]]
'''

Как видите, NumPy значительно упростил нашу задачу.

Обратная матрица NumPy

NumPy вычисляет обратную матрицу функцией numpy.linalg.inv: произведение матрицы на обратную дает единичную матрицу.

import numpy as np

A = np.array([[4, 7], [2, 6]])
A_inv = np.linalg.inv(A)
print(A_inv)

# Вывод:
# [[ 0.6 -0.7]
#  [-0.2  0.4]]

Обратная матрица существует только у квадратной матрицы с ненулевым определителем. У вырожденной матрицы (определитель равен нулю) numpy.linalg.inv останавливается с ошибкой numpy.linalg.LinAlgError: Singular matrix — обратной у такой матрицы нет.

Доступ к элементам матрицы, строкам и столбцам

Доступ к элементам матрицы

Также можно получить доступ к элементам матрицы, используя индекс. Начнем с одномерного массива NumPy.

import numpy as np
A = np.array([2, 4, 6, 8, 10])

print("A[0] =", A[0])     # Первый элемент
print("A[2] =", A[2])     # Третий элемент
print("A[-1] =", A[-1])   # Последний элемент

Когда вы запустите эту программу, результат будет следующий:

A[0] = 2
A[2] = 6
A[-1] = 10

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

import numpy as np

A = np.array([[1, 4, 5, 12],
    [-5, 8, 9, 0],
    [-6, 7, 11, 19]])

#  Первый элемент первой строки
print("A[0][0] =", A[0][0])

# Третий элемент второй строки
print("A[1][2] =", A[1][2])

# Последний элемент последней строки
print("A[-1][-1] =", A[-1][-1])

Когда мы запустим эту программу, результат будет следующий:

A[0][0] = 1
A[1][2] = 9
A[-1][-1] = 19

Доступ к строкам матрицы

import numpy as np

A = np.array([[1, 4, 5, 12],
    [-5, 8, 9, 0],
    [-6, 7, 11, 19]])

print("A[0] =", A[0]) # Первая строка
print("A[2] =", A[2]) # Третья строка
print("A[-1] =", A[-1]) # Последняя строка (третья строка в данном случае)

Когда мы запустим эту программу, результат будет следующий:

A[0] = [ 1  4  5 12]
A[2] = [-6  7 11 19]
A[-1] = [-6  7 11 19]

Доступ к столбцам матрицы

import numpy as np

A = np.array([[1, 4, 5, 12],
    [-5, 8, 9, 0],
    [-6, 7, 11, 19]])

print("A[:, 0] =", A[:, 0]) # Первый столбец
print("A[:, 3] =", A[:, 3]) # Четвёртый столбец
print("A[:, -1] =", A[:, -1]) # Последний столбец (четвёртый столбец в данном случае)

Когда мы запустим эту программу, результат будет следующий:

A[:, 0] = [ 1 -5 -6]
A[:, 3] = [12  0 19]
A[:, -1] = [12  0 19]

Если вы не знаете, как работает приведенный выше код, прочтите раздел «Срезы (slicing) матрицы».

Срезы (slicing) матрицы

Срез одномерного массива NumPy работает как срез списка. Рассмотрим пример:

import numpy as np
numbers = np.array([1, 3, 5, 7, 9, 7, 5])

# с 3-го по 5-й элементы
print(numbers[2:5])        # Вывод: [5 7 9]

# с 1-го по 2-й элементы
print(numbers[:-5])        # Вывод: [1 3]

# с 6-го до последнего элемента
print(numbers[5:])         # Вывод: [7 5]

# весь массив
print(numbers[:])          # Вывод: [1 3 5 7 9 7 5]

# массив в обратном порядке
print(numbers[::-1])       # Вывод: [5 7 9 7 5 3 1]

Теперь посмотрим, как сделать срез матрицы.

import numpy as np

A = np.array([[1, 4, 5, 12, 14],
    [-5, 8, 9, 0, 17],
    [-6, 7, 11, 19, 21]])

print(A[:2, :4])  # две строки, четыре столбца

''' Вывод:
[[ 1  4  5 12]
 [-5  8  9  0]]
'''

print(A[:1,])  # первая строка, все столбцы

''' Вывод:
[[ 1  4  5 12 14]]
'''

print(A[:,2])  # все строки, третий столбец

''' Вывод:
[ 5  9 11]
'''

print(A[:, 2:5])  # все строки, с третьего по пятый столбец
''' Вывод:
[[ 5 12 14]
 [ 9  0 17]
 [11 19 21]]
'''

Добавление элементов: np.append()

Функция numpy.append добавляет элементы в конец массива и возвращает новый массив — исходный она не меняет.

import numpy as np

A = np.array([1, 2, 3])
B = np.append(A, [4, 5])

print(B)
print(A)

# Вывод:
# [1 2 3 4 5]
# [1 2 3]

Также можно добавить строку в двумерный массив — через параметр axis; формы массивов должны совпадать по остальным осям:

import numpy as np

C = np.array([[1, 2], [3, 4]])
D = np.append(C, [[5, 6]], axis=0)
print(D)

# Вывод:
# [[1 2]
#  [3 4]
#  [5 6]]

Каждый вызов numpy.append копирует весь массив: для разового добавления это нормально, а в цикле — дороже, чем собрать список и один раз обернуть его в numpy.array.

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

Комментарии

Оставьте свой комментарий
Александр Л.

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