在numpy向量中找到最频繁的数字


123

假设我在python中有以下列表:

a = [1,2,3,1,2,1,1,1,3,2,2,1]

如何以一种简洁的方式在此列表中找到最频繁的号码?

Answers:


192

如果您的列表包含所有非负整数,则应查看numpy.bincounts:

http://docs.scipy.org/doc/numpy/reference/generated/numpy.bincount.html

然后可能使用np.argmax:

a = np.array([1,2,3,1,2,1,1,1,3,2,2,1])
counts = np.bincount(a)
print np.argmax(counts)

对于更复杂的列表(可能包含负数或非整数值),可以np.histogram类似的方式使用。另外,如果您只想在python中工作而不使用numpy,collections.Counter则是处理此类数据的一种好方法。

from collections import Counter
a = [1,2,3,1,2,1,1,1,3,2,2,1]
b = Counter(a)
print b.most_common(1)

58
+1。可能只是np.bincount([1, 2, 3, 1, 2, 1, 1, 1, 3, 2, 2, 1]).argmax()
Nikolai Fetissov 2011年

1
+1。scipy.stats.mode尽管不那么普遍,但这至少比它快一个数量级。
Fred Foo

好答案!但是,如果有人使用python 2.6,collections.Counter将不可用。在这种情况下,请参阅下面的答案。
JJC

19
对于2016年之后访问我们的人们:我不喜欢这个答案,因为bincount(arr)返回的数组与arr中最大的元素一样大,因此具有大范围的小数组将创建一个过大的数组。尽管我认为numpy.unique()并不存在于2011年(创建此答案时),但下面的Apoengtus答案要好得多。
Wehrdo '16

2
Python 3Counter(array).most_common(1)[0][0]
diralik

80

您可以使用

(values,counts) = np.unique(a,return_counts=True)
ind=np.argmax(counts)
print values[ind]  # prints the most frequent element

如果某个元素与另一个元素一样频繁,则此代码将仅返回第一个元素。


4
我发现这是最有用的,因为它是通用的,简短的,并允许通过某些派生索引从值或计数中提取元素。
ryanjdillon

2
如果我们有多个最频繁的值,values[counts.argmax()]将返回第一个值。要获得所有这些,我们可以使用values[counts == counts.max()]
W. Zhu

44

如果您愿意使用SciPy

>>> from scipy.stats import mode
>>> mode([1,2,3,1,2,1,1,1,3,2,2,1])
(array([ 1.]), array([ 6.]))
>>> most_frequent = mode([1,2,3,1,2,1,1,1,3,2,2,1])[0][0]
>>> most_frequent
1.0

30

在此处找到一些解决方案的性能(使用iPython):

>>> # small array
>>> a = [12,3,65,33,12,3,123,888000]
>>> 
>>> import collections
>>> collections.Counter(a).most_common()[0][0]
3
>>> %timeit collections.Counter(a).most_common()[0][0]
100000 loops, best of 3: 11.3 µs per loop
>>> 
>>> import numpy
>>> numpy.bincount(a).argmax()
3
>>> %timeit numpy.bincount(a).argmax()
100 loops, best of 3: 2.84 ms per loop
>>> 
>>> import scipy.stats
>>> scipy.stats.mode(a)[0][0]
3.0
>>> %timeit scipy.stats.mode(a)[0][0]
10000 loops, best of 3: 172 µs per loop
>>> 
>>> from collections import defaultdict
>>> def jjc(l):
...     d = defaultdict(int)
...     for i in a:
...         d[i] += 1
...     return sorted(d.iteritems(), key=lambda x: x[1], reverse=True)[0]
... 
>>> jjc(a)[0]
3
>>> %timeit jjc(a)[0]
100000 loops, best of 3: 5.58 µs per loop
>>> 
>>> max(map(lambda val: (a.count(val), val), set(a)))[1]
12
>>> %timeit max(map(lambda val: (a.count(val), val), set(a)))[1]
100000 loops, best of 3: 4.11 µs per loop
>>> 

对于像这样的小型阵列,最好是“最大”和“设置” 。

