私はこのような派手な配列を持っています: [1 2 2 0 0 1 3 5]
要素のインデックスを2D配列として取得することは可能ですか?たとえば、上記の入力に対する答えは次のようになります[[3 4], [0 5], [1 2], [6], [], [7]]
現在、私はさまざまな値をループし、各値を呼び出すnumpy.where(input == i)
必要があります。これは、十分な大きさの入力でのパフォーマンスが非常に悪いものです。
私はこのような派手な配列を持っています: [1 2 2 0 0 1 3 5]
要素のインデックスを2D配列として取得することは可能ですか?たとえば、上記の入力に対する答えは次のようになります[[3 4], [0 5], [1 2], [6], [], [7]]
現在、私はさまざまな値をループし、各値を呼び出すnumpy.where(input == i)
必要があります。これは、十分な大きさの入力でのパフォーマンスが非常に悪いものです。
回答:
以下は、O(max(x)+ len(x))を使用したアプローチscipy.sparse
です。
import numpy as np
from scipy import sparse
x = np.array("1 2 2 0 0 1 3 5".split(),int)
x
# array([1, 2, 2, 0, 0, 1, 3, 5])
M,N = x.max()+1,x.size
sparse.csc_matrix((x,x,np.arange(N+1)),(M,N)).tolil().rows.tolist()
# [[3, 4], [0, 5], [1, 2], [6], [], [7]]
これは、位置(x [0]、0)、(x [1]、1)、...にエントリを持つスパース行列を作成することで機能します。CSC
(圧縮スパース列)形式を使用すると、これはかなり単純です。次に、マトリックスはLIL
(リンクされたリスト)形式に変換されます。このフォーマットは、各行の列インデックスをリストとしてリストに保存しますrows
属性のので、それを取得してリストに変換するだけです。
小さな配列の場合は注意してください argsort
ベースのソリューションの場合はおそらくより高速ですが、めちゃくちゃ大きなサイズではない場合もあります。
編集:
argsort
-based numpy
-only solution:
np.split(x.argsort(kind="stable"),np.bincount(x)[:-1].cumsum())
# [array([3, 4]), array([0, 5]), array([1, 2]), array([6]), array([], dtype=int64), array([7])]
グループ内のインデックスの順序が重要でない場合は、次の方法も試してみてくださいargpartition
(この小さな例ではたまたま違いはありませんが、これは一般に保証されていません)。
bb = np.bincount(x)[:-1].cumsum()
np.split(x.argpartition(bb),bb)
# [array([3, 4]), array([0, 5]), array([1, 2]), array([6]), array([], dtype=int64), array([7])]
編集:
@Divakarはの使用を推奨していませんnp.split
。代わりに、ループの方がおそらく高速です。
A = x.argsort(kind="stable")
B = np.bincount(x+1).cumsum()
[A[B[i-1]:B[i]] for i in range(1,len(B))]
または、まったく新しい(Python3.8 +)セイウチ演算子を使用することもできます。
A = x.argsort(kind="stable")
B = np.bincount(x)
L = 0
[A[L:(L:=L+b)] for b in B.tolist()]
編集(編集済み):
(純粋なnumpyではありません):numbaの代わりに(@senderleの投稿を参照)、pythranを使用することもできます。
でコンパイル pythran -O3 <filename.py>
import numpy as np
#pythran export sort_to_bins(int[:],int)
def sort_to_bins(idx, mx):
if mx==-1:
mx = idx.max() + 1
cnts = np.zeros(mx + 2, int)
for i in range(idx.size):
cnts[idx[i] + 2] += 1
for i in range(3, cnts.size):
cnts[i] += cnts[i-1]
res = np.empty_like(idx)
for i in range(idx.size):
res[cnts[idx[i]+1]] = i
cnts[idx[i]+1] += 1
return [res[cnts[i]:cnts[i+1]] for i in range(mx)]
ここnumba
では、ウィスカーのパフォーマンスの点で勝っています。
repeat(lambda:enum_bins_numba_buffer(x),number=10)
# [0.6235917090671137, 0.6071486569708213, 0.6096088469494134]
repeat(lambda:sort_to_bins(x,-1),number=10)
# [0.6235359431011602, 0.6264424560358748, 0.6217901279451326]
古いもの:
import numpy as np
#pythran export bincollect(int[:])
def bincollect(a):
o = [[] for _ in range(a.max()+1)]
for i,j in enumerate(a):
o[j].append(i)
return o
タイミングvs numba(旧)
timeit(lambda:bincollect(x),number=10)
# 3.5732191529823467
timeit(lambda:enumerate_bins(x),number=10)
# 6.7462647299980745
np.split
。
データのサイズに応じて可能なオプションの1つは、単にドロップしnumpy
て使用することcollections.defaultdict
です。
In [248]: from collections import defaultdict
In [249]: d = defaultdict(list)
In [250]: l = np.random.randint(0, 100, 100000)
In [251]: %%timeit
...: for k, v in enumerate(l):
...: d[v].append(k)
...:
10 loops, best of 3: 22.8 ms per loop
次に、の辞書になり{value1: [index1, index2, ...], value2: [index3, index4, ...]}
ます。時間のスケーリングは配列のサイズとほぼ線形であるため、10,000,000は私のマシンで約2.7秒かかります。これは十分に妥当と思われます。
リクエストは解決策ですnumpy
が、興味深いnumba
ベースの解決策があるかどうかを確認することにしました。そして確かにあります!これは、事前に割り当てられた単一のバッファーに格納された不規則な配列としてパーティションリストを表す方法です。これはargsort
、Paul Panzerが提案したアプローチから着想を得ています。(同じようには機能しなかったが、より単純な古いバージョンについては、以下を参照してください。)
@numba.jit(numba.void(numba.int64[:],
numba.int64[:],
numba.int64[:]),
nopython=True)
def enum_bins_numba_buffer_inner(ints, bins, starts):
for x in range(len(ints)):
i = ints[x]
bins[starts[i]] = x
starts[i] += 1
@numba.jit(nopython=False) # Not 100% sure this does anything...
def enum_bins_numba_buffer(ints):
ends = np.bincount(ints).cumsum()
starts = np.empty(ends.shape, dtype=np.int64)
starts[1:] = ends[:-1]
starts[0] = 0
bins = np.empty(ints.shape, dtype=np.int64)
enum_bins_numba_buffer_inner(ints, bins, starts)
starts[1:] = ends[:-1]
starts[0] = 0
return [bins[s:e] for s, e in zip(starts, ends)]
これは、1千万のアイテムリストを75ミリ秒で処理します。これは、純粋なPythonで記述されたリストベースのバージョンと比べて約50倍のスピードアップです。
遅いが多少読みやすいバージョンの場合、動的サイズの「型付きリスト」に最近追加された実験的なサポートに基づいて、以前のバージョンをここに示します。これにより、各ビンを順不同ではるかに迅速に埋めることができます。
これはnumba
の型推論エンジンと少し取り組んでいますが、その部分を処理するためのより良い方法があると確信しています。これはまた、上記よりもほぼ10倍遅いことがわかります。
@numba.jit(nopython=True)
def enum_bins_numba(ints):
bins = numba.typed.List()
for i in range(ints.max() + 1):
inner = numba.typed.List()
inner.append(0) # An awkward way of forcing type inference.
inner.pop()
bins.append(inner)
for x, i in enumerate(ints):
bins[i].append(x)
return bins
私はこれらを以下に対してテストしました:
def enum_bins_dict(ints):
enum_bins = defaultdict(list)
for k, v in enumerate(ints):
enum_bins[v].append(k)
return enum_bins
def enum_bins_list(ints):
enum_bins = [[] for i in range(ints.max() + 1)]
for x, i in enumerate(ints):
enum_bins[i].append(x)
return enum_bins
def enum_bins_sparse(ints):
M, N = ints.max() + 1, ints.size
return sparse.csc_matrix((ints, ints, np.arange(N + 1)),
(M, N)).tolil().rows.tolist()
また、enum_bins_numba_buffer
(以下で詳細に説明する)に似た、プリコンパイルされたcythonバージョンに対してもテストしました。
1000万個のランダム整数のリスト(ints = np.random.randint(0, 100, 10000000)
)の、次の結果が得られます。
enum_bins_dict(ints)
3.71 s ± 80.2 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
enum_bins_list(ints)
3.28 s ± 52.3 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
enum_bins_sparse(ints)
1.02 s ± 34.7 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
enum_bins_numba(ints)
693 ms ± 5.81 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
enum_bins_cython(ints)
82.3 ms ± 1.77 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
enum_bins_numba_buffer(ints)
77.4 ms ± 2.06 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
印象的な、での作業のこの方法numba
よりも優れAcython
は、境界チェックがオフになっていても、同じ関数のバージョンます。pythran
このアプローチを使用してテストするための十分な知識はまだありませんが、比較を見たいと思います。この高速化に基づいて、pythran
と、このアプローチバージョンもかなり高速になる可能性があります。
これcython
は参考用のバージョンであり、いくつかのビルド手順があります。cython
インストールしたら、次のsetup.py
ような単純なファイルが必要になります。
from distutils.core import setup
from distutils.extension import Extension
from Cython.Build import cythonize
import numpy
ext_modules = [
Extension(
'enum_bins_cython',
['enum_bins_cython.pyx'],
)
]
setup(
ext_modules=cythonize(ext_modules),
include_dirs=[numpy.get_include()]
)
そしてcythonモジュールenum_bins_cython.pyx
:
# cython: language_level=3
import cython
import numpy
cimport numpy
@cython.boundscheck(False)
@cython.cdivision(True)
@cython.wraparound(False)
cdef void enum_bins_inner(long[:] ints, long[:] bins, long[:] starts) nogil:
cdef long i, x
for x in range(len(ints)):
i = ints[x]
bins[starts[i]] = x
starts[i] = starts[i] + 1
def enum_bins_cython(ints):
assert (ints >= 0).all()
# There might be a way to avoid storing two offset arrays and
# save memory, but `enum_bins_inner` modifies the input, and
# having separate lists of starts and ends is convenient for
# the final partition stage.
ends = numpy.bincount(ints).cumsum()
starts = numpy.empty(ends.shape, dtype=numpy.int64)
starts[1:] = ends[:-1]
starts[0] = 0
bins = numpy.empty(ints.shape, dtype=numpy.int64)
enum_bins_inner(ints, bins, starts)
starts[1:] = ends[:-1]
starts[0] = 0
return [bins[s:e] for s, e in zip(starts, ends)]
これら2つのファイルを作業ディレクトリに置いて、次のコマンドを実行します。
python setup.py build_ext --inplace
その後、を使用して関数をインポートできfrom enum_bins_cython import enum_bins_cython
ます。
これは本当にひどい方法ですが、これはひどいですが、共有するのは面白すぎてわかりませんでしたnumpy
。
out = np.array([''] * (x.max() + 1), dtype = object)
np.add.at(out, x, ["{} ".format(i) for i in range(x.size)])
[[int(i) for i in o.split()] for o in out]
Out[]:
[[3, 4], [0, 5], [1, 2], [6], [], [7]]
編集:これは私がこの道に沿って見つけることができる最高の方法です。それでも、@ PaulPanzerのargsort
ソリューションよりも10倍遅くなります。
out = np.empty((x.max() + 1), dtype = object)
out[:] = [[]] * (x.max() + 1)
coords = np.empty(x.size, dtype = object)
coords[:] = [[i] for i in range(x.size)]
np.add.at(out, x, coords)
list(out)
あなたは数字の辞書を作ることでそれを行うことができます、キーは数字であり、値はその数字が見られるインデックスでなければなりません、これはそれを行う最も速い方法の一つです、あなたは以下のコードを見ることができます:
>>> import numpy as np
>>> a = np.array([1 ,2 ,2 ,0 ,0 ,1 ,3, 5])
>>> b = {}
# Creating an empty list for the numbers that exist in array a
>>> for i in range(np.min(a),np.max(a)+1):
b[str(i)] = []
# Adding indices to the corresponding key
>>> for i in range(len(a)):
b[str(a[i])].append(i)
# Resulting Dictionary
>>> b
{'0': [3, 4], '1': [0, 5], '2': [1, 2], '3': [6], '4': [], '5': [7]}
# Printing the result in the way you wanted.
>>> for i in sorted (b.keys()) :
print(b[i], end = " ")
[3, 4] [0, 5] [1, 2] [6] [] [7]
疑似コード:
numpy配列の最小値を最大値から減算してから1を加算することにより、「2d配列内の1d配列の数」を取得します。あなたの場合、それは5-0 + 1 = 6になります
2D配列をその中の1D配列の数で初期化します。あなたのケースでは、6の1d配列で2d配列を初期化します。各1d配列はnumpy配列の一意の要素に対応します。たとえば、最初の1d配列は「0」に対応し、2番目の1d配列は「1」に対応します...
numpy配列をループして、要素のインデックスを右の対応する1次元配列に入れます。あなたの場合、numpy配列の最初の要素のインデックスは2番目の1d配列に入れられ、numpy配列の2番目の要素のインデックスは3番目の1d配列に入れられます...
この疑似コードは、numpy配列の長さに依存するため、実行に線形時間がかかります。
これはあなたが望むものをあなたに正確に与え、私のマシンで10,000,000のために約2.5秒かかります:
import numpy as np
import timeit
# x = np.array("1 2 2 0 0 1 3 5".split(),int)
x = np.random.randint(0, 100, 100000)
def create_index_list(x):
d = {}
max_value = -1
for i,v in enumerate(x):
if v > max_value:
max_value = v
try:
d[v].append(i)
except:
d[v] = [i]
result_list = []
for i in range(max_value+1):
if i in d:
result_list.append(d[i])
else:
result_list.append([])
return result_list
# print(create_index_list(x))
print(timeit.timeit(stmt='create_index_list(x)', number=1, globals=globals()))
したがって、要素のリストが与えられた場合、(要素、インデックス)ペアを作成する必要があります。線形時間では、これは次のように実行できます。
hashtable = dict()
for idx, val in enumerate(mylist):
if val not in hashtable.keys():
hashtable[val] = list()
hashtable[val].append(idx)
newlist = sorted(hashtable.values())
これにはO(n)時間かかるはずです。今のところ、これより速い解決策は考えられませんが、そうした場合はここで更新します。
np.argsort([1, 2, 2, 0, 0, 1, 3, 5])
与えarray([3, 4, 0, 5, 1, 2, 6, 7], dtype=int64)
ます。その後、次の要素を比較できます。