numpy: Die effizienteste Frequenz zählt für eindeutige Werte in einem Array


244

Gibt es in numpy/ scipyeine effiziente Möglichkeit, Frequenzzählungen für eindeutige Werte in einem Array abzurufen?

Etwas in diese Richtung:

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

(Für Sie, R-Benutzer da draußen, suche ich im Grunde nach der table()Funktion)


5
Ist collections.Counter(x)ausreichend
Pylang

1
Ich denke, es wäre besser, wenn Sie diese Antwort jetzt als richtig für Ihre Frage ankreuzen : stackoverflow.com/a/25943480/9024698 .
Ausgestoßener

Collections.counter ist ziemlich langsam. Siehe meinen Beitrag: stackoverflow.com/questions/41594940/…
Sembei Norimaki

Antworten:


161

Schauen Sie sich an 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]

Und dann:

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

oder:

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

oder wie auch immer Sie die Anzahl und die eindeutigen Werte kombinieren möchten.


42
Hallo, das würde nicht funktionieren, wenn Elemente von x einen anderen dtype als int haben.
Manoj

7
Es funktioniert nicht, wenn es sich um etwas anderes als nicht negative Ints handelt, und es ist sehr platzsparend, wenn die Ints voneinander entfernt sind.
Erik

Mit numpy Version 1.10 habe ich festgestellt, dass das Zählen von Ganzzahlen ungefähr sechsmal schneller ist als np.unique. Beachten Sie auch, dass es auch negative Ints zählt, wenn die richtigen Parameter angegeben werden.
Jihun

@Manoj: Meine Elemente x sind Arrays. Ich teste die Lösung von jme.
Catalina Chircu

508

Ab Numpy 1.9 ist die einfachste und schnellste Methode die einfache Verwendung numpy.unique, die jetzt ein return_countsSchlüsselwortargument enthält:

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

Welches gibt:

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

Ein schneller Vergleich mit 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
Danke für das Update! Dies ist nun, IMO, die richtige Antwort.
Erve1879

1
BAM! Deshalb aktualisieren wir ... wenn wir Antworten wie diese finden. So lange numpy 1.8. Wie können wir dies an die Spitze der Liste bringen?
user1269942

Wenn Sie den Fehler erhalten: TypeError: unique () hat ein unerwartetes Schlüsselwortargument 'return_counts' erhalten, tun Sie einfach: unique, count = np.unique (x, True)
NumesSanguis

3
@NumesSanguis Welche Version von Numpy verwenden Sie? Vor Version 1.1 war das return_countsSchlüsselwortargument nicht vorhanden, was die Ausnahme erklären könnte. In diesem Fall schlagen die Dokumente vor , dass dies np.unique(x, True)äquivalent zu ist np.unique(x, return_index=True), was keine Anzahl zurückgibt.
jme

1
In älteren Numpy-Versionen war die typische Redewendung, um das Gleiche zu bekommen unique, idx = np.unique(x, return_inverse=True); counts = np.bincount(idx). Als diese Funktion hinzugefügt wurde (siehe hier ), wurde bei einigen informellen Tests return_countsmehr als fünfmal schneller getaktet.
Jaime

133

Update: Die in der ursprünglichen Antwort erwähnte Methode ist veraltet. Wir sollten stattdessen den neuen Weg verwenden:

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

Ursprüngliche Antwort:

Sie können scipy.stats.itemfreq verwenden

>>> 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
Scheint bei weitem der pythonischste Ansatz zu sein. Außerdem sind Probleme mit "Objekt zu tief für gewünschtes Array" mit np.bincount auf 100k x 100k-Matrizen aufgetreten.
Metasequoia

1
Ich schlage eher den ursprünglichen Fragesteller vor, die angeforderte Antwort von der ersten auf diese zu ändern, um ihre Sichtbarkeit zu erhöhen
wiswit

Für Versionen vor 0.14 ist es jedoch langsam.
Jason S

Beachten Sie, dass, wenn das Array voller Zeichenfolgen ist, beide Elemente in jedem der zurückgegebenen Elemente auch Zeichenfolgen sind.
user1269942

Sieht aus wie itemfreq veraltet ist
Terence Parr

48

Das hat mich auch interessiert, also habe ich einen kleinen Leistungsvergleich durchgeführt (mit perfplot , einem meiner Lieblingsprojekte ). Ergebnis:

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

ist bei weitem der schnellste. (Beachten Sie die Protokollskalierung.)

Geben Sie hier die Bildbeschreibung ein


Code zum Generieren des Plots:

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
Vielen Dank, dass Sie den Code zum Generieren des Plots veröffentlicht haben. Ich wusste vorher nichts über Perfplot . Sieht praktisch aus.
Ruffsl

Ich konnte Ihren Code ausführen, indem ich die Option equality_check=array_sorteqin hinzufügte perfplot.show(). Was einen Fehler verursachte (in Python 2) war pd.value_counts(auch mit sort = False).
user2314737

33

Pandas-Modul verwenden:

>>> 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 () ist nicht erforderlich. Ansonsten gutes Beispiel. Numpy auch. Pandas können eine einfache Liste als Eingabe verwenden.
Yohan Obadia

1
@YohanObadia - Abhängig von der Größe des Arrays hat die erste Konvertierung in eine Serie den endgültigen Vorgang für mich beschleunigt. Ich würde die Marke von rund 50.000 Werten schätzen.
n1k31t4