根据@David Sanders的说法,如果将数组大小增加到100,000个元素,则“最大w / set”算法最终将是最差的,而“ numpy bincount”方法是最佳的。


1
@IuliusCurt为了指出最好的方法,我们需要针对多种情况进行测试:小数组,大数组,随机数组,现实世界数组(如timsort进行排序),...但是我同意
iuridiniz

3
就像您的方法一样,仅使用较小的数组将无法很好地区分不同的算法。
大卫·桑德斯

10
如果将测试列表的大小增加到100000(a = (np.random.rand(100000) * 1000).round().astype('int'); a_list = list(a)),则“最大w /设置”算法最终将成为最差的算法,而“ numpy bincount”方法则是最佳方法。我使用a_list本机python代码和anumpy代码进行了此测试,以避免编组成本导致结果变差。
大卫·桑德斯

4

另外,如果您想获得最频繁的值(正数或负数)而不加载任何模块,则可以使用以下代码:

lVals = [1,2,3,1,2,1,1,1,3,2,2,1]
print max(map(lambda val: (lVals.count(val), val), set(lVals)))

1
这是从前一阵子开始的,但为了后代:这等效于更易于阅读的max(set(lVals), key=lVals.count),它对的每个唯一元素的O(n)计数lVals大约为O(n ^ 2)(假设O(n)唯一)元素)。collections.Counter(lVals).most_common(1)[0][0]JoshAdel建议,从标准库使用仅为O(n)。
Dougal 2012年

3

虽然上面的大多数答案很有用,但在以下情况下您可能会:1)需要它来支持非正整数值(例如浮点数或负整数;-)),以及2)不在Python 2.7上(哪个collections.Counter) 3)不想在代码中添加scipy(甚至numpy)的依赖项,那么纯Python 2.6解决方案就是O(nlogn)(即有效),它就是这样:

from collections import defaultdict

a = [1,2,3,1,2,1,1,1,3,2,2,1]

d = defaultdict(int)
for i in a:
  d[i] += 1
most_frequent = sorted(d.iteritems(), key=lambda x: x[1], reverse=True)[0]

2

我喜欢JoshAdel的解决方案。

但是只有一个收获。

np.bincount()解决方案仅适用于数字。

如果你有琴弦 collections.Counter解决方案将为您服务。


1

扩展此方法,适用于查找数据模式,在该模式下可能需要实际数组的索引才能查看该值与分布中心的距离。

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

记住当len(np.argmax(counts))> 1时放弃该模式



1

从开始Python 3.4,标准库包含statistics.mode返回单个最常见数据点的功能。

from statistics import mode

mode([1, 2, 3, 1, 2, 1, 1, 1, 3, 2, 2, 1])
# 1

如果存在多个具有相同频率的模式,则statistics.mode返回遇到的第一个模式。


从开始于Python 3.8,该statistics.multimode函数将按最先出现的顺序返回最频繁出现的值的列表:

from statistics import multimode

multimode([1, 2, 3, 1, 2])
# [1, 2]

0

这是一个纯解决方案,可以使用纯粹的numpy沿轴应用而不管其值如何。我还发现,如果有很多唯一值,这比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]

-1

我最近正在做一个项目,并使用collections.Counter。(这折磨了我)。

我认为收藏中的Counter的表现非常非常差。这只是包装dict()的类。

更糟糕的是,如果使用cProfile来分析其方法,则应该看到很多“ __missing__”和“ __instancecheck__”东西一直在浪费。

使用它的most_common()时要小心,因为每次调用它都会使它变得极其缓慢。如果使用most_common(x),它将调用堆排序,这也很慢。

顺便说一句,numpy的bincount也有一个问题:如果使用np.bincount([1,2,4000000]),您将得到一个包含4000000个元素的数组。


3
dict是Python中最优化的数据结构,是计数任意对象的理想选择。相反,装仓仅适用于数值,而不能防止间隔很小的离散值之间出现混叠。在Counter的情况下,仅在首次看到元素时才调用__missing__方法。否则,它的存在是免费的。注意,在大多数情况下,most_common()方法非常快,因为与总数据集相比,堆非常小。在大多数情况下,most_common()方法所做的比较仅比min()多一点
Raymond Hettinger
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.