Python 等效于 MATLAB 的“ismember”函数

Posted

技术标签:

【中文标题】Python 等效于 MATLAB 的“ismember”函数【英文标题】:Python equivalent of MATLAB's "ismember" function 【发布时间】:2013-03-29 15:33:46 【问题描述】:

经过多次尝试优化代码后,最后一个资源似乎是尝试使用多个内核运行下面的代码。我不确切知道如何转换/重新构造我的代码,以便它可以使用多个内核运行得更快。如果我能得到指导以实现最终目标,我将不胜感激。最终目标是能够尽可能快地为数组 A 和 B 运行此代码,其中每个数组包含大约 700,000 个元素。这是使用小数组的代码。 700k 元素数组被注释掉了。

import numpy as np

def ismember(a,b):
    for i in a:
        index = np.where(b==i)[0]
        if index.size == 0:
            yield 0
        else:
            yield index


def f(A, gen_obj):
    my_array = np.arange(len(A))
    for i in my_array:
        my_array[i] = gen_obj.next()
    return my_array


#A = np.arange(700000)
#B = np.arange(700000)
A = np.array([3,4,4,3,6])
B = np.array([2,5,2,6,3])

gen_obj = ismember(A,B)

f(A, gen_obj)

print 'done'
# if we print f(A, gen_obj) the output will be: [4 0 0 4 3]
# notice that the output array needs to be kept the same size as array A.

我想做的是模仿一个名为ismember[2] 的MATLAB函数(格式为:[Lia,Locb] = ismember(A,B)。我只是想得到Locb部分而已。

来自 Matlab:Locb,对于 A 中属于 B 的每个值,包含 B 中的最低索引。输出数组 Locb 包含 0,只要 A 不是 B 的成员

主要问题之一是我需要能够尽可能高效地执行此操作。为了测试,我有两个 700k 元素的数组。创建一个生成器并检查生成器的值似乎并不能快速完成工作。

【问题讨论】:

【参考方案1】:

在担心多核之前,我会通过使用字典来消除您的 ismember 函数中的线性扫描:

def ismember(a, b):
    bind = 
    for i, elt in enumerate(b):
        if elt not in bind:
            bind[elt] = i
    return [bind.get(itm, None) for itm in a]  # None can be replaced by any other "not in b" value

您的原始实现需要针对 A 中的每个元素对 B 中的元素进行全面扫描,使其成为 O(len(A)*len(B))。上面的代码需要对 B 进行一次完整扫描才能生成 dict Bset。通过使用 dict,您可以有效地将 B 中的每个元素查找为 A 的每个元素的常量,从而使操作 O(len(A)+len(B))。如果这仍然太慢,那就担心让上述函数在多核上运行。

编辑:我还稍微修改了您的索引。 Matlab 使用 0 是因为它的所有数组都从索引 1 开始。Python/numpy 从 0 开始数组,所以如果你的数据集看起来像这样

A = [2378, 2378, 2378, 2378]
B = [2378, 2379]

如果没有元素返回 0,那么你的结果将排除 A 的所有元素。上面的例程返回 None 没有索引而不是 0。返回 -1 是一个选项,但 Python 会将其解释为数组中的最后一个元素。 None 如果用作数组的索引,将引发异常。如果您想要不同的行为,请将Bind.get(item,None) 表达式中的第二个参数更改为您想要返回的值。

【讨论】:

哇,这速度真快!你不知道我多么欣赏你的解决方案。非常感谢 !您是否使用特定工具来输出性能配置文件? @z5151 不,这是简单的算法分析。使用Big-O notation:np.where 必须对B 进行线性扫描,这需要O(len(B)) 操作。然后,您使用需要 O(len(A)) 操作的外部循环,使您的原始算法大致为 O(len(A)*len(B)) 操作。生成Bind 需要len(B) 操作。字典实现为hash tables,有常量O(1)查找,所以扫描A为O(len(A));整体复杂度为O(len(A)+len(B)) 知道了。感谢您的***参考。 @EOL 不,你破坏了密码。返回的元素现在是列表中的最后一个,而不是第一个。我没有在原始代码中使用字典理解是有原因的。 @EOL 据我所知,您可以通过遍历反向范围来使用字典理解: B[i] : i for i in xrange(len(B)-1,-1,-1) 。或者使用反向迭代器: elt : len(B)-i-1) for (i,elt) in enumerate(reversed(B)) 两者都不漂亮(或简单)。第一个假设 B 是可转位的,第二个假设 B 是可逆的。他们还假设随机索引/反向迭代很便宜。如果 B 是一个非常大的链表,那么你的表现将会很糟糕。有没有一种使用推导式来检索仅假设迭代的第一个索引的方法?【参考方案2】:

sfstewman 的出色回答很可能为您解决了问题。

我想补充一下如何在 numpy 中实现同样的功能。

我使用了 numpy 的 unique 和 in1d 函数。

B_unique_sorted, B_idx = np.unique(B, return_index=True)
B_in_A_bool = np.in1d(B_unique_sorted, A, assume_unique=True)
B_unique_sorted 包含已排序的 B 中的唯一值。 B_idx 为这些值保留原始 B 的索引。 B_in_A_bool 是一个布尔数组,大小为 B_unique_sorted 存储 B_unique_sorted 中的值是否在 A 中。注意:我需要在 A 中查找 (来自 B 的唯一 val),因为我需要要返回的输出与 B_idx 相关注意:我假设 A 已经是唯一的。

现在您可以使用 B_in_A_bool 来获取常用 vals

