numpy: la frecuencia más eficiente cuenta para valores únicos en una matriz

244

En numpy/ scipy, ¿hay una manera eficiente de obtener conteos de frecuencia para valores únicos en una matriz?

Algo en este 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 ustedes, usuarios de R, básicamente estoy buscando la table()función)

Abe
fuente
55
Es collections.Counter(x)suficiente?
pylang
1
Creo que sería mejor si marca ahora esta respuesta como correcta para su pregunta: stackoverflow.com/a/25943480/9024698 .
Desterrado el
Collections.counter es bastante lento. Vea mi publicación: stackoverflow.com/questions/41594940/…
Sembei Norimaki

Respuestas:

161

Echa un vistazo a 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]

Y entonces:

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

o:

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

o como quiera combinar los recuentos y los valores únicos.

JoshAdel
fuente
42
Hola, esto no funcionaría si los elementos de x tienen un tipo diferente de int.
Manoj
77
No funcionará si son algo más que entradas no negativas, y será muy poco eficiente en cuanto al espacio si las entradas están espaciadas.
Erik
Con la versión numpy 1.10 descubrí que, para contar enteros, es aproximadamente 6 veces más rápido que np.unique. Además, tenga en cuenta que también cuenta entradas negativas, si se dan los parámetros correctos.
Jihun
@Manoj: Mis elementos x son matrices. Estoy probando la solución de jme.
Catalina Chircu
508

A partir de Numpy 1.9, el método más fácil y rápido es simplemente usar numpy.unique, que ahora tiene un return_countsargumento de palabra clave:

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

Lo que da:

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

Una comparación rápida con 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
fuente
22
Gracias por actualizar! Esta es ahora, OMI, la respuesta correcta.
Erve1879
1
BAM! es por eso que actualizamos ... cuando encontramos respuestas como estas. Hasta luego numpy 1.8. ¿Cómo podemos llevar esto a la parte superior de la lista?
usuario1269942
Si obtiene el error: TypeError: unique () obtuvo un argumento de palabra clave inesperado 'return_counts', solo haga: unique, count = np.unique (x, True)
NumesSanguis
3
@NumesSanguis ¿Qué versión de numpy estás usando? Antes de v1.9, el return_countsargumento de la palabra clave no existía, lo que podría explicar la excepción. En ese caso, los documentos sugieren que np.unique(x, True)es equivalente a np.unique(x, return_index=True), que no devuelve conteos.
jme
1
En versiones anteriores de numpy, el idioma típico para obtener lo mismo era unique, idx = np.unique(x, return_inverse=True); counts = np.bincount(idx). Cuando se agregó esta función (ver aquí ), algunas pruebas informales tuvieron el uso del return_countsreloj más de 5 veces más rápido.
Jaime
133

Actualización: El método mencionado en la respuesta original está en desuso, en su lugar deberíamos usar la nueva forma:

>>> 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]])

Respuesta original:

puedes 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
fuente
1
Parece el enfoque más pitónico con diferencia. Además, encontré problemas con el "objeto demasiado profundo para la matriz deseada" con np.bincount en matrices de 100k x 100k.
metasequoia
1
Yo más bien sugeriría que el poser pregunta original para cambiar la respuesta accpted del primero a éste, para aumentar su visiblity de
wiswit
Sin embargo, es lento para versiones anteriores a 0.14.
Jason S
tenga en cuenta que si la matriz está llena de cadenas, ambos elementos en cada uno de los elementos devueltos también son cadenas.
user1269942
Parece que itemfreq ha quedado en desuso
Terence Parr
48

También estaba interesado en esto, así que hice una pequeña comparación de rendimiento (usando perfplot , un proyecto mío mío). Resultado:

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

Es, con mucho, el más rápido. (Tenga en cuenta la escala de registro).

ingrese la descripción de la imagen aquí


Código para generar la trama:

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
fuente
1
Gracias por publicar el código para generar la trama. No sabía sobre perfplot antes de ahora. Parece práctico
ruffsl
Yo era capaz de ejecutar su código añadiendo la opción equality_check=array_sorteqde perfplot.show(). Lo que estaba causando un error (en Python 2) fue pd.value_counts(incluso con sort = False).
user2314737
33

Usando el módulo de 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
fuente
55
pd.Series () no es necesario. De lo contrario, buen ejemplo. Numpy también. Los pandas pueden tomar una lista simple como entrada.
Yohan Obadia el
1
@YohanObadia: dependiendo del tamaño de la matriz, convertirlo primero en una serie ha hecho que la operación final sea más rápida para mí. Supongo que la marca de alrededor de 50,000 valores.
n1k31t4
1
Edité mi respuesta para tener en cuenta el comentario relevante de @YohanObadia
ivankeller
19

