intorpidito: la frequenza più efficiente conta per valori univoci in un array


244

In numpy/ scipy, esiste un modo efficiente per ottenere conteggi di frequenza per valori univoci in un array?

Qualcosa del genere:

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

(Per te, R utenti là fuori, sto praticamente cercando la table()funzione)


5
È collections.Counter(x)sufficiente?
Pylang,

1
Sarebbe meglio, penso, se ora spunta questa risposta come corretta per la tua domanda: stackoverflow.com/a/25943480/9024698 .
Emarginato il

Collections.counter è piuttosto lento. Vedere il mio post: stackoverflow.com/questions/41594940/...
Sembei Norimaki

Risposte:


161

Dai un'occhiata 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]

E poi:

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 comunque si desidera combinare i conteggi e i valori univoci.


42
Ciao, questo non funzionerebbe se gli elementi di x hanno un tipo diverso da int.
Manoj,

7
Non funzionerà se si tratta di qualcosa di diverso dagli ints non negativi e sarà molto inefficiente nello spazio se gli ints sono distanziati.
Erik,

Con numpy versione 1.10 ho scoperto che, per il conteggio intero, è circa 6 volte più veloce di np.unique. Inoltre, tieni presente che conta anche ints negativi, se vengono forniti i parametri giusti.
Jihun,

@Manoj: I miei elementi x sono array. Sto testando la soluzione di jme.
Catalina Chircu,

508

A partire da Numpy 1.9, il metodo più semplice e veloce è semplicemente quello di utilizzare numpy.unique, che ora ha un return_countsargomento di parole chiave:

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

Che dà:

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

Un rapido confronto 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

22
Grazie per l'aggiornamento! Questa è ora, IMO, la risposta corretta.
Erve1879,

1
BAM! ecco perché aggiorniamo ... quando troviamo risposte come queste. Così lungo intorpidito 1.8. Come possiamo portarlo in cima all'elenco?
user1269942

Se viene visualizzato l'errore: TypeError: unique () ha un argomento inaspettato per la parola chiave "return_counts", basta fare: unique, conteggi = np.unique (x, True)
NumesSanguis

3
@NumesSanguis Quale versione di numpy stai usando? Prima della v1.9, l' return_countsargomento della parola chiave non esisteva, il che potrebbe spiegare l'eccezione. In tal caso, i documenti suggeriscono che np.unique(x, True)è equivalente a np.unique(x, return_index=True), che non restituisce conteggi.
jme

1
Nelle vecchie versioni intorpidite il tipico idioma per ottenere la stessa cosa era unique, idx = np.unique(x, return_inverse=True); counts = np.bincount(idx). Quando è stata aggiunta questa funzione (vedi qui ) alcuni test informali hanno avuto il tempo di eseguire il return_countsclock oltre 5 volte più velocemente.
Jaime,

133

Aggiornamento: il metodo indicato nella risposta originale è obsoleto, dovremmo invece utilizzare il nuovo modo:

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

Risposta originale:

puoi usare 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.]])

1
Sembra di gran lunga l'approccio più pitonico. Inoltre, ho riscontrato problemi con "oggetto troppo profondo per l'array desiderato" con np.bincount su matrici 100k x 100k.
metasequoia,

1
Preferisco suggerire la domanda iniziale per cambiare la risposta accettata dalla prima a questa, per aumentare la sua visibilità
wiswit

Tuttavia, è lento per le versioni precedenti alla 0.14.
Jason S,

prendere nota che se l'array è pieno di stringhe, anche entrambi gli elementi in ciascuno degli elementi restituiti sono stringhe.
user1269942

Sembra che itemfreq sia stato deprecato
Terence Parr,

48

Mi interessava anche questo, quindi ho fatto un piccolo confronto delle prestazioni (usando perfplot , un mio progetto per animali domestici). Risultato:

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

è di gran lunga il più veloce. (Notare il ridimensionamento del registro.)

inserisci qui la descrizione dell'immagine


Codice per generare 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)",
)

1
Grazie per aver pubblicato il codice per generare la trama. Prima non sapevo di perfplot . Sembra a portata di mano.
ruffsl

Sono stato in grado di eseguire il tuo codice aggiungendo l'opzione equality_check=array_sorteqin perfplot.show(). Ciò che stava causando un errore (in Python 2) era pd.value_counts(anche con sort = False).
user2314737

33

Utilizzando il modulo Panda:

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

5
pd.Series () non è necessario. Altrimenti, buon esempio. Numpy pure. I panda possono prendere un semplice elenco come input.
Yohan Obadia,

1
@YohanObadia - a seconda della dimensione dell'array, la prima conversione in una serie ha reso l'operazione finale più veloce per me. Immagino al segno di circa 50.000 valori.
n1k31t4,

1
Ho modificato la mia risposta per tenere conto del commento pertinente di @YohanObadia
ivankeller

19

