NumPy数组中唯一值的频率计数

396

如何高效地获取NumPy数组中每个唯一值的频率计数?

>>> x = np.array([1,1,1,2,2,2,5,25,1,1])
>>> freq_count(x)
[(1, 5), (2, 3), (5, 1), (25, 1)]

7
collections.Counter(x)是否足够? - pylang
1
我认为,如果您现在将此答案标记为正确答案,会更好:https://dev59.com/2Wgv5IYBdhLWcg3wPOfz#25943480。 - Outcast
Collections.counter的速度相当慢。请参考我的帖子:https://dev59.com/PVgR5IYBdhLWcg3wM7Hh - user2261062
17个回答

767

使用 numpy.unique 并加上 return_counts=True (适用于 NumPy 1.9+):

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

24
感谢更新!在我看来,这现在是正确的答案。 - Erve1879
2
这就是为什么我们要更新...当我们找到像这样的答案时。告别numpy 1.8。我们如何将其置于列表顶部? - user1269942
3
你使用的numpy版本是什么?在1.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倍以上。 - Jaime
这是最好的答案,谢谢! - David
显示剩余5条评论

201
请看 np.bincounthttp://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]])

或者你可以根据自己的需求组合计数和唯一值。

50
你好,如果x中的元素具有除int以外的dtype,则此方法将无法运行。 - Manoj
8
如果它们不是非负整数,那么它将无法工作,如果这些整数之间有空隙,那么它将非常浪费空间。 - Erik
使用numpy版本1.10,我发现对于整数计数,它比np.unique快大约6倍。此外,请注意,如果给定正确的参数,它也会计算负整数。 - Jihun
@Manoj:我的元素x是数组。我正在测试jme的解决方案。 - Catalina Chircu
这里的“return_inverse”选项有什么好的类比呢? - Yuval

167
使用这个:
>>> 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.]])

2
看起来这是迄今为止最Pythonic的方法。此外,我在100k x 100k矩阵上使用np.bincount时遇到了“对象太深而无法生成数组”的问题。 - metasequoia
1
我建议原问题提出者将已接受的答案从第一个更改为这个,以增加其可见性。 - wiswit
但是在0.14版本之前,速度较慢。 - Jason S
请注意,如果数组中装满了字符串,则返回的每个项中的两个元素也都是字符串。 - user1269942
看起来itemfreq已经被弃用了。 - Terence Parr

71

我也对此很感兴趣,所以我进行了一些性能比较(使用我的一个宠物项目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), dtype=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


b = perfplot.bench(
    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)],
    xlabel="len(a)",
)
b.save("out.png")
b.show()

2
感谢您发布生成图表的代码。之前不知道 perfplot 这个工具,看起来很方便。 - ruffsl
我通过在perfplot.show()中添加选项equality_check=array_sorteq来运行您的代码。导致错误(在Python 2中)的是pd.value_counts(即使sort=False也是如此)。 - user2314737

51

使用 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也是如此。Pandas可以接受一个简单的列表作为输入。 - Yohan Obadia
1
@YohanObadia - 根据数组的大小,我发现先转换为序列再进行最终操作速度更快。我猜测在大约 50,000 个数值左右。 - n1k31t4
1
我编辑了我的答案,考虑到@YohanObadia的相关评论。 - ivankeller
df = df.astype('category') print(df.describe()) ```将提供信息,例如```count 10 unique 4 top 1 freq 5 ```,这可能会很有用。 - Subham

20
这是目前最通用和高效的解决方案,令人惊讶的是它还没有被发布。
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'对象没有'at'属性 - P.R.
一个更简单的方法是调用 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

虽然这是一个老问题,但我想提供自己的解决方案,这个方法是最快的,使用常规的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

参考下面有关缓存和其他在RAM中产生的副作用的评论,这些副作用会对小数据集高度重复的测试结果产生影响。


1
这个答案非常好,因为它表明numpy不一定是最佳选择。 - Mahdi
@Rain Lee 很有趣。您是否还在一些不可缓存的数据集大小上进行了列表假设的交叉验证?假设我们在任何表示中都有150,000个随机项,并通过 aZmqStopwatch.start();count(aRepresentation);aZmqStopwatch.stop() 的单次运行来更准确地测量? - user3666197
进行了一些测试,确实存在真实数据集性能上的巨大差异。测试需要比运行简单的暴力循环和引用非现实的体外纳秒更深入地了解Python内部机制。经过测试-一个np.bincount()可以在不到600 [us]的时间内处理150,000个数组,而上述的预转换列表表示的def-ed **count()则需要超过122,000 [us]**的时间。 - user3666197
是的,我的经验法则是对于能够处理小量延迟但有潜力非常大的数据使用 numpy ,对于延迟关键的较小数据集使用 lists ,当然,进行 真正的基准测试 最好 :) - David

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想要一个类似于R中table函数的输出。保留Series可能更有用。 - pylang
请注意,如果是多维数组,则需要转换为pd.Series(x).reshape(-1) - natsuapo

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接