Перейти к содержимому

NumPy - библиотека Python для вычислении на массивах

NumPy - библиотека Python для вычислении на массивах

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

Главная мысль: если вы умеете уверенно работать с ndarray (массивом NumPy), то дальше почти все в NumPy становится понятным: создание массивов, изменение формы, операции по осям, линеиная алгебра, статистика и ввод-вывод.

Содержание

1. Цели урока

  • Понимать, что такое ndarray и чем он отличается от обычного списка Python.
  • Создавать массивы разными способами: array, zeros, ones, full, empty, arange, linspace, eye.
  • Менять форму массива и ориентироваться в осях: shape, axis, reshape, transpose.
  • Склеивать и разделять массивы: concatenate, stack, vstack, hstack, array_split.
  • Применять функции по осям и по элементам: apply_along_axis, vectorize, piecewise.
  • Понимать базовые инструменты numpy.linalg и статистики (mean, std, percentile).
  • Делать "специальный вывод" массивов через set_printoptions.
Что особенно важно запомнить: если вы путаетесь в NumPy, почти всегда проблема в одном из трех: shape, axis или dtype.
^ К оглавлению

2. Что такое NumPy и как читать документацию

Новые термины и определения

  • NumPy -- библиотека Python для вычислении на многомерных массивах и матрицах, с большим набором математических функции.
  • ndarray -- основной тип NumPy: многомерный массив фиксированного типа элементов.
  • shape -- форма массива: кортеж размеров по осям. Пример: (2, 3).
  • axis -- номер оси, вдоль которой выполняется операция. Часто: axis=0 -- по строкам вниз (то есть "по столбцам"), axis=1 -- по столбцам вправо (то есть "по строкам").
  • dtype -- тип данных элементов массива (например int64, float64).
  • векторизация -- стиль программирования, когда вы пишете операции над целыми массивами, а не циклы по элементам.
  • broadcasting (распространение размеров) -- правило NumPy, по которому массивы разных форм могут автоматически "растягиваться" для поэлементных операций, если их размеры совместимы.
  • матрица -- двумерный массив (размер \(m \times n\)).
  • скалярное произведение двух векторов \(a\) и \(b\) -- число \(a \cdot b\), которое можно записать формулой:

$$ a \cdot b = \sum_{i=1}^{n} a_i b_i $$

Про версию и совместимость

Определения:
  • версия библиотеки -- номер релиза пакета (например 1.25.0), который помогает понять, какие функции и поведение доступны.
  • релиз -- выпуск новой версии.

В этом уроке примеры ориентированы на NumPy версии 1.25 (как в исходном материале). Важно понимать: NumPy развивается, и в новых версиях могут меняться детали (например предупреждения, типы, поведение печати). Поэтому полезная привычка -- проверять версию в коде.

import numpy as np

print(np.__version__)
# 1.25.0
Важно: если у вас в консоли выводится не 1.25.0, это не ошибка. Это означает, что у вас установлена другая версия. Большинство примеров все равно будет работать, но иногда вывод может немного отличаться.

Частая путаница

Запомните: NumPy-массив -- это не список Python. У массива есть shape, dtype и операции по осям. Список чаще всего хранит "что угодно", а NumPy старается хранить "однотипные числа" и работать с ними быстро.
import numpy as np

lst = [1, 2, 3]
arr = np.array([1, 2, 3])

print(type(lst))
# <class 'list'>

print(type(arr))
# <class 'numpy.ndarray'>
^ К оглавлению

3. Массивы NumPy: создание и базовые свойства

ndarray, shape, dtype

Самое важное, что нужно уметь делать с массивом NumPy -- это смотреть его форму и тип.

import numpy as np

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

print(a)
# [[1 2 3]
#  [4 5 6]]

print(a.shape)
# (2, 3)

print(a.dtype)
# int64
Определение: кортеж -- неизменяемая последовательность Python, записывается в круглых скобках: (2, 3).
Важно: int64 в выводе -- типичный вариант на 64-битных системах. На некоторых системах вы можете увидеть int32. Если вам нужна строго фиксированная разрядность, задавайте dtype явно.

