Стандартные подклассы массивов#

Примечание

Наследование от numpy.ndarray возможно, но если ваша цель — создать массив с изменено поведение, как и массивы dask для распределённых вычислений и массивы cupy для вычислений на GPU, наследование не рекомендуется. Вместо этого используйте механизм диспетчеризации рекомендуется.

The ndarray может быть унаследован (в Python или C), если это необходимо. Поэтому он может служить основой для многих полезных классов. Часто выбор между созданием подкласса объекта массива или простым использованием основного компонента массива как внутренней части нового класса является сложным решением и может быть просто вопросом выбора. NumPy имеет несколько инструментов для упрощения взаимодействия вашего нового объекта с другими объектами массивов, поэтому выбор может не быть значимым в конечном итоге. Один из способов упростить вопрос — спросить себя, может ли объект, который вас интересует, быть заменён одним массивом или ему действительно требуется два или более массива в своей основе.

Обратите внимание, что asarray всегда возвращает базовый класс ndarray. Если вы уверены, что ваше использование объекта массива может обрабатывать любой подкласс ndarray, то asanyarray может использоваться для того, чтобы подклассы могли более чисто распространяться через вашу подпрограмму. В принципе, подкласс может переопределить любой аспект массива и, следовательно, согласно строгим правилам, asanyarray редко будет полезным. Однако большинство подклассов объекта массива не будут переопределять определённые аспекты объекта массива, такие как интерфейс буфера или атрибуты массива. Один важный пример, однако, почему ваша подпрограмма может не обрабатывать произвольный подкласс массива, заключается в том, что матрицы переопределяют оператор "*" как матричное умножение, а не поэлементное умножение.

Специальные атрибуты и методы#

Смотрите также

Наследование от ndarray

NumPy предоставляет несколько хуков, которые классы могут настраивать:

класс.__array_ufunc__(универсальная функция (ufunc), метод, *входные данные, **kwargs)#

Любой класс, подкласс ndarray или нет, может определить этот метод или установить его в None, чтобы переопределить поведение универсальных функций NumPy. Это работает аналогично __mul__ и другие процедуры бинарных операций.

  • универсальная функция (ufunc) является объектом ufunc, который был вызван.

  • метод — это строка, указывающая, какой метод Ufunc был вызван (один из "__call__", "reduce", "reduceat", "accumulate", "outer", "inner").

  • входные данные является кортежем входных аргументов для ufunc.

  • kwargs это словарь, содержащий необязательные входные аргументы ufunc. Если задан, любой out аргументы, как позиционные, так и ключевые, передаются как tuple в kwargs. См. обсуждение в Универсальные функции (ufunc) подробности.

Метод должен возвращать либо результат операции, либо NotImplemented если запрошенная операция не реализована.

Если один из входных, выходных или where аргументы имеют __array_ufunc__ метод, он выполняется вместо универсальной функции. Если более одного из аргументов реализует __array_ufunc__, они пробуются в порядке: подклассы перед суперклассами, входы перед выходами, выходы перед where, в противном случае слева направо. Первая процедура, возвращающая что-либо отличное от NotImplemented определяет результат. Если все __array_ufunc__ операции возвращают NotImplemented, a TypeError вызывается исключение.

Примечание

Мы планируем перереализовать функции numpy как (обобщённые) Ufunc, в этом случае станет возможным их переопределение с помощью __array_ufunc__ метод. Основной кандидат — matmul, который в настоящее время не является Ufunc, но может быть относительно легко переписан как (набор) обобщённых Ufuncs. То же может произойти с функциями, такими как median, amin, и argsort.

Как и с некоторыми другими специальными методами в Python, такими как __hash__ и __iter__, возможно указать, что ваш класс не не поддержка ufuncs путем установки __array_ufunc__ = None. Универсальные функции всегда вызывают TypeError при вызове на объекте, который устанавливает __array_ufunc__ = None.

Наличие __array_ufunc__ также влияет на то, как ndarray обрабатывает бинарные операции, такие как arr + obj и arr < obj когда arr является ndarray и obj является экземпляром пользовательского класса. Есть две возможности. Если obj.__array_ufunc__ присутствует и не равен None, тогда ndarray.__add__ и аналогичные функции будут делегировать механизму универсальных функций, означая, что arr + obj становится np.add(arr, obj), а затем add вызывает obj.__array_ufunc__. Это полезно, если вы хотите определить объект, который ведёт себя как массив.

