Разность строк матрицы, вывести логический вектор

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

Ни в A, ни в B нет повторяющихся строк.

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


person John Manak    schedule 25.06.2015    source источник


Ответы (3)


Вы можете следовать тому же шаблону, который показан в ответе jterrace, за исключением использования _ 1_ вместо 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 не является C-смежным массивом). Поэтому, как правило, нам нужно использовать np.ascontiguous(A) перед вызовом view.


Как сказал Б. предлагает вместо этого просмотреть каждую строку, используя "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)

Это безопасно использовать с целочисленными типами. Однако обратите внимание, что

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

поэтому использование np.in1d после просмотра как void может вернуть неверные результаты для массивов с float 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
person unutbu    schedule 25.06.2015
comment
Красивый. в этом случае вы можете выиграть с двумя факторами, просто написав Ad = A.view('V12'). - person B. M.; 25.06.2015
comment
Спасибо, @ B.M .; Я добавил using_void в смесь. - person unutbu; 25.06.2015

поскольку здесь всего 3 столбца, одним из решений было бы просто уменьшить количество столбцов:

>>> 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 промежуточный массив, который может неэффективно использовать память.

В качестве альтернативы, если значения являются индексами, то есть неотрицательными целыми числами, вы можете использовать _ 3_, чтобы преобразовать в одномерные массивы. Это эффективный алгоритм, который панды используют внутри 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)
person behzad.nouri    schedule 25.06.2015

Вы можете рассматривать A и B как два набора массивов XYZ и вычислять euclidean distances между ними с помощью _ 4_. Нам были бы интересны нулевые расстояния. Предполагается, что это вычисление расстояния будет довольно эффективной реализацией, поэтому, надеюсь, у нас будет эффективное решение для нашего случая. Итак, реализация для поиска такого логического вывода будет выглядеть так:

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)
person Divakar    schedule 25.06.2015