array, zeros, ones, full, empty, arange, linspace, eye

np.array преобразует список (или список списков) в NumPy-массив.

import numpy as np

a = np.array([10, 20, 30], dtype=np.int64)
print(a)
# [10 20 30]

print(a.dtype)
# int64

zeros, ones, full создают массивы заданной формы.

import numpy as np

z = np.zeros((2, 3), dtype=np.int64)
o = np.ones((2, 3), dtype=np.int64)
f = np.full((2, 3), fill_value=7, dtype=np.int64)

print(z)
# [[0 0 0]
#  [0 0 0]]

print(o)
# [[1 1 1]
#  [1 1 1]]

print(f)
# [[7 7 7]
#  [7 7 7]]

empty создает массив без инициализации элементов. Это значит: содержимое "какое было в памяти", то и может оказаться. Поэтому содержимое такого массива нельзя использовать, пока вы не заполнили его сами. Чтобы пример был честным и стабильным, мы будем выводить только форму и тип, а не содержимое.

import numpy as np

e = np.empty((2, 3), dtype=np.float64)

print(e.shape)
# (2, 3)

print(e.dtype)
# float64

arange делает "диапазон" значений. В отличие от range он может работать с числами с плавающей точкой (но есть нюанс про округления).

import numpy as np

a1 = np.arange(0, 5, 1)
a2 = np.arange(0.0, 1.0, 0.2)

print(a1)
# [0 1 2 3 4]

print(a2)
# [0.  0.2 0.4 0.6 0.8]
Определение: число с плавающей точкой (float) -- число, которое хранится в формате, где возможны дробные части, но из-за двоичного представления иногда возникают маленькие ошибки округления.

linspace создает заданное количество точек на отрезке \([start, stop]\). Это часто удобнее, чем шаг в arange, если вам важны именно "N точек".

import numpy as np

x = np.linspace(0, 1, num=5)
print(x)
# [0.   0.25 0.5  0.75 1.  ]

eye и identity создают единичные матрицы. Единичная матрица \(I\) это квадратная матрица, где на главной диагонали стоят 1, а остальные элементы 0:

$$ I = \begin{pmatrix} 1 & 0 & 0 \\ 0 & 1 & 0 \\ 0 & 0 & 1 \end{pmatrix} $$

import numpy as np

m1 = np.eye(3, dtype=np.float64)
m2 = np.identity(3, dtype=np.float64)

print(m1)
# [[1. 0. 0.]
#  [0. 1. 0.]
#  [0. 0. 1.]]

print(m2)
# [[1. 0. 0.]
#  [0. 1. 0.]
#  [0. 0. 1.]]

Типичные ошибки

Ошибка 1: ожидать, что empty() заполнит нулями

empty не инициализирует значения. Если вам нужны нули, используйте zeros.

import numpy as np

e = np.empty((2, 2), dtype=np.int64)
z = np.zeros((2, 2), dtype=np.int64)

print(z)
# [[0 0]
#  [0 0]]

Ошибка 2: arange с float дает "странные" числа

Это эффект двоичного представления float. Если вам важна сетка из N точек, используйте linspace.

import numpy as np

a = np.arange(0.0, 0.3, 0.1)
print(a)
# [0.  0.1 0.2]
^ К оглавлению

4. Форма массива и склеивание: reshape, transpose, concatenate, stack, split

reshape, flatten, transpose

Определения:
  • reshape -- изменение формы массива без изменения последовательности элементов.
  • flatten -- получение одномерной копии массива.
  • transpose -- перестановка осей. Для матрицы это транспонирование: строки становятся столбцами.
import numpy as np

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

b = a.reshape((2, 3))
print(b)
# [[1 2 3]
#  [4 5 6]]

c = b.flatten()
print(c)
# [1 2 3 4 5 6]

t = b.T
print(t)
# [[1 4]
#  [2 5]
#  [3 6]]

