numpy: a frequência mais eficiente conta para valores únicos em uma matriz

244

Em numpy/ scipy, existe uma maneira eficiente de obter contagens de frequência para valores exclusivos em uma matriz?

Algo nesse sentido:

x = array( [1,1,1,2,2,2,5,25,1,1] )
y = freq_count( x )
print y

>> [[1, 5], [2,3], [5,1], [25,1]]

(Para você, usuários R por aí, basicamente estou procurando a table()função)

Abe
fonte
5
É collections.Counter(x)suficiente?
Pylang 18/05
1
Seria melhor eu acho que se você marcar agora esta resposta como correta para sua pergunta: stackoverflow.com/a/25943480/9024698 .
Outcast
O contador Collections.counter é bastante lento. Veja meu post: stackoverflow.com/questions/41594940/…
Sembei Norimaki

Respostas:

161

Dê uma olhada em np.bincount:

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

import numpy as np
x = np.array([1,1,1,2,2,2,5,25,1,1])
y = np.bincount(x)
ii = np.nonzero(y)[0]

E depois:

zip(ii,y[ii]) 
# [(1, 5), (2, 3), (5, 1), (25, 1)]

ou:

np.vstack((ii,y[ii])).T
# array([[ 1,  5],
         [ 2,  3],
         [ 5,  1],
         [25,  1]])

ou no entanto você deseja combinar as contagens e os valores exclusivos.

JoshAdel
fonte
42
Olá, isso não funcionaria se os elementos de x tivessem um tipo diferente de int.
Manoj 24/02
7
Não funcionará se não houver ints não negativos e será muito ineficiente em espaço se os ints estiverem espaçados.
Erik
Na versão numpy 1.10, descobri que, para contar números inteiros, é cerca de 6 vezes mais rápido que o np.unique. Além disso, observe que ele também conta ints negativos, se os parâmetros corretos forem fornecidos.
Jihun
@ Manoj: Meus elementos x são matrizes. Estou testando a solução de jme.
Catalina Chircu
508

A partir do Numpy 1.9, o método mais fácil e rápido é simplesmente usar numpy.unique, que agora possui um return_countsargumento de palavra - chave:

import numpy as np

x = np.array([1,1,1,2,2,2,5,25,1,1])
unique, counts = np.unique(x, return_counts=True)

print np.asarray((unique, counts)).T

Que dá:

 [[ 1  5]
  [ 2  3]
  [ 5  1]
  [25  1]]

Uma rápida comparação com scipy.stats.itemfreq:

In [4]: x = np.random.random_integers(0,100,1e6)

In [5]: %timeit unique, counts = np.unique(x, return_counts=True)
10 loops, best of 3: 31.5 ms per loop

In [6]: %timeit scipy.stats.itemfreq(x)
10 loops, best of 3: 170 ms per loop
jme
fonte
22
Obrigado por atualizar! Agora é a resposta correta, IMO.
Erve1879
1
BAM! é por isso que atualizamos ... quando encontramos respostas como estas. Tão longo numpy 1.8. Como podemos colocar isso no topo da lista?
user1269942
Se você receber o erro: TypeError: única () tem um argumento chave inesperado 'return_counts', basta fazer: únicas, contagens = np.unique (x, True)
NumesSanguis
3
@NumesSanguis Qual versão do numpy você está usando? Antes da v1.9, o return_countsargumento da palavra - chave não existia, o que poderia explicar a exceção. Nesse caso, os documentos sugerem que np.unique(x, True)é equivalente a np.unique(x, return_index=True), que não retorna contagens.
JME
1
Nas versões numpy mais antigas, o idioma típico para obter a mesma coisa era unique, idx = np.unique(x, return_inverse=True); counts = np.bincount(idx). Quando esse recurso foi adicionado (veja aqui ), alguns testes informais tiveram o uso de return_countsclock mais de 5x mais rápido.
Jaime
133

Atualização: o método mencionado na resposta original foi descontinuado. Em vez disso, devemos usar a nova maneira:

>>> import numpy as np
>>> x = [1,1,1,2,2,2,5,25,1,1]
>>> np.array(np.unique(x, return_counts=True)).T
    array([[ 1,  5],
           [ 2,  3],
           [ 5,  1],
           [25,  1]])

Resposta original:

