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/…
Сембей Норимаки

Ответы:


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 имеют dtype, отличный от int.
Маной,

7
Это не сработает, если они являются чем-то иным, чем неотрицательные целые, и будет очень неэффективно, если они удалены
Эрик

В numpy версии 1.10 я обнаружил, что для подсчета целых чисел он примерно в 6 раз быстрее, чем np.unique. Также обратите внимание, что он также учитывает отрицательные числа, если заданы правильные параметры.
Джихун

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

1
BAM! Вот почему мы обновляем ... когда мы находим такие ответы. Так долго нюми 1.8. Как мы можем получить это в верхней части списка?
user1269942

Если вы получили сообщение об ошибке: TypeError: unique () получил неожиданный аргумент ключевого слова 'return_counts', просто сделайте: unique, countts = np.unique (x, True)
NumesSanguis

3
@NumesSanguis Какую версию NumPy вы используете? До v1.9 ключевой return_countsаргумент не существовал, что могло бы объяснить исключение. В этом случае документы предполагают, что np.unique(x, True)это эквивалентно тому np.unique(x, return_index=True), что не возвращает счет.
JME

1
В более старых версиях numpy типичная идиома - получить то же самое 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.
метасеквойя

1
Я предпочитаю, чтобы оригинальный задающий вопрос изменил принятый ответ с первого на этот, чтобы увеличить его видимость
wiswit

Это медленно для версий до 0.14, хотя.
Джейсон С,

обратите внимание, что если массив полон строк, оба элемента в каждом из возвращаемых элементов также являются строками.
user1269942

Похоже, 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
Спасибо за размещение кода для создания сюжета. Не знал о перфлоте до сих пор. Выглядит удобно.
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 () не требуется. В остальном хороший пример. Numpy также. Панды могут взять простой список в качестве входных данных.
Йохан Обадия

1
@YohanObadia - в зависимости от размера массива, первое преобразование его в серию ускорило выполнение последней операции. Я бы предположил на отметке около 50000 значений.
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'
пиар

Более простым методом было бы вызвать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)также дают тот же результат. Я считаю, что OP хочет вывод, который напоминает tableфункцию R. Хранение Seriesможет быть более полезным.
Пиланг

Обратите внимание, что было бы необходимо перенести в, pd.Series(x).reshape(-1)если это многомерный массив.
natsuapo

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


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это не обязательно путь.
Махди

@ Рейн Ли интересно. Вы перепроверяли гипотезу списка также относительно некоторого размера набора данных, не поддерживающего кэширование? Предположим, что в каждом представлении было 150 000 случайных элементов, и за один прогон мы измерили немного более точно, как на примере aZmqStopwatch.start (); count (aRepresentation); aZmqStopwatch.stop () ?
user3666197

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

Да, мое эмпирическое правило не применимо ко всему, что может справиться с небольшими задержками, но потенциально может быть очень большим, списки для небольших наборов данных, где важна задержка, и, конечно, реальный сравнительный анализ FTW :)
David

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.