【Pandas】DataFrame内で重複する列名を削除する【Python】
複数のDataFrameを結合していると、列名が重複してしまう場合がある。
マージの場合、マージ時に列名が重複する場合、自動でサフィックスが付加される(手動でも設定できる)。
マージは2つのDataFrameを結合する際に非常に便利な関数だが、結合キーがindexで複数のDataFrameをまとめて結合したい場合、concatを使う方がラクにできる。
concatでの結合時に列名が重複した場合、削除するかサフィックスなどを付加するかが必要となる(列名が重複したままで操作を行うと、想定外のことが起こりやすい)
データ作成
concatを使って重複している列名を持つDataFrameを作成する。
1# データ作成
2df1 = pd.DataFrame(data={
3 "A": [1, 2, 3],
4 "B": [4, 5, 6],
5 "C": [7, 8, 9],
6})
7df2 = pd.DataFrame(data={
8 "B": [1, 2, 3],
9 "C": [2, 3, 4],
10 "D": [3, 4, 5]
11})
12df3 = pd.DataFrame(data={
13 "C": [1, 2, 3],
14 "E": [2, 3, 4]
15})
16df = pd.concat([df1, df2, df3], axis=1)
17print(df)
18# A B C B C D C E
19# 0 1 4 7 1 2 3 1 2
20# 1 2 5 8 2 3 4 2 3
21# 2 3 6 9 3 4 5 3 4
B列が2回、C列が3回登場する。
DataFrame内で重複している列名を確認する
DataFrame.columnsに対してduplicated()を使うことで重複の確認ができる。
duplicated()を使う重複確認
1# 列名が重複しているかを確認する
2# 最初に出現した列はFalseとなり、2回目以降同じ列名が出現した場合はTrueとなる
3is_duplicate_columns = df.columns.duplicated()
4print(is_duplicate_columns)
5# [False False False True True False True False
6# 各True、Falseは以下の内容に対応
7# A B C B C D C E
2回目以降に登場したB、CにはTrueが立つ。それ以外はFalseとなる。
duplicated(keep=”last”)を使う重複確認
duplicatedの引数にkeep=”last”を指定することで、重複列のTrueとFalseを変えられる。
1# keep="last"を設定した場合、
2# 重複がある列のうち、最後に出現した列はFalseとなり、それ以前に出現した列はTrueとなる。
3is_duplicate_columns = df.columns.duplicated(keep="last")
4print(is_duplicate_columns)
5# [False True True False True False False False]
6# A B C B C D C E
B、Cは複数回登場するが、最後のB、C以外にTrueが立つようになる。
これらを利用して重複する列を取得していく。
DataFrame内で重複している列名を取得する
1# 重複している列名を取得する
2# 今回の例の場合、Cが3列あり、そのうち2回分が重複している列として取得されるため、unique()も行う
3duplicate_columns = df.columns[df.columns.duplicated()].unique()
4print(duplicate_columns)
5# Index(['B', 'C'], dtype='object')
1# 重複している列名を取得する
2# 今回の例の場合、Cが3列あり、そのうち2回分が重複している列として取得されるため、unique()も行う
3duplicate_columns = df.columns[df.columns.duplicated(keep="last")].unique()
4print(duplicate_columns)
5# Index(['B', 'C'], dtype='object')
duplicated()で重複している列名のTrue、Falseのリストが得られるので、そのTrue・Falseのリストを使って列名を取得する。
3回以上登場する列名は重複している列名のリストにも2回以上登場することになるため、unique()で一意にする。
DataFrame内で重複している列を削除する
重複している列を削除したい場合、dropなどで削除するのではなく、重複していない列を取得する感じの処理になる。
重複する列のうち、最初の列以外を削除する
1# df.columns.duplicated()で、列名に重複がある場合、2回目以降の列にはTrueが立つ
2# ~で否定することで2回目以降の列がFalseになり、除外される
3df_keep_first = df.loc[:, ~df.columns.duplicated()]
4print(df_keep_first)
5# A B C D E
6# 0 1 4 7 3 2
7# 1 2 5 8 4 3
8# 2 3 6 9 5 4
重複する列のうち、最後の列以外を削除する
keep=”last”にすることで、重複がある列は最後の列を保持するようになる。
1# duplicatedの引数にkeep=lastを設定することで、重複がある列の最後の列以外Trueが立つようになる
2# ~で否定することで、最後の列のみがTrueになる。
3df_keep_last = df.loc[:, ~df.columns.duplicated(keep="last")]
4print(df_keep_last)
5# A B D C E
6# 0 1 1 3 1 2
7# 1 2 2 4 2 3
8# 2 3 3 5 3 4
DataFrame内で重複している列にサフィックスを付ける
1# 列名のSeriesを作成する
2cols = pd.Series(df.columns)
3# 重複する列目でループ
4for dup in cols[cols.duplicated()].unique():
5 cols[cols[cols == dup].index.values.tolist()] = [dup + '_' + str(i) if i != 0 else dup for i in range(sum(cols == dup))]
6df.columns = cols
7
3項演算子のif elseでi==0(重複列の最初に出現する列)の場合、サフィックスを付加しないようにしているが、どんなサフィックスを付加するかは、if-elseあたりを適当に修正すればOK。
ぱっと見では何してるのか分かりにくい。
プレフィックスやサフィックスを付ける場合、concat後ではなくconcat前に対応することも考えられる。
まとめ
1# 重複している列名を取得する
2duplicate_columns = df.columns[df.columns.duplicated()].unique()
3
4# 重複する列のうち、最初の列以外を除外する
5df_keep_first = df.loc[:, ~df.columns.duplicated()]
6
7# 重複する列のうち、最後の列以外を除外する
8df_keep_last = df.loc[:, ~df.columns.duplicated(keep="last")]