NumPy: абсолютные основы для начинающих
Добро пожаловать в руководство для абсолютных начинающих по NumPy!
NumPy (Numerical Python) — это открытая библиотека Python, широко используемая в научных и инженерных приложениях. Библиотека NumPy содержит многомерные массивы данных, такие как однородные N-мерные ndarray, и большой набор функций, которые эффективно работают с этими структурами данных. Узнайте больше о NumPy на странице Что такое NumPy, и если у вас есть комментарии или предложения, пожалуйста, свяжитесь с нами здесь!
Как импортировать NumPy
После установки NumPy, его можно импортировать в код Python, например:
import numpy as np
Эта распространённая конвенция позволяет получить доступ к функциям NumPy с коротким, узнаваемым префиксом (np.) и одновременно отличать функции NumPy от других функций с одинаковыми именами.
Чтение примера кода
В документации NumPy вы найдете блоки, похожие на:
>>> a = np.array([[1, 2, 3], ... [4, 5, 6]]) >>> a.shape (2, 3)
Текст, предваряемый >>> или ..., является вводом, кодом, который вы вводите в скрипт или в интерактивную среду Python. Всё остальное — это вывод, результаты выполнения вашего кода. Обратите внимание, что >>> и ... не являются частью кода и могут вызвать ошибку, если ввести их в интерактивную среду Python.
Почему использовать NumPy?
Списки Python — это отличные универсальные контейнеры. Они могут быть «неоднородными», то есть могут содержать элементы различных типов, и достаточно быстры при использовании для выполнения отдельных операций над небольшим количеством элементов.
В зависимости от характеристик данных и типа необходимых операций, другие контейнеры могут быть более подходящими; используя эти характеристики, мы можем повысить скорость, снизить потребление памяти и предложить высокоуровневый синтаксис для выполнения различных распространённых задач обработки. NumPy прекрасно подходит, когда необходимо обработать большое количество «однородных» (одного типа) данных на ЦП.
Что такое «массив»?
В программировании, массив — это структура для хранения и извлечения данных. Мы часто представляем массив как сетку в пространстве, где каждый ячейка хранит один элемент данных. Например, если каждый элемент данных — это число, мы можем визуализировать одномерный массив как список:
Двумерный массив будет похож на таблицу:
Трехмерный массив будет похож на набор таблиц, возможно, сложенных как отдельные страницы. В NumPy эта идея обобщается на любое количество измерений, и поэтому базовый класс массива называется ndarray: он представляет собой «N-мерный массив».
Большинство массивов NumPy имеют некоторые ограничения. Например:
- Все элементы массива должны быть одного типа данных.
- После создания общий размер массива не может меняться.
- Форма должна быть «прямоугольной», а не «ступенчатой»; например, каждая строка двумерного массива должна иметь одинаковое количество столбцов.
При соблюдении этих условий NumPy использует эти характеристики для повышения скорости, эффективности использования памяти и удобства использования по сравнению с менее жёсткими структурами данных.
В оставшейся части документа мы будем использовать слово «массив» для обозначения экземпляра ndarray.
Основы массивов
Один из способов инициализации массива — использование последовательности Python, например, списка. Например:
>>> a = np.array([1, 2, 3, 4, 5, 6]) >>> a array([1, 2, 3, 4, 5, 6])
Элементы массива можно получить различными способами. Например, мы можем получить доступ к отдельному элементу этого массива, как если бы мы обращались к элементу в исходном списке: используя целочисленный индекс элемента в квадратных скобках.
>>> a[0] 1
Примечание
Как и встроенные последовательности Python, массивы NumPy «нулево-индексируемы»: к первому элементу массива обращаются используя индекс 0, а не 1.
Как и исходный список, массив является изменяемым.
>>> a[0] = 10 >>> a array([10, 2, 3, 4, 5, 6])
Также, как и исходный список, для индексирования можно использовать нотацию срезов Python.
>>> a[:3] array([10, 2, 3])
Основное отличие заключается в том, что индексирование срезом списка копирует элементы в новый список, а индексирование срезом массива возвращает вид: объект, ссылающийся на данные в исходном массиве. Исходный массив может быть изменён с использованием вида.
>>> b = a[3:] >>> b array([4, 5, 6]) >>> b[0] = 40 >>> a array([ 10, 2, 3, 40, 5, 6])
См. Копии и виды для более подробного объяснения случаев, когда операции с массивом возвращают виды, а не копии.
Двумерные и многомерные массивы могут быть инициализированы из вложенных последовательностей Python:
>>> a = np.array([[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12]])
>>> a
array([[ 1, 2, 3, 4],
[ 5, 6, 7, 8],
[ 9, 10, 11, 12]])
В NumPy измерение массива иногда называют «осью». Эта терминология может быть полезной для разграничения между размерностью массива и размерностью данных, представленных массивом. Например, массив a может представлять три точки, каждая из которых лежит в четырёхмерном пространстве, но a имеет только две «оси».
Другое различие между массивом и списком списков заключается в том, что к элементу массива можно получить доступ, указав индекс по каждой оси в одной паре квадратных скобок, разделенных запятыми. Например, элемент 8 находится в строке 1 и столбце 3:
>>> a[1, 3] 8
Примечание
В математике принято ссылаться на элементы матрицы, сначала указав индекс строки, а затем столбца. Это верно для двумерных массивов, но лучшая ментальная модель — думать об индексе столбца как о последнем, а индексе строки — как о предпоследнем. Это обобщается на массивы с любым количеством измерений.
Примечание
Вы можете услышать, что 0-мерный (нульмерный) массив называют «скаляром», 1-мерный (одномерный) массив — «вектором», 2-мерный (двумерный) массив — «матрицей», или N-мерный (N-мерный, где «N» обычно целое число больше 2) массив — «тензором». Для ясности лучше избегать математических терминов при ссылке на массив, потому что математические объекты с такими именами ведут себя иначе, чем массивы (например, «умножение матриц» принципиально отличается от «умножения массивов»), и существуют другие объекты в научном экосистеме Python, которые имеют эти названия (например, основной тип данных PyTorch — «тензор»).
Атрибуты массива
В этом разделе рассматриваются ndim, shape, size, и dtype атрибуты массива.
Количество измерений массива содержится в атрибуте ndim.
>>> a.ndim 2
Форма массива — это кортеж целых неотрицательных чисел, которые задают количество элементов по каждому измерению.
>>> a.shape (3, 4) >>> len(a.shape) == a.ndim True
Фиксированное общее количество элементов в массиве содержится в атрибуте size.
>>> a.size 12 >>> import math >>> a.size == math.prod(a.shape) True
Массивы обычно «однородные», то есть содержат элементы только одного «типа данных». Тип данных записывается в атрибуте dtype.
>>> a.dtype
dtype('int64') # "int" for integer, "64" for 64-bit
Подробнее об атрибутах массива здесь и ознакомьтесь с объектами массивов здесь.
Как создать базовый массив
В этом разделе рассматриваются np.zeros(), np.ones(), np.empty(), np.arange(), np.linspace()
Помимо создания массива из последовательности элементов, вы можете легко создать массив, заполненный 0:
>>> np.zeros(2) array([0., 0.])
Или массив, заполненный 1:
>>> np.ones(2) array([1., 1.])
Или даже пустой массив! Функция empty создаёт массив, начальное содержимое которого случайное и зависит от состояния памяти. Причина использования empty вместо zeros (или чего-то подобного) — скорость; просто убедитесь, что вы заполнили каждый элемент впоследствии!
>>> # Create an empty array with 2 elements >>> np.empty(2) array([3.14, 42. ]) # may vary
Вы можете создать массив с диапазоном элементов:
>>> np.arange(4) array([0, 1, 2, 3])
И даже массив, содержащий диапазон равномерно распределенных интервалов. Для этого вы укажете первое число, последнее число и шаг.
>>> np.arange(2, 9, 2) array([2, 4, 6, 8])
Вы также можете использовать np.linspace() для создания массива со значениями, линейно распределёнными в заданном интервале:
>>> np.linspace(0, 10, num=5) array([ 0. , 2.5, 5. , 7.5, 10. ])
Указание типа данных
Хотя тип данных по умолчанию — с плавающей точкой (np.float64), вы можете явно указать желаемый тип данных, используя ключевое слово dtype.
>>> x = np.ones(2, dtype=np.int64) >>> x array([1, 1])
Добавление, удаление и сортировка элементов
В этом разделе рассматривается np.sort(), np.concatenate()
Сортировка элемента проста с помощью np.sort(). Вы можете указать ось, вид и порядок при вызове функции.
Если вы начинаете с этого массива:
>>> arr = np.array([2, 1, 5, 3, 7, 4, 6, 8])
Вы можете быстро отсортировать числа по возрастанию с помощью:
>>> np.sort(arr) array([1, 2, 3, 4, 5, 6, 7, 8])
Помимо sort, которая возвращает отсортированную копию массива, вы можете использовать:
-
argsort, которая является косвенной сортировкой по заданной оси, -
lexsort, которая является косвенной устойчивой сортировкой по нескольким ключам, -
searchsorted, которая найдет элементы в отсортированном массиве, и -
partition, которая является частичной сортировкой.
Чтобы узнать больше о сортировке массива, см.: sort.
Если вы начинаете с этих массивов:
>>> a = np.array([1, 2, 3, 4]) >>> b = np.array([5, 6, 7, 8])
Вы можете объединить их с помощью np.concatenate().
>>> np.concatenate((a, b)) array([1, 2, 3, 4, 5, 6, 7, 8])
Или, если вы начинаете с этих массивов:
>>> x = np.array([[1, 2], [3, 4]]) >>> y = np.array([[5, 6]])
Вы можете объединить их с помощью:
>>> np.concatenate((x, y), axis=0)
array([[1, 2],
[3, 4],
[5, 6]])
Чтобы удалить элементы из массива, достаточно использовать индексацию для выбора элементов, которые нужно сохранить.
Чтобы узнать больше об объединении, см.: concatenate.
Как узнать форму и размер массива?
В этом разделе рассматривается ndarray.ndim, ndarray.size, ndarray.shape
ndarray.ndim покажет вам количество осей или измерений массива.
ndarray.size покажет вам общее количество элементов массива. Это произведение элементов формы массива.
ndarray.shape отобразит кортеж целых чисел, которые указывают количество элементов, хранящихся вдоль каждого измерения массива. Например, если у вас есть двумерный массив с 2 строками и 3 столбцами, форма вашего массива равна (2, 3).
Например, если вы создаете этот массив:
>>> array_example = np.array([[[0, 1, 2, 3], ... [4, 5, 6, 7]], ... ... [[0, 1, 2, 3], ... [4, 5, 6, 7]], ... ... [[0 ,1 ,2, 3], ... [4, 5, 6, 7]]])
Чтобы найти количество измерений массива, выполните:
>>> array_example.ndim 3
Чтобы найти общее количество элементов в массиве, выполните:
>>> array_example.size 24
И чтобы найти форму вашего массива, выполните:
>>> array_example.shape (3, 2, 4)
Можно ли изменить форму массива?
В этом разделе рассматривается arr.reshape()
Да!
Использование arr.reshape() даст новую форму массиву, не изменяя данные. Просто помните, что при использовании метода reshape массив, который вы хотите получить, должен иметь то же количество элементов, что и исходный массив. Если вы начинаете с массива с 12 элементами, вам необходимо убедиться, что ваш новый массив также содержит в общей сложности 12 элементов.
Если вы начинаете с этого массива:
>>> a = np.arange(6) >>> print(a) [0 1 2 3 4 5]
Вы можете использовать reshape() для изменения формы массива. Например, вы можете изменить форму этого массива на массив с тремя строками и двумя столбцами:
>>> b = a.reshape(3, 2) >>> print(b) [[0 1] [2 3] [4 5]]
С помощью np.reshape, вы можете указать несколько необязательных параметров:
>>> np.reshape(a, newshape=(1, 6), order='C') array([[0, 1, 2, 3, 4, 5]])
a — массив, подлежащий изменению формы.
newshape — новая форма, которую вы хотите. Вы можете указать целое число или кортеж целых чисел. Если вы указываете целое число, результатом будет массив такой длины. Форма должна быть совместима с исходной формой.
order: C означает чтение/запись элементов в порядке индексов, подобном C, F означает чтение/запись элементов в порядке индексов, подобном Fortran, A означает чтение/запись элементов в порядке индексов, подобном Fortran, если a находится в памяти, последовательной для Fortran, в противном случае — в порядке индексов, подобном C. (Это необязательный параметр, и его не обязательно указывать.)
Если вы хотите узнать больше о порядке C и Fortran, вы можете подробнее ознакомиться с внутренней организацией массивов NumPy здесь. По сути, порядок C и Fortran имеет дело с тем, как индексы соответствуют порядку хранения массива в памяти. В Fortran, когда перемещаетесь по элементам двумерного массива, как он хранится в памяти, первый индекс изменяется наиболее быстро. По мере того, как первый индекс переходит к следующей строке при изменении, матрица хранится по одному столбцу за раз. Именно поэтому Fortran считается языком со столбцовой ориентацией. В C, наоборот, последний индекс изменяется наиболее быстро. Матрица хранится по строкам, что делает его языком со строчной ориентацией. Что вы делаете для C или Fortran, зависит от того, что важнее: сохранить соглашение об индексации или не переупорядочить данные.
Как преобразовать одномерный массив в двумерный массив (как добавить новую ось к массиву)
В этом разделе рассматривается np.newaxis, np.expand_dims
Вы можете использовать np.newaxis и np.expand_dims для увеличения размерности вашего существующего массива.
Использование np.newaxis увеличит размерность вашего массива на одно измерение при однократном использовании. Это означает, что одномерный массив станет двумерным, двумерный массив станет трехмерным и так далее.
Например, если вы начинаете с этого массива:
>>> a = np.array([1, 2, 3, 4, 5, 6]) >>> a.shape (6,)
Вы можете использовать np.newaxis для добавления новой оси:
>>> a2 = a[np.newaxis, :] >>> a2.shape (1, 6)
Вы можете явно преобразовать одномерный массив в строку или столбец с помощью np.newaxis. Например, вы можете преобразовать одномерный массив в строку, вставив ось по первому измерению:
>>> row_vector = a[np.newaxis, :] >>> row_vector.shape (1, 6)
Или, для столбца, вы можете вставить ось по второму измерению:
>>> col_vector = a[:, np.newaxis] >>> col_vector.shape (6, 1)
Вы также можете расширить массив, вставив новую ось в указанную позицию с помощью np.expand_dims.
Например, если вы начинаете с этого массива:
>>> a = np.array([1, 2, 3, 4, 5, 6]) >>> a.shape (6,)
Вы можете использовать np.expand_dims для добавления оси в позицию индекса 1 с помощью:
>>> b = np.expand_dims(a, axis=1) >>> b.shape (6, 1)
Вы можете добавить ось в позицию индекса 0 с помощью:
>>> c = np.expand_dims(a, axis=0) >>> c.shape (1, 6)
Дополнительную информацию о newaxis здесь и expand_dims вы найдете в expand_dims.
Индексирование и срезы
Вы можете индексировать и извлекать срезы массивов NumPy так же, как и срезы списков Python.
>>> data = np.array([1, 2, 3]) >>> data[1] 2 >>> data[0:2] array([1, 2]) >>> data[1:] array([2, 3]) >>> data[-2:] array([2, 3])
Вы можете визуализировать это так:
Вам может потребоваться взять часть вашего массива или определенные элементы массива для использования в дальнейших анализах или дополнительных операциях. Для этого вам нужно будет подмножества, срезы и/или индексы ваших массивов.
Если вы хотите выбрать значения из вашего массива, которые удовлетворяют определенным условиям, с NumPy это просто.
Например, если вы начинаете с этого массива:
>>> a = np.array([[1 , 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12]])
Вы можете легко вывести все значения в массиве, которые меньше 5.
>>> print(a[a < 5]) [1 2 3 4]
Вы также можете выбрать, например, числа, которые равны или больше 5, и использовать это условие для индексирования массива.
>>> five_up = (a >= 5) >>> print(a[five_up]) [ 5 6 7 8 9 10 11 12]
Вы можете выбрать элементы, которые делятся на 2:
>>> divisible_by_2 = a[a%2==0] >>> print(divisible_by_2) [ 2 4 6 8 10 12]
Или вы можете выбрать элементы, которые удовлетворяют двум условиям, используя операторы & и |:
>>> c = a[(a > 2) & (a < 11)] >>> print(c) [ 3 4 5 6 7 8 9 10]
Вы также можете использовать логические операторы & и |, чтобы вернуть булевы значения, указывающие, удовлетворяют ли значения в массиве определенному условию. Это может быть полезно для массивов, содержащих имена или другие категориальные значения.
>>> five_up = (a > 5) | (a == 5) >>> print(five_up) [[False False False False] [ True True True True] [ True True True True]]
Вы также можете использовать np.nonzero() для выбора элементов или индексов из массива.
Начиная с этого массива:
>>> a = np.array([[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12]])
Вы можете использовать np.nonzero() для вывода индексов элементов, которые, например, меньше 5:
>>> b = np.nonzero(a < 5) >>> print(b) (array([0, 0, 0, 0]), array([0, 1, 2, 3]))
В этом примере возвращается кортеж массивов: один для каждого измерения. Первый массив представляет индексы строк, где эти значения находятся, а второй массив представляет индексы столбцов, где значения находятся.
Если вы хотите сгенерировать список координат, где существуют элементы, вы можете использовать функцию zip, итерировать по списку координат и выводить их. Например:
>>> list_of_coordinates= list(zip(b[0], b[1])) >>> for coord in list_of_coordinates: ... print(coord) (np.int64(0), np.int64(0)) (np.int64(0), np.int64(1)) (np.int64(0), np.int64(2)) (np.int64(0), np.int64(3))
Вы также можете использовать np.nonzero() для вывода элементов в массиве, которые меньше 5, с помощью:
>>> print(a[b]) [1 2 3 4]
Если искомого элемента в массиве нет, то возвращаемый массив индексов будет пустым. Например:
>>> not_there = np.nonzero(a == 42) >>> print(not_there) (array([], dtype=int64), array([], dtype=int64))
Узнайте больше об индексировании и срезах здесь и здесь.
Дополнительную информацию об использовании функции nonzero см.: nonzero.
Как создать массив из существующих данных
В этом разделе рассматривается slicing and indexing, np.vstack(), np.hstack(), np.hsplit(), .view(), copy()
Вы легко можете создать новый массив из части существующего массива.
Предположим, у вас есть этот массив:
>>> a = np.array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
Вы можете создать новый массив из части своего массива в любое время, указав, где вы хотите выполнить срез массива.
>>> arr1 = a[3:8] >>> arr1 array([4, 5, 6, 7, 8])
Здесь вы взяли часть своего массива с индексом 3 до индекса 8.
Вы также можете объединить два существующих массива, как вертикально, так и горизонтально. Предположим, у вас есть два массива, a1 и a2:
>>> a1 = np.array([[1, 1], ... [2, 2]]) >>> a2 = np.array([[3, 3], ... [4, 4]])
Вы можете объединить их вертикально с помощью vstack:
>>> np.vstack((a1, a2))
array([[1, 1],
[2, 2],
[3, 3],
[4, 4]])
Или объединить их горизонтально с помощью hstack:
>>> np.hstack((a1, a2))
array([[1, 1, 3, 3],
[2, 2, 4, 4]])
Вы можете разделить массив на несколько меньших массивов, используя hsplit. Вы можете указать либо количество массивов с одинаковой формой, которые нужно вернуть, либо столбцы после которых должно произойти разделение.
Предположим, у вас есть этот массив:
>>> x = np.arange(1, 25).reshape(2, 12)
>>> x
array([[ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12],
[13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24]])
Если вы хотите разделить этот массив на три массива одинаковой формы, вы выполните:
>>> np.hsplit(x, 3)
[array([[ 1, 2, 3, 4],
[13, 14, 15, 16]]), array([[ 5, 6, 7, 8],
[17, 18, 19, 20]]), array([[ 9, 10, 11, 12],
[21, 22, 23, 24]])]
Если вы хотите разделить свой массив после третьего и четвертого столбца, вы выполните:
>>> np.hsplit(x, (3, 4))
[array([[ 1, 2, 3],
[13, 14, 15]]), array([[ 4],
[16]]), array([[ 5, 6, 7, 8, 9, 10, 11, 12],
[17, 18, 19, 20, 21, 22, 23, 24]])]
Дополнительные сведения об объединении и разделении массивов см. здесь.
Вы можете использовать метод view для создания нового объекта массива, который смотрит на те же данные, что и исходный массив (неглубокая копия).
Просмотры являются важной концепцией NumPy! Функции NumPy, а также операции, такие как индексирование и срез, будут возвращать просмотры, когда это возможно. Это экономит память и быстрее (не нужно создавать копию данных). Однако важно знать об этом — изменение данных в представлении также изменяет исходный массив!
Предположим, вы создаёте этот массив:
>>> a = np.array([[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12]])
Теперь мы создаём массив b1 путём среза a и изменяем первый элемент b1. Это также изменит соответствующий элемент в a!
>>> b1 = a[0, :]
>>> b1
array([1, 2, 3, 4])
>>> b1[0] = 99
>>> b1
array([99, 2, 3, 4])
>>> a
array([[99, 2, 3, 4],
[ 5, 6, 7, 8],
[ 9, 10, 11, 12]])
Использование метода copy создаст полную копию массива и его данных (глубокая копия). Чтобы использовать это со своим массивом, вы можете выполнить:
>>> b2 = a.copy()
Дополнительные сведения о копиях и представлениях см. здесь.
Основные операции с массивами
В этом разделе рассматриваются сложение, вычитание, умножение, деление и многое другое
После создания массивов вы можете начать работать с ними. Например, предположим, что вы создали два массива, один под названием «data», а другой — «ones»
Вы можете сложить массивы, используя знак плюс.
>>> data = np.array([1, 2]) >>> ones = np.ones(2, dtype=int) >>> data + ones array([2, 3])
Конечно, вы можете делать не только сложение!
>>> data - ones array([0, 1]) >>> data * data array([1, 4]) >>> data / data array([1., 1.])
Основные операции просты с NumPy. Если вы хотите найти сумму элементов в массиве, используйте sum(). Это работает для 1D массивов, 2D массивов и массивов высших размерностей.
>>> a = np.array([1, 2, 3, 4]) >>> a.sum() 10
Чтобы добавить строки или столбцы в 2D массив, укажите ось.
Если вы начинаете с этого массива:
>>> b = np.array([[1, 1], [2, 2]])
Вы можете суммировать по оси строк с помощью:
>>> b.sum(axis=0) array([3, 3])
Вы можете суммировать по оси столбцов с помощью:
>>> b.sum(axis=1) array([2, 4])
Вещание
Иногда вам может потребоваться выполнить операцию между массивом и одним числом (также называемой операцией между вектором и скаляром) или между массивами двух разных размеров. Например, ваш массив (назовём его «data») может содержать информацию о расстоянии в милях, но вы хотите преобразовать информацию в километры. Вы можете выполнить эту операцию с помощью:
>>> data = np.array([1.0, 2.0]) >>> data * 1.6 array([1.6, 3.2])
NumPy понимает, что умножение должно происходить с каждой ячейкой. Эта концепция называется вещанием. Вещание — это механизм, позволяющий NumPy выполнять операции над массивами разной формы. Размеры вашего массива должны быть совместимы, например, когда размеры обоих массивов равны или когда один из них равен 1. Если размеры не совместимы, вы получите ValueError.
Дополнительные полезные операции с массивами
В этом разделе рассматриваются максимальное, минимальное, суммирование, среднее, произведение, стандартное отклонение и многое другое
NumPy также выполняет агрегационные функции. В дополнение к min, max, и sum, вы легко можете запустить mean для получения среднего значения, prod для получения результата умножения элементов, std для получения стандартного отклонения и многое другое.
>>> data.max() 2.0 >>> data.min() 1.0 >>> data.sum() 3.0
Начнём с этого массива, названного «a»
>>> a = np.array([[0.45053314, 0.17296777, 0.34376245, 0.5510652], ... [0.54627315, 0.05093587, 0.40067661, 0.55645993], ... [0.12697628, 0.82485143, 0.26590556, 0.56917101]])
Очень часто нужно выполнить агрегацию по строке или столбцу. По умолчанию каждая функция агрегации NumPy вернёт агрегат всего массива. Чтобы найти сумму или минимум элементов в вашем массиве, выполните:
>>> a.sum() 4.8595784
Или:
>>> a.min() 0.05093587
Вы можете указать, по какой оси нужно вычислить агрегационную функцию. Например, вы можете найти минимальное значение в каждом столбце, указав axis=0.
>>> a.min(axis=0) array([0.12697628, 0.05093587, 0.26590556, 0.5510652 ])
Четыре значения, указанных выше, соответствуют количеству столбцов в вашем массиве. В массиве с четырьмя столбцами вы получите четыре значения в качестве результата.
Дополнительные сведения об методах массивов см. здесь.
Создание матриц
Вы можете передать Python-списки списков, чтобы создать двумерный массив (или «матрицу»), представляющий их в NumPy.
>>> data = np.array([[1, 2], [3, 4], [5, 6]])
>>> data
array([[1, 2],
[3, 4],
[5, 6]])
Операции индексирования и среза полезны при манипулировании матрицами:
>>> data[0, 1]
2
>>> data[1:3]
array([[3, 4],
[5, 6]])
>>> data[0:2, 0]
array([1, 3])
Вы можете агрегировать матрицы так же, как вы агрегировали векторы:
>>> data.max() 6 >>> data.min() 1 >>> data.sum() 21
Вы можете агрегировать все значения в матрице, а также агрегировать их по столбцам или строкам, используя параметр axis. Чтобы проиллюстрировать эту точку, давайте посмотрим на немного изменённый набор данных:
>>> data = np.array([[1, 2], [5, 3], [4, 6]])
>>> data
array([[1, 2],
[5, 3],
[4, 6]])
>>> data.max(axis=0)
array([5, 6])
>>> data.max(axis=1)
array([2, 5, 6])
После создания матриц вы можете складывать и умножать их с помощью арифметических операторов, если у вас две матрицы одинакового размера.
>>> data = np.array([[1, 2], [3, 4]])
>>> ones = np.array([[1, 1], [1, 1]])
>>> data + ones
array([[2, 3],
[4, 5]])
Вы можете выполнять эти арифметические операции над матрицами разного размера, но только если одна из матриц имеет только один столбец или одну строку. В этом случае NumPy будет использовать свои правила вещания для операции.
>>> data = np.array([[1, 2], [3, 4], [5, 6]])
>>> ones_row = np.array([[1, 1]])
>>> data + ones_row
array([[2, 3],
[4, 5],
[6, 7]])
Обратите внимание, что при печати N-мерных массивов NumPy последний ось проходит быстрее, а первая — медленнее. Например:
>>> np.ones((4, 3, 2))
array([[[1., 1.],
[1., 1.],
[1., 1.]],
[[1., 1.],
[1., 1.],
[1., 1.]],
[[1., 1.],
[1., 1.],
[1., 1.]],
[[1., 1.],
[1., 1.],
[1., 1.]]])
Часто нам нужно, чтобы NumPy инициализировал значения массива. NumPy предлагает функции, такие как ones() и zeros(), и класс random.Generator для генерации случайных чисел для этого. Всё, что вам нужно сделать, это указать количество элементов, которые вы хотите сгенерировать:
>>> np.ones(3) array([1., 1., 1.]) >>> np.zeros(3) array([0., 0., 0.]) >>> rng = np.random.default_rng() # the simplest way to generate random numbers >>> rng.random(3) array([0.63696169, 0.26978671, 0.04097352])
Вы также можете использовать ones(), zeros(), и random() для создания двумерного массива, если передадите им кортеж, описывающий размеры матрицы:
>>> np.ones((3, 2))
array([[1., 1.],
[1., 1.],
[1., 1.]])
>>> np.zeros((3, 2))
array([[0., 0.],
[0., 0.],
[0., 0.]])
>>> rng.random((3, 2))
array([[0.01652764, 0.81327024],
[0.91275558, 0.60663578],
[0.72949656, 0.54362499]]) # may vary
Дополнительные сведения о создании массивов, заполненных 0 и 1, другими значениями или неинициализированными, см. в функциях создания массивов.
Генерация случайных чисел
Использование генерации случайных чисел является важной частью настройки и оценки многих численных и алгоритмов машинного обучения. Независимо от того, нужно ли вам случайным образом инициализировать веса в искусственной нейронной сети, разделить данные на случайные наборы или случайным образом перемешать ваши данные, возможность генерировать случайные числа (на самом деле, воспроизводимые псевдослучайные числа) имеет решающее значение.
С помощью Generator.integers вы можете сгенерировать случайные целые числа от low (пожалуйста, обратите внимание, что в NumPy это значение включительно) до high (исключительно). Вы можете установить endpoint=True для того, чтобы сделать high число включительно.
Вы можете сгенерировать 2x4 массив случайных целых чисел от 0 до 4 с помощью:
>>> rng.integers(5, size=(2, 4))
array([[2, 1, 1, 0],
[0, 0, 0, 4]]) # may vary
Дополнительные сведения о генерации случайных чисел см. здесь.
Как получить уникальные элементы и их количество
В этом разделе рассматривается np.unique()
Вы можете легко найти уникальные элементы в массиве с помощью np.unique
Например, если вы начинаете с этого массива:
>>> a = np.array([11, 11, 12, 13, 14, 15, 16, 17, 12, 13, 11, 14, 18, 19, 20])
вы можете использовать np.unique для вывода уникальных значений в вашем массиве:
>>> unique_values = np.unique(a) >>> print(unique_values) [11 12 13 14 15 16 17 18 19 20]
Чтобы получить индексы уникальных значений в массиве NumPy (массив первых индексных позиций уникальных значений в массиве), просто передайте аргумент return_index в np.unique() вместе с вашим массивом.
>>> unique_values, indices_list = np.unique(a, return_index=True) >>> print(indices_list) [ 0 2 3 4 5 6 7 12 13 14]
Вы можете передать аргумент return_counts в np.unique() вместе с вашим массивом, чтобы получить частоту уникальных значений в массиве NumPy.
>>> unique_values, occurrence_count = np.unique(a, return_counts=True) >>> print(occurrence_count) [3 2 2 2 1 1 1 1 1 1]
Это также работает с двумерными массивами! Если вы начинаете с этого массива:
>>> a_2d = np.array([[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12], [1, 2, 3, 4]])
Вы можете найти уникальные значения с помощью:
>>> unique_values = np.unique(a_2d) >>> print(unique_values) [ 1 2 3 4 5 6 7 8 9 10 11 12]
Если аргумент axis не передан, ваш двумерный массив будет сглажен.
Если вы хотите получить уникальные строки или столбцы, убедитесь, что передали аргумент axis. Чтобы найти уникальные строки, укажите axis=0, а для столбцов — axis=1
>>> unique_rows = np.unique(a_2d, axis=0) >>> print(unique_rows) [[ 1 2 3 4] [ 5 6 7 8] [ 9 10 11 12]]
Чтобы получить уникальные строки, индексные позиции и количество вхождений, вы можете использовать:
>>> unique_rows, indices, occurrence_count = np.unique( ... a_2d, axis=0, return_counts=True, return_index=True) >>> print(unique_rows) [[ 1 2 3 4] [ 5 6 7 8] [ 9 10 11 12]] >>> print(indices) [0 1 2] >>> print(occurrence_count) [2 1 1]
Чтобы узнать больше о поиске уникальных элементов в массиве, см. unique.
Транспонирование и изменение формы матрицы
В этом разделе рассматривается arr.reshape(), arr.transpose(), arr.T
Часто требуется транспонировать матрицы. Массивы NumPy обладают свойством T, которое позволяет транспонировать матрицу.
Вам также может потребоваться изменить размер матрицы. Это может произойти, например, когда у вас есть модель, которая ожидает определённой формы входных данных, отличающейся от формы вашего набора данных. Именно здесь метод reshape может быть полезен. Вам просто нужно указать новые размеры, которые вы хотите для матрицы.
>>> data.reshape(2, 3)
array([[1, 2, 3],
[4, 5, 6]])
>>> data.reshape(3, 2)
array([[1, 2],
[3, 4],
[5, 6]])
Вы также можете использовать .transpose() для инвертирования или изменения осей массива в соответствии с указанными значениями.
Если вы начинаете с этого массива:
>>> arr = np.arange(6).reshape((2, 3))
>>> arr
array([[0, 1, 2],
[3, 4, 5]])
Вы можете транспонировать ваш массив с помощью arr.transpose()
>>> arr.transpose()
array([[0, 3],
[1, 4],
[2, 5]])
Вы также можете использовать arr.T:
>>> arr.T
array([[0, 3],
[1, 4],
[2, 5]])
Чтобы узнать больше о транспонировании и изменении формы массивов, см. transpose и reshape.
Как перевернуть массив
В этом разделе рассматривается np.flip()
Функция np.flip() NumPy позволяет переворачивать содержимое массива вдоль оси. При использовании np.flip(), укажите массив, который вы хотите перевернуть, и ось. Если вы не укажете ось, NumPy перевернёт содержимое по всем осям вашего входного массива.
Переворот одномерного массива
Если вы начинаете с одномерного массива, такого как этот:
>>> arr = np.array([1, 2, 3, 4, 5, 6, 7, 8])
Вы можете перевернуть его с помощью:
>>> reversed_arr = np.flip(arr)
Если вы хотите вывести перевернутый массив, вы можете выполнить:
>>> print('Reversed Array: ', reversed_arr)
Reversed Array: [8 7 6 5 4 3 2 1]
Переворот двумерного массива
Двумерный массив работает аналогично.
Если вы начинаете с этого массива:
>>> arr_2d = np.array([[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12]])
Вы можете перевернуть содержимое по всем строкам и всем столбцам с помощью:
>>> reversed_arr = np.flip(arr_2d) >>> print(reversed_arr) [[12 11 10 9] [ 8 7 6 5] [ 4 3 2 1]]
Вы можете легко перевернуть только строки с помощью:
>>> reversed_arr_rows = np.flip(arr_2d, axis=0) >>> print(reversed_arr_rows) [[ 9 10 11 12] [ 5 6 7 8] [ 1 2 3 4]]
Или перевернуть только столбцы с помощью:
>>> reversed_arr_columns = np.flip(arr_2d, axis=1) >>> print(reversed_arr_columns) [[ 4 3 2 1] [ 8 7 6 5] [12 11 10 9]]
Вы также можете перевернуть содержимое только одного столбца или строки. Например, вы можете перевернуть содержимое строки с индексной позицией 1 (вторая строка):
>>> arr_2d[1] = np.flip(arr_2d[1]) >>> print(arr_2d) [[ 1 2 3 4] [ 8 7 6 5] [ 9 10 11 12]]
Вы также можете перевернуть столбец с индексной позицией 1 (второй столбец):
>>> arr_2d[:,1] = np.flip(arr_2d[:,1]) >>> print(arr_2d) [[ 1 10 3 4] [ 8 7 6 5] [ 9 2 11 12]]
Подробнее о переворачивании массивов см. flip.
Изменение формы и сглаживание многомерных массивов
В этом разделе рассматривается .flatten(), ravel()
Существует два популярных способа сглаживания массива: .flatten() и .ravel(). Основное различие между ними заключается в том, что новый массив, созданный с помощью ravel(), фактически является ссылкой на родительский массив (то есть «вид»). Это означает, что любые изменения в новом массиве повлияют и на родительский массив. Поскольку ravel не создаёт копию, он более экономичен с точки зрения памяти.
Если вы начинаете с этого массива:
>>> x = np.array([[1 , 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12]])
Вы можете использовать flatten для сглаживания вашего массива в одномерный массив.
>>> x.flatten() array([ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12])
Когда вы используете flatten, изменения в вашем новом массиве не повлияют на родительский массив.
Например:
>>> a1 = x.flatten() >>> a1[0] = 99 >>> print(x) # Original array [[ 1 2 3 4] [ 5 6 7 8] [ 9 10 11 12]] >>> print(a1) # New array [99 2 3 4 5 6 7 8 9 10 11 12]
Но когда вы используете ravel, изменения, которые вы вносите в новый массив, повлияют на родительский массив.
Например:
>>> a2 = x.ravel() >>> a2[0] = 98 >>> print(x) # Original array [[98 2 3 4] [ 5 6 7 8] [ 9 10 11 12]] >>> print(a2) # New array [98 2 3 4 5 6 7 8 9 10 11 12]
Подробнее о flatten см. ndarray.flatten, а о ravel см. ravel.
Как получить доступ к строке документации для получения дополнительной информации
В этом разделе рассматривается help(), ?, ??
В экосистеме науки о данных Python и NumPy созданы с учётом пользователя. Одним из лучших примеров этого является встроенный доступ к документации. Каждый объект содержит ссылку на строку, которая известна как строка документации. В большинстве случаев эта строка документации содержит краткое и точное описание объекта и как его использовать. Python имеет встроенную функцию help(), которая поможет вам получить доступ к этой информации. Это означает, что практически каждый раз, когда вам нужна дополнительная информация, вы можете использовать help(), чтобы быстро найти необходимую информацию.
Например:
>>> help(max)
Help on built-in function max in module builtins:
max(...)
max(iterable, *[, default=obj, key=func]) -> value
max(arg1, arg2, *args, *[, key=func]) -> value
With a single iterable argument, return its biggest item. The
default keyword-only argument specifies an object to return if
the provided iterable is empty.
With two or more arguments, return the largest argument.
Поскольку доступ к дополнительной информации так полезен, IPython использует символ ? в качестве сокращения для доступа к этой документации вместе с другой полезной информацией. IPython — это командная оболочка для интерактивного вычисления на нескольких языках. Дополнительную информацию об IPython можно найти здесь.
Например:
In [0]: max? max(iterable, *[, default=obj, key=func]) -> value max(arg1, arg2, *args, *[, key=func]) -> value With a single iterable argument, return its biggest item. The default keyword-only argument specifies an object to return if the provided iterable is empty. With two or more arguments, return the largest argument. Type: builtin_function_or_method
Вы даже можете использовать эту запись для методов объектов и самих объектов.
Предположим, вы создаёте этот массив:
>>> a = np.array([1, 2, 3, 4, 5, 6])
Затем вы можете получить много полезной информации (сначала подробности об a , а затем строку документации ndarray, экземпляром которого является a):
In [1]: a?
Type: ndarray
String form: [1 2 3 4 5 6]
Length: 6
File: ~/anaconda3/lib/python3.9/site-packages/numpy/__init__.py
Docstring: <no docstring>
Class docstring:
ndarray(shape, dtype=float, buffer=None, offset=0,
strides=None, order=None)
An array object represents a multidimensional, homogeneous array
of fixed-size items. An associated data-type object describes the
format of each element in the array (its byte-order, how many bytes it
occupies in memory, whether it is an integer, a floating point number,
or something else, etc.)
Arrays should be constructed using `array`, `zeros` or `empty` (refer
to the See Also section below). The parameters given here refer to
a low-level method (`ndarray(...)`) for instantiating an array.
For more information, refer to the `numpy` module and examine the
methods and attributes of an array.
Parameters
----------
(for the __new__ method; see Notes below)
shape : tuple of ints
Shape of created array.
...
Это также работает для функций и других объектов, которые вы создаёте. Просто помните, что нужно включить строку документации в вашу функцию, используя строковый литерал (""" """ или ''' ''' вокруг вашей документации).
Например, если вы создаёте эту функцию:
>>> def double(a): ... '''Return a * 2''' ... return a * 2
Вы можете получить информацию о функции:
In [2]: double? Signature: double(a) Docstring: Return a * 2 File: ~/Desktop/<ipython-input-23-b5adf20be596> Type: function
Вы можете получить доступ к ещё более подробной информации, прочитав исходный код интересующего вас объекта. Использование двойного знака вопроса (??) позволяет вам получить доступ к исходному коду.
Например:
In [3]: double??
Signature: double(a)
Source:
def double(a):
'''Return a * 2'''
return a * 2
File: ~/Desktop/<ipython-input-23-b5adf20be596>
Type: function
Если объект скомпилирован на языке, отличном от Python, использование ?? вернёт ту же информацию, что и ?. Вы найдёте это для многих встроенных объектов и типов, например:
In [4]: len? Signature: len(obj, /) Docstring: Return the number of items in a container. Type: builtin_function_or_method
и :
In [5]: len?? Signature: len(obj, /) Docstring: Return the number of items in a container. Type: builtin_function_or_method
дают одинаковый результат, потому что были скомпилированы на языке программирования, отличном от Python.
Работа с математическими формулами
Легкость реализации математических формул, работающих с массивами, — это одна из причин, по которой NumPy так широко используется в научном сообществе Python.
Например, это формула средней квадратичной ошибки (центральная формула, используемая в моделях машинного обучения с учителем, которые занимаются регрессией):
Реализация этой формулы в NumPy проста и понятна:
Что делает это эффективным, так это то, что predictions и labels могут содержать одно или тысячи значений. Они только должны быть одного размера.
Вы можете представить это так:
В этом примере векторы предсказаний и меток содержат по три значения, что означает, что n имеет значение три. После вычитания значения в векторе возводятся в квадрат. Затем NumPy суммирует значения, и ваш результат — это значение ошибки для данного предсказания и оценка качества модели.
Как сохранять и загружать объекты NumPy
Эта секция охватывает np.save, np.savez, np.savetxt, np.load, np.loadtxt
В какой-то момент вам захочется сохранить массивы на диск и загрузить их обратно, не выполняя код заново. К счастью, есть несколько способов сохранять и загружать объекты с помощью NumPy. Объекты ndarray могут быть сохранены и загружены из файлов на диске с помощью функций loadtxt и savetxt, которые обрабатывают обычные текстовые файлы, load и save функций, которые обрабатывают двоичные файлы NumPy с расширением .npy, и функцией savez, которая обрабатывает файлы NumPy с расширением .npz.
Файлы .npy и .npz хранят данные, форму, тип данных и другую информацию, необходимую для реконструкции ndarray таким образом, что массив можно правильно извлечь, даже если файл находится на другой машине с другой архитектурой.
Если вы хотите сохранить один объект ndarray, сохраните его как файл .npy, используя np.save. Если вы хотите сохранить более одного объекта ndarray в одном файле, сохраните его как файл .npz, используя np.savez. Вы также можете сохранить несколько массивов в один файл в сжатом формате npz с помощью savez_compressed.
Файлы .npy и .npz хранят данные, форму, тип данных и другую информацию, необходимую для реконструкции ndarray таким образом, что массив можно правильно извлечь, даже если файл находится на другой машине с другой архитектурой.
Если вы хотите сохранить один объект ndarray, сохраните его как файл .npy, используя np.save. Если вы хотите сохранить более одного объекта ndarray в одном файле, сохраните его как файл .npz, используя np.savez. Вы также можете сохранить несколько массивов в один файл в сжатом формате npz с помощью savez_compressed.
Сохранить и загрузить массив с помощью np.save() очень просто. Просто укажите массив, который вы хотите сохранить, и имя файла. Например, если вы создаете этот массив:
>>> a = np.array([1, 2, 3, 4, 5, 6])
Вы можете сохранить его как “filename.npy” с помощью:
>>> np.save('filename', a)
Вы можете использовать np.load() для восстановления вашего массива.
>>> b = np.load('filename.npy')
Если вы хотите проверить свой массив, вы можете запустить:
>>> print(b) [1 2 3 4 5 6]
Вы можете сохранить массив NumPy в обычный текстовый файл, например, в файл .csv или .txt, с помощью np.savetxt.
Например, если вы создаете этот массив:
>>> csv_arr = np.array([1, 2, 3, 4, 5, 6, 7, 8])
Вы можете легко сохранить его как файл .csv с именем “new_file.csv” следующим образом:
>>> np.savetxt('new_file.csv', csv_arr)
Вы можете быстро и легко загрузить сохраненный текстовый файл, используя loadtxt():
>>> np.loadtxt('new_file.csv')
array([1., 2., 3., 4., 5., 6., 7., 8.])
Функции savetxt() и loadtxt() принимают дополнительные необязательные параметры, такие как заголовок, подвал и разделитель. Хотя текстовые файлы могут быть проще для обмена, файлы .npy и .npz меньше и быстрее читаются. Если вам нужна более сложная обработка текстового файла (например, если вам нужно работать с линиями, содержащими пропущенные значения), вы захотите использовать функцию genfromtxt.
С помощью savetxt вы можете указать заголовки, подвалы, комментарии и многое другое.
Узнайте больше о процедурах ввода-вывода здесь.
Импорт и экспорт CSV
Просто читать CSV, содержащий существующую информацию. Наиболее простой и эффективный способ сделать это — использовать Pandas.
>>> import pandas as pd
>>> # If all of your columns are the same type:
>>> x = pd.read_csv('music.csv', header=0).values
>>> print(x)
[['Billie Holiday' 'Jazz' 1300000 27000000]
['Jimmie Hendrix' 'Rock' 2700000 70000000]
['Miles Davis' 'Jazz' 1500000 48000000]
['SIA' 'Pop' 2000000 74000000]]
>>> # You can also simply select the columns you need:
>>> x = pd.read_csv('music.csv', usecols=['Artist', 'Plays']).values
>>> print(x)
[['Billie Holiday' 27000000]
['Jimmie Hendrix' 70000000]
['Miles Davis' 48000000]
['SIA' 74000000]]
Использовать Pandas для экспорта вашего массива также просто. Если вы новичок в NumPy, вы можете создать DataFrame Pandas из значений в вашем массиве, а затем записать DataFrame в CSV-файл с помощью Pandas.
Если вы создали этот массив “a”
>>> a = np.array([[-2.58289208, 0.43014843, -1.24082018, 1.59572603], ... [ 0.99027828, 1.17150989, 0.94125714, -0.14692469], ... [ 0.76989341, 0.81299683, -0.95068423, 0.11769564], ... [ 0.20484034, 0.34784527, 1.96979195, 0.51992837]])
Вы могли бы создать DataFrame Pandas
>>> df = pd.DataFrame(a)
>>> print(df)
0 1 2 3
0 -2.582892 0.430148 -1.240820 1.595726
1 0.990278 1.171510 0.941257 -0.146925
2 0.769893 0.812997 -0.950684 0.117696
3 0.204840 0.347845 1.969792 0.519928
Вы легко можете сохранить свой DataFrame с помощью:
>>> df.to_csv('pd.csv')
И прочитать ваш CSV с помощью:
>>> data = pd.read_csv('pd.csv')
Вы также можете сохранить свой массив с помощью метода NumPy savetxt.
>>> np.savetxt('np.csv', a, fmt='%.2f', delimiter=',', header='1, 2, 3, 4')
Если вы используете командную строку, вы можете прочитать сохраненный CSV в любое время с помощью команды, например:
$ cat np.csv # 1, 2, 3, 4 -2.58,0.43,-1.24,1.60 0.99,1.17,0.94,-0.15 0.77,0.81,-0.95,0.12 0.20,0.35,1.97,0.52
Или вы можете открыть файл в любое время с помощью текстового редактора!
Если вас интересует более подробная информация о Pandas, ознакомьтесь с официальной документацией Pandas. Узнайте, как установить Pandas с помощью официальной информации по установке Pandas.
Построение графиков массивов с помощью Matplotlib
Если вам нужно создать график для ваших значений, это очень просто с помощью Matplotlib.
Например, у вас может быть массив, подобный этому:
>>> a = np.array([2, 1, 5, 7, 4, 6, 8, 14, 10, 9, 18, 20, 22])
Если у вас уже установлен Matplotlib, вы можете импортировать его с помощью:
>>> import matplotlib.pyplot as plt # If you're using Jupyter Notebook, you may also want to run the following # line of code to display your code in the notebook: %matplotlib inline
Для построения графика ваших значений вам нужно выполнить:
>>> plt.plot(a) # If you are running from a command line, you may need to do this: # >>> plt.show()
Например, вы можете построить график одномерного массива так:
>>> x = np.linspace(0, 5, 20) >>> y = np.linspace(0, 10, 20) >>> plt.plot(x, y, 'purple') # line >>> plt.plot(x, y, 'o') # dots
С помощью Matplotlib у вас есть доступ к огромному количеству вариантов визуализации.
>>> fig = plt.figure() >>> ax = fig.add_subplot(projection='3d') >>> X = np.arange(-5, 5, 0.15) >>> Y = np.arange(-5, 5, 0.15) >>> X, Y = np.meshgrid(X, Y) >>> R = np.sqrt(X**2 + Y**2) >>> Z = np.sin(R) >>> ax.plot_surface(X, Y, Z, rstride=1, cstride=1, cmap='viridis')
Чтобы узнать больше о Matplotlib и его возможностях, ознакомьтесь с официальной документацией. Инструкции по установке Matplotlib см. в официальном разделе установки.
Благодарность за изображения: Jay Alammar https://jalammar.github.io/
© 2005–2024 NumPy Developers
Licensed under the 3-clause BSD License.
https://numpy.org/doc/2.0/user/absolute_beginners.html