você pode usar scipy.stats.itemfreq

>>> from scipy.stats import itemfreq
>>> x = [1,1,1,2,2,2,5,25,1,1]
>>> itemfreq(x)
/usr/local/bin/python:1: DeprecationWarning: `itemfreq` is deprecated! `itemfreq` is deprecated and will be removed in a future version. Use instead `np.unique(..., return_counts=True)`
array([[  1.,   5.],
       [  2.,   3.],
       [  5.,   1.],
       [ 25.,   1.]])
McKelvin
fonte
1
Parece a abordagem mais pitônica de longe. Além disso, encontrei problemas com problemas "objeto muito profundo para a matriz desejada" com np.bincount em matrizes 100k x 100k.
31514
1
Eu prefiro sugerir a poser pergunta original para alterar a resposta accpted do primeiro para este, para aumentar a sua visiblity
wiswit
É lento para versões anteriores a 0.14.
Jason S
observe que, se a matriz estiver cheia de cadeias, os dois elementos em cada um dos itens retornados também serão cadeias.
user1269942
Looks como itemfreq foi preterido
Terence Parr
48

Eu também estava interessado nisso, então fiz uma pequena comparação de desempenho (usando o perfplot , um projeto meu para animais de estimação). Resultado:

y = np.bincount(a)
ii = np.nonzero(y)[0]
out = np.vstack((ii, y[ii])).T

é de longe o mais rápido. (Observe a escala do log.)

insira a descrição da imagem aqui


Código para gerar o gráfico:

import numpy as np
import pandas as pd
import perfplot
from scipy.stats import itemfreq


def bincount(a):
    y = np.bincount(a)
    ii = np.nonzero(y)[0]
    return np.vstack((ii, y[ii])).T


def unique(a):
    unique, counts = np.unique(a, return_counts=True)
    return np.asarray((unique, counts)).T


def unique_count(a):
    unique, inverse = np.unique(a, return_inverse=True)
    count = np.zeros(len(unique), np.int)
    np.add.at(count, inverse, 1)
    return np.vstack((unique, count)).T


def pandas_value_counts(a):
    out = pd.value_counts(pd.Series(a))
    out.sort_index(inplace=True)
    out = np.stack([out.keys().values, out.values]).T
    return out


perfplot.show(
    setup=lambda n: np.random.randint(0, 1000, n),
    kernels=[bincount, unique, itemfreq, unique_count, pandas_value_counts],
    n_range=[2 ** k for k in range(26)],
    logx=True,
    logy=True,
    xlabel="len(a)",
)
Nico Schlömer
fonte
1
Obrigado por postar o código para gerar o gráfico. Não sabia sobre perfplot até agora. Parece útil.
ruffsl
Eu era capaz de executar o código, adicionando a opção equality_check=array_sorteqna perfplot.show(). O que estava causando um erro (no Python 2) foi pd.value_counts(mesmo com sort = False).
user2314737
33

Usando o módulo pandas:

>>> import pandas as pd
>>> import numpy as np
>>> x = np.array([1,1,1,2,2,2,5,25,1,1])
>>> pd.value_counts(x)
1     5
2     3
25    1
5     1
dtype: int64
ivankeller
fonte
5
pd.Series () não é necessário. Caso contrário, bom exemplo. Numpy também. Os pandas podem usar uma lista simples como entrada.
Yohan Obadia
1
@YohanObadia - dependendo do tamanho da matriz, a primeira conversão para uma série tornou a operação final mais rápida para mim. Eu acho que na marca de cerca de 50.000 valores.
N1k31t4 30/10
1
Eu editei a minha resposta para levar em conta o comentário relevante a partir @YohanObadia
ivankeller
19

Essa é de longe a solução mais geral e com melhor desempenho; surpreso ainda não ter sido publicado.

import numpy as np

def unique_count(a):
    unique, inverse = np.unique(a, return_inverse=True)
    count = np.zeros(len(unique), np.int)
    np.add.at(count, inverse, 1)
    return np.vstack(( unique, count)).T

print unique_count(np.random.randint(-10,10,100))

Diferentemente da resposta atualmente aceita, funciona em qualquer tipo de dados que possa ser classificado (não apenas ints positivo) e possui desempenho ideal; a única despesa significativa está na classificação feita pelo np.unique.