Esta es, con mucho, la solución más general y eficaz; sorprendido no ha sido publicado todavía.

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

A diferencia de la respuesta actualmente aceptada, funciona en cualquier tipo de datos que se pueda ordenar (no solo en entradas positivas), y tiene un rendimiento óptimo; el único gasto significativo está en la clasificación realizada por np.unique.

Eelco Hoogendoorn
fuente
no funciona:AttributeError: 'numpy.ufunc' object has no attribute 'at'
PR
Un método más simple sería llamarnp.bincount(inverse)
ali_m
15

numpy.bincountEs probablemente la mejor opción. Si su matriz contiene algo además de pequeños enteros densos, podría ser útil envolverla de esta manera:

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

Por ejemplo:

>>> 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
fuente
8

Aunque ya ha sido respondido, sugiero un enfoque diferente que hace uso de numpy.histogram. Dicha función, dada una secuencia, devuelve la frecuencia de sus elementos agrupados en contenedores .

Sin embargo, tenga cuidado : funciona en este ejemplo porque los números son enteros. Si fueran números reales, entonces esta solución no se aplicaría tan bien.

>>> 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
fuente
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()))

Esto le da: {1: 5, 2: 3, 5: 1, 25: 1}

Kerem T
fuente
1
collections.Counter(x)También dan el mismo resultado. Creo que el OP quiere una salida que se parezca a la tablefunción R. Mantener el Seriespuede ser más útil.
pylang
Tenga en cuenta que sería necesario transferir a pd.Series(x).reshape(-1)si se trata de una matriz multidimensional.
natsuapo
4

Para contar números no enteros únicos , similar a la respuesta de Eelco Hoogendoorn pero considerablemente más rápido (factor 5 en mi máquina), solía weave.inlinecombinarme numpy.uniquecon un poco 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

Información de perfil

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

La numpyversión pura de Eelco :

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

Nota

Aquí hay redundancia (también se uniquerealiza una ordenación), lo que significa que el código probablemente podría optimizarse aún más al poner la uniquefuncionalidad dentro del bucle de código C.

jmetz
fuente
4

Antigua pregunta, pero me gustaría proporcionar mi propia solución que resulte ser la más rápida, usar normal en listlugar de np.arraycomo entrada (o transferir a la lista en primer lugar), según mi prueba de banco.

Compruébalo si te encuentras con él también.

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

Por ejemplo,

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

100000 bucles, lo mejor de 3: 2.26 µs por bucle

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

100000 bucles, lo mejor de 3: 8.8 µs por bucle

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

100000 bucles, lo mejor de 3: 5.85 µs por bucle

Si bien la respuesta aceptada sería más lenta, y la scipy.stats.itemfreqsolución es aún peor.


Una prueba más profunda no confirmó la 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

Árbitro. Los comentarios a continuación sobre el caché y otros efectos secundarios en la RAM que influyen en un pequeño conjunto de datos son resultados de pruebas masivamente repetitivos.

Rain Lee
fuente
Esta respuesta es realmente buena, ya que muestra numpyque no es necesariamente el camino a seguir.
Mahdi
@Rain Lee interesante. ¿Ha validado de forma cruzada la hipótesis de la lista también en algún tamaño de conjunto de datos que no admite memoria caché? Supongamos 150,000 elementos aleatorios en cualquier representación y medidos un poco más precisos en una sola ejecución como en un ejemplo de aZmqStopwatch.start (); count (aRepresentation); aZmqStopwatch.stop () ?
user3666197
Hice algunas pruebas y sí, hay grandes diferencias en el rendimiento real del conjunto de datos. Las pruebas requieren un poco más de información sobre la mecánica interna de Python que ejecutar solo bucles escalados de fuerza bruta y citar nanosegundos in vitro no realistas . Según lo probado, se puede hacer un np.bincount () para manejar 150,000 arreglos dentro de menos de 600 [us] mientras que el conteo def -ed anterior () en una representación de lista
preconvertida del
Sí, mi regla general es incómoda para cualquier cosa que pueda manejar pequeñas cantidades de latencia, pero tiene el potencial de ser muy grande, listas para conjuntos de datos más pequeños donde la latencia es crítica y, por supuesto, la evaluación comparativa real FTW :)
David
1

algo como esto debería hacerlo:

#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

Además, esta publicación anterior sobre el conteo eficiente de elementos únicos parece bastante similar a su pregunta, a menos que me falte algo.

benjaminmgross
fuente
La pregunta vinculada es similar, pero parece que está trabajando con tipos de datos más complicados.
Abe
1

recuento de frecuencia multidimensional, es decir, recuento de matrices.

>>> 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
fuente
1
import pandas as pd
import numpy as np

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