2020年7月13日月曜日

【Numpy】ndarrayの複製

ndarrayをコピーする場合、そのやり方によってコピー元・先のオブジェクトが束縛される場合と別のオブジェクトとなる場合があり注意が必要。代入演算子で複製すると参照渡しとなり同じメモリ領域を参照するためコピー先のオブジェクトとコピー元のオブジェクトに互いに影響される。コピー先のオブジェクトをコピー元のオブジェクトとリンクさせたくない場合はndarray.copynumpy.copycopyモジュールを使う。


1. 代入演算子=によるリストのコピー

ndarrayを代入演算子で複製すると参照渡しとなり、コピー先のオブジェクトとコピー元のオブジェクトが同じメモリ領域を参照することになる。このためどちらかの値を変更するともう一方の値も変更されてしまう。

代入演算子=によりyにndarray xを代入した場合。代入後にxの値を変更するとyの値も変更される。関数id()によりidを確認するとx,yが同じidとなっている、つまり同じオブジェクトとして取り扱われていることが分かる。

import numpy as np

x = np.array([1,2,3])
y = x

print(x, y)

x[0] = 4

print(x, y)

print(id(x), id(y))

実行結果

[1 2 3] [1 2 3]
[4 2 3] [4 2 3]
1616122140032 1616122140032


2. スライスによるndarrayのコピー

コピー元のndarrayの要素をスライシングにより取り出してコピー先のndarrayに代入すると、ndarrayidは別になるが要素の変更の影響を受ける。これは要素が参照渡しとなっているため。(要素のidは同じ)

import numpy as np

x = np.array([1,2,3])
y = x[:]

print(x, y)

x[0] = 4

print(x, y)

print(id(x), id(y))
print(id(x[0]), id(y[0]))

実行結果

[1 2 3] [1 2 3]
[4 2 3] [4 2 3]
1616227256800 1616227256320
1616225748576 1616225748576


3. ndarray.copy()メソッドによるndarrayのコピー

ndarray.copyによりコピーするとコピー元、コピー先は別のオブジェクトとなるためお互いに値を変更した際に影響を受けない。

import numpy as np

x = np.array([1,2,3])
y = x.copy()

print(x, y)

x[0] = 4

print(x, y)

print(id(x), id(y))

実行結果

[1 2 3] [1 2 3]
[4 2 3] [1 2 3]
1616227255680 1616227254560

ただし、ndarrayが要素としてリストをやndarrayを含む場合、copyによりコピーした場合も要素となっているリストやndarrayを変更するとコピー先、コピー元両方のリストが影響を受ける。コピーしたリスト自体は参照されていないが、その要素であるリストは参照渡しとなっているためである。(浅いコピー)
ndarray a, bを要素として持つndarray xをcopyでyにコピーし、aの要素を変更した場合。x, yのidは異なりx, yは参照関係に無いが、aは参照が維持されているためx, y両方の要素が変更される。これを避けるためにはnumpy.deepcopyにより「深いコピー」を行う必要がある。

import numpy as np

a = np.array([1, 2])
b = np.array([3])

x = np.array([a, b])
y = x.copy()

print(x, y)

a[0] = 4

print(x, y)

print(id(x), id(y))

実行結果


[array([1, 2]) array([3])] [array([1, 2]) array([3])]
[array([4, 2]) array([3])] [array([4, 2]) array([3])]
1616227256320 1616227256160



4. 関数numpy.copy()によるリストのコピー

numpy.copyを用いるとcopy同様のコピーができる。

import numpy as np

x = np.array([1,2,3])
y = np.copy(x)

print(x, y)

x[0] = 4

print(x, y)

print(id(x), id(y))

実行結果

[1 2 3] [1 2 3]
[4 2 3] [1 2 3]
2230028332208 2230028332368

関数numpy.copyによるコピーは浅いコピーであり、要素としてndarrayやリストを含むリストを複製した際の挙動はndarray.copy同様に互いの要素の変更の影響を受ける

import numpy as np

a = np.array([1, 2])
b = np.array([3])

x = np.array([a, b])
y = np.copy(x)

print(x, y)

a[0] = 4

print(x, y)

print(id(x), id(y))

実行結果


[array([1, 2]) array([3])] [array([1, 2]) array([3])]
[array([4, 2]) array([3])] [array([4, 2]) array([3])]
1616227255840 1616227256400

5. 関数copy.deepcopy()によるリストのコピー(深いコピー)

ndarrayが要素としてndarrayやリストを含む場合、ndarray.copynumpy.copyでコピーした場合、要素であるndarrayやリストは参照渡しとなる。コピー先とコピー元をリンクさせたくない場合は、標準ライブラリcopycopy.deepcopyにより「深いコピー」を行う。標準ライブラリcopyはimportが必要。
copy.deepcopy()ndarray xをndarray yにコピーしたのち、xの要素であるndarray aに値を追加する。xの要素は変更されるが、yは参照関係が切れており要素が変更されない。

import numpy as np
import copy

a = np.array([1, 2])
b = np.array([3])

x = np.array([a, b])
y = copy.deepcopy(x)

print(x, y)

a[0] = 4

print(x, y)

print(id(x), id(y))

実行結果


[array([1, 2]) array([3])] [array([1, 2]) array([3])]
[array([4, 2]) array([3])] [array([1, 2]) array([3])]
1616227863648 1616227257440


6. リファレンス

NumPy > numpy.copy
NumPy > numpy.ndarray.copy
NumPy > numpy.copy
Python 標準ライブラリ > データ型 > copy --- 浅いコピーおよび深いコピー操作

使用バージョン:Python 3.7.0/numpy 1.18.4

0 件のコメント:

コメントを投稿