Eelco Hoogendoorn
fonte
não funciona:AttributeError: 'numpy.ufunc' object has no attribute 'at'
PR
Um método mais simples seria chamarnp.bincount(inverse)
ali_m
15

numpy.bincounté provavelmente a melhor escolha. Se sua matriz contiver algo além de números inteiros densos, pode ser útil agrupá-la da seguinte forma:

def count_unique(keys):
    uniq_keys = np.unique(keys)
    bins = uniq_keys.searchsorted(keys)
    return uniq_keys, np.bincount(bins)

Por exemplo:

>>> x = array([1,1,1,2,2,2,5,25,1,1])
>>> count_unique(x)
(array([ 1,  2,  5, 25]), array([5, 3, 1, 1]))
Bi Rico
fonte
8

Mesmo que já tenha sido respondido, sugiro uma abordagem diferente que faça uso numpy.histogram. Tal função, dada uma sequência, retorna a frequência de seus elementos agrupados em posições .

Cuidado, porém : ele funciona neste exemplo porque os números são inteiros. Se eles onde números reais, então esta solução não se aplicaria tão bem.

>>> from numpy import histogram
>>> y = histogram (x, bins=x.max()-1)
>>> y
(array([5, 3, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
       1]),
 array([  1.,   2.,   3.,   4.,   5.,   6.,   7.,   8.,   9.,  10.,  11.,
        12.,  13.,  14.,  15.,  16.,  17.,  18.,  19.,  20.,  21.,  22.,
        23.,  24.,  25.]))
Jir
fonte
5
import pandas as pd
import numpy as np
x = np.array( [1,1,1,2,2,2,5,25,1,1] )
print(dict(pd.Series(x).value_counts()))

Isso fornece: {1: 5, 2: 3, 5: 1, 25: 1}

Kerem T
fonte
1
collections.Counter(x)também dá o mesmo resultado. Eu acredito que o OP quer uma saída que se assemelhe à tablefunção R. Manter o Seriespode ser mais útil.
Pylang #
Observe que seria necessário transferir para pd.Series(x).reshape(-1)se for um array multidimensional.
Natsuapo
4

Para contar não inteiros únicos - semelhante à resposta de Eelco Hoogendoorn, mas consideravelmente mais rápido (fator 5 na minha máquina), eu costumava weave.inlinecombinar numpy.uniquecom um pouco de código c;

import numpy as np
from scipy import weave

def count_unique(datain):
  """
  Similar to numpy.unique function for returning unique members of
  data, but also returns their counts
  """
  data = np.sort(datain)
  uniq = np.unique(data)
  nums = np.zeros(uniq.shape, dtype='int')

  code="""
  int i,count,j;
  j=0;
  count=0;
  for(i=1; i<Ndata[0]; i++){
      count++;
      if(data(i) > data(i-1)){
          nums(j) = count;
          count = 0;
          j++;
      }
  }
  // Handle last value
  nums(j) = count+1;
  """
  weave.inline(code,
      ['data', 'nums'],
      extra_compile_args=['-O2'],
      type_converters=weave.converters.blitz)
  return uniq, nums

Informações do perfil

> %timeit count_unique(data)
> 10000 loops, best of 3: 55.1 µs per loop

numpyVersão pura da Eelco :

> %timeit unique_count(data)
> 1000 loops, best of 3: 284 µs per loop

Nota

Há redundância aqui (também uniqueexecuta uma classificação), o que significa que o código provavelmente poderia ser otimizado ainda mais, colocando a uniquefuncionalidade dentro do loop do código c.

jmetz
fonte
4

Pergunta antiga, mas eu gostaria de fornecer a minha própria solução, que acabou sendo a mais rápida, use o normal em listvez de np.arraycomo entrada (ou transfira para a lista primeiro), com base no meu teste de bancada.

Confira se você o encontrar também.

def count(a):
    results = {}
    for x in a:
        if x not in results:
            results[x] = 1
        else:
            results[x] += 1
    return results

Por exemplo,

>>>timeit count([1,1,1,2,2,2,5,25,1,1]) would return:

100000 loops, o melhor de 3: 2,26 µs por loop

>>>timeit count(np.array([1,1,1,2,2,2,5,25,1,1]))

100000 loops, o melhor de 3: 8,8 µs por loop