Questa è di gran lunga la soluzione più generale e performante; sorpreso che non sia stato ancora pubblicato.

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 differenza della risposta attualmente accettata, funziona su qualsiasi tipo di dati che è ordinabile (non solo ints positivi) e ha prestazioni ottimali; l'unica spesa significativa è nell'ordinamento fatto da np.unique.


non funziona:AttributeError: 'numpy.ufunc' object has no attribute 'at'
PR

Un metodo più semplice sarebbe quello di chiamarenp.bincount(inverse)
ali_m il

15

numpy.bincountè probabilmente la scelta migliore. Se il tuo array contiene qualcosa di diverso da piccoli numeri densi, potrebbe essere utile avvolgerlo in questo modo:

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

Per esempio:

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

8

Anche se ha già ricevuto una risposta, suggerisco un approccio diverso che si avvale di numpy.histogram. Tale funzione, data una sequenza, restituisce la frequenza dei suoi elementi raggruppati in bin .

Attenzione però : funziona in questo esempio perché i numeri sono numeri interi. Se fossero numeri reali, questa soluzione non si applicherebbe altrettanto bene.

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

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

Questo ti dà: {1: 5, 2: 3, 5: 1, 25: 1}


1
collections.Counter(x)anche dare lo stesso risultato. Credo che l'OP voglia un output che assomigli alla tablefunzione R. Mantenere Seriespotrebbe essere più utile.
Pylang,

Si noti che sarebbe necessario trasferire pd.Series(x).reshape(-1)se si tratta di un array multidimensionale.
natsuapo,

4

Per contare non interi interi - simile alla risposta di Eelco Hoogendoorn ma considerevolmente più veloce (fattore 5 sulla mia macchina), ero solito weave.inlinecombinare numpy.uniquecon un po 'di codice 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

Informazioni sul profilo

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

La numpyversione pura di Eelco :

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

Nota

C'è ridondanza qui ( uniqueesegue anche un ordinamento), il che significa che probabilmente il codice potrebbe essere ulteriormente ottimizzato inserendo la uniquefunzionalità all'interno del ciclo c-code.


4

Vecchia domanda, ma mi piacerebbe fornire la mia soluzione che risulta essere la più veloce, usare normale listinvece che np.arraycome input (o trasferire prima nell'elenco), in base al mio test al banco.

Dai un'occhiata se lo incontri anche tu.

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

Per esempio,

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

100000 loop, meglio di 3: 2,26 µs per loop

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

100000 loop, meglio di 3: 8,8 µs per loop

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

100000 loop, meglio di 3: 5,85 µs per loop

Mentre la risposta accettata sarebbe più lenta e la scipy.stats.itemfreqsoluzione è ancora peggiore.


Un test più approfondito non ha confermato le aspettative formulate.

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. commenti di seguito sulla cache e altri effetti collaterali in-RAM che influenzano un piccolo set di dati risultati di test notevolmente ripetitivi.


Questa risposta è davvero buona, poiché dimostra che numpynon è necessariamente la strada da percorrere.
Mahdi,

@Rain Lee interessante. Hai convalidato in modo incrociato l'ipotesi dell'elenco anche su alcune dimensioni del set di dati che non sono in grado di memorizzare nella cache? Supponiamo che 150.000 elementi casuali in entrambe le rappresentazioni e misurati un po 'più precisi su una singola corsa come da un esempio di aZmqStopwatch.start (); count (aRepresentation); aZmqStopwatch.stop () ?
user3666197

Ho effettuato alcuni test e sì, ci sono enormi differenze nelle prestazioni del set di dati reale. I test richiedono un po 'più di comprensione della meccanica interna di Python rispetto all'esecuzione di soli cicli scalati a forza bruta e citano nanosecondi in vitro non realistici . Come testato - un np.bincount () può essere fatto per gestire 150.000 matrice all'interno inferiore a 600 [noi] , mentre il sopra def -ed count () su una rappresentazione lista pre-convertita esso ha richiesto più di 122.000 [noi]
user3666197

Sì, la mia regola empirica è insensibile per tutto ciò che può gestire piccole quantità di latenza ma ha il potenziale per essere molto grande, elenchi per set di dati più piccoli in cui la latenza è critica e, naturalmente, un vero benchmarking FTW :)
David

1

qualcosa del genere dovrebbe farlo:

#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

Inoltre, questo post precedente su Conteggio efficiente di elementi unici sembra abbastanza simile alla tua domanda, a meno che non mi manchi qualcosa.


La domanda collegata è in qualche modo simile, ma sembra che stia lavorando con tipi di dati più complicati.
Abe,

1

conteggio di frequenza multidimensionale, ovvero conteggio di array.

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

1
import pandas as pd
import numpy as np

print(pd.Series(name_of_array).value_counts())

0
from collections import Counter
x = array( [1,1,1,2,2,2,5,25,1,1] )
mode = counter.most_common(1)[0][0]
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.