В качестве альтернативы, если obj.__array_ufunc__ установлено в None, тогда как особый случай, специальные методы, такие как ndarray.__add__ заметит это и безусловно raise TypeError. Это полезно, если вы хотите создавать объекты, которые взаимодействуют с массивами через бинарные операции, но сами не являются массивами. Например, система обработки единиц измерения может иметь объект m представляющий единицу "метры", и хотите поддерживать синтаксис arr * m чтобы указать, что массив имеет единицы измерения «метры», но не хотите иначе взаимодействовать с массивами через ufuncs или иным образом. Это можно сделать, установив __array_ufunc__ = None и определение __mul__ и __rmul__ методы. (Обратите внимание, что это означает, что написание __array_ufunc__ который всегда возвращает NotImplemented не совсем то же самое, что установка __array_ufunc__ = None: в первом случае, arr + obj вызовет исключение TypeError, тогда как в последнем случае можно определить __radd__ метод для предотвращения этого.)

Вышесказанное не относится к операторам на месте, для которых ndarray никогда не возвращает NotImplemented. Следовательно, arr += obj всегда приводило бы к TypeError. Это потому, что для массивов операции на месте не могут быть в общем случае заменены простой обратной операцией. (Например, по умолчанию, arr += obj будет переведено как arr = arr + obj, т.е., arr будет заменён, вопреки ожидаемому для операций с массивами на месте.)

Примечание

Если вы определите __array_ufunc__:

  • Если вы не являетесь подклассом ndarray, мы рекомендуем вашему классу определять специальные методы, такие как __add__ и __lt__ которые делегируют ufuncs, как это делает ndarray. Простой способ сделать это — создать подкласс от NDArrayOperatorsMixin.

  • Если вы создаёте подкласс ndarray, мы рекомендуем поместить всю вашу логику переопределения в __array_ufunc__ и не переопределять специальные методы. Это гарантирует, что иерархия классов определяется только в одном месте, а не отдельно механизмом ufunc и правилами бинарных операций (которые отдают предпочтение специальным методам подклассов; альтернативный способ обеспечить иерархию только в одном месте — установка __array_ufunc__ в None, казалось бы очень неожиданным и, следовательно, запутанным, так как тогда подкласс вообще не будет работать с ufuncs).

  • ndarray определяет свой собственный __array_ufunc__, который, вычисляет ufunc, если нет переопределений аргументов, и возвращает NotImplemented в противном случае. Это может быть полезно для подклассов, для которых __array_ufunc__ преобразует любые экземпляры своего класса в ndarray: он может затем передать их в свой суперкласс с помощью super().__array_ufunc__(*inputs, **kwargs), и наконец возвращает результаты после возможного обратного преобразования. Преимущество этой практики в том, что она гарантирует возможность иметь иерархию подклассов, расширяющих поведение. См. Наследование от ndarray подробности.

класс.__array_function__(функция, типы, args, kwargs)#
  • func — это произвольный вызываемый объект, доступный через публичный API NumPy, который был вызван в форме func(*args, **kwargs).

  • types является коллекцией collections.abc.Collection уникальных типов аргументов из исходного вызова функции NumPy, которые реализуют __array_function__.

  • Кортеж args и dict kwargs непосредственно передаются из исходного вызова.

В качестве удобства для __array_function__ реализаторов, types предоставляет все типы аргументов с '__array_function__' атрибут. Это позволяет разработчикам быстро идентифицировать случаи, когда они должны делегировать __array_function__ реализации для других аргументов. Реализации не должны полагаться на порядок итерации types.

Большинство реализаций __array_function__ начнёт с двух проверок:

  1. Является ли данная функция чем-то, что мы знаем, как перегрузить?

  2. Все ли аргументы имеют тип, который мы умеем обрабатывать?

Если эти условия выполняются, __array_function__ должен вернуть результат вызова его реализации для func(*args, **kwargs). В противном случае, он должен возвращать сторожевой значение NotImplemented, указывая, что функция не реализована для этих типов.

Нет общих требований к возвращаемому значению от __array_function__, хотя большинство разумных реализаций, вероятно, должны возвращать массив(ы) с тем же типом, что и один из аргументов функции.

