在Numpy中的向量上应用bins函数

时间:2012-07-26 11:46:36

标签: python numpy

我如何将聚合函数(例如“sum()”或“max()”)应用于向量中的bin。

如果我有:

  1. 长度为N的值x的向量
  2. 长度为N
  3. 的bin标签b的向量

    这样b表示x中每个值所属的bin。 对于b中的每个可能值,我想对属于该bin的x的所有值应用聚合函数“func()”。

    >> x = [1,2,3,4,5,6]
    >> b = ["a","b","a","a","c","c"]    
    

    输出应该是2个向量(比如聚合函数是产品函数):

    >>(labels, y) = apply_to_bins(values = x, bins = b, func = prod)
    
    labels = ["a","b","c"]
    y = [12, 2, 30]
    

    我想在numpy(或者只是python)中尽可能优雅地做到这一点,因为很明显我只能“for循环”它。

6 个答案:

答案 0 :(得分:1)

import itertools as it
import operator as op

def apply_to_bins(values, bins, func):
    return {k: func(x[1] for x in v) for k,v in it.groupby(sorted(zip(bins, values), key=op.itemgetter(0)), key=op.itemgetter(0))}

x = [1,2,3,4,5,6]
b = ["a","b","a","a","c","c"]   

print apply_to_bins(x, b, sum) # returns {'a': 8, 'b': 2, 'c': 11}
print apply_to_bins(x, b, max) # returns {'a': 4, 'b': 2, 'c': 6}

答案 1 :(得分:1)

>>> from itertools import groupby
>>> x = np.array([1, 2, 3, 4, 5, 6])
>>> zip(*[(k, np.product(x[list(v)]))
...       for k, v in groupby(np.argsort(b), key=lambda i: b[i])])
[('a', 'b', 'c'), (12, 2, 30)]

或者,一步一步:

>>> np.argsort(b)
array([0, 2, 3, 1, 4, 5])

b中的键排序为x(或b)的索引列表。

>>> [(k, list(v)) for k, v in groupby(np.argsort(b), key=lambda i: b[i])]
[('a', [0, 2, 3]), ('b', [1]), ('c', [4, 5])]

根据b的密钥分组的指数。

>>> [(k, x[list(v)]) for k, v in groupby(np.argsort(b), key=lambda i: b[i])]
[('a', array([1, 3, 4])), ('b', array([2])), ('c', array([5, 6]))]

使用索引从x获取正确的元素。

>>> [(k, np.product(x[list(v)]))
...  for k, v in groupby(np.argsort(b), key=lambda i: b[i])]
[('a', 12), ('b', 2), ('c', 30)]

申请np.product

所以,把所有东西放在一起,

def apply_to_bins(values, bins, op):
    grouped = groupby(np.argsort(bins), key=lambda i: bins[i])
    applied = [(bin, op(x[list(indices)])) for bin, indices in grouped]
    return zip(*applied)

答案 2 :(得分:1)

如果您打算做这类事情,我强烈建议您使用Pandas套餐。有一个很好的groupby()方法,你可以调用数据框或系列,使这种事情变得容易。

示例:


In [450]: lst = [1, 2, 3, 1, 2, 3]
In [451]: s = Series([1, 2, 3, 10, 20, 30], lst)
In [452]: grouped = s.groupby(level=0)
In [455]: grouped.sum()
Out[455]: 
1    11
2    22
3    33

答案 3 :(得分:1)

有几个有趣的解决方案不依赖于groupby。第一个很简单:

def apply_to_bins(func, values, bins):
    return zip(*((bin, func(values[bins == bin])) for bin in set(bins)))

这使用“花式索引”代替分组,并且对小输入表现得相当好;基于列表理解的变体做得更好(参见下面的时间安排)。

def apply_to_bins2(func, values, bins):
    bin_names = sorted(set(bins))
    return bin_names, [func(values[bins == bin]) for bin in bin_names]

这些具有可读性的优点。对于小输入,两者都比groupby好,但对于大输入,它们会慢得多,特别是当有很多箱子时;他们的表现是O(n_items * n_bins)。对于小输入,不同的基于numpy的方法较慢,但对于大输入则要快得多,尤其是对于具有大量区域的大输入:

def apply_to_bins3(func, values, bins):
    bins_argsort = bins.argsort()
    values = values[bins_argsort]
    bins = bins[bins_argsort]
    group_indices = (bins[1:] != bins[:-1]).nonzero()[0] + 1
    groups = numpy.split(values, group_indices)
    return numpy.unique(bins), [func(g) for g in groups]

一些测试。首先是小投入:

>>> def apply_to_bins_groupby(func, x, b):
...         return zip(*[(k, np.product(x[list(v)]))
...                  for k, v in groupby(np.argsort(b), key=lambda i: b[i])])
... 
>>> x = numpy.array([1, 2, 3, 4, 5, 6])
>>> b = numpy.array(['a', 'b', 'a', 'a', 'c', 'c'])
>>> 
>>> %timeit apply_to_bins(numpy.prod, x, b)
10000 loops, best of 3: 31.9 us per loop
>>> %timeit apply_to_bins2(numpy.prod, x, b)
10000 loops, best of 3: 29.6 us per loop
>>> %timeit apply_to_bins3(numpy.prod, x, b)
10000 loops, best of 3: 122 us per loop
>>> %timeit apply_to_bins_groupby(numpy.prod, x, b)
10000 loops, best of 3: 67.9 us per loop

