numpy配列でモードを見つける最も効率的な方法


89

整数(正または負の両方)を含む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

モードに複数の値がある場合、いずれか(ランダムに選択)をモードとして設定できることに注意してください。

一度に1つずつ列検索モードを繰り返すことができますが、numpyにそれを行うための組み込み関数があることを期待していました。または、ループせずに効率的にそれを見つけるためのトリックがある場合。



1
@ tom10:scipy.stats.mode()のことですか?もう1つは、マスクされた配列を出力しているようです。
fgb 2013年

@fgb:そうです、訂正してくれてありがとう(そしてあなたの答えは+1)。
tom10 2013年

回答:


121

チェック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]]

4
では、numpy自体はそのような機能をサポートしていませんか?
Nik 2013年

1
明らかにそうではありませんが、scipyの実装はnumpyのみに依存しているため、そのコードを独自の関数にコピーするだけで済みます。
fgb 2013年

12
将来これを見ている人のために、注意してください。import scipy.stats明示的にする必要がありますimport scipy。単に。を実行した場合は含まれません。
2013

1
モード値とカウントがどの程度正確に表示されているか説明していただけますか?出力を提供された入力と関連付けることができませんでした。
Rahul 2017

2
@Rahul:デフォルトの2番目の引数を考慮する必要がありますaxis=0。上記のコードは、入力の列ごとのモードを報告しています。カウントは、各列で報告されたモードを何回確認したかを示しています。全体的なモードが必要な場合は、を指定する必要がありますaxis=None。詳細については、docs.scipy.org
doc /

22

更新

scipy.stats.mode機能が大幅にこのポスト以降に最適化されており、推奨される方法になります

古い答え

軸に沿ってモードを計算することはあまりないので、これは難しい問題です。解決策は、arg as 。とともに、numpy.bincount便利な1次元配列の場合は簡単です。私が見る最も一般的なn次元関数はscipy.stats.modeですが、特に多くの一意の値を持つ大きな配列の場合、非常に遅くなります。解決策として、私はこの関数を開発し、それを頻繁に使用します。numpy.uniquereturn_countsTrue

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

編集:より多くの背景を提供し、よりメモリ効率が高くなるようにアプローチを変更しました


1
他の人もそれから利益を得ることができるように、それをscipyの統計モジュールに貢献してください。
ARF

大きなintndarrayの高次元の問題の場合、ソリューションはscipy.stats.modeよりもはるかに高速であるように見えます。4x250x250x500 ndarrayの最初の軸に沿ってモードを計算する必要があり、関数には10秒かかりましたが、scipy.stats.modeにはほぼ600秒かかりました。
チェシャ猫

11

この方法を拡張して、値が分布の中心からどれだけ離れているかを確認するために実際の配列のインデックスが必要になる可能性があるデータのモードを見つけるために適用されます。

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

len(np.argmax(counts))> 1の場合はモードを破棄することを忘れないでください。また、モードが実際にデータの中央分布を表しているかどうかを検証するために、標準偏差間隔内にあるかどうかを確認できます。


軸を指定しない場合、np.argmaxが1より大きい長さの何かを返すのはいつですか?
loganjones 1619年

10

そのきちんとした解決策のみの使用numpy(ないscipyCounterクラス):

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)

array([1、3、2、2、1、1])


1
素晴らしく簡潔ですが、bincountは元の配列A [i]ごとにlen(max(A [i]))を使用してbin配列を作成するため、元の配列に非常に大きな数が含まれている場合は注意して使用する必要があります。
scottlittle

これは素晴らしいソリューションです。には実際には欠点がありscipy.stats.modeます。最も多く発生する値が複数ある場合(複数のモード)、期待値がスローされます。ただし、このメソッドは自動的に「最初のモード」になります。
クリストファー

5

numpyのみを使用する場合:

x = [-1, 2, 1, 3, 3]
vals,counts = np.unique(x, return_counts=True)

与える

(array([-1,  1,  2,  3]), array([1, 1, 1, 2]))

そしてそれを抽出します:

index = np.argmax(counts)
return vals[index]

このメソッドのように、整数だけでなく、浮動小数点数、さらには文字列もサポートします。
クリストファー

3

非常に簡単な方法は、Counterクラスを使用することだと思います。次に、ここで説明するように、Counterインスタンスのmost_common()関数を使用できます

1次元配列の場合:

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

これは効率的な実装である場合とそうでない場合がありますが、便利です。


2
from collections import Counter

n = int(input())
data = sorted([int(i) for i in input().split()])

sorted(sorted(Counter(data).items()), key = lambda x: x[1], reverse = True)[0][0]

print(Mean)

Counter(data)周波数をカウントし、defaultdictを返します。sorted(Counter(data).items())頻度ではなく、キーを使用してソートします。最後に、でソートされた別の頻度を使用して頻度をソートする必要がありkey = lambda x: x[1]ます。逆は、Pythonに頻度を最大から最小にソートするように指示します。


6年前に質問されたので、あまり評判が良くなかったのが普通です。
ZelihaBektas19年

1

リストまたは配列のモードを取得するPythonの最も簡単な方法

   import statistics
   print("mode = "+str(statistics.(mode(a)))

それでおしまい

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.