>>>timeit count(np.array([1,1,1,2,2,2,5,25,1,1]).tolist())

100000 loops, o melhor de 3: 5,85 µs por loop

Embora a resposta aceita seja mais lenta, a scipy.stats.itemfreqsolução é ainda pior.


Um teste mais aprofundado não confirmou a expectativa formulada.

from zmq import Stopwatch
aZmqSTOPWATCH = Stopwatch()

aDataSETasARRAY = ( 100 * abs( np.random.randn( 150000 ) ) ).astype( np.int )
aDataSETasLIST  = aDataSETasARRAY.tolist()

import numba
@numba.jit
def numba_bincount( anObject ):
    np.bincount(    anObject )
    return

aZmqSTOPWATCH.start();np.bincount(    aDataSETasARRAY );aZmqSTOPWATCH.stop()
14328L

aZmqSTOPWATCH.start();numba_bincount( aDataSETasARRAY );aZmqSTOPWATCH.stop()
592L

aZmqSTOPWATCH.start();count(          aDataSETasLIST  );aZmqSTOPWATCH.stop()
148609L

Ref. comentários abaixo sobre cache e outros efeitos colaterais na RAM que influenciam um pequeno conjunto de dados massivamente resultados de testes repetitivos.

Rain Lee
fonte
Essa resposta é realmente boa, pois mostra que numpynão é necessariamente o caminho a percorrer.
Mahdi
@Rain Lee interessante. Você validou cruzadamente a hipótese da lista também em algum tamanho de conjunto de dados não capaz de armazenar em cache? Vamos assumir 150.000 itens aleatórios em qualquer representação e medidos um pouco mais precisos em uma única execução, como no exemplo de aZmqStopwatch.start (); count (aRepresentation); aZmqStopwatch.stop () ?
user3666197
Fiz alguns testes e sim, existem enormes diferenças no desempenho real do conjunto de dados. O teste requer um pouco mais de compreensão da mecânica interna do python do que executar apenas um loop de força bruta e citar nanossegundos in-vitro não realistas . Conforme testado - um np.bincount () pode ser feito para lidar com 150.000 matrizes em menos de 600 [us] enquanto a contagem def -ed acima () em uma representação de lista pré-convertida levou mais de 122.000 [us]
user3666197
Sim, minha regra de ouro é insensível a qualquer coisa que possa lidar com pequenas quantidades de latência, mas tem potencial para ser muito grande, listas para conjuntos de dados menores onde a latência é crítica e, é claro, o real benchmarking FTW :)
David,
1

algo assim deve fazer:

#create 100 random numbers
arr = numpy.random.random_integers(0,50,100)

#create a dictionary of the unique values
d = dict([(i,0) for i in numpy.unique(arr)])
for number in arr:
    d[j]+=1   #increment when that value is found

Além disso, este post anterior sobre a contagem eficiente de elementos únicos parece bastante semelhante à sua pergunta, a menos que esteja faltando alguma coisa.

benjaminmgross
fonte
A questão vinculada é meio semelhante, mas parece que ele está trabalhando com tipos de dados mais complicados.
Abe
1

contagem de freqüência multidimensional, ou seja, contando matrizes.

>>> print(color_array    )
  array([[255, 128, 128],
   [255, 128, 128],
   [255, 128, 128],
   ...,
   [255, 128, 128],
   [255, 128, 128],
   [255, 128, 128]], dtype=uint8)


>>> np.unique(color_array,return_counts=True,axis=0)
  (array([[ 60, 151, 161],
    [ 60, 155, 162],
    [ 60, 159, 163],
    [ 61, 143, 162],
    [ 61, 147, 162],
    [ 61, 162, 163],
    [ 62, 166, 164],
    [ 63, 137, 162],
    [ 63, 169, 164],
   array([     1,      2,      2,      1,      4,      1,      1,      2,
         3,      1,      1,      1,      2,      5,      2,      2,
       898,      1,      1,  
vishal
fonte
1
import pandas as pd
import numpy as np

print(pd.Series(name_of_array).value_counts())
RAJAT BHATHEJA
fonte
0
from collections import Counter
x = array( [1,1,1,2,2,2,5,25,1,1] )
mode = counter.most_common(1)[0][0]
伍宜昌
fonte