【问题标题】:check if numpy array is subset of another array检查numpy数组是否是另一个数组的子集
【发布时间】:2013-05-14 16:43:28
【问题描述】:

已经在 SO 上提出了类似的问题,但它们有更具体的限制,并且它们的答案不适用于我的问题。

一般来说,确定任意 numpy 数组是否是另一个数组的子集的最 Pythonic 方法是什么?更具体地说,我有一个大约 20000x3 的数组,我需要知道完全包含在一个集合中的 1x3 元素的索引。更一般地说,是否有更 Pythonic 的方式来编写以下内容:

master = [12, 155, 179, 234, 670, 981, 1054, 1209, 1526, 1667, 1853]  # some indices of interest
triangles = np.random.randint(2000, size=(20000, 3))  # some data

for i, x in enumerate(triangles):
    if x[0] in master and x[1] in master and x[2] in master:
        print i

对于我的用例,我可以安全地假设 len(master)

【问题讨论】:

    标签: python numpy set


    【解决方案1】:

    您可以通过在列表理解中遍历数组来轻松完成此操作。一个玩具示例如下:

    import numpy as np
    x = np.arange(30).reshape(10,3)
    searchKey = [4,5,8]
    x[[0,3,7],:] = searchKey
    x
    

    给予

     array([[ 4,  5,  8],
            [ 3,  4,  5],
            [ 6,  7,  8],
            [ 4,  5,  8],
            [12, 13, 14],
            [15, 16, 17],
            [18, 19, 20],
            [ 4,  5,  8],
            [24, 25, 26],
            [27, 28, 29]])
    

    现在遍历元素:

    ismember = [row==searchKey for row in x.tolist()]
    

    结果是

    [True, False, False, True, False, False, False, True, False, False]
    

    您可以将其修改为您的问题中的子集:

    searchKey = [2,4,10,5,8,9]  # Add more elements for testing
    setSearchKey = set(searchKey)
    ismember = [setSearchKey.issuperset(row) for row in x.tolist()]
    

    如果您需要索引,请使用

    np.where(ismember)[0]
    

    它给了

    array([0, 3, 7])
    

    【讨论】:

    • 谢谢你的回答——你的列表理解比我的 for 循环更 Pythonic——但你的回答没有解决问题的子集部分。 row in searchKey 不返回 row 是否是 searchKey 的子集。在这个例子中,它总是返回一个 False 数组。
    • 我已经更新了您问题的答案。更新版本考虑了集合。
    • 这个想法总体上是好的,但你应该去掉列表理解。类似set_searchKey = set(searchKey); [set_searchKey.issuperset(row) for row in x] 的东西,这样你就不会在每次迭代时将 searchkey 转换为一个集合。另请注意,x 不需要转换为列表。
    • @BiRico 我已经进行了相应的修改。非常感谢。
    • 这很奇怪,你应该能够像任何其他容器一样迭代一个 numpy 数组。你使用的是什么版本的 python/numpy。
    【解决方案2】:

    您可以尝试以下两种方法:

    1,使用集合。集合的实现很像 python 字典,并且具有恒定的时间查找。这看起来很像您已经拥有的代码,只需从 master 创建一个集合:

    master = [12,155,179,234,670,981,1054,1209,1526,1667,1853]
    master_set = set(master)
    triangles = np.random.randint(2000,size=(20000,3)) #some data
    for i, x in enumerate(triangles):
      if master_set.issuperset(x):
        print i
    

    2、使用搜索排序。这很好,因为它不需要您使用可散列类型并使用 numpy 内置函数。 searchsorted 在 master 的大小中是 log(N),在 triangels 的大小中是 O(N),所以它也应该非常快,可能更快,具体取决于数组的大小等。

    master = [12,155,179,234,670,981,1054,1209,1526,1667,1853]
    master = np.asarray(master)
    triangles = np.random.randint(2000,size=(20000,3)) #some data
    idx = master.searchsorted(triangles)
    idx.clip(max=len(master) - 1, out=idx)
    print np.where(np.all(triangles == master[idx], axis=1))
    

    第二种情况假设 master 已排序,正如 searchsorted 所暗示的那样。

    【讨论】:

    • searchsorted 在这种情况下没有帮助,因为 searchsorted 实际上将元素插入到列表中的正确位置,无论它们实际上是否在master 中。也就是说,虚假条目 [11,154,178] 将返回与感兴趣条目 [12,155,179] 相同的内容。实际上,您的代码甚至没有走那么远,它崩溃了,因为 1853 和 2000 之间的插入超过了 master 的大小。
    • 最后一行处理虚拟插入,但你是对的,你需要剪辑来处理大小问题。我在剪辑中添加了一条线。
    【解决方案3】:

    对于 numpy 中的集合操作,一个更自然(可能更快)的解决方案是使用 numpy.lib.arraysetops 中的函数。这些通常允许您避免在 Python 的 set 类型之间来回转换。要检查一个数组是否是另一个数组的子集,请使用numpy.setdiff1d() 并测试返回的数组的长度是否为 0:

    import numpy as np
    a = np.arange(10)
    b = np.array([1, 5, 9])
    c = np.array([-5, 5, 9])
    # is `a` a subset of `b`?
    len(np.setdiff1d(a, b)) == 0 # gives False
    # is `b` a subset of `a`?
    len(np.setdiff1d(b, a)) == 0 # gives True
    # is `c` a subset of `a`?
    len(np.setdiff1d(c, a)) == 0 # gives False
    

    您还可以选择设置assume_unique=True 以获得潜在的速度提升。

    我实际上有点惊讶numpy 没有类似内置的issubset() 函数来执行上述操作(类似于set.issubset())。

    另一种选择是使用numpy.in1d()(参见https://stackoverflow.com/a/37262010/2020363

    编辑:我刚刚意识到在遥远的过去的某个时候,这让我很困扰,以至于我编写了自己的简单函数:

    def issubset(a, b):
        """Return whether sequence `a` is a subset of sequence `b`"""
        return len(np.setdiff1d(a, b)) == 0
    

    【讨论】:

      【解决方案4】:

      也可以使用np.isin,这可能比@petrichor's answer 中的列表理解更有效。使用相同的设置:

      import numpy as np
      
      x = np.arange(30).reshape(10, 3)
      searchKey = [4, 5, 8]
      x[[0, 3, 7], :] = searchKey
      array([[ 4,  5,  8],
             [ 3,  4,  5],
             [ 6,  7,  8],
             [ 4,  5,  8],
             [12, 13, 14],
             [15, 16, 17],
             [18, 19, 20],
             [ 4,  5,  8],
             [24, 25, 26],
             [27, 28, 29]])
      

      现在可以使用np.isin;默认情况下,它将按元素工作:

      np.isin(x, searchKey)
      array([[ True,  True,  True],
             [False,  True,  True],
             [False, False,  True],
             [ True,  True,  True],
             [False, False, False],
             [False, False, False],
             [False, False, False],
             [ True,  True,  True],
             [False, False, False],
             [False, False, False]])
      

      我们现在必须过滤所有条目评估为True 的行,我们可以使用all

      np.isin(x, searchKey).all(1)
      array([ True, False, False,  True, False, False, False,  True, False,
             False])
      

      如果现在想要相应的索引,可以使用np.where

      np.where(np.isin(x, searchKey).all(1))
      (array([0, 3, 7]),)
      

      【讨论】:

        【解决方案5】:

        开始于:

        master=[12,155,179,234,670,981,1054,1209,1526,1667,1853] #some indices of interest

        triangles=np.random.randint(2000,size=(20000,3)) #some data

        查找包含在 master 中的三元组索引的最 Pythonic 方法是什么?尝试将np.in1d 与列表理解一起使用:

        inds = [j for j in range(len(triangles)) if all(np.in1d(triangles[j], master))]
        

        %timeit 表示 ~0.5 秒 = 半秒

        --> 更快的方式(1000 倍!)避免 python 的慢循环?尝试使用np.isinnp.sum 来获得np.arange 的布尔掩码:

        inds = np.where(
         np.sum(np.isin(triangles, master), axis=-1) == triangles.shape[-1])
        

        %timeit 表示 ~0.0005 秒 = 半毫秒!

        建议:尽可能避免循环遍历列表,因为以与包含一个算术运算的 python 循环的单次迭代相同的价格,您可以调用一个执行数千个相同算术运算的 numpy 函数

        结论

        看来np.isin(arr1=triangles, arr2=master)是你要找的函数,它给出了一个与arr1形状相同的布尔掩码,告诉arr1的每个元素是否也是arr2的一个元素;从这里开始,要求掩码行的总和为 3(即三角形中一行的全长)为所需的三角形行(或索引,使用np.arange)提供一维掩码。

        【讨论】:

          猜你喜欢
          • 2010-09-24
          • 2019-11-12
          • 2012-05-20
          • 1970-01-01
          • 2016-12-13
          • 2021-07-10
          • 1970-01-01
          • 2020-03-10
          相关资源
          最近更新 更多