Trouvez le nombre le plus fréquent dans un vecteur numpy

123

Supposons que j'ai la liste suivante en python:

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

Comment trouver le numéro le plus fréquent dans cette liste de manière soignée?

JustInTime
la source

Réponses:

193

Si votre liste contient tous les entiers non négatifs, vous devriez jeter un œil à numpy.bincounts:

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

puis utilisez probablement 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))

Pour une liste plus compliquée (qui contient peut-être des nombres négatifs ou des valeurs non entières), vous pouvez utiliser np.histogramde la même manière. Alternativement, si vous souhaitez simplement travailler en python sans utiliser numpy, collections.Counterc'est un bon moyen de gérer ce type de données.

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
la source
58
+1. Pourrait être justenp.bincount([1, 2, 3, 1, 2, 1, 1, 1, 3, 2, 2, 1]).argmax()
Nikolai Fetissov
1
+1. C'est au moins un ordre de grandeur plus rapide que scipy.stats.mode, bien que moins général.
Fred Foo le
Bonne réponse! Cependant, si quelqu'un est sur python 2.6, collections.Counter n'est pas disponible. Dans ce cas, voyez ma réponse ci-dessous.
JJC
19
À ceux d'entre nous qui visitent après 2016: je n'aime pas cette réponse, car bincount (arr) renvoie un tableau aussi grand que le plus grand élément de arr, donc un petit tableau avec une grande plage créerait un tableau excessivement grand. La réponse d'Apoengtus ci-dessous est bien meilleure, même si je ne pense pas que numpy.unique () existait en 2011, lorsque cette réponse a été créée.
Wehrdo
2
Python 3 :Counter(array).most_common(1)[0][0]
diralik
80

Vous pouvez utiliser

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

Si un élément est aussi fréquent qu'un autre, ce code ne retournera que le premier élément.

Apogentus
la source
4
Je trouve cela le plus utile car il est générique, court et permet d'extraire des éléments de valeurs ou de décomptes par un index dérivé.
ryanjdillon le
2
Si nous avons plusieurs valeurs les plus fréquentes, values[counts.argmax()]renvoie la première valeur. Pour les obtenir tous, nous pouvons utiliser values[counts == counts.max()].
W.Zhu
44

Si vous souhaitez utiliser 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
la source
30

Performances (avec iPython) pour certaines solutions trouvées ici:

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

Le mieux est «max» avec «set» pour les petits tableaux comme le problème.

Selon @David Sanders, si vous augmentez la taille du tableau à quelque chose comme 100 000 éléments, l'algorithme "max w / set" finit par être de loin le pire alors que la méthode "numpy bincount" est la meilleure.

iuridiniz
la source
1
@IuliusCurt pour indiquer la meilleure approche dont nous avons besoin pour le tester dans plusieurs cas: petits tableaux, grands tableaux, tableaux aléatoires, tableaux du monde réel (comme le fait timsort pour le tri), ... Mais je suis d'accord avec vous
iuridiniz
3
Utiliser seulement un petit tableau, comme dans votre approche, ne fera pas très bien la distinction entre les différents algorithmes.
David Sanders
10
Si vous augmentez la taille de la liste de tests à 100000 ( a = (np.random.rand(100000) * 1000).round().astype('int'); a_list = list(a)), votre algorithme "max w / set" finit par être de loin le pire alors que la méthode "numpy bincount" est la meilleure. J'ai effectué ce test en utilisant a_listdu code python natif et adu code numpy pour éviter que les coûts de marshalling ne gâchent les résultats.
David Sanders
4

De plus, si vous souhaitez obtenir la valeur la plus fréquente (positive ou négative) sans charger de modules, vous pouvez utiliser le code suivant:

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
la source
1
C'est d'il y a quelque temps, mais pour la postérité: cela équivaut au plus facile à lire max(set(lVals), key=lVals.count), qui fait un compte O (n) pour chaque élément unique de lValspour approximativement O (n ^ 2) (en supposant que O (n) unique éléments). L'utilisation collections.Counter(lVals).most_common(1)[0][0]de la bibliothèque standard, comme suggéré par JoshAdel , n'est que O (n).
Dougal
3

Bien que la plupart des réponses ci-dessus soient utiles, au cas où vous: 1) en auriez besoin pour prendre en charge des valeurs entières non positives (par exemple, des nombres flottants ou des entiers négatifs ;-)), et 2) ne sont pas sur Python 2.7 (quelles collections. requiert), et 3) préférez ne pas ajouter la dépendance de scipy (ou même numpy) à votre code, alors une solution purement python 2.6 qui est O (nlogn) (c'est-à-dire efficace) est juste ceci:

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
la source
2

J'aime la solution de JoshAdel.

Mais il n'y a qu'un seul hic.

La np.bincount()solution ne fonctionne que sur les nombres.

Si vous avez des chaînes, la collections.Countersolution fonctionnera pour vous.

Vikas
la source
1

Extension de cette méthode , appliquée à la recherche du mode des données pour lequel vous pouvez avoir besoin de l'index du tableau réel pour voir à quelle distance la valeur est du centre de la distribution.

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

N'oubliez pas de supprimer le mode lorsque len (np.argmax (count))> 1

Bravo maigre
la source
1

Dans Python 3, les éléments suivants devraient fonctionner:

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

À partir de Python 3.4, la bibliothèque standard inclut la statistics.modefonction pour renvoyer le point de données le plus courant.

from statistics import mode

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

S'il existe plusieurs modes avec la même fréquence, statistics.moderenvoie le premier rencontré.


À partir de Python 3.8, la statistics.multimodefonction renvoie une liste des valeurs les plus fréquentes dans l'ordre dans lequel elles ont été rencontrées pour la première fois:

from statistics import multimode

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

Voici une solution générale qui peut être appliquée le long d'un axe, quelles que soient les valeurs, en utilisant purement numpy. J'ai également trouvé que c'est beaucoup plus rapide que scipy.stats.mode s'il y a beaucoup de valeurs uniques.

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
la source
-1

Je fais récemment un projet et j'utilise des collections.Counter (ce qui m'a torturé).

Le compteur dans les collections a une très très mauvaise performance à mon avis. C'est juste une classe enveloppant dict ().

Ce qui est pire, si vous utilisez cProfile pour profiler sa méthode, vous devriez voir beaucoup de choses '__missing__' et '__instancecheck__' gaspiller tout le temps.

Soyez prudent en utilisant son most_common (), car à chaque fois il invoquerait un tri qui le rend extrêmement lent. et si vous utilisez most_common (x), il invoquera un tri de tas, qui est également lent.

Btw, bincount de numpy a aussi un problème: si vous utilisez np.bincount ([1,2,4000000]), vous obtiendrez un tableau avec 4000000 éléments.

Weichu Liu
la source
3
Un dict est la structure de données la plus finement réglée en Python et est idéal pour compter des objets arbitraires. En revanche, le regroupement ne fonctionne que sur les valeurs numériques et ne vous permet pas d'empêcher l'aliasing entre des valeurs discrètes étroitement espacées. Dans le cas de Counter, la méthode __missing__ n'est appelée que lorsqu'un élément est vu pour la première fois; sinon, sa présence est gratuite. Notez que la méthode most_common () est incroyablement rapide dans la plupart des cas car le tas est très petit par rapport à l'ensemble de données total. Dans la plupart des cas, la méthode most_common () ne fait que légèrement plus de comparaisons que min () .
Raymond Hettinger