Django-Admin.ru

NumPy: массивы и вычисления в Python

Библиотека NumPy в Python с нуля: установка, создание массивов, dtype, shape и reshape, векторные операции, срезы, broadcasting и полезные функции.

Редакция Django-Admin.ru
· обновлено · 11 мин чтения

Содержание · 11 разделов
  1. Установка NumPy
  2. Массив ndarray и чем он отличается от списка
  3. Типы данных: dtype
  4. Форма массива: shape и reshape
  5. Векторные операции
  6. Индексы и срезы
  7. Broadcasting: операции с массивами разной формы
  8. Полезные функции
  9. Частые ошибки
  10. ValueError: The truth value of an array … is ambiguous
  11. nan портит среднее
  12. Массив из строк вместо чисел
  13. Списки разной длины
  14. Неожиданное изменение исходных данных
  15. Частые вопросы
  16. Как сохранить массив в файл и загрузить обратно?
  17. Как добавить элемент к массиву?
  18. Как найти индексы элементов по условию?
  19. Как убрать экспоненциальную запись при выводе?
  20. Чем NumPy отличается от pandas?
  21. Итог

NumPy — библиотека Python для работы с числовыми массивами: она хранит данные компактно и выполняет математику сразу над всем массивом, без циклов for. На NumPy построены pandas, Matplotlib, scikit-learn и OpenCV. Короткий ответ, как начать работать с numpy в Python:

import numpy as np

a = np.array([1, 2, 3, 4])
print(a * 2)              # → [2 4 6 8]
print(a.sum(), a.mean())  # → 10 2.5

Ниже — установка, создание массивов, типы данных, форма и reshape, векторные операции, срезы, broadcasting и функции, которые нужны каждый день. Примеры проверены на NumPy 2.5 и Python 3.12.

Установка NumPy

Библиотека NumPy не входит в стандартную библиотеку Python. Установите её через pip, лучше в виртуальном окружении проекта:

# Windows
py -m pip install numpy

# macOS / Linux
python3 -m pip install numpy

Проверка:

import numpy as np

print(np.__version__)  # → 2.5.3 (у вас может быть другая версия)

Псевдоним np — общепринятое соглашение, его используют документация и почти все примеры. Если импорт падает с ModuleNotFoundError: No module named 'numpy', пакет установлен в другой интерпретатор — подробности в статье про ModuleNotFoundError и в руководстве по установке библиотек Python.

Массив ndarray и чем он отличается от списка

Главный объект NumPy — массив ndarray. В отличие от списка Python, все его элементы имеют один тип, а операции применяются поэлементно:

print([1, 2, 3] * 2)           # → [1, 2, 3, 1, 2, 3] — список повторился
print(np.array([1, 2, 3]) * 2)  # → [2 4 6] — каждый элемент умножен

Способы создать массив:

np.array([1, 2, 3])        # из списка
np.zeros(3)                # → [0. 0. 0.]
np.ones((2, 3))            # матрица 2×3 из единиц
np.full(3, 7)              # → [7 7 7]
np.arange(0, 10, 2)        # → [0 2 4 6 8] — как range()
np.linspace(0, 1, 5)       # → [0.   0.25 0.5  0.75 1.  ] — 5 точек от 0 до 1 включительно
np.eye(2)                  # единичная матрица 2×2

Обратите внимание: zeros и ones по умолчанию создают дробные числа (float64), поэтому в выводе стоят точки. Размер многомерного массива передаётся кортежем: np.ones((2, 3)), а не np.ones(2, 3).

Основные атрибуты массива:

m = np.array([[1, 2, 3], [4, 5, 6]])
print(m.shape)  # → (2, 3) — 2 строки, 3 столбца
print(m.ndim)   # → 2 — число измерений
print(m.size)   # → 6 — всего элементов
print(m.dtype)  # → int64 — тип элементов

Типы данных: dtype

NumPy определяет тип по содержимому, а задать его можно параметром dtype:

print(np.array([1, 2, 3]).dtype)    # → int64
print(np.array([1.5, 2, 3]).dtype)  # → float64 — одно дробное число делает дробным весь массив
print(np.array([1, 2, 3], dtype=np.float32))  # → [1. 2. 3.]
print(np.array([True, False]).dtype)          # → bool

Преобразовать готовый массив — метод astype(). Он создаёт новый массив; дробная часть при переводе в целые отбрасывается, а не округляется:

print(np.array([1.7, 2.2]).astype(int))  # → [1 2]

Основные типы:

dtypeЧто хранит
int64, int32целые числа со знаком
uint8целые от 0 до 255 (пиксели)
float64, float32дробные числа
boolTrue / False
<U… (например <U2)строки Unicode фиксированной длины
objectпроизвольные объекты Python

Важно: у типов фиксированного размера есть переполнение. np.array([200], dtype=np.uint8) + 100 даёт [44], а не 300 — значение «перешло через ноль» без ошибки.

Форма массива: shape и reshape