concatenate, stack, vstack, hstack, array_split

Определение: axis в склеивании означает "вдоль какой оси мы склеиваем". Для двумерного массива:
  • axis=0 -- склеиваем по строкам (увеличиваем число строк).
  • axis=1 -- склеиваем по столбцам (увеличиваем число столбцов).
import numpy as np

a = np.array([[1, 2],
              [3, 4]])
b = np.array([[10, 20],
              [30, 40]])

print(np.concatenate([a, b], axis=0))
# [[ 1  2]
#  [ 3  4]
#  [10 20]
#  [30 40]]

print(np.concatenate([a, b], axis=1))
# [[ 1  2 10 20]
#  [ 3  4 30 40]]

stack добавляет новую ось, то есть увеличивает размерность.

import numpy as np

a = np.array([1, 2, 3])
b = np.array([10, 20, 30])

s0 = np.stack([a, b], axis=0)
s1 = np.stack([a, b], axis=1)

print(s0)
# [[ 1  2  3]
#  [10 20 30]]

print(s1)
# [[ 1 10]
#  [ 2 20]
#  [ 3 30]]

print(s0.shape)
# (2, 3)

print(s1.shape)
# (3, 2)

vstack и hstack это удобные обертки для "вертикального" и "горизонтального" склеивания.

import numpy as np

a = np.array([[1, 2],
              [3, 4]])
b = np.array([[10, 20],
              [30, 40]])

print(np.vstack([a, b]))
# [[ 1  2]
#  [ 3  4]
#  [10 20]
#  [30 40]]

print(np.hstack([a, b]))
# [[ 1  2 10 20]
#  [ 3  4 30 40]]

array_split делит массив на части. Это полезно, когда нужно "порезать" данные по блокам.

import numpy as np

a = np.arange(1, 11)
parts = np.array_split(a, 3)

print(parts[0])
# [1 2 3 4]

print(parts[1])
# [5 6 7]

print(parts[2])
# [ 8  9 10]

delete, insert, append, unique

Определение: операции delete, insert, append обычно создают новый массив. Это не как "list.append" по смыслу. В NumPy чаще стараются заранее выделять нужную форму и заполнять ее, потому что частые вставки и удаления могут быть дорогими.
import numpy as np

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

print(np.delete(a, 1))
# [1 3 2 1]

print(np.insert(a, 2, 99))
# [ 1  2 99  3  2  1]

print(np.append(a, [7, 8]))
# [1 2 3 2 1 7 8]

print(np.unique(a))
# [1 2 3]

Типичные ошибки

Ошибка 1: reshape на несовместимый размер

Число элементов должно совпадать. Поймаем тип ошибки через try/except.

import numpy as np

a = np.arange(6)

try:
    b = a.reshape((4, 2))
    print(b)
except Exception as e:
    print("ERROR:", type(e).__name__)
# ERROR: ValueError

Ошибка 2: неправильный axis при concatenate

Если формы не совпадают по всем осям, кроме указанной, NumPy не сможет склеить массивы.

import numpy as np

a = np.zeros((2, 2), dtype=np.int64)
b = np.zeros((3, 2), dtype=np.int64)

try:
    print(np.concatenate([a, b], axis=1))
except Exception as e:
    print("ERROR:", type(e).__name__)
# ERROR: ValueError
^ К оглавлению

5. Функциональные приемы: apply_along_axis, vectorize, piecewise, broadcasting

axis: как думать про оси

Если у вас матрица размера \(m \times n\), то:

  • axis=0 означает "вдоль строк вниз". Обычно это "операция по столбцам" (мы берем столбец и что-то с ним делаем).
  • axis=1 означает "вдоль столбцов вправо". Обычно это "операция по строкам".
import numpy as np

a = np.array([[1, 2, 3],
              [10, 20, 30]])

print(np.sum(a, axis=0))
# [11 22 33]

print(np.sum(a, axis=1))
# [ 6 60]

apply_along_axis

