python - numpy: recuentos de frecuencia más eficientes para valores únicos en una matriz
arrays performance (12)
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 argumento de palabra clave return_counts
:
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
En numpy
/ scipy
, ¿hay una manera eficiente de obtener recuentos de frecuencia para valores únicos en una matriz?
Algo en esta línea:
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 busco la función table()
)
Aunque ya se ha respondido, sugiero un enfoque diferente que haga uso de numpy.histogram
. Tal función dada una secuencia devuelve la frecuencia de sus elementos agrupados en contenedores .
Sin embargo, ten cuidado : funciona en este ejemplo porque los números son enteros. Si fueran números reales, 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.]))
Eche 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.
Esta es de lejos la solución más general y eficiente; sorprendido de que no haya 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 inglés), y tiene un rendimiento óptimo; el único gasto significativo es en la clasificación hecha por np.unique.
Para contar enteros no enteros únicos , similares a la respuesta de Eelco Hoogendoorn pero considerablemente más rápidos (factor de 5 en mi máquina), utilicé weave.inline
para combinar numpy.unique
con 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 versión numpy
pura de numpy
:
> %timeit unique_count(data)
> 1000 loops, best of 3: 284 µs per loop
Nota
Aquí hay redundancia (el unique
realiza una clasificación), lo que significa que el código probablemente podría optimizarse aún más al colocar la funcionalidad unique
dentro del ciclo c-loop.
También me interesó esto, así que hice una pequeña comparación de rendimiento (usando perfplot , un proyecto mío favorito). 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).
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(22)],
logx=True,
logy=True,
xlabel=''len(a)''
)
Una vieja pregunta, pero me gustaría ofrecer mi propia solución, que resulta ser la más rápida, utilizar la list
normal en lugar de np.array
como entrada (o transferirla a la lista en primer lugar), según mi prueba de banco.
Compruébalo si lo encuentras 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 ciclo
>>>timeit count(np.array([1,1,1,2,2,2,5,25,1,1]))
100000 bucles, lo mejor de 3: 8,8 μs por ciclo
>>>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 ciclo
Mientras que la respuesta aceptada sería más lenta, y la solución scipy.stats.itemfreq
es aún peor.
Una prueba más en profundidad 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. comentarios a continuación sobre el caché y otros efectos secundarios en RAM que influyen en un pequeño conjunto de datos de resultados de pruebas masivamente repetitivos.
Usando el 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(pd.Series(x))
1 5
2 3
25 1
5 1
dtype: int64
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 tu pregunta, a menos que me falta algo.
puedes usar scipy.stats.itemfreq
>>> from scipy.stats import itemfreq
>>> x = [1,1,1,2,2,2,5,25,1,1]
>>> itemfreq(x)
array([[ 1., 5.],
[ 2., 3.],
[ 5., 1.],
[ 25., 1.]])
numpy.bincount
es probablemente la mejor opción. Si su matriz contiene algo además de enteros pequeños y densos, podría ser útil envolverlo de la siguiente 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]))
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 te da: {1: 5, 2: 3, 5: 1, 25: 1}