numpy 배열에서 모드를 찾는 가장 효율적인 방법


84

정수 (양수 또는 음수)를 포함하는 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에 내장 기능이 있기를 바라고 있습니다. 또는 반복하지 않고 효율적으로 찾을 수있는 트릭이있는 경우.



1
@ tom10 : scipy.stats.mode () 의미 합니까? 다른 하나는 마스크 된 배열을 출력하는 것 같습니다.
fgb

@fgb : 맞습니다. 정정 해 주셔서 감사합니다 (답변에 +1).
tom10 2013 년

답변:


115

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

1
분명히 그렇지는 않지만 scipy의 구현은 numpy에만 의존 하므로 해당 코드를 자신의 함수에 복사 할 수 있습니다.
fgb

11
나중에 이것을 보는 사람들을 위해 참고로, import scipy.stats명시 적으로해야합니다 import scipy. 단순히 .
ffledgling aug

1
모드 값과 카운트를 정확히 어떻게 표시하는지 설명해 주시겠습니까? 제공된 입력과 출력을 연결할 수 없습니다.
Rahul

2
@Rahul : 기본 두 번째 인수 인 axis=0. 위 코드는 입력 열당 모드를보고합니다. 카운트는 각 열에서보고 된 모드를 본 횟수를 나타냅니다. 전체 모드를 원하면을 지정해야합니다 axis=None. 자세한 정보는 docs.scipy.org/doc/scipy/reference/generated/…
fgb

22

최신 정보

scipy.stats.mode기능은 크게이 게시물 이후로 최적화되어 있으며, 권장되는 방법이 될 것입니다

이전 답변

축을 따라 모드를 계산할 것이 많지 않기 때문에 이것은 까다로운 문제입니다. 해결책은 1D 배열의 경우 numpy.bincount간단 numpy.unique하며 return_countsarg as 와 함께 편리합니다 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

편집 : 배경을 더 많이 제공하고 접근 방식을 메모리 효율성을 높이도록 수정했습니다.


1
다른 사람들도 혜택을 볼 수 있도록 scipy의 통계 모듈에 기여해주세요.
ARF

큰 int ndarrays의 고차원 문제의 경우 솔루션이 scipy.stats.mode보다 훨씬 더 빠른 것 같습니다. 4x250x250x500 ndarray의 첫 번째 축을 따라 모드를 계산해야했고 함수는 10 초가 걸렸고 scipy.stats.mode는 거의 600 초가 걸렸습니다.
CheshireCat

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보다 큰 것을 언제 반환합니까?
loganjones16 nov.

9

( 클래스 도 아님) 사용 하는 깔끔한 솔루션 :numpyscipyCounter

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)

배열 ([1, 3, 2, 2, 1, 1])


1
훌륭하고 간결하지만, bincount가 각 원래 배열 A [i]에 대해 len (max (A [i]))를 사용하여 bin 배열을 생성하기 때문에 원래 배열에 매우 많은 수가 포함되어있는 경우주의해서 사용해야합니다.
scottlittle

이것은 멋진 솔루션입니다. 실제로 scipy.stats.mode. 가장 많이 발생하는 값이 여러 개있는 경우 (다중 모드) 예상을 던집니다. 그러나이 방법은 자동으로 "첫 번째 모드"를 사용합니다.
Christopher

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]

이 방법은 정수뿐만 아니라 float, 심지어 문자열도 지원하기 때문입니다!
Christopher

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]. 그 반대는 파이썬에게 가장 큰 것에서 가장 작은 것까지 빈도를 정렬하도록 지시합니다.


6 년 전에 질문을했기 때문에 그다지 명성을 얻지 못한 것은 당연하다.
Zeliha Bektas 2019-08-23

1

목록이나 배열의 모드를 얻는 파이썬에서 가장 간단한 방법

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

그게 다야

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.