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.