Определение: apply_along_axis(func1d, axis, arr) применяет функцию func1d к одномерным срезам массива вдоль оси axis. То есть NumPy будет "доставать" кусочки (векторы) и передавать их в вашу функцию.
import numpy as np

def sum_of_squares(v):
    return np.sum(v**2)

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

res0 = np.apply_along_axis(sum_of_squares, axis=0, arr=a)
res1 = np.apply_along_axis(sum_of_squares, axis=1, arr=a)

print(res0)
# [17 29 45]

print(res1)
# [14 77]

vectorize и piecewise

Определения:
  • vectorize -- обертка, которая позволяет применить обычную Python-функцию к массиву "как будто поэлементно". Это удобно, но не всегда дает ускорение.
  • piecewise -- поэлементно вычисляет кусочную функцию: выбирает формулу по условию.
  • кусочная функция -- функция, заданная разными формулами на разных участках.

Пример кусочнои функции:

$$ f(x) = \begin{cases} x^2, & x < 0 \\ x, & x \ge 0 \end{cases} $$

import numpy as np

x = np.array([-2, -1, 0, 1, 2])

y = np.piecewise(
    x,
    [x < 0, x >= 0],
    [lambda t: t**2, lambda t: t]
)

print(y)
# [4 1 0 1 2]
import numpy as np

def f(x):
    return x*x + 1

vf = np.vectorize(f)

a = np.array([1, 2, 3])
print(vf(a))
# [ 2  5 10]

broadcasting (распространение размеров)

Определение: broadcasting -- правило, по которому NumPy может автоматически "растянуть" массив меньшей размерности, чтобы выполнить поэлементную операцию с массивом большей размерности. Это работает, если размеры совместимы (например 3 и 1).

Пример: к строке длины 3 прибавим "столбец" размера \(2 \times 1\). Результат будет матрица \(2 \times 3\).

import numpy as np

row = np.array([1, 2, 3])          # shape (3,)
col = np.array([[10],
                [100]])            # shape (2, 1)

res = col + row
print(res)
# [[ 11  12  13]
#  [101 102 103]]

print(res.shape)
# (2, 3)

Типичные ошибки

Ошибка 1: ожидать, что vectorize ускоряет код

vectorize делает удобный интерфейс, но часто выполняет Python-функцию много раз. Для скорости обычно используют "настоящие" векторные операции NumPy (ufunc), а не vectorize.

import numpy as np

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

# Векторная операция (обычно быстрее):
print(a*a + 1)
# [ 2  5 10]

Ошибка 2: broadcasting не сработал из-за несовместимых форм

Поймаем тип ошибки.

import numpy as np

a = np.zeros((2, 3), dtype=np.int64)
b = np.zeros((2, 2), dtype=np.int64)

try:
    print(a + b)
except Exception as e:
    print("ERROR:", type(e).__name__)
# ERROR: ValueError
^ К оглавлению

6. Ввод-вывод, линеиная алгебра, статистика и формат вывода

save/load, savetxt/loadtxt, tolist

Определения:
  • .npy -- бинарный формат NumPy для одного массива.
  • .npz -- архив, который может хранить несколько массивов.
  • сериализация -- сохранение объекта в файл так, чтобы его можно было восстановить.

Мини пример: сохранить в .npy и загрузить обратно.

import numpy as np
import tempfile
import os

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

tmpdir = tempfile.mkdtemp()
path = os.path.join(tmpdir, "a.npy")

np.save(path, a)
b = np.load(path)

print(b)
# [[1 2]
#  [3 4]]

tolist() превращает массив в обычные Python-списки.

import numpy as np

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

print(a.tolist())
# [[1, 2], [3, 4]]

dot, matmul (@), linalg.solve, det, inv, norm

Определения:
  • dot -- обобщенное произведение: для 1D это скалярное произведение, для 2D это матричное умножение.
  • matmul и оператор @ -- матричное умножение.
  • linalg.solve -- решение системы линейных уравнений \(A x = b\).
  • det -- определитель матрицы: \(\det(A)\).
  • inv -- обратная матрица \(A^{-1}\), если она существует.
  • norm -- норма вектора или матрицы (мера "длины" или "размера").

