2015-06-25 5 views
4

У меня есть m x 3 матрица A и ее подмножество строк B (n x 3). Оба являются наборами индексов в другую большую 4D-матрицу; их тип данных - dtype('int64'). Я хотел бы генерировать логический вектор x, где x[i] = True, если B не содержит строки A[i,:].Матричная разность строк, вывод булева вектора

Нет одинаковых строк в A или B.

Мне было интересно, если есть эффективный способ, как это сделать в Numpy? Я нашел ответ, который несколько связан: https://stackoverflow.com/a/11903368/265289; однако он возвращает фактические строки (а не булев вектор).

ответ

4

Вы могли следовать той же схеме, как показано на jterrace's answer, за исключением использования np.in1d вместо np.setdiff1d:

import numpy as np 
np.random.seed(2015) 

m, n = 10, 5 
A = np.random.randint(10, size=(m,3)) 
B = A[np.random.choice(m, n, replace=False)] 
print(A) 
# [[2 2 9] 
# [6 8 5] 
# [7 8 0] 
# [6 7 8] 
# [3 8 6] 
# [9 2 3] 
# [1 2 6] 
# [2 9 8] 
# [5 8 4] 
# [8 9 1]] 

print(B) 
# [[2 2 9] 
# [1 2 6] 
# [2 9 8] 
# [3 8 6] 
# [9 2 3]] 

def using_view(A, B, assume_unique=False): 
    Ad = np.ascontiguousarray(A).view([('', A.dtype)] * A.shape[1]) 
    Bd = np.ascontiguousarray(B).view([('', B.dtype)] * B.shape[1]) 
    return ~np.in1d(Ad, Bd, assume_unique=assume_unique) 

print(using_view(A, B, assume_unique=True)) 

урожаи

[False True True True False False False False True True] 

Вы можете использовать assume_unique=True (который может ускорить вычисление), так как нет дубликатов строк в A или B.


Учтите, что A.view(...) поднимет

ValueError: new type not compatible with array. 

, если A.flags['C_CONTIGUOUS'] является False (то есть, если A не является С-непрерывный массив). Таким образом, в общем случае мы должны использовать np.ascontiguous(A) перед вызовом view.


Как B.M. предлагаешь, вы могли бы вместо того, чтобы просмотреть каждую строку с помощью "void" dtype:

def using_void(A, B): 
    dtype = 'V{}'.format(A.dtype.itemsize * A.shape[-1]) 
    Ad = np.ascontiguousarray(A).view(dtype) 
    Bd = np.ascontiguousarray(B).view(dtype) 
    return ~np.in1d(Ad, Bd, assume_unique=True) 

Это безопасно для использования с целой dtypes. Тем не менее, обратите внимание, что

In [342]: np.array([-0.], dtype='float64').view('V8') == np.array([0.], dtype='float64').view('V8') 
Out[342]: array([False], dtype=bool) 

поэтому использование np.in1d после просмотра недействительным может возвращать неверные результаты для массивов с поплавком DTYPE.


Вот эталон некоторых из предложенных методов:

import numpy as np 
np.random.seed(2015) 

m, n = 10000, 5000 
# Note A may contain duplicate rows, 
# so don't use assume_unique=True for these benchmarks. 
# In this case, using assume_unique=False does not improve the speed much anyway. 
A = np.random.randint(10, size=(2*m,3)) 
# make A not C_CONTIGUOUS; the view methods fail for non-contiguous arrays 
A = A[::2] 
B = A[np.random.choice(m, n, replace=False)] 

def using_view(A, B, assume_unique=False): 
    Ad = np.ascontiguousarray(A).view([('', A.dtype)] * A.shape[1]) 
    Bd = np.ascontiguousarray(B).view([('', B.dtype)] * B.shape[1]) 
    return ~np.in1d(Ad, Bd, assume_unique=assume_unique) 

from scipy.spatial import distance 
def using_distance(A, B): 
    return ~np.any(distance.cdist(A,B)==0,1) 

from functools import reduce 
def using_loop(A, B): 
    pred = lambda i: A[:, i:i+1] == B[:, i] 
    return ~reduce(np.logical_and, map(pred, range(A.shape[1]))).any(axis=1) 

from pandas.core.groupby import get_group_index, _int64_overflow_possible 
from functools import partial 
def using_pandas(A, B): 
    shape = [1 + max(A[:, i].max(), B[:, i].max()) for i in range(A.shape[1])] 
    assert not _int64_overflow_possible(shape) 

    encode = partial(get_group_index, shape=shape, sort=False, xnull=False) 
    a1, b1 = map(encode, (A.T, B.T)) 
    return ~np.in1d(a1, b1) 

