首页 文章

在numpy数组中查找模式的最有效方法

提问于
浏览
36

我有一个包含整数的2D数组(正数或负数) . 每行表示特定空间站点随时间的值,而每列表示给定时间内各种空间站点的值 .

所以,如果数组如下:

1 3 4 2 2 7
5 2 2 1 4 1
3 3 2 2 1 1

结果应该是

1 3 2 2 2 1

请注意,当模式有多个值时,任何一个(随机选择)都可以设置为模式 .

我可以一次迭代查找模式的列,但我希望numpy可能有一些内置函数来做到这一点 . 或者,如果有一个技巧可以有效地找到它而不循环 .

4 回答

  • 4

    检查scipy.stats.mode()(灵感来自@ tom10的评论):

    import numpy as np
    from scipy import stats
    
    a = np.array([[1, 3, 4, 2, 2, 7],
                  [5, 2, 2, 1, 4, 1],
                  [3, 3, 2, 2, 1, 1]])
    
    m = stats.mode(a)
    print(m)
    

    输出:

    ModeResult(mode=array([[1, 3, 2, 2, 1, 1]]), count=array([[1, 2, 2, 2, 1, 2]]))
    

    如您所见,它既返回模式又返回计数 . 您可以直接通过 m[0] 选择模式:

    print(m[0])
    

    输出:

    [[1 3 2 2 1 1]]
    
  • 63

    这是一个棘手的问题,因为沿着轴计算模式并不多 . 解决方案对于1-D阵列是直接的,其中 numpy.bincount 是方便的, numpy.uniquereturn_counts arg为 True . 我看到的最常见的n维函数是scipy.stats.mode,虽然它非常慢 - 特别是对于具有许多唯一值的大型数组 . 作为一个解决方案,我开发了这个功能,并大量使用它:

    import numpy
    
    def mode(ndarray, axis=0):
        # Check inputs
        ndarray = numpy.asarray(ndarray)
        ndim = ndarray.ndim
        if ndarray.size == 1:
            return (ndarray[0], 1)
        elif ndarray.size == 0:
            raise Exception('Cannot compute mode on empty array')
        try:
            axis = range(ndarray.ndim)[axis]
        except:
            raise Exception('Axis "{}" incompatible with the {}-dimension array'.format(axis, ndim))
    
        # If array is 1-D and numpy version is > 1.9 numpy.unique will suffice
        if all([ndim == 1,
                int(numpy.__version__.split('.')[0]) >= 1,
                int(numpy.__version__.split('.')[1]) >= 9]):
            modals, counts = numpy.unique(ndarray, return_counts=True)
            index = numpy.argmax(counts)
            return modals[index], counts[index]
    
        # Sort array
        sort = numpy.sort(ndarray, axis=axis)
        # Create array to transpose along the axis and get padding shape
        transpose = numpy.roll(numpy.arange(ndim)[::-1], axis)
        shape = list(sort.shape)
        shape[axis] = 1
        # Create a boolean array along strides of unique values
        strides = numpy.concatenate([numpy.zeros(shape=shape, dtype='bool'),
                                     numpy.diff(sort, axis=axis) == 0,
                                     numpy.zeros(shape=shape, dtype='bool')],
                                    axis=axis).transpose(transpose).ravel()
        # Count the stride lengths
        counts = numpy.cumsum(strides)
        counts[~strides] = numpy.concatenate([[0], numpy.diff(counts[~strides])])
        counts[strides] = 0
        # Get shape of padded counts and slice to return to the original shape
        shape = numpy.array(sort.shape)
        shape[axis] += 1
        shape = shape[transpose]
        slices = [slice(None)] * ndim
        slices[axis] = slice(1, None)
        # Reshape and compute final counts
        counts = counts.reshape(shape).transpose(transpose)[slices] + 1
    
        # Find maximum counts and return modals/counts
        slices = [slice(None, i) for i in sort.shape]
        del slices[axis]
        index = numpy.ogrid[slices]
        index.insert(axis, numpy.argmax(counts, axis=axis))
        return sort[index], counts[index]
    

    结果:

    In [2]: a = numpy.array([[1, 3, 4, 2, 2, 7],
                             [5, 2, 2, 1, 4, 1],
                             [3, 3, 2, 2, 1, 1]])
    
    In [3]: mode(a)
    Out[3]: (array([1, 3, 2, 2, 1, 1]), array([1, 2, 2, 2, 1, 2]))
    

    一些基准:

    In [4]: import scipy.stats
    
    In [5]: a = numpy.random.randint(1,10,(1000,1000))
    
    In [6]: %timeit scipy.stats.mode(a)
    10 loops, best of 3: 41.6 ms per loop
    
    In [7]: %timeit mode(a)
    10 loops, best of 3: 46.7 ms per loop
    
    In [8]: a = numpy.random.randint(1,500,(1000,1000))
    
    In [9]: %timeit scipy.stats.mode(a)
    1 loops, best of 3: 1.01 s per loop
    
    In [10]: %timeit mode(a)
    10 loops, best of 3: 80 ms per loop
    
    In [11]: a = numpy.random.random((200,200))
    
    In [12]: %timeit scipy.stats.mode(a)
    1 loops, best of 3: 3.26 s per loop
    
    In [13]: %timeit mode(a)
    1000 loops, best of 3: 1.75 ms per loop
    

    编辑:提供更多的背景和修改方法,以提高内存效率

  • 15

    扩展this method,应用于查找数据模式,您可能需要实际数组的索引,以查看该值与分布中心的距离 .

    (_, idx, counts) = np.unique(a, return_index=True, return_counts=True)
    index = idx[np.argmax(counts)]
    mode = a[index]
    

    记得在len(np.argmax(计数))> 1时丢弃该模式,同时为了验证它是否实际代表您的数据的中心分布,您可以检查它是否在标准偏差区间内 .

  • 2

    我认为一种非常简单的方法是使用Counter类 . 然后,您可以使用Counter实例的most_common()函数,如here所述 .

    对于一维数组:

    import numpy as np
    from collections import Counter
    
    nparr = np.arange(10) 
    nparr[2] = 6 
    nparr[3] = 6 #6 is now the mode
    mode = Counter(nparr).most_common(1)
    # mode will be [(6,3)] to give the count of the most occurring value, so ->
    print(mode[0][0])
    

    对于多维数组(差别不大):

    import numpy as np
    from collections import Counter
    
    nparr = np.arange(10) 
    nparr[2] = 6 
    nparr[3] = 6 
    nparr = nparr.reshape((10,2,5))     #same thing but we add this to reshape into ndarray
    mode = Counter(nparr.flatten()).most_common(1)  # just use .flatten() method
    
    # mode will be [(6,3)] to give the count of the most occurring value, so ->
    print(mode[0][0])
    

    这可能是也可能不是有效的实现,但它很方便 .

相关问题