Encuentra el número más frecuente en un vector numpy

123

Supongamos que tengo la siguiente lista en python:

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

¿Cómo encontrar el número más frecuente en esta lista de manera ordenada?

Justo a tiempo
fuente

Respuestas:

193

Si su lista contiene todas las entradas no negativas, debería echar un vistazo a numpy.bincounts:

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

y luego probablemente use 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)

Para una lista más complicada (que tal vez contenga números negativos o valores no enteros), puede usar np.histogramde manera similar. Alternativamente, si solo desea trabajar en python sin usar numpy, collections.Counteres una buena forma de manejar este tipo de datos.

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)
JoshAdel
fuente
58
+1. Podría ser justonp.bincount([1, 2, 3, 1, 2, 1, 1, 1, 3, 2, 2, 1]).argmax()
Nikolai Fetissov
1
+1. Esto es al menos un orden de magnitud más rápido que scipy.stats.mode, aunque menos general.
Fred Foo
¡Buena respuesta! Sin embargo, si alguien está en Python 2.6, collections.Counter no está disponible. En ese caso, vea mi respuesta a continuación.
JJC
19
Para aquellos de nosotros que visitamos después de 2016: no me gusta esta respuesta, ya que bincount (arr) devuelve una matriz tan grande como el elemento más grande en arr, por lo que una matriz pequeña con un rango grande crearía una matriz excesivamente grande. La respuesta de Apoengtus a continuación es mucho mejor, aunque no creo que numpy.unique () existiera en 2011, cuando se creó esta respuesta.
Wehrdo
2
Python 3 :Counter(array).most_common(1)[0][0]
diralik
80

Puedes utilizar

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

Si algún elemento es tan frecuente como otro, este código devolverá solo el primer elemento.

Apogentus
fuente
44
Considero que esto es más útil, ya que es genérico, corto y permite extraer elementos de valores o recuentos mediante algún índice derivado.
ryanjdillon
2
Si tenemos múltiples valores más frecuentes, values[counts.argmax()]devolverá el primer valor. Para obtenerlos todos, podemos usarlos values[counts == counts.max()].
W. Zhu el
44

Si estás dispuesto a usar 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
Fred Foo
fuente
30

Actuaciones (usando iPython) para algunas soluciones encontradas aquí:

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

Lo mejor es 'max' con 'set' para matrices pequeñas como el problema.

Según @David Sanders, si aumenta el tamaño de la matriz a algo así como 100,000 elementos, el algoritmo "max w / set" termina siendo el peor con diferencia, mientras que el método "numpy bincount" es el mejor.

iuridiniz
fuente
1
@IuliusCurt para señalar el mejor enfoque que necesitamos para probarlo en múltiples casos: matrices pequeñas, matrices grandes, matrices aleatorias, matrices del mundo real (como lo hace timsort para la clasificación), ... Pero estoy de acuerdo con usted
iuridiniz
3
Usar solo una pequeña matriz, como en su enfoque, no va a distinguir muy bien entre los diferentes algoritmos.
David Sanders
10
Si aumenta el tamaño de la lista de prueba a 100000 ( a = (np.random.rand(100000) * 1000).round().astype('int'); a_list = list(a)), su algoritmo "max w / set" termina siendo el peor con diferencia, mientras que el método "numpy bincount" es el mejor. Realicé esta prueba usando el a_listcódigo nativo de Python y el acódigo numpy para evitar los costos de clasificación que arruinan los resultados.
David Sanders
4

Además, si desea obtener el valor más frecuente (positivo o negativo) sin cargar ningún módulo, puede usar el siguiente código:

lVals = [1,2,3,1,2,1,1,1,3,2,2,1]
print max(map(lambda val: (lVals.count(val), val), set(lVals)))
Artsiom Rudzenka
fuente
1
Esto es de hace un tiempo, pero para la posteridad: esto es equivalente al más fácil de leer max(set(lVals), key=lVals.count), que cuenta un O (n) para cada elemento único de lValsaproximadamente O (n ^ 2) (suponiendo que O (n) único elementos). El uso collections.Counter(lVals).most_common(1)[0][0]de la biblioteca estándar, como lo sugiere JoshAdel , es solo O (n).
Dougal
3

Si bien la mayoría de las respuestas anteriores son útiles, en caso de que: 1) lo necesite para admitir valores enteros no positivos (por ejemplo, flotantes o enteros negativos ;-)), y 2) no están en Python 2.7 (que colecciones. requiere), y 3) prefieren no agregar la dependencia de scipy (o incluso numpy) a su código, entonces una solución puramente python 2.6 que es O (nlogn) (es decir, eficiente) es solo esto:

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]
JJC
fuente
2

Me gusta la solución de JoshAdel.

Pero solo hay una trampa.

los np.bincount() solución solo funciona en números.

Si tiene cadenas, la collections.Countersolución funcionará para usted.

Vikas
fuente
1

Ampliando este método , aplicado para encontrar el modo de los datos donde puede necesitar el índice de la matriz real para ver qué tan lejos está el valor del centro de la distribución.

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

Recuerde descartar el modo cuando len (np.argmax (recuentos))> 1

Lean Bravo
fuente
1

En Python 3, lo siguiente debería funcionar:

max(set(a), key=lambda x: a.count(x))
Yury Kliachko
fuente
1

Comenzando Python 3.4, la biblioteca estándar incluye la statistics.modefunción para devolver el punto de datos más común.

from statistics import mode

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

Si hay varios modos con la misma frecuencia, statistics.modedevuelve el primero encontrado.


Al comenzar Python 3.8, la statistics.multimodefunción devuelve una lista de los valores más frecuentes en el orden en que se encontraron por primera vez:

from statistics import multimode

multimode([1, 2, 3, 1, 2])
# [1, 2]
Xavier Guihot
fuente
0

Aquí hay una solución general que puede aplicarse a lo largo de un eje, independientemente de los valores, utilizando puramente numpy. También descubrí que esto es mucho más rápido que scipy.stats.mode si hay muchos valores únicos.

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]
Devin Cairns
fuente
-1

Recientemente estoy haciendo un proyecto y usando colecciones. Contador (que me torturó).

El contador en colecciones tiene un muy, muy mal desempeño en mi opinión. Es solo una clase envolviendo dict ().

Lo que es peor, si usa cProfile para perfilar su método, debería ver muchas cosas '__missing__' y '__instancecheck__' desperdiciando todo el tiempo.

Tenga cuidado al usar su most_common (), porque cada vez invocaría un tipo que lo hace extremadamente lento. y si usa most_common (x), invocará una ordenación de montón, que también es lenta.

Por cierto, el bincount de numpy también tiene un problema: si usa np.bincount ([1,2,4000000]), obtendrá una matriz con 4000000 elementos.

Weichu Liu
fuente
3
Un dict es la estructura de datos más finamente ajustada en Python y es ideal para contar objetos arbitrarios. Por el contrario, el binning solo funciona en valores numéricos y no le permite evitar el alias entre valores discretos muy separados. En el caso de Counter, el método __missing__ solo se llama cuando se ve un elemento por primera vez; de lo contrario, su presencia es gratuita. Tenga en cuenta que el método most_common () es increíblemente rápido en la mayoría de los casos porque el montón es muy pequeño en comparación con el conjunto de datos total. En la mayoría de los casos, el método most_common () solo hace un poco más de comparaciones que min () .
Raymond Hettinger