Получать пересекающиеся строки по двум массивам 2D numpy

Я хочу получить пересекающиеся (общие) строки в двух массивах 2D numpy. Например, если в качестве входных данных передаются следующие массивы:

array([[1, 4],
       [2, 5],
       [3, 6]])

array([[1, 4],
       [3, 6],
       [7, 8]])

вывод должен быть:

array([[1, 4],
       [3, 6])

Я знаю, как это сделать с помощью циклов. Я смотрю на способ Pythonic/Numpy для этого.

Ответ 1

Для коротких массивов использование наборов, вероятно, является самым ясным и наиболее читаемым способом его выполнения.

Другой способ - использовать numpy.intersect1d. Вам придется обмануть его, рассматривая строки как одно значение, хотя... Это делает вещи немного менее удобочитаемыми...

import numpy as np

A = np.array([[1,4],[2,5],[3,6]])
B = np.array([[1,4],[3,6],[7,8]])

nrows, ncols = A.shape
dtype={'names':['f{}'.format(i) for i in range(ncols)],
       'formats':ncols * [A.dtype]}

C = np.intersect1d(A.view(dtype), B.view(dtype))

# This last bit is optional if you're okay with "C" being a structured array...
C = C.view(A.dtype).reshape(-1, ncols)

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

Ответ 2

Вы можете использовать наборы Python:

>>> import numpy as np
>>> A = np.array([[1,4],[2,5],[3,6]])
>>> B = np.array([[1,4],[3,6],[7,8]])
>>> aset = set([tuple(x) for x in A])
>>> bset = set([tuple(x) for x in B])
>>> np.array([x for x in aset & bset])
array([[1, 4],
       [3, 6]])

Как отмечает Роб Коуи, это можно сделать более кратко, так как

np.array([x for x in set(tuple(x) for x in A) & set(tuple(x) for x in B)])

Вероятно, есть способ сделать это, не переходя от массивов к кортежам, но это не приходит мне прямо сейчас.

Ответ 3

Я не мог понять, почему нет предлагаемого чистого numpy способа заставить это работать. Поэтому я нашел один, который использует широковещательную передачу numpy. Основная идея - преобразовать один из массивов в 3d путем замены осей. Пусть построено 2 массива:

a=np.random.randint(10, size=(5, 3))
b=np.zeros_like(a)
b[:4,:]=a[np.random.randint(a.shape[0], size=4), :]

При моем запуске он дал:

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

Шаги (массивы могут быть взаимозаменяемы):

#a is nxm and b is kxm
c = np.swapaxes(a[:,:,None],1,2)==b #transform a to nx1xm
# c has nxkxm dimensions due to comparison broadcast
# each nxixj slice holds comparison matrix between a[j,:] and b[i,:]
# Decrease dimension to nxk with product:
c = np.prod(c,axis=2)
#To get around duplicates://
# Calculate cumulative sum in k-th dimension
c= c*np.cumsum(c,axis=0)
# compare with 1, so that to get only one 'True' statement by row
c=c==1
#//
# sum in k-th dimension, so that a nx1 vector is produced
c=np.sum(c,axis=1).astype(bool)
# The intersection between a and b is a[c]
result=a[c]

В функции с 2 строками для уменьшения используемой памяти (исправьте меня, если не так):

def array_row_intersection(a,b):
   tmp=np.prod(np.swapaxes(a[:,:,None],1,2)==b,axis=2)
   return a[np.sum(np.cumsum(tmp,axis=0)*tmp==1,axis=1).astype(bool)]

который дал результат для моего примера:

result=array([[5, 6, 3],
       [2, 1, 4],
       [6, 7, 6]])

Это быстрее, чем заданные решения, поскольку он использует только простые операции numpy, в то время как он уменьшает постоянство размеров и идеально подходит для двух больших матриц. Наверное, я мог ошибаться в своих комментариях, так как получил ответ от экспериментов и инстинкта. Эквивалент для пересечения столбцов можно найти либо путем переноса массивов, либо путем небольшого изменения шагов. Кроме того, если нужны дубликаты, тогда шаги внутри "//" должны быть пропущены. Функция может быть отредактирована, чтобы возвращать только булевский массив индексов, который мне пригодился, пытаясь получить разные индексы массивов с одним и тем же вектором. Контрольный показатель для голосованного ответа и моего (количество элементов в каждом измерении играет определенную роль в выборе):

код:

def voted_answer(A,B):
    nrows, ncols = A.shape
    dtype={'names':['f{}'.format(i) for i in range(ncols)],
           'formats':ncols * [A.dtype]}
    C = np.intersect1d(A.view(dtype), B.view(dtype))
    return C.view(A.dtype).reshape(-1, ncols)

a_small=np.random.randint(10, size=(10, 10))
b_small=np.zeros_like(a_small)
b_small=a_small[np.random.randint(a_small.shape[0],size=[a_small.shape[0]]),:]
a_big_row=np.random.randint(10, size=(10, 1000))
b_big_row=a_big_row[np.random.randint(a_big_row.shape[0],size=[a_big_row.shape[0]]),:]
a_big_col=np.random.randint(10, size=(1000, 10))
b_big_col=a_big_col[np.random.randint(a_big_col.shape[0],size=[a_big_col.shape[0]]),:]
a_big_all=np.random.randint(10, size=(100,100))
b_big_all=a_big_all[np.random.randint(a_big_all.shape[0],size=[a_big_all.shape[0]]),:]



print 'Small arrays:'
print '\t Voted answer:',timeit.timeit(lambda:voted_answer(a_small,b_small),number=100)/100
print '\t Proposed answer:',timeit.timeit(lambda:array_row_intersection(a_small,b_small),number=100)/100
print 'Big column arrays:'
print '\t Voted answer:',timeit.timeit(lambda:voted_answer(a_big_col,b_big_col),number=100)/100
print '\t Proposed answer:',timeit.timeit(lambda:array_row_intersection(a_big_col,b_big_col),number=100)/100
print 'Big row arrays:'
print '\t Voted answer:',timeit.timeit(lambda:voted_answer(a_big_row,b_big_row),number=100)/100
print '\t Proposed answer:',timeit.timeit(lambda:array_row_intersection(a_big_row,b_big_row),number=100)/100
print 'Big arrays:'
print '\t Voted answer:',timeit.timeit(lambda:voted_answer(a_big_all,b_big_all),number=100)/100
print '\t Proposed answer:',timeit.timeit(lambda:array_row_intersection(a_big_all,b_big_all),number=100)/100

с результатами:

Small arrays:
     Voted answer: 7.47108459473e-05
     Proposed answer: 2.47001647949e-05
Big column arrays:
     Voted answer: 0.00198730945587
     Proposed answer: 0.0560171294212
Big row arrays:
     Voted answer: 0.00500325918198
     Proposed answer: 0.000308241844177
Big arrays:
     Voted answer: 0.000864889621735
     Proposed answer: 0.00257176160812

Следующий вердикт заключается в том, что если вам нужно сравнить 2 больших 2d массива из 2d точек, используйте голосовой ответ. Если у вас большие матрицы во всех измерениях, проголосовавший ответ - лучший из всех. Таким образом, это зависит от того, что вы выбираете каждый раз.

Ответ 4

Другой способ добиться этого с помощью структурированного массива:

>>> a = np.array([[3, 1, 2], [5, 8, 9], [7, 4, 3]])
>>> b = np.array([[2, 3, 0], [3, 1, 2], [7, 4, 3]])
>>> av = a.view([('', a.dtype)] * a.shape[1]).ravel()
>>> bv = b.view([('', b.dtype)] * b.shape[1]).ravel()
>>> np.intersect1d(av, bv).view(a.dtype).reshape(-1, a.shape[1])
array([[3, 1, 2],
       [7, 4, 3]])

Просто для ясности структурированный вид выглядит следующим образом:

>>> a.view([('', a.dtype)] * a.shape[1])
array([[(3, 1, 2)],
       [(5, 8, 9)],
       [(7, 4, 3)]],
       dtype=[('f0', '<i8'), ('f1', '<i8'), ('f2', '<i8')])

Ответ 5

np.array(set(map(tuple, b)).difference(set(map(tuple, a))))

Это также может работать

Ответ 6

A = np.array([[1,4],[2,5],[3,6]])
B = np.array([[1,4],[3,6],[7,8]])

def matching_rows(A,B):
  matches=[i for i in range(B.shape[0]) if np.any(np.all(A==B[i],axis=1))]
  if len(matches)==0:
    return B[matches]
  return np.unique(B[matches],axis=0)

>>> matching_rows(A,B)
array([[1, 4],
       [3, 6]])

Это, конечно, предполагает, что все строки имеют одинаковую длину.

Ответ 7

import numpy as np

A=np.array([[1, 4],
       [2, 5],
       [3, 6]])

B=np.array([[1, 4],
       [3, 6],
       [7, 8]])

intersetingRows=[(B==irow).all(axis=1).any() for irow in A]
print(A[intersetingRows])