Система линейных уравнений в матричном виде:

$$ A x = b $$

import numpy as np

A = np.array([[2.0, 1.0],
              [5.0, 3.0]])
b = np.array([1.0, 2.0])

x = np.linalg.solve(A, b)

print(x)
# [ 1. -1.]

Проверка подстановкой: \(A x\) должно дать \(b\).

import numpy as np

A = np.array([[2.0, 1.0],
              [5.0, 3.0]])
x = np.array([1.0, -1.0])

print(A @ x)
# [1. 2.]

Определитель и обратная матрица.

import numpy as np

A = np.array([[2.0, 1.0],
              [5.0, 3.0]])

print(np.linalg.det(A))
# 1.0

print(np.linalg.inv(A))
# [[ 3. -1.]
#  [-5.  2.]]

Норма вектора \(v=(v_1,\dots,v_n)\) в одном из частых вариантов (евклидова норма):

$$ \lVert v \rVert_2 = \sqrt{\sum_{i=1}^{n} v_i^2} $$

import numpy as np

v = np.array([3.0, 4.0])
print(np.linalg.norm(v))
# 5.0

mean, var, std, percentile, corrcoef, histogram

Определения:
  • mean -- среднее арифметическое.
  • var -- дисперсия.
  • std -- стандартное отклонение (корень из дисперсии).
  • percentile -- персентиль (квантиль), значение, ниже которого лежит заданный процент данных.
  • corrcoef -- матрица корреляции (обычно корреляция Пирсона).
  • histogram -- гистограмма: разбиение значений на интервалы и подсчет количества в каждом интервале.

Среднее арифметическое для набора \(x_1,\dots,x_n\):

$$ \bar{x} = \frac{1}{n}\sum_{i=1}^{n} x_i $$

import numpy as np

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

print(np.mean(a))
# 2.5

print(np.var(a))
# 1.25

print(np.std(a))
# 1.118033988749895
import numpy as np

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

print(np.median(a))
# 3.0

print(np.percentile(a, 50))
# 3.0
import numpy as np

x = np.array([1, 2, 3], dtype=np.float64)
y = np.array([1, 5, 7], dtype=np.float64)

print(np.corrcoef(x, y))
# [[1.         0.98198051]
#  [0.98198051 1.        ]]
import numpy as np

a = np.array([0, 0, 1, 1, 1, 2, 3, 3], dtype=np.int64)
counts, bins = np.histogram(a, bins=4)

print(counts)
# [2 3 1 2]

print(bins)
# [0.   0.75 1.5  2.25 3.  ]

set_printoptions: специальное форматирование вывода

Определение: set_printoptions -- настройка того, как NumPy печатает массивы (точность, научная нотация, подавление очень маленьких чисел и т.д.).
import numpy as np

np.set_printoptions(precision=3, suppress=True)

a = np.array([1.234567, 0.000012345, 1000.0], dtype=np.float64)
print(a)
# [   1.235    0.    1000.   ]

Типичные ошибки

Ошибка 1: использовать * вместо @ для матриц

Оператор * делает поэлементное умножение. Для матричного умножения используйте @ или np.matmul.

import numpy as np

A = np.array([[1, 2],
              [3, 4]])
B = np.array([[10, 20],
              [30, 40]])

print(A * B)
# [[ 10  40]
#  [ 90 160]]

print(A @ B)
# [[ 70 100]
#  [150 220]]

Ошибка 2: решить систему с вырожденной матрицей

Если \(\det(A)=0\), система может не иметь единственного решения. Покажем тип ошибки безопасно.

import numpy as np

A = np.array([[1.0, 2.0],
              [2.0, 4.0]])
b = np.array([1.0, 2.0])

print(np.linalg.det(A))
# 0.0

try:
    x = np.linalg.solve(A, b)
    print(x)
