2014-09-25 4 views
7

Я пытаюсь подсчитать уникальные значения в массиве numpy.подсчет уникальных элементов в массиве numpy: почему scipy.stats.itemfreq так медленно?

import numpy as np 
from collections import defaultdict 
import scipy.stats 
import time 

x = np.tile([1,2,3,4,5,6,7,8,9,10],20000) 
for i in [44,22,300,403,777,1009,800]: 
    x[i] = 11 

def getCounts(x): 
    counts = defaultdict(int) 
    for item in x: 
     counts[item] += 1 
    return counts 

flist = [getCounts, scipy.stats.itemfreq] 

for f in flist: 
    print f 
    t1 = time.time() 
    y = f(x) 
    t2 = time.time() 
    print y 
    print '%.5f sec' % (t2-t1) 

Я не мог найти встроенную функцию в первый, чтобы сделать это, так что я написал getCounts(); то я нашел scipy.stats.itemfreq, поэтому подумал, что буду использовать это вместо этого. Но это медленно! Вот что я получаю на своем ПК. Почему это так медленно по сравнению с такой простой рукописной функцией?

<function getCounts at 0x0000000013C78438> 
defaultdict(<type 'int'>, {1: 19998, 2: 20000, 3: 19999, 4: 19999, 5: 19999, 6: 20000, 7: 20000, 8: 19999, 9: 20000, 10: 19999, 11: 7}) 
0.04700 sec 
<function itemfreq at 0x0000000013C5D208> 
[[ 1.00000000e+00 1.99980000e+04] 
[ 2.00000000e+00 2.00000000e+04] 
[ 3.00000000e+00 1.99990000e+04] 
[ 4.00000000e+00 1.99990000e+04] 
[ 5.00000000e+00 1.99990000e+04] 
[ 6.00000000e+00 2.00000000e+04] 
[ 7.00000000e+00 2.00000000e+04] 
[ 8.00000000e+00 1.99990000e+04] 
[ 9.00000000e+00 2.00000000e+04] 
[ 1.00000000e+01 1.99990000e+04] 
[ 1.10000000e+01 7.00000000e+00]] 
2.04100 sec 
+3

Существует эта проблема с этой функцией [здесь] (https://github.com/scipy/scipy/issues/599). Похоже, что у сопровождающих была схожая озабоченность по поводу того, как она была написана. Если вы хотите знать, почему это было так медленно, возможно, проверьте [это фиксация] (https://github.com/scipy/scipy/commit/7e04d6630f229693cca3522b62aa16226f174053). В зависимости от того, используете ли вы scipy версию до или после этой фиксации, вы сможете увидеть, как она реализована. – jedwards

ответ

14

Если вы можете использовать Numpy 1.9, вы можете использовать numpy.unique с аргументом return_counts=True. То есть

unique_items, counts = np.unique(x, return_counts=True) 

На самом деле, itemfreq был обновлен для использования np.unique, но SciPy в настоящее время поддерживает Numpy версии обратно на 1.5, так что не использует return_counts аргумент.

Вот полная реализация itemfreq в SciPy 0,14:

def itemfreq(a): 
    items, inv = np.unique(a, return_inverse=True) 
    freq = np.bincount(inv) 
    return np.array([items, freq]).T 
+0

Я никогда не понимал ускорения функции numpy. Заставляет вас задаться вопросом, почему он не используется в функции scipy. –

+0

На самом деле я могу просто использовать 'np.bincount' в моем приложении, так как у меня есть большой массив небольших целых чисел. –

3

Во-первых, time.time неправильная функция для использования при синхронизации, так как он измеряет время от стены часы, а не процессорное время (см this question). В идеале вы бы использовали модуль timeit, но time.clock также лучше.

Кроме того, кажется, что вы можете использовать устаревшую версию scipy. Я использую Python 3.4 и SciPy 0.14.0 и это мои тайминги:

x = np.tile([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], 20000) 
for i in [44, 22, 300, 403, 777, 1009, 800]: 
    x[i] = 11 

%timeit getCounts(x) 
# 10 loops, best of 3: 55.6 ms per loop 

%timeit scipy.stats.itemfreq(x) 
# 10 loops, best of 3: 20.8 ms per loop 

%timeit collections.Counter(x) 
# 10 loops, best of 3: 39.9 ms per loop 

%timeit np.unique(x, return_counts=True) 
# 100 loops, best of 3: 4.13 ms per loop 
+0

Хорошо, вы могли быть правы. Я использую scipy 0.13.0 –

0

Спасибо за ответы. Я не могу использовать NumPy 1.9 или еще SciPy 0,14 из-за некоторых конфликтов модулей в моем приложении, но выглядит как новый scipy.stats.itemfreq гораздо быстрее:

import numpy as np 
from collections import defaultdict, Counter 
import scipy.stats 
import time 
import timeit 

x = np.tile([1,2,3,4,5,6,7,8,9,10],20000) 
for i in [44,22,300,403,777,1009,800]: 
    x[i] = 11 

def getCounts(x): 
    counts = defaultdict(int) 
    for item in x: 
     counts[item] += 1 
    return counts 

def itemfreq_scipy14(x): 
    '''this is how itemfreq works in 0.14: 
    https://github.com/scipy/scipy/commit/7e04d6630f229693cca3522b62aa16226f174053 
    ''' 
    items, inv = np.unique(x, return_inverse=True) 
    freq = np.bincount(inv) 
    return np.array([items, freq]).T 

flist = [getCounts, scipy.stats.itemfreq, np.bincount, itemfreq_scipy14, Counter] 


for f in flist: 
    print f 
    print timeit.timeit(lambda: f(x),number=3) 

, который дает на моем компьютере:

<function getCounts at 0x0000000013F8EB38> 
0.148138969181 
<function itemfreq at 0x0000000013C5D208> 
6.15385023664 
<built-in function bincount> 
0.00313706656675 
<function itemfreq_scipy14 at 0x0000000013F8EDD8> 
0.0757223407165 
<class 'collections.Counter'> 
0.255281199559 
Смежные вопросы