numpy: найефективніший підрахунок частоти для унікальних значень у масиві


244

У numpy/ scipyчи існує ефективний спосіб отримати підрахунок частоти для унікальних значень масиву?

Щось у цьому напрямку:

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

(Для вас, R користувачів там, я в основному шукаю table()функцію)


5
Чи collections.Counter(x)достатньо?
піланг

1
Було б краще, я думаю, якщо ви відзначите зараз цю відповідь правильною на своє запитання: stackoverflow.com/a/25943480/9024698 .
Оголений

Collections.counter йде досить повільно. Дивіться мій пост: stackoverflow.com/questions/41594940 / ...
Sembei Norimaki

Відповіді:


161

Погляньте на 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]

І потім:

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

або:

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

або, однак, ви хочете поєднати підрахунки та унікальні значення.


42
Привіт, це не працює, якщо елементи x мають інший тип, ніж int.
Маной

7
Він не працюватиме, якщо вони не відрізняються від негативних входів, і буде дуже неефективним, якщо вставки будуть розташовані між собою.
Ерік

З нумерованою версією 1.10 я виявив, що для підрахунку цілого числа це приблизно в 6 разів швидше, ніж np.unique. Також зауважте, що він також враховує негативні вставки, якщо вказані правильні параметри.
Jihun

@Manoj: Мої елементи x - це масиви. Я тестую рішення jme.
Каталіна Чірку

508

Станом на Numpy 1.9, найпростіший і найшвидший метод - це просто використовувати numpy.unique, який тепер має 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

Що дає:

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

Швидке порівняння з 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
Дякуємо за оновлення! Це зараз, ІМО, правильна відповідь.
Erve1879

1
БАМ! ось чому ми оновлюємось ... коли знаходимо відповіді на кшталт цих. Так довго нудить 1,8. Як ми можемо потрапити до цього списку?
користувач1269942

Якщо ви отримаєте помилку: TypeError: unique () отримав несподіваний аргумент ключового слова 'return_counts', просто зробіть: унікальний, counts = np.unique (x, True)
NumesSanguis

3
@NumesSanguis Яку версію numpy ви використовуєте? До v1.9 return_countsаргумент ключового слова не існував, що може пояснити виняток. У цьому випадку документи пропонують np.unique(x, True)еквівалентний np.unique(x, return_index=True), який не повертає рахунки.
jme

1
У старих нумерованих версіях типовою ідіомою було те саме unique, idx = np.unique(x, return_inverse=True); counts = np.bincount(idx). Коли ця функція була додана (див. Тут ), деякі неформальні тестування використовували return_countsтактову функцію на 5 разів швидше.
Хайме

133

Оновлення: метод, згаданий у вихідній відповіді, застарілий, замість цього слід використовувати новий спосіб:

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

Оригінальна відповідь:

ви можете використовувати 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
Здається, найбільш пітонічний підхід на сьогоднішній день. Крім того, у мене виникли проблеми з "об’єктом занадто глибоким для потрібного масиву" з np.bincount на матрицях 100k x 100k.
metasequoia

1
Я радше пропоную початковому питателю змінити прийняту відповідь з першої на цю, щоб збільшити її видимість
wiswit

Однак для версій це повільно до 0,14.
Jason S

зауважте, що якщо масив заповнений рядками, обидва елементи у кожному поверненому елементі також є рядками.
користувач1269942

Схоже, що itemfreq застаріло
Теренс Парр

48

Мене теж це зацікавило, тому я зробив невелике порівняння продуктивності (використовуючи perfplot , проект мого домашнього улюбленця). Результат:

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

на сьогоднішній день найшвидший. (Зверніть увагу на масштабування журналу.)

введіть тут опис зображення


Код для створення сюжету:

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
Дякуємо, що опублікували код для створення сюжету. Я не знав про perfplot раніше. Виглядає зручно.
ruffsl

Мені вдалося запустити ваш код, додавши параметр equality_check=array_sorteqв perfplot.show(). Те, що спричиняло помилку (у Python 2), було pd.value_counts(навіть із sort = False).
user2314737

33

Використання модуля 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

5
pd.Series () не потрібен. Інакше хороший приклад. Охайний також. Панди можуть взяти простий список як вихідний.
Йохан Обадія

1
@YohanObadia - залежно від розміру масиву, перше перетворення його на серію зробило для мене остаточну операцію швидшою. Я б здогадався на позначці близько 50 000 значень.
n1k31t4