except Exception as e:
    print("ERROR:", type(e).__name__)
# ERROR: LinAlgError
^ К оглавлению

7. Практика: задачи (с решениями)

Блок 1: создание и форма массивов

Задача 1: создать массив и вывести shape и dtype

Создайте массив 2 x 3 и выведите сам массив, shape и dtype.

import numpy as np

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

print(a)
# [[1 2 3]
#  [4 5 6]]

print(a.shape)
# (2, 3)

print(a.dtype)
# int64

Задача 2: построить сетку из 5 точек на [0, 2]

Используйте linspace. Должно получиться 5 чисел, включая границы.

import numpy as np

x = np.linspace(0, 2, num=5)
print(x)
# [0.  0.5 1.  1.5 2. ]

Задача 3: сделать reshape из 12 элементов в 3 x 4

Создайте arange от 1 до 12 включительно и преобразуйте в матрицу 3 x 4.

import numpy as np

a = np.arange(1, 13)
m = a.reshape((3, 4))

print(m)
# [[ 1  2  3  4]
#  [ 5  6  7  8]
#  [ 9 10 11 12]]

Задача 4: транспонировать матрицу

Возьмите матрицу 2 x 3 и выведите ее транспонированную форму 3 x 2.

import numpy as np

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

print(m.T)
# [[1 4]
#  [2 5]
#  [3 6]]

Блок 2: оси, линеиная алгебра, статистика, ввод-вывод

Задача 5: сумма по axis=0 и axis=1

Посчитайте суммы по столбцам и по строкам.

import numpy as np

a = np.array([[1, 2, 3],
              [10, 20, 30]])

print(np.sum(a, axis=0))
# [11 22 33]

print(np.sum(a, axis=1))
# [ 6 60]

Задача 6: решить систему Ax=b

Решите \(A x = b\), где:

$$ A = \begin{pmatrix} 2 & 1 \\ 5 & 3 \end{pmatrix}, \quad b = \begin{pmatrix} 1 \\ 2 \end{pmatrix} $$

import numpy as np

A = np.array([[2.0, 1.0],
              [5.0, 3.0]])
b = np.array([1.0, 2.0])

x = np.linalg.solve(A, b)
print(x)
# [ 1. -1.]

Задача 7: сравнить A*B и A@B

Покажите, что * это поэлементно, а @ это матрично.

import numpy as np

A = np.array([[1, 2],
              [3, 4]])
B = np.array([[10, 20],
              [30, 40]])

print(A * B)
# [[ 10  40]
#  [ 90 160]]

print(A @ B)
# [[ 70 100]
#  [150 220]]

Задача 8: mean, median, percentile

Посчитайте среднее, медиану и 50-й персентиль для массива.

import numpy as np

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

print(np.mean(a))
# 22.0

print(np.median(a))
# 3.0

print(np.percentile(a, 50))
# 3.0
^ К оглавлению

8. Чек-лист самопроверки

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

+/-НавыкПроверка
Понимаю ndarray Могу объяснить разницу между list и ndarray
Читаю shape и dtype Могу по выводу понять форму и тип массива
Создаю массивы разными способами Могу использовать array, zeros, ones, full, empty, arange, linspace, eye
Меняю форму массива Могу сделать reshape и объяснить, когда возникает ValueError
Понимаю transpose Могу получить A.T и объяснить, что изменилось
Склеиваю и делю массивы Могу применить concatenate/stack и array_split
Понимаю axis Могу посчитать сумму по axis=0 и axis=1 и объяснить разницу
Отличаю * от @ Могу показать поэлементное и матричное умножение
Решаю Ax=b Могу применить np.linalg.solve и проверить результат через A@x
Считаю базовую статистику Могу посчитать mean, median, percentile и объяснить смысл
Делаю специальный вывод Могу настроить np.set_printoptions и получить нужную точность
^ К оглавлению

Ссылки по теме

Воскресенье, 01 марта 2026
NumPy - библиотека Python для вычислении на массивах