Также может быть удобно определить пользовательские декораторы (implements ниже) для регистрации __array_function__ реализации.

HANDLED_FUNCTIONS = {}

class MyArray:
    def __array_function__(self, func, types, args, kwargs):
        if func not in HANDLED_FUNCTIONS:
            return NotImplemented
        # Note: this allows subclasses that don't override
        # __array_function__ to handle MyArray objects
        if not all(issubclass(t, MyArray) for t in types):
            return NotImplemented
        return HANDLED_FUNCTIONS[func](*args, **kwargs)

def implements(numpy_function):
    """Register an __array_function__ implementation for MyArray objects."""
    def decorator(func):
        HANDLED_FUNCTIONS[numpy_function] = func
        return func
    return decorator

@implements(np.concatenate)
def concatenate(arrays, axis=0, out=None):
    ...  # implementation of concatenate for MyArray objects

@implements(np.broadcast_to)
def broadcast_to(array, shape):
    ...  # implementation of broadcast_to for MyArray objects

Обратите внимание, что не требуется для __array_function__ реализации для включения все соответствующих необязательных аргументов функции NumPy (например, broadcast_to выше опускает нерелевантные subok аргумент). Необязательные аргументы передаются только в __array_function__ если они были явно использованы в вызове функции NumPy.

Так же, как и в случае встроенных специальных методов, таких как __add__, правильно написанный __array_function__ методы всегда должны возвращать NotImplemented при встрече неизвестного типа. В противном случае будет невозможно правильно переопределить функции NumPy из другого объекта, если операция также включает один из ваших объектов.

В большинстве случаев правила диспетчеризации с __array_function__ соответствуют тем, что для __array_ufunc__. В частности:

  • NumPy будет собирать реализации __array_function__ из всех указанных входов и вызывать их по порядку: подклассы перед суперклассами, а в остальном слева направо. Обратите внимание, что в некоторых крайних случаях, связанных с подклассами, это немного отличается от текущее поведение Python.

  • Реализации __array_function__ указывают, что они могут обрабатывать операцию, возвращая любое значение, кроме NotImplemented.

  • Если все __array_function__ методы возвращают NotImplemented, NumPy вызовет TypeError.

Если нет __array_function__ методы существуют, NumPy по умолчанию будет вызывать свою собственную реализацию, предназначенную для использования с массивами NumPy. Этот случай возникает, например, когда все аргументы, подобные массивам, являются числами Python или списками. (Массивы NumPy имеют __array_function__ метод, приведённый ниже, но он всегда возвращает NotImplemented если любой аргумент, кроме подкласса массива NumPy, реализует __array_function__.)

Одно отклонение от текущего поведения __array_ufunc__ заключается в том, что NumPy будет вызывать только __array_function__ на первый аргумент каждого уникального типа. Это соответствует правило вызова отраженных методов, и это гарантирует, что проверка перегрузок имеет приемлемую производительность даже при большом количестве перегруженных аргументов.

класс.__array_finalize__(obj)#

Этот метод вызывается всякий раз, когда система внутренне выделяет новый массив из obj, где obj является подклассом (подтипом) ndarray. Это может использоваться для изменения атрибутов self после создания (чтобы обеспечить, например, 2-мерную матрицу), или для обновления метаинформации от "родителя". Подклассы наследуют реализацию по умолчанию этого метода, которая ничего не делает.

класс.__array_wrap__(массив, контекст=None, return_scalar=False)#

В конце каждого универсальная функция (ufunc), этот метод вызывается для входного объекта с наивысшим приоритетом массива или выходного объекта, если он был указан. Массив, вычисленный ufunc, передается, и то, что возвращается, передается пользователю. Подклассы наследуют реализацию этого метода по умолчанию, которая преобразует массив в новый экземпляр класса объекта. Подклассы могут использовать этот метод для преобразования выходного массива в экземпляр подкласса и обновления метаданных перед возвратом массива пользователю.

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

Изменено в версии 2.0: return_scalar теперь передается как либо False (обычно) или True указывая, что NumPy вернет скаляр. Подклассы могут игнорировать значение или возвращать array[()] вести себя больше как NumPy.

Примечание

Надеемся, что в конечном итоге этот метод будет устаревшим в пользу __array_ufunc__ для ufuncs (и __array_function__ для нескольких других функций, таких как numpy.squeeze).

