派手なベクトルで最も頻繁な数を見つける


123

Pythonで次のリストがあるとします。

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

このリストで最も頻度の高い番号をきちんと見つける方法は?

回答:


193

リストに負でない整数がすべて含まれている場合は、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、同様の方法で使用できます。あるいは、numpyを使用せずにpythonで作業したいだけの場合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()
ニコライFetissov

1
+1。これは、scipy.stats.mode一般的ではありませんが、少なくとも1桁高速です。
Fred Foo

素敵な答え!ただし、誰かがpython 2.6を使用している場合、collections.Counterは使用できません。その場合は、以下の私の回答を参照してください。
JJC 2013

19
bincount(arr)はarrの最大要素と同じ大きさの配列を返すので、2016年以降に訪れるユーザーにはこの答えは嫌いです。そのため、範囲が大きい小さな配列は、非常に大きな配列を作成します。以下のApoengtusの回答の方がはるかに優れていますが、この回答が作成された2011年にnumpy.unique()が存在したとは思いません。
Wehrdo 2016年

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

80

あなたは使うかもしれません

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

一部の要素が別の要素と同じくらい頻繁である場合、このコードは最初の要素のみを返します。


4
これは一般的で短く、いくつかの派生インデックスによって値またはカウントから要素を取得できるため、これが最も役立ちます。
ライアンジディロン

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
>>> 

問題のような小さな配列の場合は、「max」と「set」が最適です。

@David Sandersによると、配列のサイズを100,000要素のようなものに増やすと、「最大w /セット」アルゴリズムが最悪となり、「numpy bincount」メソッドが最善です。


1
@IuliusCurtは、小さな配列、大きな配列、ランダム配列、実世界の配列(並べ替えのためのtimsortのように)など、複数のケースに対してテストする必要がある最良のアプローチを示すために...しかし、私はあなたに同意します
iuridiniz

3
アプローチのように小さな配列だけを使用しても、異なるアルゴリズムを十分に区別することはできません。
David Sanders

10
テストリストのサイズを100000(a = (np.random.rand(100000) * 1000).round().astype('int'); a_list = list(a))に増やすと、「最大w /セット」アルゴリズムが最悪となり、「numpy bincount」メソッドが最適です。a_listネイティブpythonコードとanumpyコードを使用してこのテストを実行し、マーシャリングコストが結果を台無しにすることを回避しました。
David Sanders

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
これはしばらく前のものですが、後世のためです。これは、およそO(n ^ 2)のmax(set(lVals), key=lVals.count)各一意の要素に対してO(n)カウントを行う読みやすいと同等ですlVals(O(n)が一意であると仮定)要素)。JoshAdelによって提案されcollections.Counter(lVals).most_common(1)[0][0]ているように、標準ライブラリからの使用はO(n)のみです。
Dougal 2012

3

上記の答えのほとんどは便利ですが、次の場合に役立ちます:1)非正の整数値をサポートする必要がある(例:浮動小数点数または負の整数;-))、および2)Python 2.7にない(どのcollections.Counter必要)、および3)scipy(またはnumpy)の依存関係をコードに追加しないことを好む場合、O(nlogn)(つまり、効率的)である純粋なpython 2.6ソリューションはこれだけです:

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のソリューションが気に入っています。

しかし、問題は1つだけです。

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.8statistics.multimode関数は最も頻繁に発生する値のリストを、最初に遭遇した順序で返します。

from statistics import multimode

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

0

以下は、値に関係なく、純粋にナンピーを使用して軸に沿って適用できる一般的なソリューションです。また、一意の値が多数ある場合、これは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を使用しています(これは私を拷問しました)。

コレクションのカウンターは私の意見では非常に非常に悪いパフォーマンスを持っています。これは、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()よりもわずかに多くの比較を行います。
レイモンドヘッティンガー2013年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.