Najbardziej efektywny sposób na znalezienie trybu w tablicy numpy

89

Mam tablicę 2D zawierającą liczby całkowite (zarówno dodatnie, jak i ujemne). Każdy wiersz przedstawia wartości w czasie dla określonego miejsca przestrzennego, podczas gdy każda kolumna przedstawia wartości dla różnych miejsc przestrzennych w danym czasie.

Więc jeśli tablica wygląda następująco:

1 3 4 2 2 7
5 2 2 1 4 1
3 3 2 2 1 1

Wynik powinien być

1 3 2 2 2 1

Zwróć uwagę, że gdy istnieje wiele wartości trybu, dowolna (wybrana losowo) może zostać ustawiona jako tryb.

Mogę iterować tryb wyszukiwania kolumn pojedynczo, ale miałem nadzieję, że numpy może mieć wbudowaną funkcję do tego. Lub jeśli istnieje sztuczka, aby znaleźć to skutecznie bez zapętlania.

Nik
źródło
1
@ tom10: Masz na myśli scipy.stats.mode () , prawda? Drugi wydaje się wysyłać zamaskowaną tablicę.
fgb
@fgb: tak, dziękuję za korektę (i +1 za odpowiedź).
tom10

Odpowiedzi:

121

Sprawdź scipy.stats.mode()(inspirowane komentarzem @ 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)

Wynik:

ModeResult(mode=array([[1, 3, 2, 2, 1, 1]]), count=array([[1, 2, 2, 2, 1, 2]]))

Jak widać, zwraca zarówno tryb, jak i liczby. Możesz wybrać tryby bezpośrednio poprzez m[0]:

print(m[0])

Wynik:

[[1 3 2 2 1 1]]
fgb
źródło
4
Więc numpy sam w sobie nie obsługuje takiej funkcjonalności?
Nik
1
Najwyraźniej nie, ale implementacja scipy opiera się tylko na numpy , więc możesz po prostu skopiować ten kod do własnej funkcji.
fgb
12
Uwaga dla osób, które patrzą na to w przyszłości: musisz import scipy.statswyraźnie zaznaczyć, nie jest to uwzględniane, gdy po prostu wykonujesz plik import scipy.
ffledgling
1
Czy możesz wyjaśnić, jak dokładnie wyświetla wartości trybu i liczbę? Nie mogłem powiązać danych wyjściowych z dostarczonymi danymi wejściowymi.
Rahul
2
@Rahul: musisz wziąć pod uwagę domyślny drugi argument axis=0. Powyższy kod raportuje tryb w każdej kolumnie wejścia. Licznik mówi nam, ile razy widział raportowany tryb w każdej z kolumn. Jeśli chcesz tryb ogólny, musisz określić axis=None. Więcej informacji można znaleźć pod adresem docs.scipy.org/doc/scipy/reference/generated/ ...
fgb
22

Aktualizacja

scipy.stats.modeFunkcja została znacznie zoptymalizowany od tego postu, a byłoby to zalecana metoda

Stara odpowiedź

Jest to trudny problem, ponieważ nie ma zbyt wiele do obliczenia trybu wzdłuż osi. Rozwiązanie to jest proste dla tablic 1-D, w których numpy.bincountjest przydatny, a także numpy.uniquez return_countsarg True. Najczęstszą funkcją n-wymiarową, jaką widzę, jest scipy.stats.mode, chociaż jest ona zbyt wolna - szczególnie w przypadku dużych tablic z wieloma unikalnymi wartościami. Jako rozwiązanie opracowałem tę funkcję i intensywnie z niej korzystam:

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]

Wynik:

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

Niektóre testy porównawcze:

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

EDYCJA: Zapewniono więcej tła i zmodyfikowano podejście, aby zwiększyć wydajność pamięci

Devin Cairns
źródło
1
Prosimy o przesłanie go do modułu statystyk scipy, aby inni też mogli z niego skorzystać.
ARF
W przypadku problemów z większymi wymiarami z dużymi int ndarrays, twoje rozwiązanie wydaje się być nadal znacznie szybsze niż scipy.stats.mode. Musiałem obliczyć tryb wzdłuż pierwszej osi ndarray 4x250x250x500, a twoja funkcja zajęła 10 sekund, a scipy.stats.mode prawie 600 sekund.
CheshireCat
11

Rozwinięcie tej metody , zastosowane do znalezienia trybu danych, w którym może być potrzebny indeks rzeczywistej tablicy, aby zobaczyć, jak daleko wartość znajduje się od środka rozkładu.

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

Pamiętaj, aby odrzucić tryb, gdy len (np.argmax (counts))> 1, również w celu sprawdzenia, czy jest on rzeczywiście reprezentatywny dla centralnego rozkładu Twoich danych, możesz sprawdzić, czy mieści się on w Twoim przedziale odchylenia standardowego.

Lean Bravo
źródło
Kiedy np.argmax zwraca kiedykolwiek coś o długości większej niż 1, jeśli nie określisz osi?
loganjones
10

Zgrabne rozwiązanie, które wykorzystuje tylkonumpy (nie scipyani Counterklasę):

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)

tablica ([1, 3, 2, 2, 1, 1])

Def_Os
źródło
1
Ładny i zwięzły, ale powinien być używany ostrożnie, jeśli oryginalne tablice zawierają bardzo dużą liczbę, ponieważ bincount utworzy tablice bin z len (max (A [i])) dla każdej oryginalnej tablicy A [i].
scottlittle
To świetne rozwiązanie. W rzeczywistości jest wada scipy.stats.mode. Gdy istnieje wiele wartości, które mają najwięcej występowania (wiele trybów), wyrzuci to oczekiwanie. Ale ta metoda automatycznie przyjmie „tryb pierwszy”.
Christopher,
6

Jeśli chcesz używać tylko numpy:

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

daje

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

I wyodrębnij:

index = np.argmax(counts)
return vals[index]
trucizna
źródło
Podoba mi się ta metoda, ponieważ obsługuje nie tylko liczby całkowite, ale także zmiennoprzecinkowe, a nawet łańcuchy!
Christopher,
3

Myślę, że bardzo prostym sposobem byłoby użycie klasy Counter. Następnie możesz użyć funkcji most_common () instancji Counter, jak wspomniano tutaj .

W przypadku macierzy 1-w:

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

W przypadku tablic wielowymiarowych (niewielka różnica):

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

Może to być skuteczna implementacja lub nie, ale jest wygodna.

Ali_Ayub
źródło
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)Liczy się częstotliwość i zwraca defaultdict. sorted(Counter(data).items())sortuje za pomocą klawiszy, a nie częstotliwości. Na koniec należy posortować częstotliwość przy użyciu innej posortowanej z key = lambda x: x[1]. Odwrotna sytuacja mówi Pythonowi, aby sortował częstotliwość od największej do najmniejszej.

Zeliha Bektas
źródło
Ponieważ pytanie zadano 6 lat temu, to normalne, że nie zyskał on zbytniej reputacji.
Zeliha Bektas
1

najprostszy sposób w Pythonie, aby uzyskać tryb listy lub tablicy a

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

Otóż ​​to

Ashutosh K Singh
źródło