numpy: nombre de fréquences le plus efficace pour des valeurs uniques dans un tableau

244

Dans numpy/ scipy, existe-t-il un moyen efficace d'obtenir des nombres de fréquences pour des valeurs uniques dans un tableau?

Quelque chose dans ce sens:

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

(Pour vous, utilisateurs R, je recherche essentiellement la table()fonction)

Abe
la source
5
Est collections.Counter(x)suffisant?
pylang
1
Ce serait mieux je pense que si vous cochez maintenant cette réponse comme correcte pour votre question: stackoverflow.com/a/25943480/9024698 .
Outcast
Collections.counter est assez lent. Voir mon article: stackoverflow.com/questions/41594940/…
Sembei Norimaki

Réponses:

161

Jetez un œil à 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]

Puis:

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

ou:

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

ou comme vous voulez combiner les nombres et les valeurs uniques.

JoshAdel
la source
42
Salut, Cela ne fonctionnerait pas si les éléments de x ont un dtype autre que int.
Manoj
7
Cela ne fonctionnera pas s'ils sont autre chose que des entiers non négatifs, et il sera très peu efficace si les entiers sont espacés.
Erik
Avec numpy version 1.10, j'ai trouvé que, pour compter un entier, il était environ 6 fois plus rapide que np.unique. Notez également qu'il prend également en compte les nombres négatifs si des paramètres corrects sont fournis.
Jihun
@Manoj: Mes éléments x sont des tableaux. Je teste la solution de jme.
Catalina Chircu
508

Depuis Numpy 1.9, la méthode la plus simple et la plus rapide consiste à simplement utiliser numpy.unique, qui a maintenant un return_countsargument mot - clé:

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

Qui donne:

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

Une comparaison rapide avec 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
la source
22
Merci d'avoir mis à jour! C'est maintenant, OMI, la bonne réponse.
Erve1879
1
BAM! c'est pourquoi nous mettons à jour ... quand nous trouvons des réponses comme celles-ci. So long numpy 1.8. Comment pouvons-nous placer cela en tête de liste?
user1269942
Si vous obtenez l'erreur: TypeError: unique () a obtenu un argument de mot clé inattendu 'return_counts', faites simplement: unique, counts = np.unique (x, True)
NumesSanguis
3
@NumesSanguis Quelle version de numpy utilisez-vous? Avant la v1.9, l' return_countsargument mot - clé n'existait pas, ce qui pourrait expliquer l'exception. Dans ce cas, les documents suggèrent que np.unique(x, True)c'est équivalent à np.unique(x, return_index=True), ce qui ne renvoie pas de décompte.
jme
1
Dans les anciennes versions numpy, l'idiome typique pour obtenir la même chose était unique, idx = np.unique(x, return_inverse=True); counts = np.bincount(idx). Lorsque cette fonctionnalité a été ajoutée (voir ici ), certains tests informels ont return_countspermis de cadencer 5 fois plus rapidement.
Jaime
133

Mise à jour: La méthode mentionnée dans la réponse d'origine est déconseillée, nous devrions utiliser la nouvelle méthode à la place:

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

Réponse originale:

vous pouvez utiliser 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
la source
1
Semble de loin l'approche la plus pythonique. En outre, j'ai rencontré des problèmes avec des problèmes «d'objet trop profond pour le tableau souhaité» avec np.bincount sur des matrices 100k x 100k.
metasequoia
1
Je suggère plutôt au poseur de questions d'origine de changer la réponse acceptée de la première à celle-ci, pour augmenter sa visibilité
avec le
C'est cependant lent pour les versions antérieures à 0.14.
Jason S
notez que si le tableau est plein de chaînes, les deux éléments de chacun des éléments retournés sont également des chaînes.
user1269942
On dirait que itemfreq a été déprécié
Terence Parr
48

J'étais également intéressé par cela, j'ai donc fait une petite comparaison des performances (en utilisant perfplot , un de mes projets favoris ). Résultat:

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

