numpy: najbardziej wydajna częstotliwość liczy się dla unikalnych wartości w tablicy


244

W numpy/ scipy, czy istnieje skuteczny sposób na uzyskanie liczby częstotliwości dla unikalnych wartości w tablicy?

Coś w tym stylu:

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

(Dla ciebie, użytkowników R tam, po prostu szukam table()funkcji)


5
Jest collections.Counter(x)wystarczający?
pylang

1
Myślę, że byłoby lepiej, gdybyś zaznaczył teraz tę odpowiedź jako poprawną dla twojego pytania: stackoverflow.com/a/25943480/9024698 .
Wyrzutek

Collect.counter działa dość wolno. Zobacz mój post: stackoverflow.com/questions/41594940/…
Sembei Norimaki

Odpowiedzi:


161

Spójrz na 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]

I wtedy:

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

lub:

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

lub jednak chcesz połączyć liczby i unikalne wartości.


42
Cześć, To nie działałoby, jeśli elementy x mają typ inny niż int.
Manoj

7
Nie zadziała, jeśli nie są to intencje ujemne, i będzie bardzo nieefektywne przestrzennie, jeśli ints będą rozmieszczone w odstępach.
Erik,

W wersji numerycznej 1.10 odkryłem, że do zliczania liczb całkowitych jest około 6 razy szybszy niż np.unique. Zauważ też, że nie uwzględnia on również liczb całkowitych ujemnych, jeśli podano odpowiednie parametry.
Jihun

@Manoj: Moje elementy x są tablicami. Testuję rozwiązanie jme.
Catalina Chircu

508

Począwszy od Numpy 1.9, najłatwiejszą i najszybszą metodą jest po prostu użycie numpy.unique, która ma teraz return_countsargument słowa kluczowego:

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

Co daje:

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

Szybkie porównanie z 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
Dziękujemy za aktualizację! To jest teraz, IMO, poprawna odpowiedź.
Erve1879,

1
BAM! dlatego aktualizujemy ... kiedy znajdziemy takie odpowiedzi. Tak długo numpy 1.8. Jak możemy to zrobić na górze listy?
user1269942,

Jeśli pojawi się błąd: TypeError: unique () otrzymał nieoczekiwany argument słowa kluczowego „return_counts”, po prostu zrób: unique, counts = np.unique (x, True)
NumesSanguis

3
@NumesSanguis Jakiej wersji numpy używasz? Przed wersją 1.9 return_countsargument słowa kluczowego nie istniał, co może wyjaśniać wyjątek. W takim przypadku dokumenty sugerują, że np.unique(x, True)jest to równoważne np.unique(x, return_index=True), co nie zwraca liczby.
jme

1
W starszych wersjach numpy typowym idiomem tego samego było unique, idx = np.unique(x, return_inverse=True); counts = np.bincount(idx). Po dodaniu tej funkcji (patrz tutaj ) niektóre nieformalne testy wykorzystywały return_countstaktowanie ponad 5 razy szybciej.
Jaime

133

Aktualizacja: Metoda wymieniona w pierwotnej odpowiedzi jest przestarzała, zamiast tego powinniśmy użyć nowego sposobu:

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

Oryginalna odpowiedź:

możesz użyć 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
Zdaje się, że jest to jak dotąd najbardziej pythonowe podejście. Ponadto napotkałem problemy z „obiektem zbyt głębokim dla pożądanej tablicy” z np.bincount na matrycach 100k x 100k.
metasequoia

1
Raczej sugeruję oryginalnemu pytaczowi, aby zmienił odpowiedź z pierwszej na tę, aby zwiększyć jej widoczność
widz

Jest jednak powolny w przypadku wersji wcześniejszych niż 0.14.
Jason S

zwróć uwagę, że jeśli tablica jest pełna ciągów, oba elementy w każdym zwracanym elemencie są również ciągami.
user1269942,

Wygląda na to, że itemfreq jest przestarzały
Terence Parr

48

Byłem również tym zainteresowany, więc zrobiłem małe porównanie wydajności (używając perfplot , mojego projektu dla zwierząt domowych). Wynik:

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

jest zdecydowanie najszybszy. (Zwróć uwagę na skalowanie dziennika).

wprowadź opis zdjęcia tutaj


Kod do wygenerowania wykresu:

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
Dziękujemy za opublikowanie kodu do wygenerowania fabuły. Nie wiedziałem wcześniej o perfplot . Wygląda na przydatny.
ruffl

Udało mi się uruchomić Twój kod, dodając opcję equality_check=array_sorteqw perfplot.show(). Przyczyną błędu (w Pythonie 2) było pd.value_counts(nawet z sort = False).
user2314737

33

Za pomocą modułu pandy:

>>> 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 () nie jest konieczne. W przeciwnym razie dobry przykład. Numpy też. Pandy mogą przyjmować prostą listę jako dane wejściowe.
Yohan Obadia

1
@YohanObadia - w zależności od rozmiaru tablicy, najpierw przekształcenie jej w serię przyspieszyło moją końcową operację. Domyślam się na poziomie około 50 000 wartości.
n1k31t4

1
Zredagowałem swoją odpowiedź, aby uwzględnić odpowiedni komentarz od @YohanObadia
ivankeller

19

