Есть ли векторизованный способ проверить, присутствует ли i-й элемент 1D-массива в i-м элементе 3D-массива? - PullRequest
1 голос
/ 01 февраля 2020

У меня есть одномерный массив длины k с некоторыми произвольными значениями и трехмерный массив измерений k * i * j с некоторыми данными.

import numpy as np

# create 1D and 3D array
values = np.array([2, 5, 1], dtype=np.int)
arr = np.zeros((3, 4, 4), dtype=np.int)

# insert some random numbers in the 3D array
arr[0, 3, 2] = 5
arr[1, 1, 1] = 2
arr[2, 2, 3] = 1
>>> print(values)
[2 5 1]

>>> print(arr)
[[[0 0 0 0]
  [0 0 0 0]
  [0 0 0 0]
  [0 0 5 0]]

 [[0 0 0 0]
  [0 2 0 0]
  [0 0 0 0]
  [0 0 0 0]]

 [[0 0 0 0]
  [0 0 0 0]
  [0 0 0 1]
  [0 0 0 0]]]

Моя цель состоит в том, чтобы определить, присутствует ли i th элемент values ( т.е. скаляр) в элемент i th из arr ( т.е. двумерный массив) и получение логического массива длиной k .

В моем примере , Я ожидаю получить массив [False, False, True], так как 1 является единственным числом, присутствующим в соответствующем 2D-массиве (arr[2]).

Поскольку функция np.isin не является опцией, я пришел пока что есть два возможных решения.

1) Создайте трехмерный массив, повторяя числа в values, а затем проведите поэлементное сравнение:

rep = np.ones(arr.shape) * values.reshape(-1, 1, 1)
>>> print(rep)
[[[2. 2. 2. 2.]
  [2. 2. 2. 2.]
  [2. 2. 2. 2.]
  [2. 2. 2. 2.]]

 [[5. 5. 5. 5.]
  [5. 5. 5. 5.]
  [5. 5. 5. 5.]
  [5. 5. 5. 5.]]

 [[1. 1. 1. 1.]
  [1. 1. 1. 1.]
  [1. 1. 1. 1.]
  [1. 1. 1. 1.]]]

>>> np.any((arr == rep), axis=(1, 2))
array([False, False,  True])

Однако этот подход кажется плохой идеей с точки зрения памяти, если и values, и arr имеют большие фигуры.

2) Выполните итерацию по каждому значению в values и проверьте, присутствует ли оно в соответствующем Нет 2D-массив arr.

result = []
for i, value in enumerate(values):
    result.append(value in arr[i])
>>> print(result)
[False, False, True]

Этот подход, конечно, лучше с точки зрения памяти, но, опять же, при реализации с большими массивами он может занять много времени (представьте, что k означает 1000000 вместо 3).

Есть ли какая-либо другая функция numpy, которую мне не хватает, или, возможно, лучший подход к достижению sh моей цели здесь?

Я уже принял взгляните на ответы на аналогичный вопрос , но они не подходят для моего варианта использования.

Ответы [ 4 ]

2 голосов
/ 01 февраля 2020

с использованием широковещания может помочь:

np.any(values[:,None,None] == arr, axis=(1,2))

- это один вкладыш, который дает [False,False,True]. обратите внимание, что если вы храните arr, то хранение аналогичного массива bool не должно быть слишком плохим

обратите внимание, что именно values[:,None,None] == arr выполняет расширенное, странное индексирование с помощью None эквивалентно вашему reshape (но мне кажется, что это идиоматизм c)

1 голос
/ 02 февраля 2020

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

Пример

import numpy as np
import numba as nb

#Turn off parallelization for tiny problems
@nb.njit(parallel=True)
def example(values,arr):
    #Make sure that the first dimension is the same
    assert arr.shape[0]==values.shape[0]
    out=np.empty(values.shape[0],dtype=nb.bool_)

    for i in nb.prange(arr.shape[0]):
        out[i]=False
        for j in range(arr.shape[1]):
            if arr[i,j]==values[i]:
                out[i]=True
                break
    return out

Синхронизация (маленькие массивы)

#your input data
%timeit example(values,arr.reshape(arr.shape[0],-1))# #parallel=True
#10.7 µs ± 34.5 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
%timeit example(values,arr.reshape(arr.shape[0],-1))# #parallel=False
#2.15 µs ± 49.9 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

#Methods from other answers
%timeit (values[:,None,None]==arr).any(axis=(1,2))
#9.52 µs ± 323 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
%timeit [(i==a).any() for i,a in zip(values, arr)]
#23.9 µs ± 435 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)

Синхронизация (большие массивы)

values=np.random.randint(low=1,high=100_000,size=1_000_000)
arr=np.random.randint(low=1,high=10_00,size=1_000_000*100).reshape(1_000_000,10,10)

%timeit example(values,arr.reshape(arr.shape[0],-1)) #parallel=True
#48.2 ms ± 5.02 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
%timeit example(values,arr.reshape(arr.shape[0],-1)) #parallel=False
#90.5 ms ± 618 µs per loop (mean ± std. dev. of 7 runs, 1 loop each)

#Methods from other answers
%timeit (values[:,None,None]==arr).any(axis=(1,2))
#186 ms ± 5.47 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
%timeit [(i==a).any() for i,a in zip(values, arr)]
#6.63 s ± 69 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
1 голос
/ 01 февраля 2020

Вы в основном определили два варианта:

In [35]: timeit [(i==a).any() for i,a in zip(values, arr)]                                     
29 µs ± 543 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)
In [36]: timeit (values[:,None,None]==arr).any(axis=(1,2))                                     
11.4 µs ± 10.5 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

В этом небольшом случае подход с большим массивом выполняется быстрее. Но для большего случая итерация может быть лучше. Управление памятью с помощью больших массивов может отменить экономию времени. Часто бывает так, что несколько итераций по сложной проблеме лучше, чем полностью «векторизованная» версия.

Если это то, что вы делаете неоднократно, вы можете потратить время на создание гибридного решения, которое повторяется на блоках. , Но вам придется самим об этом судить.

isin и связанный с ним код: либо ors некоторые тесты, либо использование какого-либо sort для размещения одинаковых значений рядом для упрощения сравнения.

Другой подход - написать полностью итеративное решение и позволить numba скомпилировать его для вас.

1 голос
/ 01 февраля 2020

Я обнаружил, что ваша проблема эквивалентна

[np.any(arr[i]==values[i]) for i in range(len(values))]

Я согласен, что это отнимает много времени. Здесь нельзя избежать сравнения элементов, поэтому np.any(arr[i]==values[i]) или values[i] in arr[i] здесь просто необходимо. Что касается векторизации, я обнаружил, что довольно сложно заменить понимание списка, используемое и здесь. Это мой способ использования np.vectorize:

def myfunc(i): return np.any(arr[i]==values[i])
vfunc = np.vectorize(myfunc)
vfunc(np.arange(len(values)))
# output: array([False, False,  True])
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...