Trova il numero più frequente in un vettore numpy


123

Supponiamo di avere il seguente elenco in Python:

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

Come trovare il numero più frequente in questo elenco in modo ordinato?

Risposte:


193

Se la tua lista contiene tutti gli int non negativi, dovresti dare un'occhiata a numpy.bincounts:

http://docs.scipy.org/doc/numpy/reference/generated/numpy.bincount.html

e quindi probabilmente usa 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))

Per un elenco più complicato (che forse contiene numeri negativi o valori non interi), puoi usarlo np.histogramin modo simile. In alternativa, se vuoi solo lavorare in Python senza usare numpy, collections.Counterè un buon modo per gestire questo tipo di dati.

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. Potrebbe essere solonp.bincount([1, 2, 3, 1, 2, 1, 1, 1, 3, 2, 2, 1]).argmax()
Nikolai Fetissov

1
+1. Questo è almeno un ordine di grandezza più veloce di scipy.stats.mode, sebbene meno generale.
Fred Foo

Bella risposta! Tuttavia, se qualcuno utilizza python 2.6, collections.Counter non è disponibile. In tal caso, vedere la mia risposta di seguito.
JJC

19
Per quelli di noi che visitano dopo il 2016: Non mi piace questa risposta, poiché bincount (arr) restituisce un array grande quanto l'elemento più grande in arr, quindi un array piccolo con un intervallo ampio creerebbe un array eccessivamente grande. La risposta di Apoengtus di seguito è molto migliore, anche se non credo che numpy.unique () esistesse nel 2011, quando è stata creata questa risposta.
Wehrdo

2
Python 3 :Counter(array).most_common(1)[0][0]
diralik

80

Puoi usare

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

Se un elemento è frequente come un altro, questo codice restituirà solo il primo elemento.


4
Trovo questo il più utile in quanto è generico, breve e consente di estrarre elementi da valori o conteggi da un indice derivato.
ryanjdillon

2
Se abbiamo più valori più frequenti, values[counts.argmax()]restituirà il primo valore. Per ottenerli tutti, possiamo usare values[counts == counts.max()].
W. Zhu

44

Se sei disposto a utilizzare 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

Prestazioni (utilizzando iPython) per alcune soluzioni trovate qui:

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

La cosa migliore è 'max' con 'set' per piccoli array come il problema.

Secondo @David Sanders, se aumenti la dimensione dell'array a qualcosa come 100.000 elementi, l'algoritmo "max w / set" finisce per essere di gran lunga il peggiore mentre il metodo "numpy bincount" è il migliore.


1
@IuliusCurt per indicare l'approccio migliore di cui abbiamo bisogno per testarlo su più casi: piccoli array, grandi array, array casuali, array del mondo reale (come fa timsort per l'ordinamento), ... Ma sono d'accordo con te
iuridiniz

3
Usare solo un piccolo array, come nel tuo approccio, non distinguerà molto bene tra i diversi algoritmi.
David Sanders

10
Se aumenti la dimensione dell'elenco di test a 100000 (a = (np.random.rand(100000) * 1000).round().astype('int'); a_list = list(a) ), il tuo algoritmo "max w / set" finisce per essere di gran lunga il peggiore mentre il metodo "numpy bincount" è il migliore. Ho condotto questo test utilizzando a_listper il codice nativo Python e aper il codice numpy per evitare che i costi di marshalling rovinino i risultati.
David Sanders

4

Inoltre se vuoi ottenere il valore più frequente (positivo o negativo) senza caricare alcun modulo puoi usare il seguente codice:

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
Questo è di qualche tempo fa, ma per i posteri: questo è equivalente al più facile da leggere max(set(lVals), key=lVals.count), che conta un O (n) per ogni elemento unico di lValsapprossimativamente O (n ^ 2) (assumendo O (n) unico elementi). L'utilizzo collections.Counter(lVals).most_common(1)[0][0]dalla libreria standard, come suggerito da JoshAdel , è solo O (n).
Dougal

3

Sebbene la maggior parte delle risposte sopra siano utili, nel caso in cui: 1) ne hai bisogno per supportare valori interi non positivi (ad esempio float o numeri interi negativi ;-)), e 2) non sono su Python 2.7 (quali raccolte. richiede) e 3) preferisci non aggiungere la dipendenza di scipy (o anche numpy) al tuo codice, quindi una soluzione puramente python 2.6 che è O (nlogn) (cioè efficiente) è proprio questa:

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

Mi piace la soluzione di JoshAdel.

Ma c'è solo un problema.

La np.bincount()soluzione funziona solo sui numeri.

Se hai delle stringhe, la collections.Countersoluzione funzionerà per te.


1

Espandendo questo metodo , applicato alla ricerca della modalità dei dati in cui potrebbe essere necessario l'indice dell'array effettivo per vedere quanto è lontano il valore dal centro della distribuzione.

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

Ricordarsi di eliminare la modalità quando len (np.argmax (counts))> 1


1

In Python 3 dovrebbe funzionare quanto segue:

max(set(a), key=lambda x: a.count(x))

1

A partire da Python 3.4, la libreria standard include la statistics.modefunzione per restituire il singolo punto dati più comune.

from statistics import mode

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

Se sono presenti più modalità con la stessa frequenza, statistics.moderestituisce la prima incontrata.


A partire da Python 3.8, la statistics.multimodefunzione restituisce un elenco dei valori ricorrenti nell'ordine in cui sono stati rilevati per la prima volta:

from statistics import multimode

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

0

Ecco una soluzione generale che può essere applicata lungo un asse, indipendentemente dai valori, utilizzando esclusivamente numpy. Ho anche scoperto che è molto più veloce di scipy.stats.mode se ci sono molti valori univoci.

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

Recentemente sto facendo un progetto e usando le collezioni.Counter. (Il che mi ha torturato).

Il contatore nelle collezioni ha una pessima prestazione secondo me. È solo un dict di wrapping di classe ().

Quel che è peggio, se usi cProfile per profilare il suo metodo, dovresti vedere molte cose '__missing__' e '__instancecheck__' che sprecano tutto il tempo.

Fai attenzione a usare most_common (), perché ogni volta invoca un ordinamento che lo rende estremamente lento. e se usi most_common (x), richiamerà un ordinamento di heap, anch'esso lento.

A proposito, anche il bincount di numpy ha un problema: se usi np.bincount ([1,2,4000000]), otterrai un array con 4000000 elementi.


3
Un dict è la struttura dati più finemente sintonizzata in Python ed è ideale per contare oggetti arbitrari. Al contrario, la categorizzazione funziona solo su valori numerici e non consente di impedire l'aliasing tra valori discreti ravvicinati. Nel caso di Counter, il metodo __missing__ viene chiamato solo quando un elemento viene visto per la prima volta; in caso contrario, la sua presenza è gratuita. Nota, il metodo most_common () è incredibilmente veloce nella maggior parte dei casi perché l'heap è molto piccolo rispetto al set di dati totale. Nella maggior parte dei casi, il metodo most_common () effettua solo leggermente più confronti rispetto a min () .
Raymond Hettinger
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.