Jest to zdecydowanie najbardziej ogólne i wydajne rozwiązanie; zaskoczony, że nie został jeszcze opublikowany.

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

W przeciwieństwie do obecnie akceptowanej odpowiedzi, działa na każdym typie danych, który jest sortowalny (nie tylko na dodatnich liczbach całkowitych) i ma optymalną wydajność; jedynym znaczącym wydatkiem jest sortowanie wykonane przez np.unique.


nie działa:AttributeError: 'numpy.ufunc' object has no attribute 'at'
PR

Prostszą metodą byłoby wywołanienp.bincount(inverse)
ali_m

15

numpy.bincountjest prawdopodobnie najlepszym wyborem. Jeśli tablica zawiera coś poza małymi gęstymi liczbami całkowitymi, przydatne może być zawinięcie jej w następujący sposób:

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

Na przykład:

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

Mimo że zostało już udzielone odpowiedzi, sugeruję inne podejście, które wykorzystuje numpy.histogram. Taka funkcja, biorąc pod uwagę sekwencję, zwraca częstotliwość elementów zgrupowanych w przedziałach .

Uwaga : działa w tym przykładzie, ponieważ liczby są liczbami całkowitymi. Jeśli byłyby to liczby rzeczywiste, to rozwiązanie nie miałoby tak dobrego zastosowania.

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

To daje: {1: 5, 2: 3, 5: 1, 25: 1}


1
collections.Counter(x)dają również ten sam wynik. Wierzę, że OP chce wyjścia, które przypomina tablefunkcję R. Zachowanie Seriesmoże być bardziej przydatne.
pylang

Należy pamiętać, że konieczne byłoby przeniesienie do, pd.Series(x).reshape(-1)jeśli jest to tablica wielowymiarowa.
natsuapo,

4

Aby policzyć unikalne liczby całkowite - podobne do odpowiedzi Eelco Hoogendoorna, ale znacznie szybsze (współczynnik 5 na mojej maszynie), zwykłem weave.inlinełączyć się numpy.uniquez odrobiną kodu 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

Informacje o profilu

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

Czysta numpywersja Eelco :

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

Uwaga

Jest tu nadmiarowość ( uniquewykonuje również sortowanie), co oznacza, że ​​kod można by prawdopodobnie zoptymalizować, umieszczając uniquefunkcjonalność w pętli c-code.


4

Stare pytanie, ale chciałbym podać własne rozwiązanie, które okazuje się najszybsze, listzamiast tego używaj normalnegonp.array wejściowego (lub najpierw przenieś do listy), w oparciu o mój test laboratoryjny.

Sprawdź to, jeśli go spotkasz.

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

Na przykład,

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

100000 pętli, najlepiej 3: 2,26 µs na pętlę

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

100000 pętli, najlepiej 3: 8,8 µs na pętlę

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

100000 pętli, najlepiej 3: 5,85 µs na pętlę

Chociaż przyjęta odpowiedź byłaby wolniejsza, a scipy.stats.itemfreqrozwiązanie jest jeszcze gorsze.


Bardziej szczegółowe badanie nie potwierdziło sformułowanych oczekiwań.

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

Nr ref. komentarze poniżej dotyczące pamięci podręcznej i innych skutków ubocznych w pamięci RAM, które wpływają na mały zestaw danych masowo powtarzających się wyników testów.


Ta odpowiedź jest naprawdę dobra, ponieważ pokazuje, że numpyniekoniecznie jest to najlepsza droga.
Mahdi

@Rain Lee ciekawe. Czy zweryfikowałeś krzyżowo hipotezę listy również w przypadku niektórych rozmiarów zbioru danych, który nie może być buforowany? Załóżmy 150 000 losowych pozycji w obu reprezentacjach i mierzyliśmy nieco dokładniej w jednym przebiegu, jak na przykładzie aZmqStopwatch.start (); count (aRepresentation); aZmqStopwatch.stop () ?
user3666197,

Przeprowadziłem pewne testy i tak, istnieją ogromne różnice w rzeczywistej wydajności zestawu danych. Testowanie wymaga nieco większego wglądu w wewnętrzną mechanikę pytona niż uruchamianie tylko pętli o skali brutalnej siły i cytowanie nierealistycznych nanosekund in vitro . Zgodnie z testami - a np.bincount () można obsługiwać 150.000 w tablicy poniżej 600 [us] Chociaż powyższy def -ed Ilość () na wstępnie przekształconej liście reprezentację jego trwało ponad 122.000 [us]
user3666197

Tak, moja ogólna zasada jest niezliczona w przypadku wszystkiego, co może poradzić sobie z niewielkimi opóźnieniami, ale może być bardzo duże, listy dla mniejszych zestawów danych, w których opóźnienia są krytyczne, i oczywiście prawdziwe testy porównawcze FTW :)
David

1

coś takiego powinno to zrobić:

#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

Również ten poprzedni post na temat Skutecznie liczących unikalne elementy wydaje się bardzo podobny do twojego pytania, chyba że coś mi umknie.


Połączone pytanie jest trochę podobne, ale wygląda na to, że pracuje z bardziej skomplikowanymi typami danych.
Abe

1

wielowymiarowa liczba częstotliwości, tj. tablice zliczające.

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


0
from collections import Counter
x = array( [1,1,1,2,2,2,5,25,1,1] )
mode = counter.most_common(1)[0][0]
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.