est de loin le plus rapide. (Notez l'échelle du journal.)

entrez la description de l'image ici


Code pour générer le tracé:

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
la source
1
Merci d'avoir publié le code pour générer l'intrigue. Je ne connaissais pas perfplot avant maintenant. Ça a l'air pratique.
ruffsl
J'ai pu exécuter votre code en ajoutant l'option equality_check=array_sorteqdans perfplot.show(). Ce qui provoquait une erreur (en Python 2) était pd.value_counts(même avec sort = False).
user2314737
33

Utilisation du module 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
la source
5
pd.Series () n'est pas nécessaire. Sinon, bon exemple. Numpy aussi. Les pandas peuvent prendre une simple liste en entrée.
Yohan Obadia
1
@YohanObadia - selon la taille du tableau, le convertir d'abord en série a rendu l'opération finale plus rapide pour moi. Je devinerais la marque d'environ 50 000 valeurs.
n1k31t4
1
J'ai modifié ma réponse pour prendre en compte le commentaire pertinent de @YohanObadia
ivankeller
19

C'est de loin la solution la plus générale et la plus performante; surpris qu'il n'ait pas encore été publié.

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

Contrairement à la réponse actuellement acceptée, elle fonctionne sur tout type de données triable (pas seulement les entiers positifs) et ses performances sont optimales; la seule dépense importante concerne le tri effectué par np.unique.

Eelco Hoogendoorn
la source
ne fonctionne pas:AttributeError: 'numpy.ufunc' object has no attribute 'at'
PR
Une méthode plus simple serait d'appelernp.bincount(inverse)
ali_m
15

numpy.bincountest probablement le meilleur choix. Si votre tableau contient autre chose que de petits nombres entiers denses, il pourrait être utile de l'envelopper quelque chose comme ceci:

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

Par exemple:

>>> 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
la source
8

Même s'il a déjà été répondu, je suggère une approche différente qui utilise numpy.histogram. Cette fonction étant donnée une séquence, elle renvoie la fréquence de ses éléments regroupés dans des bacs .

Attention cependant : cela fonctionne dans cet exemple car les nombres sont des entiers. S'ils étaient en nombres réels, cette solution ne s'appliquerait pas aussi 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
la source
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()))

Cela vous donne: {1: 5, 2: 3, 5: 1, 25: 1}

Kerem T
la source
1
collections.Counter(x)donnent également le même résultat. Je crois que l'OP veut une sortie qui ressemble à la tablefonction R. Garder le Seriespeut être plus utile.
pylang
Veuillez noter qu'il serait nécessaire de transférer vers pd.Series(x).reshape(-1)s'il s'agit d'un tableau multidimensionnel.
natsuapo
4

Pour compter des non-entiers uniques - similaires à la réponse d'Eelco Hoogendoorn mais considérablement plus rapides (facteur 5 sur ma machine), j'ai l'habitude weave.inlinede combiner numpy.uniqueavec un peu de code 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

Informations sur le profil

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

La numpyversion pure d'Eelco :

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

Remarque

Il y a redondance ici ( uniqueeffectue également un tri), ce qui signifie que le code pourrait probablement être encore optimisé en plaçant la uniquefonctionnalité à l'intérieur de la boucle de code C.

jmetz
la source
4

Vieille question, mais je voudrais fournir ma propre solution qui s'avère être la plus rapide, utiliser la normale listau lieu de np.arrayla saisir (ou la transférer dans la liste en premier), sur la base de mon test au banc.

Vérifiez-le si vous le rencontrez également.

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

Par exemple,

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

100000 boucles, le meilleur de 3: 2,26 µs par boucle

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

100000 boucles, le meilleur de 3: 8,8 µs par boucle

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

100000 boucles, meilleur de 3: 5,85 µs par boucle

Alors que la réponse acceptée serait plus lente, et la scipy.stats.itemfreqsolution est encore pire.


Un test plus approfondi n'a pas confirmé l'attente formulée.

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

Réf. commentaires ci-dessous sur le cache et autres effets secondaires en RAM qui influencent les résultats d'un test massivement répétitif d'un petit ensemble de données.

Rain Lee
la source
Cette réponse est vraiment bonne, car elle montre que ce numpyn'est pas nécessairement la voie à suivre.
Mahdi
@Rain Lee intéressant. Avez-vous également validé l'hypothèse de liste sur une taille de jeu de données non compatible avec le cache? Supposons 150 000 éléments aléatoires dans l'une ou l'autre représentation et mesurés un peu plus précis sur une seule exécution, comme par exemple aZmqStopwatch.start (); count (aRepresentation); aZmqStopwatch.stop () ?
user3666197
A fait quelques tests et oui, il y a d' énormes différences dans les performances réelles de l'ensemble de données. Les tests nécessitent un peu plus de connaissances sur la mécanique interne de Python que l'exécution de boucles à l'échelle de force brute et citent des nanosecondes in vitro non réalistes . Testée - un np.bincount () peut être fait pour gérer ensemble 150,000 dans les moins de 600 [nous] alors que ci - dessus def -ed count () sur une représentation de liste pré-convertie de celui - ci a pris plus de 122.000 [nous]
user3666197
Ouais, ma règle de base est numpy pour tout ce qui peut gérer de petites quantités de latence mais a le potentiel d'être très grand, des listes pour des ensembles de données plus petits où la latence est critique, et bien sûr une véritable analyse comparative FTW :)
David
1

quelque chose comme ça devrait le faire:

#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

En outre, ce post précédent sur le comptage efficace des éléments uniques semble assez similaire à votre question, sauf si je manque quelque chose.

benjaminmgross
la source
La question liée est un peu similaire, mais il semble qu'il travaille avec des types de données plus complexes.
Abe
1

comptage de fréquences multidimensionnelles, c. à d. tableaux de comptage.

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

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