1
Ich habe meine Antwort bearbeitet, um den relevanten Kommentar von @YohanObadia
ivankeller

19

Dies ist bei weitem die allgemeinste und performanteste Lösung; überrascht, dass es noch nicht veröffentlicht wurde.

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

Im Gegensatz zur derzeit akzeptierten Antwort funktioniert es mit jedem Datentyp, der sortierbar ist (nicht nur mit positiven Ints), und bietet eine optimale Leistung. Der einzige erhebliche Aufwand liegt in der Sortierung nach np.unique.


funktioniert nicht:AttributeError: 'numpy.ufunc' object has no attribute 'at'
PR

Eine einfachere Methode wäre,np.bincount(inverse)
ali_m

15

numpy.bincountist die wahrscheinlich beste Wahl. Wenn Ihr Array etwas anderes als kleine dichte Ganzzahlen enthält, kann es nützlich sein, es wie folgt zu verpacken:

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

Beispielsweise:

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

Obwohl es bereits beantwortet wurde, schlage ich einen anderen Ansatz vor, der davon Gebrauch macht numpy.histogram. Eine solche Funktion gibt bei einer Sequenz die Häufigkeit ihrer in Bins gruppierten Elemente zurück .

Beachten Sie jedoch, dass dies in diesem Beispiel funktioniert, da Zahlen Ganzzahlen sind. Wenn sie reelle Zahlen wären, würde diese Lösung nicht so gut zutreffen.

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

Dies gibt Ihnen: {1: 5, 2: 3, 5: 1, 25: 1}


1
collections.Counter(x)Geben Sie auch das gleiche Ergebnis. Ich glaube, das OP möchte einen Ausgang, der der R- tableFunktion ähnelt . Das zu behalten Serieskann nützlicher sein.
Pylang

Bitte beachten Sie, dass eine Übertragung erforderlich ist, pd.Series(x).reshape(-1)wenn es sich um ein mehrdimensionales Array handelt.
Natsuapo

4

Um eindeutige Nicht-Ganzzahlen zu zählen - ähnlich wie bei Eelco Hoogendoorn, aber erheblich schneller (Faktor 5 auf meinem Computer) - habe ich früher mit etwas C-Code weave.inlinekombiniert numpy.unique.

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

Profil Information

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

Eelcos reine numpyVersion:

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

Hinweis

Hier gibt es Redundanz ( uniqueführt auch eine Sortierung durch), was bedeutet, dass der Code wahrscheinlich weiter optimiert werden könnte, indem die uniqueFunktionalität in die C-Code-Schleife eingefügt wird.


4

Alte Frage, aber ich möchte meine eigene Lösung bereitstellen, die sich als die schnellste herausstellt. Verwenden Sie normal listanstelle von np.arrayals Eingabe (oder zuerst zur Liste übertragen), basierend auf meinem Bench-Test.

Probieren Sie es aus, wenn Sie auch darauf stoßen.

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

Beispielsweise,

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

100000 Schleifen, am besten 3: 2,26 µs pro Schleife

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

100000 Schleifen, am besten 3: 8,8 µs pro Schleife

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

100000 Schleifen, am besten 3: 5,85 µs pro Schleife

Während die akzeptierte Antwort langsamer wäre und die scipy.stats.itemfreqLösung noch schlechter ist.


Eine eingehendere Prüfung bestätigte die formulierte Erwartung nicht.

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. Kommentare unten zu Cache und anderen In-RAM-Nebenwirkungen, die einen kleinen Datensatz beeinflussen, der sich massiv wiederholt.


Diese Antwort ist wirklich gut, da sie numpynicht unbedingt den richtigen Weg darstellt.
Mahdi

@ Regen Lee interessant. Haben Sie die Listenhypothese auch für eine nicht Cache-fähige Datensatzgröße überprüft? Nehmen wir in beiden Darstellungen 150.000 zufällige Elemente an und messen sie in einem einzelnen Lauf etwas genauer als am Beispiel von aZmqStopwatch.start (); count (aRepresentation); aZmqStopwatch.stop () ?
user3666197

Habe einige Tests durchgeführt und ja, es gibt große Unterschiede in der tatsächlichen Datensatzleistung. Das Testen erfordert etwas mehr Einblick in die interne Mechanik von Python als nur das Ausführen von Brute-Force-skalierten Schleifen und das Zitieren nicht realistischer In-vitro- Nanosekunden. Wie getestet - ein np.bincount () kann innerhalb behandeln 150.000 Array gemacht werden weniger als 600 [us] , während die oben def -ed count () auf einem bereits konvertierten Listendarstellung davon nahm mehr als 122.000 [us]
user3666197

Ja, meine Faustregel ist numpy für alles, was mit kleinen Latenzzeiten umgehen kann, aber das Potenzial hat, sehr groß zu sein, Listen für kleinere Datensätze, bei denen die Latenz kritisch ist, und natürlich echtes Benchmarking FTW :)
David

1

so etwas sollte es tun:

#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

Außerdem scheint dieser vorherige Beitrag über das effiziente Zählen eindeutiger Elemente Ihrer Frage ziemlich ähnlich zu sein, es sei denn, ich vermisse etwas.


Die verknüpfte Frage ist ein bisschen ähnlich, aber es sieht so aus, als würde er mit komplizierteren Datentypen arbeiten.
Abe

1

mehrdimensionale Frequenzzählung, dh Zählen von Arrays.

>>> 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]
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.