def using_void(A, B): 
    dtype = 'V{}'.format(A.dtype.itemsize * A.shape[-1]) 
    Ad = np.ascontiguousarray(A).view(dtype) 
    Bd = np.ascontiguousarray(B).view(dtype) 
    return ~np.in1d(Ad, Bd) 

# Sanity check: make sure all the functions return the same result 
for func in (using_distance, using_loop, using_pandas, using_void): 
    assert (func(A, B) == using_view(A, B)).all() 

In [384]: %timeit using_pandas(A, B) 
100 loops, best of 3: 1.99 ms per loop 

In [381]: %timeit using_void(A, B) 
100 loops, best of 3: 6.72 ms per loop 

In [378]: %timeit using_view(A, B) 
10 loops, best of 3: 35.6 ms per loop 

In [383]: %timeit using_loop(A, B) 
1 loops, best of 3: 342 ms per loop 

In [379]: %timeit using_distance(A, B) 
1 loops, best of 3: 502 ms per loop 
+0

Красивая. вы можете выиграть два фактора, если просто написать 'Ad = A.view ('V12')' в этом случае. –

+0

Спасибо, @ B.M .; Я добавил 'использование_void' в микс. – unutbu

0

Вы можете лечить A и B как два набора XYZ массивов и вычислить euclidean distances между ними с scipy.spatial.distance.cdist. Нам были бы интересны нулевые расстояния. Этот расчет расстояний должен быть довольно эффективной, поэтому, надеюсь, у нас будет эффективное решение для решения нашего дела.Таким образом, реализация найти такой логический вывод будет выглядеть следующим образом -

from scipy.spatial import distance 

out = ~np.any(distance.cdist(A,B)==0,1) 
# OR np.all(distance.cdist(A,B)!=0,1) 

Пример запуск -

In [582]: A 
Out[582]: 
array([[0, 2, 2], 
     [1, 0, 3], 
     [3, 3, 3], 
     [2, 0, 3], 
     [2, 0, 1], 
     [1, 1, 1]]) 

In [583]: B 
Out[583]: 
array([[2, 0, 3], 
     [2, 3, 3], 
     [1, 1, 3], 
     [2, 0, 1], 
     [0, 2, 2], 
     [2, 2, 2], 
     [1, 2, 3]]) 

In [584]: out 
Out[584]: array([False, True, True, False, False, True], dtype=bool) 
3

, так как есть только три колонок, одно решение было бы просто уменьшить accross колонн :

>>> a 
array([[2, 2, 9], 
     [6, 8, 5], 
     [7, 8, 0], 
     [6, 7, 8], 
     [3, 8, 6], 
     [9, 2, 3], 
     [1, 2, 6], 
     [2, 9, 8], 
     [5, 8, 4], 
     [8, 9, 1]]) 
>>> b 
array([[2, 2, 9], 
     [1, 2, 6], 
     [2, 9, 8], 
     [3, 8, 6], 
     [9, 2, 3]]) 

>>> from functools import reduce 
>>> pred = lambda i: a[:, i:i+1] == b[:,i] 
>>> reduce(np.logical_and, map(pred, range(a.shape[1]))).any(axis=1) 
array([ True, False, False, False, True, True, True, True, False, False], dtype=bool) 

хотя это создаст m x n промежуточный массив, который не может быть память эффективным.

В качестве альтернативы, если значения индексы, то есть целые неотрицательные числа, вы можете использовать pandas.groupby.get_group_index для преобразования в одномерные массивы. Это эффективный алгоритм, который панды используют внутри для операций groupby; Единственное предостережение состоит в том, что вам может потребоваться проверить, что не будет никакого переполнения целого числа:

>>> from pandas.core.groupby import get_group_index, _int64_overflow_possible 
>>> from functools import partial 

>>> shape = [1 + max(a[:, i].max(), b[:, i].max()) for i in range(a.shape[1])] 
>>> assert not _int64_overflow_possible(shape) 

>>> encode = partial(get_group_index, shape=shape, sort=False, xnull=False) 
>>> a1, b1 = map(encode, (a.T, b.T)) 
>>> np.in1d(a1, b1) 
array([ True, False, False, False, True, True, True, True, False, False], dtype=bool) 
Смежные вопросы