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

Nik*_*Nik 61 python 2d numpy mode

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

所以,如果数组如下:

1 3 4 2 2 7
5 2 2 1 4 1
3 3 2 2 1 1
Run Code Online (Sandbox Code Playgroud)

结果应该是

1 3 2 2 2 1
Run Code Online (Sandbox Code Playgroud)

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

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

fgb*_*fgb 88

检查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)
Run Code Online (Sandbox Code Playgroud)

输出:

ModeResult(mode=array([[1, 3, 2, 2, 1, 1]]), count=array([[1, 2, 2, 2, 1, 2]]))
Run Code Online (Sandbox Code Playgroud)

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

print(m[0])
Run Code Online (Sandbox Code Playgroud)

输出:

[[1 3 2 2 1 1]]
Run Code Online (Sandbox Code Playgroud)

  • 请注意,对于将来看这个的人来说:你需要明确地"导入scipy.stats",当你只是执行`import scipy`时它不包括在内. (9认同)
  • numpy本身不支持任何这样的功能? (4认同)
  • @Rahul:您必须考虑默认的第二个参数“ axis = 0”。上面的代码报告输入每一列的模式。该计数告诉我们在每一列中已经看到报告模式多少次了。如果需要整体模式,则需要指定“ axis = None”。有关更多信息,请参阅https://docs.scipy.org/doc/scipy/reference/generated/scipy.stats.mode.html (2认同)

Dev*_*rns 20

这是一个棘手的问题,因为沿着轴计算模式并不多.对于1-D阵列来说,解决方案是直截了当的,在这里scipy.stats.mode可以很方便地numpy.bincount使用numpy.uniquearg as return_counts.我看到的最常见的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]
Run Code Online (Sandbox Code Playgroud)

结果:

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]))
Run Code Online (Sandbox Code Playgroud)

一些基准:

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
Run Code Online (Sandbox Code Playgroud)

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

  • 对于大 int ndarray 的高维问题,您的解决方案似乎仍然比 scipy.stats.mode 快得多。我必须沿着 4x250x250x500 ndarray 的第一个轴计算众数,而你的函数花了 10 秒,而 scipy.stats.mode 花了近 600 秒。 (2认同)

Def*_*_Os 13

一个使用numpy(不是scipy也不是Counter类)的简洁解决方案:

A = np.array([[1,3,4,2,2,7], [5,2,2,1,4,1], [3,3,2,2,1,1]])

np.apply_along_axis(lambda x: np.bincount(x).argmax(), axis=0, arr=A)
Run Code Online (Sandbox Code Playgroud)

数组([1, 3, 2, 2, 1, 1])

  • 简洁明了,但如果原始数组包含非常大的数字,则应谨慎使用,因为 bincount 将为每个原始数组 A[i] 创建带有 len( max(A[i]) ) 的 bin 数组。 (2认同)

小智 13

如果您只想使用 numpy:

x = [-1, 2, 1, 3, 3]
vals,counts = np.unique(x, return_counts=True)
Run Code Online (Sandbox Code Playgroud)

(array([-1,  1,  2,  3]), array([1, 1, 1, 2]))
Run Code Online (Sandbox Code Playgroud)

并提取它:

index = np.argmax(counts)
return vals[index]
Run Code Online (Sandbox Code Playgroud)

  • 喜欢这个方法,因为它不仅支持整数,还支持浮点数甚至字符串! (2认同)

Ash*_*ngh 11

Python 中获取列表或数组 a 的模式的最简单方法

import statistics
a=[7,4,4,4,4,25,25,6,7,4867,5,6,56,52,32,44,4,4,44,4,44,4]
print(f"{statistics.mode(a)} is the mode (most frequently occurring number)")
Run Code Online (Sandbox Code Playgroud)

就是这样

  • 谢谢。为什么这不是最佳答案? (2认同)

小智 8

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

(_, idx, counts) = np.unique(a, return_index=True, return_counts=True)
index = idx[np.argmax(counts)]
mode = a[index]
Run Code Online (Sandbox Code Playgroud)

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