keisukeのブログ

***乱雑です!自分用のメモです!*** 統計や機械学習の勉強と、読み物を書く練習と、備忘録用のブログ

【numpy】viewとcopy

配列のviewとは、もとの配列と同じデータを指している。 一方、copyは、もとの配列と同じデータのコピーを指している。

つまり、配列aviewであるvが存在するとして、vの要素を書き換えるとaの値も同時に書き換わる。 しかし、acopyであるcの要素を書き換えてもaの値は影響を受けない。

例えば、

import numpy as np
a = np.arange(5)  # もとの配列
v = a[:]  # aへのview
c = a.copy()  # aのcopy

# 最初の値の確認
print(a)  # array([0, 1, 2, 3, 4])
print(v)  # array([0, 1, 2, 3, 4])
print(c)  # array([0, 1, 2, 3, 4])

# 元の配列aに対して変更を加えると?
a[0] = 100
print(a)  # array([100,   1,   2,   3,   4])
print(v)  # array([100,   1,   2,   3,   4])
print(c)  # array([0, 1, 2, 3, 4])
# viewが影響を受ける。copyは無事

# viewに対して変更を加えると?
v[2] = 200
print(a)  # array([100,   1, 200,   3,   4])
print(v)  # array([100,   1, 200,   3,   4])
print(c)  # array([0, 1, 2, 3, 4])
# 元の配列aが影響を受ける。copyは無事

# copyに対して変更を加えると?
c[3] = 300
print(a)  # array([100,   1, 200,   3,   4])
print(v)  # array([100,   1, 200,   3,   4])
print(c)  # array([0,     1,   2, 300,   4])
# cだけが書き換わる

viewはコピーをつくらないので基本的に速いしメモリ効率も良いが、もとの配列を壊したくないときcopyが必要となる。

どんな操作がviewを返して、どんな操作がcopyを返すのかを整理してみた。ちなみにこれは自分向けの整理なので、できるだけ誤りのないようにするが必ず正しいとは言えないので必ずドキュメントを参照してください。 また、カバーしきれていない部分がかなりあると思うので、教えてくれたら喜んで加筆します。

viewを返す操作 copyを返す操作 何してるの 備考
v = a または v = a[:] c = a.copy() 配列全ての値の取得 Pythonのlistだとa[:]はcopyを返すことに注意
v = a.ravel()*1 c = a.flatten() aを1次元ベクトルに変換 v = np.ravel(a)も可。
v = a[something] c = a[something].copy() aのslicing 例えばa[start:stop:step]*2, a[a>0]*3, a[ndarray([3,2,1])]*4など

さらに、fancy indexingは大変便利なのですが、内部の処理が複雑で多少計算時間がかかります。 そこで、fancy indexingのそれぞれの機能を分割したような高速な専用の関数が用意されています *5

共通の処理 専用の関数(速い) fancy indexing (遅い) 何してるの 備考
i = np.random.rand(10) > .5 c = a.compress[i] c = a[i] iの要素がTrueの場所だけ取得 c = np.compress(a, i)も可
i = np.array([3,2,1]) c = a.take(i) c = a[i] 3,2,1番目の要素を取得 c = np.take(a,i)も可

他にも、入力を破壊するメソッドとそうでないメソッドもある。 例えば、np.random.shuffle(a)aの値を書き換えるので、もとのaは上書きされてしまうが、同様の処理をするb = np.random.permutation(a)aを保存したまま、bにその結果を格納する、よってaの値は上書きされない:

上書きする 上書きしない 操作
np.random.shuffle(a) # 返り値なし b = np.random.permutation(a) 要素をランダムにシャッフル
a.resize((n,m)) # 返り値なし v = a.reshape((n,m)) # viewを返す*6 n行m列に変換

*1:可能な限りviewを返す。場合によってはcopyとなる

*2:startからstopまでstep刻みで要素を取得

*3:0以上の値の要素を取得

*4:3,2,1番目の要素を順に取得

*5:この場合全てcopyを返すことに注意 - 専用の関数が返すオブジェクトも、fancy indexingが返すオブジェクトもviewではありません

*6:可能な限りviewを返す。場合によってはcopyとなる