【Pandas】locとilocでビューとコピーが返ってくる条件【Python】
pandasのDataFrameには色々な操作をするメソッドがあり、戻り値として操作後のDataFrameが返ってくる。
その戻ってきたDataFrameにはビューとコピーの2種類がある。
ビューに対して、ビューと意識せずに操作をしてしまうと、思わぬところでバグが発生したりする。
かといってコピーばかりにすると、大きなデータを扱う際に多量のメモリを消費しやすくなる。
まずは違いを認識することが大切。
ビューとコピーの違い
ビューのDataFrameを更新すると元のDataFrameも更新される。その逆も然りで、元のDataFrameを更新するとビューのDataFrameも更新される。
一方、コピーの場合はコピー後のDataFrameを更新しても、コピー元のDataFrmaeは更新されない(コピーなので当たり前か)
ビューとコピーのDataFrame作成
1import pandas as pd
2
3data = [[1, 2, 3], [2, 3, 4], [3, 4, 5]]
4cols = ["A", "B", "C"]
5index = [0, 1, 2]
6df = pd.DataFrame(data=data, columns=cols, index=index)
7
8print(df)
9# A B C
10# 0 1 2 3
11# 1 2 3 4
12# 2 3 4 5
13
14# コピーの作成
15df_copy = df.copy()
16print(df_copy._is_view)
17# False
18
19# ビューの作成
20df_view = df.iloc[0:, 0:]
21print(df_view._is_view)
22# True
DataFrameの_is_viewプロパティにアクセスすることでビューかどうかを判定できる。
コピーを更新してもコピー元は更新されない
コピー後のDataFrameの0行A列の値を更新しても、元のDataFrameは更新なし。
1# コピーされたDFに対して変更を加えてもコピー元のDFは変更されない
2df_copy.iloc[0, 0] = 10
3print(df_copy)
4# A B C
5# 0 10 2 3
6# 1 2 3 4
7# 2 3 4 5
8print(df)
9# A B C
10# 0 1 2 3
11# 1 2 3 4
12# 2 3 4 5
ビューを更新すると元のDataFrameも更新される
1# ビューのDFを更新すると元のDFも更新される
2df_view.iloc[0, 0] = 20
3print(df_view)
4# A B C
5# 0 20 2 3
6# 1 2 3 4
7# 2 3 4 5
8print(df)
9# A B C
10# 0 20 2 3
11# 1 2 3 4
12# 2 3 4 5
ビューのDataFrameの0行A列を更新すると、ビューの作成元のData Frameの値も更新される。
元のDataFrameを更新するとビューも更新される
1# 元のDFを更新すると、ビューのDFも更新される
2df.iloc[1, 1] = 30
3print(df)
4# A B C
5# 0 20 2 3
6# 1 2 30 4
7# 2 3 4 5
8print(df_view)
9# A B C
10# 0 20 2 3
11# 1 2 30 4
12# 2 3 4 5
元のDataFrameの1行B列を更新すると、ビューのDataFrameの1行B列も更新される。
locでビューとコピーが返ってくるパターン
locでは指定した条件によってビューかコピーのどちらかが返ってくる。
基本的には、列をスライスで指定すると、ビューが返ってくると思っていればOK。
データ作成
以下のような列とインデックスのDataFrameを作成。
あえて列も行も一意にならないようにしているが、ビューかコピーのどちらが返ってくるかには特に影響なし。
1data = [[1, 2, 3, 4, 5], [2, 3, 4, 5, 6], [3, 4, 5, 6, 7], [4, 5, 6, 7, 8]]
2cols = ["A", "B", "C", "C", "D"]
3index = [0, 1, 1, 2]
4df = pd.DataFrame(data=data, columns=cols, index=index)
5
6print(df)
7# A B C C D
8# 0 1 2 3 4 5
9# 1 2 3 4 5 6
10# 1 3 4 5 6 7
11# 3 4 5 6 7 8
ビューが返ってくるパターン
基本的には列をスライス指定した場合にビューが返ってくる。
1
2# 列の一部をスライスを使って指定
3df_loc_slice1 = df.loc[:, :"D"]
4print(df_loc_slice1._is_view)
5# True
6
7# 行と列の一部をスライスを使って指定
8df_loc_slice2 = df.loc[0:, :"D"]
9print(df_loc_slice2._is_view)
10# True
11
12# 列の一部をスライスを使って指定。行は個別で指定。
13df_loc_slice3 = df.loc[[0, 1, 2], "C":"D"]
14print(df_loc_slice3._is_view)
15# True
16
17# 行の一部をスライスを使って指定
18df_loc_slice4 = df.loc[0:, :]
19print(df_loc_slice4._is_view)
20# True
コピーが返ってくるパターン
列をスライスなしで指定した場合はコピーが返ってくる。
また、行も列もスライスの記号だけを指定した場合もコピーが返ってくる。
1# 行も列もスライスの記号だけを指定した場合はコピーが返ってくる
2df_loc_slice5 = df.loc[:, :]
3print(df_loc_slice5._is_view)
4# False
5
6# 列をスライスなしで指定
7df_loc1 = df.loc[0:, ["A", "B", "C", "D"]]
8print(df_loc1._is_view)
9# False
ilocでビューとコピーが返ってくるパターン
ビューとコピーが返ってくるパターンはlocと同じ。
ビューが返ってくるパターン
基本的には列をスライス指定した場合にビューが返ってくる。
1
2# 列の一部をスライスを使って指定
3df_iloc_slice1 = df.iloc[:, :5]
4print(df_iloc_slice1._is_view)
5# True
6
7# 行と列の一部をスライスを使って指定
8df_iloc_slice2 = df.iloc[0:, :5]
9print(df_iloc_slice2._is_view)
10# True
11
12# 列の一部をスライスを使って指定。行は個別で指定。
13df_iloc_slice3 = df.iloc[[0, 1, 3], :5]
14print(df_iloc_slice3._is_view)
15# True
16
17# 行の一部をスライスを使って指定
18df_iloc_slice4 = df.iloc[0:, :]
19print(df_iloc_slice4._is_view)
20# True
コピーが返ってくるパターン
列をスライスなしで指定した場合はコピーが返ってくる。
また、行も列もスライスの記号だけを指定した場合もコピーが返ってくる。
1# 列をスライスなしで指定
2df_iloc1 = df.iloc[0:, [0, 1, 2, 3, 4]]
3print(df_iloc1._is_view)
4# False
5
6# 行も列もスライスの記号だけを指定した場合はコピーが返ってくる
7df_iloc_slice5 = df.iloc[:, :]
8print(df_iloc_slice5._is_view)
9# False
まとめ
- 列をスライス指定すると基本的にはビューが返ってくる
- 列を個別に指定すると基本的にはコピーが返ってくる
1# ビューが返ってくるパターン(locもilocも同じ)
2# 列の一部をスライスを使って指定
3df_loc_slice1 = df.loc[:, :"D"]
4
5# 行と列の一部をスライスを使って指定
6df_loc_slice2 = df.loc[0:, :"D"]
7
8# 列の一部をスライスを使って指定。行は個別で指定。
9df_loc_slice3 = df.loc[[0, 1, 2], "C":"D"]
10
11# 行の一部をスライスを使って指定
12df_loc_slice4 = df.loc[0:, :]
13
14# コピーが返ってくるパターン(locもilocも同じ)
15# 行も列もスライスの記号だけを指定した場合はコピーが返ってくる
16df_loc_slice5 = df.loc[:, :]
17
18# 列をスライスなしで指定
19df_loc1 = df.loc[0:, ["A", "B", "C", "D"]]
ビューが返ってくるメソッドには、loc, iloc以外にも、filter, query, groupbyなどがある。