B_unique_sorted[B_in_A_bool]

以及它们各自在原始B中的索引

B_idx[B_in_A_bool]

最后,我假设这比纯 Python for 循环要快得多,尽管我没有测试它。

【讨论】:

+1 尽可能使用 numpy,可以通过这种方式实现主要的加速(因为我已经学会了艰难的方式>_ 当心!这不会保留索引的顺序!尝试使用 range(1,6) 和 [5,1]。如果不需要索引的顺序,我认为您可以使用 np.in1d() 然后使用 np.nonzero()[0] 在此处查看答案:***.com/questions/33678543/… 以正确顺序获取索引【参考方案3】:

试试ismember 库。

pip install ismember

简单示例:

# Import library
from ismember import ismember
import numpy as np

# data
A = np.array([3,4,4,3,6])
B = np.array([2,5,2,6,3])

# Lookup
Iloc,idx = ismember(A, B)
 
# Iloc is boolean defining existence of d in d_unique
print(Iloc)
# [ True False False  True  True]

# indexes of d_unique that exists in d
print(idx)
# [4 4 3]

print(B[idx])
# [3 3 6]

print(A[Iloc])
# [3 3 6]

# These vectors will match
A[Iloc]==B[idx]

速度检查:

from ismember import ismember
from datetime import datetime

t1=[]
t2=[]
# Create some random vectors
ns = np.random.randint(10,10000,1000)

for n in ns:
    a_vec = np.random.randint(0,100,n)
    b_vec = np.random.randint(0,100,n)

    # Run stack version
    start = datetime.now()
    out1=ismember_stack(a_vec, b_vec)
    end = datetime.now()
    t1.append(end - start)

    # Run ismember
    start = datetime.now()
    out2=ismember(a_vec, b_vec)
    end = datetime.now()
    t2.append(end - start)


print(np.sum(t1))
# 0:00:07.778331

print(np.sum(t2))
# 0:00:04.609801

# %%
def ismember_stack(a, b):
    bind = 
    for i, elt in enumerate(b):
        if elt not in bind:
            bind[elt] = i
    return [bind.get(itm, None) for itm in a]  # None can be replaced by any other "not in b" value

pypi 的 ismember 函数快了近 2 倍。

大向量,例如 700000 个元素:

from ismember import ismember
from datetime import datetime

A = np.random.randint(0,100,700000)
B = np.random.randint(0,100,700000)

# Lookup
start = datetime.now()
Iloc,idx = ismember(A, B)
end = datetime.now()

# Print time
print(end-start)
# 0:00:01.194801

【讨论】:

@EOL 你觉得这个解决方案怎么样?【参考方案4】:

尝试使用列表推导;

In [1]: import numpy as np

In [2]: A = np.array([3,4,4,3,6])

In [3]: B = np.array([2,5,2,6,3])

In [4]: [x for x in A if not x in B]
Out[4]: [4, 4]

通常,列表推导比 for 循环快得多。

获得等长列表;

In [19]: map(lambda x: x if x not in B else False, A)
Out[19]: [False, 4, 4, False, False]

这对于小型数据集来说相当快:

In [20]: C = np.arange(10000)

In [21]: D = np.arange(15000, 25000)

In [22]: %timeit map(lambda x: x if x not in D else False, C)
1 loops, best of 3: 756 ms per loop

对于大型数据集,您可以尝试使用multiprocessing.Pool.map() 来加快操作速度。

【讨论】:

输出数组需要保持相同大小。 @z5151:查看增强的答案。如果需要,您可以将 lambda 表达式更改为返回 0 而不是 False,但这会掩盖结果中的真实 0。 这对于元素数量很少的数组很有用。感谢您强调列表推导比循环快得多。 您的答案返回的是元素,而不是 B 中元素的索引。【参考方案5】:

这是返回与 MATLAB 匹配的输出参数 [Lia, Locb] 的确切 MATLAB 等效项,但 Python 0 也是有效索引。因此,此函数不返回 0。它本质上返回 Locb(Locb>0)。性能也与MATLAB相当。

def ismember(a_vec, b_vec):
    """ MATLAB equivalent ismember function """

    bool_ind = np.isin(a_vec,b_vec)
    common = a[bool_ind]
    common_unique, common_inv  = np.unique(common, return_inverse=True)     # common = common_unique[common_inv]
    b_unique, b_ind = np.unique(b_vec, return_index=True)  # b_unique = b_vec[b_ind]
    common_ind = b_ind[np.isin(b_unique, common_unique, assume_unique=True)]
    return bool_ind, common_ind[common_inv]

这里是一个稍慢(~5x)但不使用独特功能的替代实现:

def ismember(a_vec, b_vec):
    ''' MATLAB equivalent ismember function. Slower than above implementation'''
    b_dict = b_vec[i]: i for i in range(0, len(b_vec))
    indices = [b_dict.get(x) for x in a_vec if b_dict.get(x) is not None]
    booleans = np.in1d(a_vec, b_vec)
    return booleans, np.array(indices, dtype=int)

【讨论】:

以上是关于Python 等效于 MATLAB 的“ismember”函数的主要内容,如果未能解决你的问题,请参考以下文章

Matlab .Fig 等效于 R

python中Matlab的等效命令'end'是啥? [复制]

matlab中是不是有等效的python函数id()?

Python中matlab imfilter的等效函数

支持 C/C++ 代码生成 (MATLAB) 的函数等效于“bwareaopen”和“bwconhull”

来自逐元素逻辑比较的 MATLAB 逻辑矩阵的 Python 等效项