numpy'isin'性能提升

时间:2018-10-29 13:25:13

标签: python performance numpy

我有一个383milj行的矩阵,我需要根据值列表(index_to_remove)对此矩阵进行过滤。该功能在1次迭代中执行了几次。有没有更快的替代方法:

def remove_from_result(matrix, index_to_remove, inv=True):
    return matrix[np.isin(matrix, index_to_remove, invert=inv)]

2 个答案:

答案 0 :(得分:3)

您的过滤功能的运行时间似乎是线性的。输入matrix的大小。请注意,使用列表set进行过滤绝对是线性的,并且在我的机器上具有相同输入的情况下,函数的运行速度大约是列表理解过滤器的两倍。您还可以看到,如果将大小增加X倍,则运行时也会增加X倍:

In [84]: test_elts = np.arange(12345)

In [85]: test_elts_set = set(test_elts)

In [86]: %timeit remove_from_result(np.arange(1000*1000), test_elts)
10 loops, best of 3: 81.5 ms per loop

In [87]: %timeit [x for x in np.arange(1000*1000) if x not in test_elts_set]
1 loop, best of 3: 201 ms per loop

In [88]: %timeit remove_from_result(np.arange(1000*1000*2), test_elts)
10 loops, best of 3: 191 ms per loop

In [89]: %timeit [x for x in np.arange(1000*1000*2) if x not in test_elts_set]
1 loop, best of 3: 430 ms per loop

In [90]: %timeit remove_from_result(np.arange(1000*1000*10), test_elts)
1 loop, best of 3: 916 ms per loop

In [91]: %timeit [x for x in np.arange(1000*1000*10) if x not in test_elts_set]
1 loop, best of 3: 2.04 s per loop

In [92]: %timeit remove_from_result(np.arange(1000*1000*100), test_elts)
1 loop, best of 3: 12.4 s per loop

In [93]: %timeit [x for x in np.arange(1000*1000*100) if x not in test_elts_set]
1 loop, best of 3: 26.4 s per loop

对于过滤非结构化数据,就算法复杂度而言,这是最快的,因为您必须触摸每个元素一次。您不能做得比线性时间还要好。以下几点可能有助于提高性能:

  1. 如果您可以访问pyspark之类的东西(如果愿意支付几美元,可以通过在AWS上使用EMR来获得),则可以更快地完成此操作。这个问题非常尴尬。您可以将输入分为K个块,为每个工作人员分配需要过滤的项目和一个块,为每个工作人员过滤,然后最后收集/合并。或者甚至可以尝试使用multiprocessing,但是您必须注意内存(multiprocessing与C的fork()类似,它将生成子进程,但是每个这些会克隆您当前的存储空间。

  2. 如果您的数据具有 some 结构(如已排序),则可能会更聪明,并获得亚线性算法的复杂性。例如,如果您需要从大型的,已排序的数组中删除相对较少的项目,则可以对要删除的每个项目执行bin搜索。这将以O(m log n)的时间运行,其中m是要删除的项目数,n是大型数组的大小。如果m相对较小(与n相比),则说明您在经商,因为您将接近O(log n)。处理这种特殊情况的方法甚至更多,但是我选择这种方法是因为它很容易解释。如果您对数据的分布情况一无所知,那么您可能会比线性时间做得更好。

HTH。

答案 1 :(得分:1)

更快的实现方式

这是一个编译版本,使用@Matt Messersmith的集合作为列表理解解决方案。它基本上是对较慢的np.isin方法的替代。在index_to_remove是标量值的情况下,我遇到了一些问题,并为此情况实现了单独的版本。

代码

import numpy as np
import numba as nb

@nb.njit(parallel=True)
def in1d_vec_nb(matrix, index_to_remove):
  #matrix and index_to_remove have to be numpy arrays
  #if index_to_remove is a list with different dtypes this 
  #function will fail

  out=np.empty(matrix.shape[0],dtype=nb.boolean)
  index_to_remove_set=set(index_to_remove)

  for i in nb.prange(matrix.shape[0]):
    if matrix[i] in index_to_remove_set:
      out[i]=False
    else:
      out[i]=True

  return out

@nb.njit(parallel=True)
def in1d_scal_nb(matrix, index_to_remove):
  #matrix and index_to_remove have to be numpy arrays
  #if index_to_remove is a list with different dtypes this 
  #function will fail

  out=np.empty(matrix.shape[0],dtype=nb.boolean)
  for i in nb.prange(matrix.shape[0]):
    if (matrix[i] == index_to_remove):
      out[i]=False
    else:
      out[i]=True

  return out


def isin_nb(matrix_in, index_to_remove):
  #both matrix_in and index_to_remove have to be a np.ndarray
  #even if index_to_remove is actually a single number
  shape=matrix_in.shape
  if index_to_remove.shape==():
    res=in1d_scal_nb(matrix_in.reshape(-1),index_to_remove.take(0))
  else:
    res=in1d_vec_nb(matrix_in.reshape(-1),index_to_remove)

  return res.reshape(shape)

示例

data = np.array([[80,1,12],[160,2,12],[240,3,12],[80,4,11]])
test_elts= np.array((80))

data[isin_nb(data[:,0],test_elts),:]

Tings

test_elts = np.arange(12345)
data=np.arange(1000*1000)

#The first call has compilation overhead of about 300ms
#which is not included in the timings
#remove_from_result:     52ms
#isin_nb:                1.59ms