Метод reshape() меняет форму без изменения данных. Произведение новых размеров должно совпадать с числом элементов, а -1 означает «вычисли сам»:

r = np.arange(12)
print(r.reshape(3, 4))
# [[ 0  1  2  3]
#  [ 4  5  6  7]
#  [ 8  9 10 11]]

print(r.reshape(2, -1).shape)    # → (2, 6)
print(r.reshape(3, 4).T.shape)   # → (4, 3) — транспонирование
print(r.reshape(3, 4).ravel())   # → [ 0  1  2  3  4  5  6  7  8  9 10 11] — снова одномерный

Если размеры не подходят, будет ошибка:

r.reshape(5, 3)
# ValueError: cannot reshape array of size 12 into shape (5,3)

Векторные операции

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

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

print(a + b)       # → [11 22 33]
print(a * b)       # → [10 40 90]
print(b / a)       # → [10. 10. 10.] — деление всегда даёт float
print(a ** 2)      # → [1 4 9]
print(np.sqrt(a))  # → [1.         1.41421356 1.73205081]
print(a > 1)       # → [False  True  True]
print(a @ b)       # → 140 — скалярное произведение

Оператор @ (или np.dot) выполняет матричное умножение, а * — поэлементное. Для матриц 2×2 разница видна сразу:

print(np.array([[1, 2], [3, 4]]) @ np.array([[5, 6], [7, 8]]))
# [[19 22]
#  [43 50]]

Индексы и срезы

Одномерный массив режется так же, как список:

a = np.arange(10)
print(a[2:5], a[-1], a[::2])  # → [2 3 4] 9 [0 2 4 6 8]
print(a[::-1])                # → [9 8 7 6 5 4 3 2 1 0]

В многомерном массиве индексы по осям перечисляются через запятую: m[строка, столбец].

m = np.arange(1, 13).reshape(3, 4)
# [[ 1  2  3  4]
#  [ 5  6  7  8]
#  [ 9 10 11 12]]

print(m[0, 1])      # → 2
print(m[1])         # → [5 6 7 8] — вторая строка
print(m[:, 0])      # → [1 5 9] — первый столбец
print(m[:2, 1:3])   # подматрица: строки 0–1, столбцы 1–2
# [[2 3]
#  [6 7]]

Булева маска отбирает элементы по условию, а список индексов — элементы на нужных позициях:

print(m[m > 6])         # → [ 7  8  9 10 11 12]
print(a[[0, 3, 5]])     # → [0 3 5]

Маской удобно заменять значения, а np.where() выбирает между двумя вариантами:

t = np.array([-2, 5, -1, 8])
print(np.where(t > 0, t, 0))  # → [0 5 0 8] — новый массив
t[t < 0] = 0                  # изменение на месте
print(t)                      # → [0 5 0 8]

Важно: обычный срез — это представление (view), а не копия. Изменение среза меняет исходный массив. Нужна независимая копия — вызовите .copy().

a = np.arange(10)
s = a[2:5]
s[0] = 100
print(a)  # → [  0   1 100   3   4   5   6   7   8   9]

Broadcasting: операции с массивами разной формы

Broadcasting («растягивание») позволяет выполнять операции над массивами разной формы. NumPy сравнивает размеры с конца: оси совместимы, если они равны или одна из них равна 1. Число «растягивается» на весь массив, строка — на каждую строку, столбец — на каждый столбец:

m = np.array([[1, 2, 3], [4, 5, 6]])  # форма (2, 3)

print(m + 10)
# [[11 12 13]
#  [14 15 16]]

print(m + np.array([10, 20, 30]))     # форма (3,) — прибавляется к каждой строке
# [[11 22 33]
#  [14 25 36]]

print(m + np.array([[100], [200]]))   # форма (2, 1) — к каждому столбцу
# [[101 102 103]
#  [204 205 206]]

Если размеры несовместимы, NumPy сообщит об этом:

m + np.array([1, 2])
# ValueError: operands could not be broadcast together with shapes (2,3) (2,)

Чтобы прибавить вектор длины 2 к строкам, превратите его в столбец: np.array([1, 2]).reshape(2, 1).

Полезные функции

Агрегирующие функции принимают параметр axis: axis=0 — по столбцам (сверху вниз), axis=1 — по строкам:

d = np.array([[1, 2, 3], [4, 5, 6]])
print(d.sum())         # → 21
print(d.sum(axis=0))   # → [5 7 9]
print(d.sum(axis=1))   # → [ 6 15]
print(d.max(), d.argmax())  # → 6 5 — максимум и его индекс в «развёрнутом» массиве
print(d.mean(axis=0))  # → [2.5 3.5 4.5]
print(d.std().round(3))  # → 1.708
ФункцияЧто делает
sum, mean, min, max, stdсумма, среднее, минимум, максимум, СКО
argmin, argmaxиндекс минимума / максимума
np.cumsumнакопленная сумма
np.sort, np.uniqueсортировка, уникальные значения
np.roundокругление до заданного числа знаков
np.concatenate, np.vstackсклеить массивы подряд / друг под другом
np.whereвыбор по условию
np.isnan, np.nanmean, np.nansumработа с пропусками nan
.tolist()вернуть обычный список Python

