Что такое NumPy?#

NumPy — это фундаментальный пакет для научных вычислений в Python. Это библиотека Python, которая предоставляет многомерный объект массива, различные производные объекты (такие как маскированные массивы и матрицы) и набор подпрограмм для быстрых операций с массивами, включая математические, логические, манипуляции с формой, сортировку, выбор, ввод-вывод, дискретные преобразования Фурье, базовую линейную алгебру, базовые статистические операции, случайное моделирование и многое другое.

В основе пакета NumPy находится ndarray объект. Это инкапсулирует n-мерные массивы однородных типов данных, с многими операциями, выполняемыми в скомпилированном коде для производительности. Есть несколько важных различий между массивами NumPy и стандартными последовательностями Python:

  • Массивы NumPy имеют фиксированный размер при создании, в отличие от списков Python (которые могут динамически расти). Изменение размера ndarray создаст новый массив и удалит исходный.

  • Элементы в массиве NumPy должны быть одного типа данных и, следовательно, одного размера в памяти. Исключение: можно иметь массивы объектов (Python, включая NumPy), что позволяет создавать массивы элементов разного размера.

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

  • Растущее множество научных и математических пакетов на основе Python используют массивы NumPy; хотя они обычно поддерживают ввод в виде последовательностей Python, они преобразуют такой ввод в массивы NumPy перед обработкой и часто выводят массивы NumPy. Другими словами, для эффективного использования большей части (возможно, даже большинства) современного научного/математического программного обеспечения на основе Python недостаточно просто знать, как использовать встроенные типы последовательностей Python — также необходимо знать, как использовать массивы NumPy.

Вопросы размера последовательности и скорости особенно важны в научных вычислениях. В качестве простого примера рассмотрим случай умножения каждого элемента в 1-D последовательности на соответствующий элемент в другой последовательности той же длины. Если данные хранятся в двух списках Python, a и b, мы могли бы перебирать каждый элемент:

c = []
for i in range(len(a)):
    c.append(a[i]*b[i])

Это дает правильный ответ, но если a и b каждый содержит миллионы чисел, мы заплатим цену за неэффективность циклов в Python. Мы могли бы выполнить ту же задачу гораздо быстрее на C, написав (для ясности мы опускаем объявления переменных и инициализации, выделение памяти и т.д.)

for (i = 0; i < rows; i++) {
  c[i] = a[i]*b[i];
}

Это сохраняет все накладные расходы, связанные с интерпретацией кода Python и манипулированием объектами Python, но за счёт преимуществ, полученных от программирования на Python. Кроме того, требуемая работа по кодированию увеличивается с размерностью наших данных. В случае 2-D массива, например, код C (сокращённый, как и раньше) расширяется до

for (i = 0; i < rows; i++) {
  for (j = 0; j < columns; j++) {
    c[i][j] = a[i][j]*b[i][j];
  }
}

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

c = a * b

делает то же, что и предыдущие примеры, со скоростью, близкой к C, но с простотой кода, которую мы ожидаем от чего-то основанного на Python. Действительно, идиома NumPy даже проще! Этот последний пример иллюстрирует две особенности NumPy, которые лежат в основе его мощи: векторизация и трансляция.

Почему NumPy быстрый?#

Векторизация описывает отсутствие явных циклов, индексации и т.д. в коде — эти операции, конечно, происходят, но «за кулисами» в оптимизированном, предварительно скомпилированном C-коде. Векторизованный код имеет много преимуществ, среди которых:

  • векторизованный код более лаконичен и легче читается

  • меньше строк кода обычно означает меньше ошибок

  • код больше напоминает стандартную математическую нотацию (что обычно упрощает правильное кодирование математических конструкций)

  • векторизация приводит к более «питоническому» коду. Без векторизации наш код был бы засорен неэффективными и трудными для чтения for циклы.

Трансляция — это термин, используемый для описания неявного поэлементного поведения операций; как правило, в NumPy все операции, не только арифметические, но и логические, побитовые, функциональные и т.д., ведут себя таким неявным поэлементным образом, т.е. они транслируются. Более того, в примере выше, a и b могут быть многомерными массивами одинаковой формы, или скаляром и массивом, или даже двумя массивами с разными формами, при условии, что меньший массив "расширяем" до формы большего таким образом, чтобы результирующее вещание было однозначным. Подробные "правила" вещания см. в Трансляция (Broadcasting).

Кто ещё использует NumPy?#

NumPy полностью поддерживает объектно-ориентированный подход, начиная, снова, с ndarray. Например, ndarray является классом, обладающим множеством методов и атрибутов. Многие из его методов дублируются функциями в самом внешнем пространстве имён NumPy, позволяя программисту кодировать в любой парадигме, которую он предпочитает. Эта гибкость позволила диалекту массивов NumPy и NumPy ndarray класс, чтобы стать де-факто язык многомерного обмена данными, используемый в Python.