класс.__array_priority__#

Значение этого атрибута используется для определения типа объекта, который следует возвращать в ситуациях, когда существует более одной возможности для типа Python возвращаемого объекта. Подклассы наследуют значение по умолчанию 0.0 для этого атрибута.

Примечание

Для ufuncs планируется в конечном итоге устаревание этого метода в пользу __array_ufunc__.

класс.__array__(dtype=None, copy=None)#

Если определён для объекта, он должен возвращать NumPy ndarray. Этот метод вызывается функциями приведения массива, такими как np.array() если объект, реализующий этот интерфейс, передаётся в эти функции.

Сторонние реализации __array__ должен принимать dtype и copy аргументы.

Устарело с версии NumPy: 2.0 Не реализуется copy и dtype устарел с NumPy 2. При их добавлении вы должны обеспечить корректное поведение для copy.

  • dtype это запрошенный тип данных возвращаемого массива и передаётся NumPy позиционно (только если запрошено пользователем). Допустимо игнорировать dtype потому что NumPy проверит результат и приведёт к dtype при необходимости. Если более эффективно привести данные к запрошенному dtype без использования NumPy, вы должны обработать это в своей библиотеке.

  • copy является логическим значением, передаваемым по ключевому слову. Если copy=True вы должен возвращает копию. Возврат представления в существующих данных приведёт к некорректному коду пользователя. Если copy=False пользователь запросил, чтобы копия никогда не создавалась, и вы должен вызывает ошибку, если не создается копия и возвращаемый массив является представлением существующих данных. Допустимо всегда вызывать ошибку для copy=False. По умолчанию copy=None (не передано) позволяет результату быть либо представлением, либо копией. Однако возврат представления следует предпочитать, когда это возможно.

Пожалуйста, обратитесь к Совместимость с NumPy для иерархии протоколов, из которых __array__ является самым старым и наименее желательным.

Примечание

Если класс (подкласс ndarray или нет), имеющий __array__ метод используется как выходной объект для универсальная функция (ufunc), результаты будут не будет записано в объект, возвращенный __array__. Эта практика вернёт TypeError.

объекты Matrix#

Примечание

Настоятельно рекомендуется не использовать подкласс matrix. Как описано ниже, это затрудняет написание функций, которые последовательно работают с матрицами и обычными массивами. В настоящее время они в основном используются для взаимодействия с scipy.sparse. Мы надеемся предоставить альтернативу для этого использования, однако, и в конечном итоге удалить matrix подкласс.

matrix объекты наследуются от ndarray и, следовательно, имеют те же атрибуты и методы, что и ndarrays. Однако есть шесть важных отличий объектов matrix, которые могут привести к неожиданным результатам, когда вы используете матрицы, но ожидаете, что они будут вести себя как массивы:

  1. Объекты Matrix можно создавать с использованием строковой нотации для разрешения синтаксиса в стиле Matlab, где пробелы разделяют столбцы, а точки с запятой (';') разделяют строки.

  2. Объекты матриц всегда двумерные. Это имеет далеко идущие последствия: m.ravel() остается двумерным (с 1 в первом измерении), и выбор элементов возвращает двумерные объекты, так что поведение последовательности принципиально отличается от массивов.

  3. Объекты Matrix переопределяют умножение для матричного умножения. Убедитесь, что вы понимаете это для функций, которые могут получать матрицы. Особенно в свете того, что asanyarray(m) возвращает матрицу, когда m является матрицей.

  4. Объекты матрицы переопределяют возведение в степень для возведения матрицы в степень. То же предупреждение об использовании power внутри функции, которая использует asanyarray(…) для получения объекта массива, справедливо и для этого факта.

  5. Значение __array_priority__ по умолчанию для объектов матрицы равно 10.0, поэтому смешанные операции с ndarrays всегда производят матрицы.

  6. Матрицы имеют специальные атрибуты, которые упрощают вычисления. Это

    matrix.T

    Возвращает транспонированную матрицу.

    matrix.H

    Возвращает (комплексно) сопряжённую транспонированную матрицу self.

    matrix.I

    Возвращает (мультипликативную) обратную величину для обратимой self.

    matrix.A

    Возвращает self как ndarray объект.

Предупреждение