Случайные числа в современном NumPy получают через генератор default_rng. С фиксированным seed результат воспроизводится:

rng = np.random.default_rng(42)
print(rng.integers(1, 7, size=5))  # → [1 5 4 3 3] — пять бросков кубика
print(rng.random(3).round(3))      # → [0.697 0.094 0.976]

Частые ошибки

ValueError: The truth value of an array … is ambiguous

Сравнение массивов возвращает массив, а не одно значение, поэтому его нельзя ставить в if:

a = np.array([1, 2])
if a == np.array([1, 2]):
    ...
# ValueError: The truth value of an array with more than one element is ambiguous. Use a.any() or a.all()

Используйте (a == b).all(), (a == b).any() или np.array_equal(a, b).

nan портит среднее

Одно значение np.nan превращает результат mean() и sum() в nan, а np.nan == np.nan даёт False. Проверяйте пропуски через np.isnan() и считайте np.nanmean():

x = np.array([1, np.nan, 3])
print(np.mean(x), np.nanmean(x))  # → nan 2.0

Массив из строк вместо чисел

Если в списке смешаны числа и строки, NumPy приведёт всё к строкам: np.array([1, "a"]) даст ['1' 'a'] с dtype <U21. Арифметика с таким массивом не работает — очистите данные до создания массива.

Списки разной длины

np.array([[1, 2], [3]])
# ValueError: setting an array element with a sequence. The requested array has an inhomogeneous shape ...

Массив NumPy всегда «прямоугольный». Выровняйте длины вложенных списков или храните такие данные в обычных списках.

Неожиданное изменение исходных данных

Срез — это view, поэтому функция, которая меняет переданный срез, меняет и исходный массив. Если данные нужно сохранить, работайте с .copy().

Частые вопросы

Как сохранить массив в файл и загрузить обратно?

Формат .npy сохраняет массив вместе с формой и dtype, текстовый CSV удобен для обмена с другими программами:

m = np.arange(6).reshape(2, 3)

np.save("data.npy", m)
m2 = np.load("data.npy")  # тот же массив, та же форма

np.savetxt("data.csv", m, delimiter=",", fmt="%d")
m3 = np.loadtxt("data.csv", delimiter=",", dtype=int)

Несколько массивов в одном файле сохраняет np.savez("arrays.npz", x=x, y=y).

Как добавить элемент к массиву?

np.append() не меняет исходный массив, а создаёт новый с копией всех данных:

a = np.array([1, 2, 3])
b = np.append(a, 4)
print(a, b)  # → [1 2 3] [1 2 3 4]

В цикле это медленно — каждая итерация копирует весь массив. Собирайте значения в обычный список, а массив создавайте один раз в конце: np.array(rows).

Как найти индексы элементов по условию?

np.flatnonzero() возвращает позиции элементов одномерного массива, для которых условие истинно; np.argwhere() — пары индексов в многомерном:

t = np.array([3, 8, 1, 9, 4])
print(np.flatnonzero(t > 3))  # → [1 3 4]

m = np.array([[1, 7], [8, 2]])
print(np.argwhere(m > 5))
# [[0 1]
#  [1 0]]

np.where(t > 3) с одним аргументом даёт те же индексы, но в виде кортежа массивов — по одному на каждую ось.

Как убрать экспоненциальную запись при выводе?

Если в массиве есть очень маленькие и большие числа, NumPy печатает их в виде 1.2345678e+04. Формат вывода меняет np.set_printoptions():

x = np.array([0.000123, 12345.678, 1.5])
np.set_printoptions(suppress=True, precision=3)
print(x)  # → [    0.    12345.678     1.5  ]

Настройка влияет только на печать — сами значения в массиве не округляются.

Чем NumPy отличается от pandas?

NumPy работает с однородными числовыми массивами по позициям: быстрая математика, матрицы, изображения. pandas построен поверх NumPy и добавляет подписи строк и столбцов, разные типы в разных столбцах, пропуски, группировку и чтение CSV или Excel. Для таблиц с данными удобнее pandas, для вычислений над числами — NumPy; DataFrame превращается в массив методом df.to_numpy().

Итог

NumPy заменяет циклы над числами операциями над целыми массивами: создайте ndarray, следите за dtype и shape, выбирайте данные срезами и масками, а broadcasting возьмёт на себя операции с массивами разной формы. Табличные данные с подписями столбцов удобнее обрабатывать в pandas, а результаты вычислений — показать на графике с помощью Matplotlib. Другие популярные пакеты собраны в обзоре библиотек Python, полный справочник — в документации NumPy.

  • #Python
  • #NumPy
  • #Массивы
  • #Анализ данных
↑ ↓ выборEnter открытьEsc закрыть