Encontre o número mais frequente em um vetor numpy

123

Suponha que eu tenha a seguinte lista em python:

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

Como encontrar o número mais frequente nesta lista de maneira organizada?

Na hora certa
fonte

Respostas:

193

Se sua lista contiver todos os ints não negativos, dê uma olhada em numpy.bincounts:

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

e provavelmente 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 uma lista mais complicada (que talvez contenha números negativos ou valores não inteiros), você pode usá-lo de np.histogrammaneira semelhante. Como alternativa, se você deseja trabalhar em python sem usar o numpy, collections.Counteré uma boa maneira de lidar com esse tipo de dados.

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
fonte
58
+1. Poderia ser apenasnp.bincount([1, 2, 3, 1, 2, 1, 1, 1, 3, 2, 2, 1]).argmax()
Nikolai Fetissov
1
+1. Esta é pelo menos uma ordem de magnitude mais rápida que scipy.stats.mode, embora menos geral.
Fred Foo
Boa resposta! No entanto, se alguém estiver no python 2.6, collections.Counter não estará disponível. Nesse caso, veja minha resposta abaixo.
JJC 01/07
19
Para aqueles que visitam depois de 2016: Não gosto dessa resposta, pois bincount (arr) retorna uma matriz do tamanho do maior elemento em arr, portanto, uma matriz pequena com uma grande variedade criaria uma matriz excessivamente grande. A resposta de Apoengtus abaixo é muito melhor, embora eu não ache que numpy.unique () existisse em 2011, quando essa resposta foi criada.
Wehrdo 13/03/16
2
Python 3 :Counter(array).most_common(1)[0][0]
diralik
80

Você pode usar

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

Se algum elemento for tão frequente quanto outro, esse código retornará apenas o primeiro elemento.

Apogentus
fonte
4
Acho isso o mais útil, pois é genérico, curto e permite extrair elementos de valores ou contagens por algum índice derivado.
Ryanjdillon
2
Se tivermos vários valores mais frequentes, values[counts.argmax()]retornará o primeiro valor. Para obter todos eles, podemos usar values[counts == counts.max()].
W. Zhu
44

Se você estiver disposto a usar o 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
fonte
30

Apresentações (usando o iPython) para algumas soluções encontradas aqui:

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

Melhor é 'max' com 'set' para pequenas matrizes como o problema.

De acordo com @David Sanders, se você aumentar o tamanho da matriz para algo como 100.000 elementos, o algoritmo "max w / set" acaba sendo o pior de longe, enquanto o método "numpy bincount" é o melhor.

iuridiniz
fonte
1
@IuliusCurt para apontar a melhor abordagem que precisamos para testá-la em vários casos: matrizes pequenas, matrizes grandes, matrizes aleatórias, matrizes do mundo real (como o timsort faz para classificar), ... Mas eu concordo com você
iuridiniz
3
Usar apenas uma matriz pequena, como na sua abordagem, não distingue muito bem os diferentes algoritmos.
David Sanders
10
Se você aumentar o tamanho da lista de testes para 100000 ( a = (np.random.rand(100000) * 1000).round().astype('int'); a_list = list(a)), o algoritmo "max w / set" acaba sendo o pior de longe, enquanto o método "numpy bincount" é o melhor. Realizei esse teste usando o a_listcódigo python nativo e o acódigo numpy para evitar custos de organização dos resultados.
David Sanders
4

Além disso, se você deseja obter o valor mais frequente (positivo ou negativo) sem carregar nenhum módulo, pode usar o seguinte 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
fonte
1
Isso é de um tempo atrás, mas para a posteridade: isso é equivalente ao mais fácil de ler max(set(lVals), key=lVals.count), que conta O (n) para cada elemento único de lValsaproximadamente O (n ^ 2) (assumindo O (n) exclusivo elementos). O uso collections.Counter(lVals).most_common(1)[0][0]da biblioteca padrão, conforme sugerido por JoshAdel , é apenas O (n).
Dougal
3

Embora a maioria das respostas acima seja útil, no caso de você: 1) precisar dar suporte a valores inteiros não positivos (por exemplo, números flutuantes ou números negativos ;-)) e 2) não estão no Python 2.7 (que coleções. requer) e 3) prefere não adicionar a dependência de scipy (ou até mesmo numpy) ao seu código, então uma solução puramente python 2.6 que é O (nlogn) (ou seja, eficiente) é exatamente isso:

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
fonte
2

Eu gosto da solução de JoshAdel.

Mas há apenas uma captura.

A np.bincount()solução funciona apenas em números.

Se você tiver seqüências de caracteres, a collections.Countersolução funcionará para você.

Vikas
fonte
1

Expandindo esse método , aplicado para encontrar o modo dos dados em que você pode precisar do índice da matriz real para ver a que distância o valor está do centro da distribuição.

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

Lembre-se de descartar o modo quando len (np.argmax (counts))> 1

Lean Bravo
fonte
1

No Python 3, o seguinte deve funcionar:

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

Começando Python 3.4, a biblioteca padrão inclui a statistics.modefunção para retornar o ponto de dados mais comum.

from statistics import mode

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

Se houver vários modos com a mesma frequência, statistics.moderetornará o primeiro encontrado.


Iniciando Python 3.8, a statistics.multimodefunção retorna uma lista dos valores que ocorrem com mais frequência na ordem em que foram encontrados:

from statistics import multimode

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

Aqui está uma solução geral que pode ser aplicada ao longo de um eixo, independentemente dos valores, usando puramente numpy. Também descobri que isso é muito mais rápido que o scipy.stats.mode se houver muitos valores exclusivos.

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
fonte
-1

Recentemente, estou fazendo um projeto e usando collections.Counter. (O que me torturou).

O contador nas coleções tem um desempenho muito, muito ruim, na minha opinião. É apenas um dict de quebra de classe ().

O que é pior: se você usar o cProfile para criar um perfil do método, verá muitas coisas '__missing__' e '__instancecheck__' desperdiçando o tempo todo.

Tenha cuidado ao usar o most_common (), porque toda vez ele invocaria uma classificação que a torna extremamente lenta. e se você usar most_common (x), ele chamará uma classificação de heap, que também é lenta.

Aliás, a conta do numpy também tem um problema: se você usar np.bincount ([1,2,4000000]), obterá uma matriz com 4000000 elementos.

Weichu Liu
fonte
3
Um dict é a estrutura de dados mais refinada do Python e é ideal para contar objetos arbitrários. Por outro lado, o binning funciona apenas com valores numéricos e não permite evitar aliases entre valores discretos espaçados. No caso de Counter, o método __missing__ é chamado apenas quando um elemento é visto pela primeira vez; caso contrário, sua presença é gratuita. Observe que o método most_common () é extremamente rápido na maioria dos casos, porque o heap é muito pequeno comparado ao conjunto de dados total. Na maioria dos casos, o método most_common () faz comparações apenas um pouco mais que min () .
Raymond Hettinger 31/03