Объекты Matrix переопределяют умножение, '*', и возведение в степень, '**', чтобы они были матричным умножением и матричной степенью соответственно. Если ваша подпрограмма может принимать подклассы и вы не преобразуете в массивы базового класса, то вы должны использовать ufuncs multiply и power, чтобы быть уверенным, что выполняете правильную операцию для всех входных данных.

Класс matrix — это подкласс Python для ndarray и может использоваться в качестве примера того, как создать свой собственный подкласс ndarray. Матрицы могут быть созданы из других матриц, строк и всего, что можно преобразовать в ndarray . Имя "mat" является псевдонимом для "matrix" в NumPy.

matrix(data[, dtype, copy])

Возвращает матрицу из объекта, подобного массиву, или из строки данных.

asmatrix(data[, dtype])

Интерпретировать входные данные как матрицу.

bmat(obj[, ldict, gdict])

Построение объекта матрицы из строки, вложенной последовательности или массива.

Пример 1: Создание матрицы из строки

>>> import numpy as np
>>> a = np.asmatrix('1 2 3; 4 5 3')
>>> print((a*a.T).I)
  [[ 0.29239766 -0.13450292]
  [-0.13450292  0.08187135]]

Пример 2: Создание матрицы из вложенной последовательности

>>> import numpy as np
>>> np.asmatrix([[1,5,10],[1.0,3,4j]])
matrix([[  1.+0.j,   5.+0.j,  10.+0.j],
        [  1.+0.j,   3.+0.j,   0.+4.j]])

Пример 3: Создание матрицы из массива

>>> import numpy as np
>>> np.asmatrix(np.random.rand(3,3)).T
matrix([[4.17022005e-01, 3.02332573e-01, 1.86260211e-01],
        [7.20324493e-01, 1.46755891e-01, 3.45560727e-01],
        [1.14374817e-04, 9.23385948e-02, 3.96767474e-01]])

Массивы файлов, отображаемые в память#

Файлы, отображаемые в память, полезны для чтения и/или изменения небольших сегментов большого файла с регулярной структурой без чтения всего файла в память. Простой подкласс ndarray использует файл, отображаемый в память, для буфера данных массива. Для небольших файлов накладные расходы на чтение всего файла в память обычно не значительны, однако для больших файлов использование отображения в память может сэкономить значительные ресурсы.

Массивы, отображённые в файл, имеют один дополнительный метод (кроме тех, которые они наследуют от ndarray): .flush() который должен быть вызван вручную пользователем, чтобы гарантировать, что любые изменения в массиве действительно записываются на диск.

memmap(filename[, dtype, mode, offset, ...])

Создать отображение памяти на массив, хранящийся в бинарный файл на диске.

memmap.flush()

Записать любые изменения в массиве в файл на диске.

Пример:

>>> import numpy as np
>>> a = np.memmap('newfile.dat', dtype=float, mode='w+', shape=1000)
>>> a[10] = 10.0
>>> a[30] = 30.0
>>> del a
>>> b = np.fromfile('newfile.dat', dtype=float)
>>> print(b[10], b[30])
10.0 30.0
>>> a = np.memmap('newfile.dat', dtype=float)
>>> print(a[10], a[30])
10.0 30.0

Символьные массивы (numpy.char)#

Примечание

The chararray класс существует для обратной совместимости с Numarray, он не рекомендуется для новой разработки. Начиная с numpy 1.4, если нужны массивы строк, рекомендуется использовать массивы dtype object_, bytes_ или str_и используйте свободные функции в numpy.char модуль для быстрых векторизованных строковых операций.

Это улучшенные массивы либо str_ тип или bytes_ тип. Эти массивы наследуются от ndarray, но специально определяют операции +, *, и % на основе (вещания) поэлементно. Эти операции недоступны в стандартном ndarray символьного типа. Кроме того, chararray имеет все стандартные strbytes) методы, выполняя их на основе каждого элемента. Возможно, самый простой способ создать chararray — использовать self.view(chararray) где self является ndarray типа str или unicode. Однако chararray также можно создать с помощью chararray конструктор, или через numpy.char.array функция:

char.chararray(shape[, itemsize, unicode, ...])

Предоставляет удобное представление для массивов строковых и юникодных значений.

char.array(obj[, itemsize, copy, unicode, order])

Создать chararray.