1
Я відредагував свою відповідь, щоб врахувати відповідний коментар від @YohanObadia
ivankeller

19

Це, безумовно, найбільш загальне та найефективніше рішення; здивований, що він ще не розміщений.

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

На відміну від прийнятої на даний момент відповіді, вона працює на будь-якому типі даних, який може бути сортованим (не лише позитивними входами), і має оптимальну продуктивність; єдиний суттєвий витрата - на сортування, здійснене np.unique.


не працює:AttributeError: 'numpy.ufunc' object has no attribute 'at'
PR

Більш простим методом було б дзвонитиnp.bincount(inverse)
ali_m

15

numpy.bincount- це, мабуть, найкращий вибір. Якщо ваш масив містить що-небудь, крім маленьких щільних цілих чисел, може бути корисним обернути його приблизно так:

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

Наприклад:

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

Хоча на нього вже відповіли, я пропоную інший підхід, який використовує numpy.histogram. Така функція, задана послідовністю, повертає частоту елементів, згрупованих у бункери .

Але будьте обережні : він працює в цьому прикладі, оскільки числа є цілими числами. Якщо вони там, де реальні цифри, то це рішення не застосовуватиметься так добре.

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

Це дає вам: {1: 5, 2: 3, 5: 1, 25: 1}


1
collections.Counter(x)також дають такий же результат. Я вважаю, що ОП хоче вихід, який нагадує tableфункцію R. Збереження Seriesможе бути кориснішим.
піланг

Зверніть увагу, що це було б необхідно перенести, pd.Series(x).reshape(-1)якщо це багатовимірний масив.
нацуапо

4

Для підрахунку унікальних не цілих чисел - схожий на відповідь Eelco Hoogendoorn, але значно швидше (коефіцієнт 5 на моїй машині), я використовував weave.inlineкомбінацію numpy.uniqueз трохи 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

Інформація про профіль

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

Чиста numpyверсія Eelco :

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

Примітка

Тут є надмірність ( uniqueвиконує також сортування), це означає, що код, можливо, може бути додатково оптимізований, ввівши uniqueфункціонал всередині циклу c-коду.


4

Старе питання, але я хотів би запропонувати власне рішення, яке виявиться найшвидшим, використовувати звичайне, listа не np.arrayяк вхідне (або перенести до списку спочатку), грунтуючись на моєму тестуванні.

Перевірте це, якщо ви також зіткнулися.

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

Наприклад,

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

100000 петель, найкраще 3: 2,26 мкс на цикл

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

100000 петель, найкраще 3: 8,8 мкс на цикл

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

100000 петель, найкраще 3: 5,85 мкс на цикл

Хоча прийнята відповідь була б повільнішою, а scipy.stats.itemfreqрішення - ще гіршою.


Більш поглиблене тестування не підтвердило сформульоване очікування.

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

Реф. коментарі нижче щодо кешу та інших побічних ефектів в ОЗУ, які впливають на невеликий набір даних, які масово повторюються результати тестування.


Ця відповідь справді хороша, оскільки, як показує numpy, не обов'язково йти шляхом.
Махді

@Rain Lee цікавий. Чи перекреслили ви гіпотезу списку також щодо певного розміру набору даних, який не може кешувати? Давайте припустимо 150 000 випадкових елементів у будь-якому представленні та виміряні трохи точніше за один запуск, як на прикладі aZmqStopwatch.start (); count (aRepresentation); aZmqStopwatch.stop () ?
user3666197

Провели тестування і так, є величезні відмінності в реальній продуктивності даних. Тестування вимагає трохи більше розуміння внутрішньої механіки пітона, ніж виконання лише циклів масштабування на грубі сили та цитування нереалістичних наносекунд in vitro . Як перевірено - в np.bincount () можна обробляти 150.000 масив в межах менш ніж 600 [нам] в той час як вище Захист -ed відлік () на попередньо перетворений список поданні їх потрібно більше 122,000 [нас]
user3666197

Так, мій принцип роботи манірний за все, що може працювати з невеликими затримками, але може бути дуже великим, списки для менших наборів даних, де затримка критична, і, звичайно, реальний показник FTW :)
Девід,

1

щось подібне повинно це робити:

#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

Крім того, цей попередній пост про Ефективний підрахунок унікальних елементів здається досить схожим на ваше запитання, якщо я щось не пропускаю.


Пов'язане запитання начебто подібне, але схоже, що він працює зі складнішими типами даних.
Абе

1

багатовимірний підрахунок частоти, тобто підрахунок масивів.

>>> 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]
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.