Home > Mobile >  How to find a missing row between two similar 2d numpy arrays
How to find a missing row between two similar 2d numpy arrays

Time:02-06

Say I have two 2d arrays:

aa = np.array([[1,2],[3,4],[9,10],[48,59]])
bb = np.array([[1,2],[9,10]])

array aa is my ideal array and bb is what I have recorded. I want to find out what rows bb is missing compared to aa. I can do this by looping through the rows but as my dataset is approximately 5Gb I want to know if there is a pythonic way of doing this to minimize run time. It would be preferable to carry out this operation as a numpy array but it doesn't HAVE to be that way if there is a better option.

Any help is appreciated. Thanks

CodePudding user response:

Try this:

bb_missing = aa[~np.all(aa==bb[:, None], axis=2).any(axis=0)]

Output:

>>> bb_missing
array([[ 3,  4],
       [48, 59]])

CodePudding user response:

The main step is to find boolean indices of aa that tells if items in aa are present in bb. For example, if:

aa = np.array([[1,2],[3,4],[9,10],[48,59]])
bb = np.array([[1,2],[9,10]])

then we should get:

idx
>>> array([ True, False,  True, False])

And we could use later:

aa[~idx]
>>> 
array([[ 3,  4],
       [48, 59]])

Since aa[~idx] is relatively fast, I'll try to review all the ways to find idx. They divide between two groups: the way of searching the data (np.isin, np.searchsort, etc.) and the way that data could be made 1-dimensional.

The way of reduction

Way 1. Make arrays aa and bb contiguous then call np.isin on their views:

def _view1D(a, b): # a, b are arrays
    a = np.ascontiguousarray(a, dtype=np.uint32)
    b = np.ascontiguousarray(b, dtype=np.uint32)
    uint32_dt = np.dtype([('', np.uint32)]*2)
    return a.view(uint32_dt).ravel(),  b.view(uint32_dt).ravel()

A, B = _view1D(aa, bb)
idx = np.isin(A, B)

Way 2. Reduce dimensionality of aa and bb then call np.isin on resulting arrays:

def numpy_dimreduce(arr, M):
    return np.ravel_multi_index(arr.T, M, order='F')

M = aa.max(axis=0) 1
idx = np.isin(numpy_dimreduce(aa, M), numpy_dimreduce(bb, M))

Way 3. Improve performance of dimensionality reduction significantly using numba:

@njit(parallel=True)
def _numba_dot(arr, dimshape, len_arr, len_dimshape, su):
    for i in prange(len_arr):
        for j in range(len_dimshape):
            su[i] = su[i]   arr[i][j] * dimshape[j]
        
def numba_dimreduce(arr, M, dtype=np.int64):
    '''not safe if allocation is exceeded'''
    dimshape = np.cumprod(np.insert(M[:-1], 0, 1))
    su = np.zeros(len(arr), dtype=dtype)
    _numba_dot(arr, dimshape, len(arr), len(dimshape), su)
    return su

M = aa.max(axis=0) 1
idx = np.isin(numba_dimreduce(aa, M), numba_dimreduce(bb, M))

The way of sorting

Way 1. Use np.isinlike in previous solutions

Way 2. Use np.searchsorted:

def searchsort(A, B):
    sidx = A.argsort()
    out = np.zeros(len(A), dtype=bool)
    s = np.searchsorted(A, B, sorter=sidx)    
    out[sidx[s]] = True
    return out

Conclusion

In conclusion, the general pattern of finding idx looks like this:

def isin2D(a, b, view=_view1D, search=np.isin):
    # choose your optional view and search
    A, B = view(a), view(b)
    idx = search(A, B)
    return idx

Actually, neither ndarray.views nor np.searchsorted helps to save time. The best option is to combine dimensionality reduction with np.isin.

Performance

Now let's time it using benchit package to illustrate what's winning

import numpy as np
from numba import njit, prange
import benchit
%matplotlib inline
benchit.setparams(rep=3)

sizes = [100000, 300000, 500000, 1000000, 1700000, 3000000, 5000000]#, 10000000, 30000000]
N = sizes[-1]
aa = np.random.randint(0, 1000000, size=(N, 2))
r = np.random.randint(0, 2, size=N).astype(bool)
M = aa.max(axis=0)   1

def _view1D(arr, dtype=np.uint32):
    a = np.ascontiguousarray(arr, dtype=dtype)
    dt = np.dtype([('', dtype)]*2)
    return a.view(dt).ravel()

def _numpy_dimreduce(arr, M):
    return np.ravel_multi_index(arr.T, M, order='C')

@njit(parallel=True)
def _numba_dot(arr, dimshape, len_arr, len_dimshape, su):
    for i in prange(len_arr):
        for j in range(len_dimshape):
            su[i] = su[i]   arr[i][j] * dimshape[j]
        
def _numba_dimreduce(arr, M, dtype=np.int64):
    '''not safe if allocation is exceeded'''
    dimshape = np.cumprod(np.insert(M[:-1], 0, 1))
    su = np.zeros(len(arr), dtype=dtype)
    _numba_dot(arr, dimshape, len(arr), len(dimshape), su)
    return su


def isin2D(a, b, view=_view1D, search=np.isin):
    A, B = view(a), view(b)
    idx = search(A, B)
    return idx

def searchsort(A, B):
    sidx = A.argsort()
    out = np.zeros(len(A), dtype=bool)
    s = np.searchsorted(A, B, sorter=sidx)    
    out[sidx[s]] = True
    return out

def isin2D_numpy_dimr(a, b, M=M): return isin2D(a, b, view=lambda arr, M=M: _numpy_dimreduce(arr, M))
def isin2D_numba_dimr(a, b, M=M): return isin2D(a, b, view=lambda arr, M=M: _numba_dimreduce(arr, M))
def isin2D_numpy_view(a, b): return isin2D(a, b)

def isin2D_numpy_dimr_searchsort(a, b, M=M): return isin2D(a, b, view=lambda arr, M=M: _numpy_dimreduce(arr, M), search=searchsort)
def isin2D_numba_dimr_searchsort(a, b, M=M): return isin2D(a, b, view=lambda arr, M=M: _numba_dimreduce(arr[:, ::-1], M[::-1]), search=searchsort)

fns = [isin2D_numpy_dimr, isin2D_numba_dimr, isin2D_numpy_view, isin2D_numpy_dimr_searchsort, isin2D_numba_dimr_searchsort]


in_ = {s/1000000: (aa[:s], aa[:s][r[:s]]) for s in sizes}
t = benchit.timings(fns, in_, multivar=True, input_name='Millions of events')
t.plot(logx=True, figsize=(12, 6), fontsize=14)

enter image description here

  •  Tags:  
  • Related