[英]Applying a function by bins on a vector in Numpy
我將如何將聚合函數(例如“ sum()
”或“ max()
”)應用於向量中的bin。
那就是我有:
這樣b表示x中每個值所屬的bin。 對於ba中的每個可能值,我想對屬於該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循環”它。
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}
>>> 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
的鍵排序的b
(或x
)索引列表。
>>> [(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)
如果您打算做這類事情,我強烈建議使用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
有幾個有趣的解決方案不依賴於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
在大多數情況下可能都很好,但不太可能按照這個線程的建議進行擴展。 使用純(er) numpy
方法,對於小輸入來說速度較慢,但只有一點點; 權衡是一個很好的權衡。
有了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_groupby
與with_pandas_groupby
的答案中給出的一些函數進行了比較 。
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
在聚合函數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
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.