web-dev-qa-db-ja.com

ループを回避してnumpy配列の要素を置き換えます

与えられた値を持つ非常に大きな1dnumpy配列Xoldがあります。これらの値は、2d numpy配列Yで指定されたルールに従って置き換えられます。例は次のようになります。

Xold=np.array([0,1,2,3,4])
Y=np.array([[0,0],[1,100],[3,300],[4,400],[2,200]])

Xoldの値がY [:、0]の値と同じである場合は常に、Xnewの新しい値はY [:、1]の対応する値である必要があります。これは、ネストされた2つのforループによって実現されます。

Xnew=np.zeros(len(Xold))
for i in range(len(Xold)):
for j in range(len(Y)):
    if Xold[i]==Y[j,0]:
        Xnew[i]=Y[j,1]

与えられた例では、これはXnew=[0,100,200,300,400]を生成します。ただし、大きなデータセットの場合、この手順は非常に遅くなります。このタスクを実行するためのより速く、よりエレガントな方法は何ですか?

6
Jann

最速の方法を選択

この質問への回答は、numpy配列の要素を置き換えるためのさまざまな方法を提供しました。どちらが一番速いか確認してみましょう。

TL; DR:Numpyインデックスが勝者です

_ def meth1(): # suggested by @Slam
    for old, new in Y:  
        Xold[Xold == old] = new

 def meth2(): # suggested by myself, convert y_dict = dict(Y) first
     [y_dict[i] if i in y_dict.keys() else i for i in Xold]

 def meth3(): # suggested by @Eelco Hoogendoom, import numpy_index as npi first
     npi.remap(Xold, keys=Y[:, 0], values=Y[:, 1])

 def meth4(): # suggested by @Brad Solomon, import pandas as pd first 
     pd.Series(Xold).map(pd.Series(Y[:, 1], index=Y[:, 0])).values

  # suggested by @jdehesa. create Xnew = Xold.copy() and index
  # idx = np.searchsorted(Xold, Y[:, 0]) first
  def meth5():             
     Xnew[idx] = Y[:, 1]
_

それほど驚くべき結果ではありません

_ In [39]: timeit.timeit(meth1, number=1000000)                                                                      
 Out[39]: 12.08

 In [40]: timeit.timeit(meth2, number=1000000)                                                                      
 Out[40]: 2.87

 In [38]: timeit.timeit(meth3, number=1000000)                                                                      
 Out[38]: 55.39

 In [12]: timeit.timeit(meth4, number=1000000)                                                                                      
 Out[12]: 256.84

 In [50]: timeit.timeit(meth5, number=1000000)                                                                                      
 Out[50]: 1.12
_

したがって、古き良きリスト内包表記は2番目に速く、勝利のアプローチはsearchsorted()と組み合わせたnumpyインデックスです。

3
Daniel Kislyuk

np.searchsortedYの最初の列のデータが必ずしもソートされていない一般的なケースの場合-

sidx = Y[:,0].argsort()
out = Y[sidx[np.searchsorted(Y[:,0], Xold, sorter=sidx)],1]

サンプル実行-

In [53]: Xold
Out[53]: array([14, 10, 12, 13, 11])

In [54]: Y
Out[54]: 
array([[ 10,   0],
       [ 11, 100],
       [ 13, 300],
       [ 14, 400],
       [ 12, 200]])

In [55]: sidx = Y[:,0].argsort()
    ...: out = Y[sidx[np.searchsorted(Y[:,0], Xold, sorter=sidx)],1]

In [56]: out
Out[56]: array([400,   0, 200, 300, 100])

すべての要素に対応するマッピングが利用できるわけではない場合は、次のようにもう少し作業を行う必要があります-

sidx = Y[:,0].argsort()
sorted_indx = np.searchsorted(Y[:,0], Xold, sorter=sidx)
sorted_indx[sorted_indx==len(sidx)] = len(sidx)-1
idx_out = sidx[sorted_indx]
out = Y[idx_out,1]
out[Y[idx_out,0]!=Xold] = 0 # NA values as 0s
3
Divakar

実行できる最初の改善は、numpyインデックスを使用することですが、それでも1つのループがあります。

for old, new in Y: 
    Xold[Xold == old] = new