apply_to_bins3在这里表现不太好,但它仍然比最快的慢一个数量级。当n_items变大时,它会做得更好:

>>> x = numpy.arange(1, 100000)
>>> b_names = numpy.array(['a', 'b', 'c', 'd'])
>>> b = b_names[numpy.random.random_integers(0, 3, 99999)]
>>> 
>>> %timeit apply_to_bins(numpy.prod, x, b)
10 loops, best of 3: 27.8 ms per loop
>>> %timeit apply_to_bins2(numpy.prod, x, b)
10 loops, best of 3: 27 ms per loop
>>> %timeit apply_to_bins3(numpy.prod, x, b)
100 loops, best of 3: 13.7 ms per loop
>>> %timeit apply_to_bins_groupby(numpy.prod, x, b)
10 loops, best of 3: 124 ms per loop

n_bins上升时,前两种方法花费的时间太长而无法在此处显示 - 大约五秒钟。 apply_to_bins3在这里是明显的赢家。

>>> x = numpy.arange(1, 100000)
>>> bn_product = product(['a', 'b', 'c', 'd', 'e'], repeat=5)
>>> b_names = numpy.array(list(''.join(s) for s in bn_product))
>>> b = b_names[numpy.random.random_integers(0, len(b_names) - 1, 99999)]
>>> 
>>> %timeit apply_to_bins3(numpy.prod, x, b)
10 loops, best of 3: 109 ms per loop
>>> %timeit apply_to_bins_groupby(numpy.prod, x, b)
1 loops, best of 3: 205 ms per loop

总体而言,groupby在大多数情况下可能都很好,但不太可能按照this thread的建议进行扩展。使用纯(呃)numpy方法,对于小输入来说速度较慢,但​​只有一点点;权衡是一个很好的权衡。

答案 4 :(得分:1)

使用pandas groupby,这将是

import pandas as pd

def with_pandas_groupby(func, x, b):
    grouped = pd.Series(x).groupby(b)
    return grouped.agg(func)

使用OP的例子:

>>> x = [1,2,3,4,5,6]
>>> b = ["a","b","a","a","c","c"]
>>> with_pandas_groupby(np.prod, x, b)
a    12
b     2
c    30

我只是对速度进行了调整,因此我将with_pandas_groupbysenderle的答案中给出的一些函数进行了比较。

  • apply_to_bins_groupby

     3 levels,      100 values: 175 us per loop
     3 levels,     1000 values: 1.16 ms per loop
     3 levels,  1000000 values: 1.21 s per loop
    
    10 levels,      100 values: 304 us per loop
    10 levels,     1000 values: 1.32 ms per loop
    10 levels,  1000000 values: 1.23 s per loop
    
    26 levels,      100 values: 554 us per loop
    26 levels,     1000 values: 1.59 ms per loop
    26 levels,  1000000 values: 1.27 s per loop
    
  • apply_to_bins3

     3 levels,      100 values: 136 us per loop
     3 levels,     1000 values: 259 us per loop
     3 levels,  1000000 values: 205 ms per loop
    
    10 levels,      100 values: 297 us per loop
    10 levels,     1000 values: 447 us per loop
    10 levels,  1000000 values: 262 ms per loop
    
    26 levels,      100 values: 617 us per loop
    26 levels,     1000 values: 795 us per loop
    26 levels,  1000000 values: 299 ms per loop
    
  • with_pandas_groupby

     3 levels,      100 values: 365 us per loop
     3 levels,     1000 values: 443 us per loop
     3 levels,  1000000 values: 89.4 ms per loop
    
    10 levels,      100 values: 369 us per loop
    10 levels,     1000 values: 453 us per loop
    10 levels,  1000000 values: 88.8 ms per loop
    
    26 levels,      100 values: 382 us per loop
    26 levels,     1000 values: 466 us per loop
    26 levels,  1000000 values: 89.9 ms per loop
    

所以pandas对于大件商品尺寸来说是最快的。更多级别(箱)对计算时间没有太大影响。 (请注意,时间是从numpy数组开始计算的,并且包含了创建pandas.Series的时间)

我用以下内容生成数据:

def gen_data(levels, size):
    choices = 'abcdefghijklmnopqrstuvwxyz'
    levels = np.asarray([l for l in choices[:nlevels]])
    index = np.random.random_integers(0, levels.size - 1, size)
    b = levels[index]
    x = np.arange(1, size + 1)
    return x, b

然后在ipython中运行基准测试,如下所示:

In [174]: for nlevels in (3, 10, 26):
   .....:     for size in (100, 1000, 10e5):
   .....:         x, b = gen_data(nlevels, size)
   .....:         print '%2d levels, ' % nlevels, '%7d values:' % size,
   .....:         %timeit function_to_time(np.prod, x, b)
   .....:     print

答案 5 :(得分:0)

在聚合函数func可以表示为和的特殊情况下,bincount似乎比pandas更快。例如,当func是产品时,它可以表示为对数的总和,我们可以这样做:

x = np.arange( 1000000 )
b = nr.randint( 0, 100, 1000000 )

def apply_to_bincount( values, bins ) :
    logy = np.bincount( bins, weights=np.log( values ) )
    return np.arange(len(logy)), np.exp( logy )

%%timeit
apply_to_bincount( x, b )
10 loops, best of 3: 16.9 ms per loop

%%timeit
with_pandas_groupby( np.prod, x, b )
10 loops, best of 3: 36.2 ms per loop