Другое отличие от стандартного ndarray с типом данных str заключается в том, что chararray наследует функцию, введённую Numarray, что пробелы в конце любого элемента массива будут игнорироваться при извлечении элементов и операциях сравнения.

Записывающие массивы#

NumPy предоставляет recarray класс, который позволяет обращаться к полям структурированного массива как к атрибутам, и соответствующий скалярный тип данных объекта record.

recarray(shape[, dtype, buf, offset, ...])

Создать ndarray, который позволяет обращаться к полям через атрибуты.

record(length_or_data, /[, dtype])

Скаляр типа данных, который позволяет обращаться к полям как к атрибутам.

Примечание

DataFrame pandas мощнее, чем массив записей. По возможности используйте DataFrame pandas.

Маскированные массивы (numpy.ma)#

Смотрите также

Маскированные массивы

Стандартный класс контейнера#

Для обратной совместимости и в качестве стандартного «контейнерного» класса, UserArray из Numeric был перенесен в NumPy и назван numpy.lib.user_array.container Класс-контейнер — это класс Python, чей атрибут self.array является ndarray. Множественное наследование, вероятно, проще с numpy.lib.user_array.container, чем с самим ndarray, поэтому он включён по умолчанию. Он не документирован здесь, кроме упоминания его существования, поскольку рекомендуется использовать класс ndarray напрямую, если это возможно.

numpy.lib.user_array.container(data[, ...])

Стандартный класс-контейнер для удобного множественного наследования.

Итераторы массивов#

Итераторы — мощная концепция для обработки массивов. По сути, итераторы реализуют обобщённый цикл for. Если myiter является объектом-итератором, тогда код Python:

for val in myiter:
    ...
    some code involving val
    ...

вызовы val = next(myiter) повторять до тех пор, пока StopIteration вызывается итератором. Существует несколько способов итерации по массиву, которые могут быть полезны: итерация по умолчанию, плоская итерация и \(N\)-мерное перечисление.

Итерация по умолчанию#

Итератор по умолчанию объекта ndarray - это стандартный итератор Python для типа последовательности. Таким образом, когда сам объект массива используется как итератор. Поведение по умолчанию эквивалентно:

for i in range(arr.shape[0]):
    val = arr[i]

Этот итератор по умолчанию выбирает подмассив размерности \(N-1\) из массива. Это может быть полезной конструкцией для определения рекурсивных алгоритмов. Для перебора всего массива требуется \(N\) циклы for.

>>> import numpy as np
>>> a = np.arange(24).reshape(3,2,4) + 10
>>> for val in a:
...     print('item:', val)
item: [[10 11 12 13]
[14 15 16 17]]
item: [[18 19 20 21]
[22 23 24 25]]
item: [[26 27 28 29]
[30 31 32 33]]

Плоская итерация#

ndarray.flat

Одномерный итератор по массиву.

Как упоминалось ранее, атрибут flat объектов ndarray возвращает итератор, который будет перебирать весь массив в порядке C-стиля непрерывности.

>>> import numpy as np
>>> for i, val in enumerate(a.flat):
...     if i%5 == 0: print(i, val)
0 10
5 15
10 20
15 25
20 30

Здесь я использовал встроенный итератор enumerate, чтобы вернуть индекс итератора а также значение.

N-мерное перечисление#

ndenumerate(arr)

Многомерный итератор индексов.

Иногда может быть полезно получить N-мерный индекс во время итерации. Итератор ndenumerate может этого достичь.

>>> import numpy as np
>>> for i, val in np.ndenumerate(a):
...     if sum(i)%5 == 0:
            print(i, val)
(0, 0, 0) 10
(1, 1, 3) 25
(2, 0, 3) 29
(2, 1, 2) 32

Итератор для трансляции#

broadcast(*arrays)

Создать объект, имитирующий вещание.

Общая концепция трансляции (broadcasting) также доступна в Python с использованием broadcast итератор. Этот объект принимает \(N\) объекты в качестве входных данных и возвращает итератор, который возвращает кортежи, предоставляющие каждый из элементов входной последовательности в широковещательном результате.

>>> import numpy as np
>>> for val in np.broadcast([[1, 0], [2, 3]], [0, 1]):
...     print(val)
(np.int64(1), np.int64(0))
(np.int64(0), np.int64(1))
(np.int64(2), np.int64(0))
(np.int64(3), np.int64(1))