2
Slam

これが1つの可能性です:

import numpy as np

Xold = np.array([0, 1, 2, 3, 4])
Y = np.array([[0, 0], [1, 100], [3, 300], [4, 400], [2, 200]])
# Check every X value against every Y first value
m = Xold == Y[:, 0, np.newaxis]
# Check which elements in X are among Y first values
# (so values that are not in Y are not replaced)
m_X = np.any(m, axis=0)
# Compute replacement
# Xold * (1 - m_X) are the non-replaced values
# np.sum(Y[:, 1, np.newaxis] * m, axis=0) * m_X are the replaced values
Xnew = Xold * (1 - m_X) + np.sum(Y[:, 1, np.newaxis] * m, axis=0) * m_X
print(Xnew)

出力:

[  0 100 200 300 400]

この方法は、Yの同じ値を2つ置き換える場合を除いて、ほぼすべての場合(並べ替えられていない配列、Xの値の複数の繰り返し、Xの値は置き換えられない、Yの値はXの何も置き換えない)で機能します。とにかく間違っているでしょう。ただし、その時間とスペースの複雑さは、XとYのサイズの積です。問題に追加の制約がある場合(データが並べ替えられている、繰り返しがないなど)、より良いことを行うことができる場合があります。たとえば、Xが繰り返し要素なしで並べ替えられ、Yのすべての値がXの値を置き換える場合(例のように)、これはおそらくより高速です。

import numpy as np

Xold = np.array([0, 1, 2, 3, 4])
Y = np.array([[0, 0], [1, 100], [3, 300], [4, 400], [2, 200]])
idx = np.searchsorted(Xold, Y[:, 0])
Xnew = Xold.copy()
Xnew[idx] = Y[:, 1]
print(Xnew)
# [  0 100 200 300 400]
2
jdehesa

slicing機能をargsortメソッドと組み合わせて使用​​できます。

Xnew = Y[Y[:,1].argsort()][:, 1][Xold] 

出力

array([  0, 100, 200, 300, 400])

pd.Series.map()による解決策

Pandasライブラリを使用することにオープンである場合は、.map()を使用してベクトル化された方法でこれを行うこともできます。

_>>> import pandas as pd
>>> pd.Series(Xold).map(pd.Series(Y[:, 1], index=Y[:, 0]))                                                                                                                                                                    
0      0
1    100
2    200
3    300
4    400
dtype: int64

>>> pd.Series(Xold).map(pd.Series(Y[:, 1], index=Y[:, 0])).values                                                                                                                                                            
array([  0, 100, 200, 300, 400])
_

シグニチャa.map(b)の場合、abのインデックスで対応するエントリを探し、bのそれぞれの値にマップします。

bpd.Series(Y[:, 1], index=Y[:, 0])で、0番目の列をインデックスとして使用し、1番目の列をマップされる値として使用します。


_pandas.core.algorithms_を直接使用する

内部 、これは.get_indexer()とCythonで実装されたtake_1d()を使用します。

_indexer = mapper.index.get_indexer(values)
new_values = algorithms.take_1d(mapper._values, indexer)
_

それを知っていると、アレイが本当に大きい場合は、次のようにオーバーヘッドを削減できます。

_from pandas.core import algorithms

indexer = pd.Index(Y[:, 0]).get_indexer(Xold)  
mapped = algorithms.take_1d(Y[:, 1], indexer)
_
0
Brad Solomon

numpy_indexed パッケージ(免責事項;私はその作者です)には、一般的な問題を解決する効率的なベクトル化された関数が含まれています。

import numpy_indexed as npi
Xnew = npi.remap(Xold, keys=Y[:, 0], values=Y[:, 1])

つまり、これはどのdtypeでも機能します。または、置き換えられるキーと値自体がndarrayであり、欠落している要素にどのように反応するかを指定するkwargを取得します。

pandasパフォーマンスに関して; SeriesやTableのようなまったく新しいデータ型で、このタイプのものにpandasを使用することについて常に気になりました。

0

y = dict(Y)を使用してYを辞書に変換してから、次のリスト内包表記を実行できます。

[y[i] if i in y.keys() else i for i in Xold]
0
Daniel Kislyuk