環境
- Python 3.12.4
- pandas 2.2.3
- numpy 1.26.4
「Copy-on-Write」で分からなかったこと
pandasの「Copy-on-Write」の記事を読んでいます。
「Read-only NumPy arrays」に関して、以下の部分が理解できませんでした。
The array is a copy if the initial DataFrame consists of more than one array:
In [49]: df = pd.DataFrame({"a": [1, 2], "b": [1.5, 2.5]})
In [50]: df.to_numpy()
Out[50]:
array([[1. , 1.5],
[2. , 2.5]])
The array shares data with the DataFrame if the DataFrame consists of only one NumPy array:
In [51]: df = pd.DataFrame({"a": [1, 2], "b": [3, 4]})
In [52]: df.to_numpy()
Out[52]:
array([[1, 3],
[2, 4]])
以下の言っている内容が分かりませんでした。どういうことでしょうか?
-
In [49]
のDataFrameは"more than one array"を含んでいる -
In [51]
のDataFrameは"only one NumPy array"を含んでいる
分かったこと
DataFrameはすべての列でdtypeが同じ場合、1個のnumpy arrrayを保持しているようです。
When your DataFrame only has a single data type for all the columns, DataFrame.to_numpy() will return the underlying data: 1
In [49]
のDataFrameは、各列でdtypeが異なるため(a
列はint64
, b
列はfloat64
)"more than one array"を含んでいました。
DataFrame._mgr
プロパティで、保持しているnumpy arrayの構成を確認することができました。
In [490]: df1 = pd.DataFrame({"a": [1, 2], "b": [1.5, 2.5]})
In [491]: df1.dtypes
Out[491]:
a int64
b float64
dtype: object
In [493]: df1._mgr
Out[493]:
BlockManager
Items: Index(['a', 'b'], dtype='object')
Axis 1: RangeIndex(start=0, stop=2, step=1)
NumpyBlock: slice(0, 1, 1), 1 x 2, dtype: int64
NumpyBlock: slice(1, 2, 1), 1 x 2, dtype: float64
In [494]: df2 = pd.DataFrame({"a": [1, 2], "b": [3, 4]})
In [495]: df2.dtypes
Out[495]:
a int64
b int64
dtype: object
In [496]: df2._mgr
Out[496]:
BlockManager
Items: Index(['a', 'b'], dtype='object')
Axis 1: RangeIndex(start=0, stop=2, step=1)
NumpyBlock: slice(0, 2, 1), 2 x 2, dtype: int64
df1._mgr
ではNumpyBlock
が2個あるのに対して、df2._mgr
ではNumpyBlock
は1個でした。
再度確認する
複数のnumpy arrayを含むDataFrameの場合
In [499]: pd.options.mode.copy_on_write = True
In [500]: df1 = pd.DataFrame({"a": [1, 2], "b": [1.5, 2.5]})
In [501]: arr1 = df1.to_numpy()
# `df1`と`arr1`はメモリを共有していないことの確認
In [502]: np.shares_memory(df1, arr1)
Out[502]: False
# `arr1`は書き換え可能であることの確認
In [513]: arr1.flags.writeable
Out[513]: True
In [507]: arr1[(0,0)]
Out[507]: 1.0
In [508]: arr1[(0,0)] = 100
In [509]: arr1
Out[509]:
array([[100. , 1.5],
[ 2. , 2.5]])
In [510]: df1
Out[510]:
a b
0 1 1.5
1 2 2.5
arr1
はdf1
のコピーなので、arr1
を書き換えてもdf1
は変更されないことを確認できました。
1個のnumpy arrayを含むDataFrameの場合
In [514]: pd.options.mode.copy_on_write = True
In [515]: df2 = pd.DataFrame({"a": [1, 2], "b": [3, 4]})
In [516]: arr2 = df2.to_numpy()
# `df2`と`arr2`はメモリを共有していることの確認
In [517]: np.shares_memory(df2, arr2)
Out[517]: True
# `arr1`は書き換え不可であることの確認
In [518]: arr2.flags.writeable
Out[518]: False
In [520]: arr2[(0,0)] = 100
---------------------------------------------------------------------------
ValueError: assignment destination is read-only
補足
mode.copy_on_write = "warn"
を指定しても「Read-only NumPy arrays」にならない
pd.options.mode.copy_on_write = "warn"
を指定した場合、to_numpy()
が返すarrayは書き換え可能です。
In [537]: pd.options.mode.copy_on_write = "warn"
In [538]: df2 = pd.DataFrame({"a": [1, 2], "b": [3, 4]})
In [539]: arr2 = df2.to_numpy()
In [540]: arr2.flags.writeable
Out[540]: True