に依存しない興味深いソリューションがいくつかあります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
ではうまくいきませんが、それでも最速よりも1桁も遅くなります。大きくなるとより良くなり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
上がると、最初の2つのアプローチは、ここに表示するのに時間がかかりすぎます(約5秒)。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
ほとんどの場合はおそらく問題ありませんが、このスレッドで示唆されているように、適切にスケーリングされる可能性は低いです。pure(er)numpy
アプローチを使用すると、入力が小さい場合は遅くなりますが、少しだけです。トレードオフは良いものです。