こんにちは。前回は、Pandas DataFrameにおける欠損値への対処法として、削除(.dropna()
)と補完(.fillna()
)について学びました。データのお掃除スキルがまた一つ上がりましたね。
さて、データクリーニングの旅はまだまだ続きます。欠損値と並んで、データ分析の前処理でよく遭遇し、対処が必要となるのが「重複データ」です。つまり、全く同じ情報が複数行にわたって記録されてしまっている状態です。
アンケートの二重回答、システムのバグによるデータ二重登録、データ統合時のミスなど、重複データが発生する原因は様々です。この重複データを放置しておくと、
- データの件数を誤って多く数えてしまう
- 平均値などの集計結果が不正確になる
- 機械学習モデルが特定のデータパターンを過剰に学習してしまう
といった問題を引き起こす可能性があります。そのため、分析を始める前に重複データを見つけ出し、適切に処理することが重要になります。
今回の記事では、Pandasを使ってこの重複データに対処する方法を学びます。具体的には、以下の内容を扱います。
- 重複データとは何か、なぜ問題になるのかを再確認する。
- どの行が重複しているのかを確認する
.duplicated()
メソッドの使い方。 - 重複している行を削除する
.drop_duplicates()
メソッドの使い方。 - 特定の列だけを見て重複を判断する方法(
subset
引数)。 - 重複している行のうち、どれを残すか(最初か、最後か、全て削除か)を指定する方法(
keep
引数)。
今回も具体的なサンプルデータを使って、コードの実行結果を見ながら、重複データの確認と削除のステップを一つずつ丁寧に解説していきます。データのお掃除、第3弾、頑張りましょう!
重複データとは? なぜ問題になる?
重複データとは、文字通り、DataFrameの中に全く同じ内容の行が複数存在している状態を指します。全ての列の値が完全に一致している行が2つ以上ある場合、それらは重複しているとみなされます。(ただし、後述するように、特定の列だけを見て重複を判断することも可能です。)
重複データが発生する原因としては、以下のようなものが考えられます。
- ユーザーの誤操作: Webフォームからの申し込みを誤って2回送信してしまった、など。
- システムの不具合: データ連携処理で同じデータが複数回送られてしまった、データベースへの書き込み処理が重複して実行された、など。
- データ収集方法の問題: 複数のソースからデータを集めた際に、同じデータが両方のソースに含まれていた、など。
- 意図的な重複(?): まれに、意図的に同じデータを複数回記録するケースもありますが、通常は上記のような意図しない原因が多いです。
では、なぜ重複データを処理する必要があるのでしょうか?主な理由は以下の通りです。
- 不正確な集計: 例えば、ユニークユーザー数を数えたいのに重複データがあると、実際の数よりも多くカウントしてしまいます。平均購入額などを計算する際も、同じユーザーのデータが複数回含まれると結果が歪みます。
- 分析結果の偏り: 特定のデータが重複して多く存在することで、分析結果がそのデータに引っ張られてしまう可能性があります。
- 機械学習モデルへの悪影響: 重複データが多いと、モデルがその特定のデータパターンだけを強く学習してしまい、未知のデータに対する予測性能(汎化性能)が低下することがあります。また、学習時間が無駄に長くなることもあります。
- リソースの無駄: 単純に、同じデータを複数保持することは、ストレージ容量やメモリの無駄遣いになります。
これらの理由から、データ分析や機械学習を行う前には、重複データの有無を確認し、必要であれば削除などの対処を行うのが一般的です。
サンプルデータの準備(重複行を含む)
重複データの処理方法を学ぶために、意図的に重複行を含んだサンプルDataFrameを作成しましょう。今回は、簡単な商品購入履歴のようなデータを想定します。
import pandas as pd # 重複行を含むサンプルデータ data_duplicates = { '注文ID': [101, 102, 103, 101, 104, 105, 103], # 101と103が重複 '顧客名': ['佐藤', '鈴木', '高橋', '佐藤', '田中', '伊藤', '高橋'], '商品': ['商品A', '商品B', '商品C', '商品A', '商品B', '商品A', '商品C'], '価格': [1000, 1500, 2000, 1000, 1500, 1000, 2000] } df_dup = pd.DataFrame(data_duplicates) # 作成したDataFrameを表示 print("--- 重複を含むDataFrame ---") print(df_dup)
実行結果:
--- 重複を含むDataFrame ---
注文ID 顧客名 商品 価格
0 101 佐藤 商品A 1000
1 102 鈴木 商品B 1500
2 103 高橋 商品C 2000
3 101 佐藤 商品A 1000 <-- インデックス0と全く同じ
4 104 田中 商品B 1500
5 105 伊藤 商品A 1000
6 103 高橋 商品C 2000 <-- インデックス2と全く同じ
このDataFrameを見ると、
- インデックス
3
の行は、インデックス0
の行と全ての列の値が完全に一致しています。 - インデックス
6
の行は、インデックス2
の行と全ての列の値が完全に一致しています。
このように、2組の重複行が存在するデータが準備できました。このdf_dup
を使って、重複の確認と削除を行っていきましょう。
重複行の確認:.duplicated()メソッド
まず、どの行が重複しているのかを確認する方法です。これには.duplicated()
メソッドを使います。このメソッドは、各行がそれよりも前に出現した行と重複しているかどうかを判定し、結果をブール値(True
/False
)のSeriesとして返します。
基本的な使い方
引数を指定せずに.duplicated()
を実行してみましょう。
# 各行が重複しているかを確認 duplicates_check = df_dup.duplicated() print(duplicates_check)
実行結果:
0 False
1 False
2 False
3 True
4 False
5 False
6 True
dtype: bool
結果はブール値のSeriesです。各インデックスに対応する値の意味は以下の通りです。
False
: その行は、それより前には同じ内容の行が出現していない。True
: その行は、それより前に全く同じ内容の行が出現している(つまり、この行は重複である)。
実行結果を見ると、インデックス3
と6
がTrue
になっています。これは、
- インデックス
3
の行は、それより前に同じ内容の行(インデックス0
)が存在するため、重複 (True
) と判定された。 - インデックス
6
の行は、それより前に同じ内容の行(インデックス2
)が存在するため、重複 (True
) と判定された。
ことを意味します。一方、インデックス0
と2
の行は、それらが最初に出現した行なので、重複とはみなされずFalse
になっています。これが.duplicated()
のデフォルトの挙動です。
どの重複行を残すか:keep引数
.duplicated()
のデフォルトの挙動(最初に出現した行はFalse
、それ以降の重複行をTrue
とする)は、keep='first'
が指定された場合と同じです。
keep
引数を使うと、どの行を重複とみなすか(True
にするか)を制御できます。
keep='first'
(デフォルト): 最初に出現した行以外の重複行をTrue
とする。keep='last'
: 最後に出現した行以外の重複行をTrue
とする。keep=False
: 全ての重複行(最初に出現したものも含む)をTrue
とする。
それぞれの挙動を見てみましょう。
keep='last'
の場合:
# 最後に出現した行以外を重複とみなす duplicates_keep_last = df_dup.duplicated(keep='last') print(duplicates_keep_last)
実行結果:
0 True
1 False
2 True
3 False
4 False
5 False
6 False
dtype: bool
今度は、インデックス0
と2
がTrue
になりました。これは、
- インデックス
0
の行は、それより後(インデックス3
)に同じ内容の行が存在するため、重複 (True
) と判定された。 - インデックス
2
の行は、それより後(インデックス6
)に同じ内容の行が存在するため、重複 (True
) と判定された。
ことを意味します。最後に出現したインデックス3
と6
の行はFalse
になっています。
keep=False
の場合:
# 全ての重複行を重複とみなす duplicates_keep_false = df_dup.duplicated(keep=False) print(duplicates_keep_false)
実行結果:
0 True
1 False
2 True
3 True
4 False
5 False
6 True
dtype: bool
インデックス0
, 2
, 3
, 6
が全てTrue
になりました。これは、これらの行が(自身を含めて)同じ内容の行が複数存在するためです。完全にユニークな行(インデックス1
, 4
, 5
)だけがFalse
になっています。
特定の列だけで重複を判断:subset引数
デフォルトでは、全ての列の値が一致する場合に重複とみなしますが、「特定の列の値だけが一致していれば重複とみなす」という場合もあります。例えば、「同じ顧客が同じ商品を複数回注文しているか調べたい(注文IDや価格は違っても良い)」といった場合です。
これにはsubset
引数を使います。subset
には、重複を判断する基準となる列名のリストを指定します。
例:「顧客名」と「商品」の組み合わせが重複しているかを確認
# '顧客名'と'商品'の組み合わせで重複を確認 (最初に出現したもの以外をTrue) duplicates_subset = df_dup.duplicated(subset=['顧客名', '商品'], keep='first') print(duplicates_subset)
実行結果:
0 False # ('佐藤', '商品A') 初出
1 False # ('鈴木', '商品B') 初出
2 False # ('高橋', '商品C') 初出
3 True # ('佐藤', '商品A') 2回目
4 False # ('田中', '商品B') 初出
5 False # ('伊藤', '商品A') 初出
6 True # ('高橋', '商品C') 2回目
dtype: bool
インデックス3
(顧客名=’佐藤’, 商品=’商品A’)とインデックス6
(顧客名=’高橋’, 商品=’商品C’)がTrue
になりました。これは、それぞれの組み合わせが前に出現しているためです。他の列(注文ID、価格)の値は無視されます。
重複行の数を数える
DataFrame内に重複行がいくつあるかを知りたい場合は、.duplicated()
の結果(ブール値のSeries)に対して.sum()
メソッドを適用します。True
が1
、False
が0
として扱われるため、True
の数(=重複行の数)が合計されます。
# 重複行の数を数える(デフォルト: keep='first') num_duplicates = df_dup.duplicated().sum() print(f"重複行の数 (最初を除く): {num_duplicates}")
実行結果:
重複行の数 (最初を除く): 2
最初に出現したものを除くと、重複行は2つあることが分かりました。
重複行の削除:.drop_duplicates()メソッド
重複している行を確認できたら、次はその重複行を削除します。これには.drop_duplicates()
メソッドを使います。
.drop_duplicates()
は、.duplicated()
と考え方が似ており、どの行を重複とみなして削除するかをkeep
引数で、どの列を基準に重複を判断するかをsubset
引数で指定できます。
基本的な使い方(デフォルト: 最初の行を残す)
引数を指定せずに.drop_duplicates()
を実行すると、デフォルト(keep='first'
)の挙動となり、重複している行のうち、最初に出現した行だけを残し、それ以降の重複行を削除します。
# 重複行を削除(デフォルト: 最初の行を残す) df_dropped = df_dup.drop_duplicates() print("--- 重複行削除後 (keep='first') ---") print(df_dropped)
実行結果:
--- 重複行削除後 (keep='first') ---
注文ID 顧客名 商品 価格
0 101 佐藤 商品A 1000
1 102 鈴木 商品B 1500
2 103 高橋 商品C 2000
4 104 田中 商品B 1500
5 105 伊藤 商品A 1000
元のDataFrameから、重複していたインデックス3
と6
の行が削除され、ユニークな行だけが残りました。インデックス0
と2
は最初に出現した行なので残っています。
どの重複行を残すか指定:keep引数
keep
引数を使うことで、残す行を指定できます。
keep='first'
(デフォルト): 最初に出現した行を残す。keep='last'
: 最後に出現した行を残す。keep=False
: 重複している行を全て削除する(ユニークな行だけを残す)。
keep='last'
の場合:
# 重複行のうち、最後の行を残す df_dropped_last = df_dup.drop_duplicates(keep='last') print("--- 重複行削除後 (keep='last') ---") print(df_dropped_last)
実行結果:
--- 重複行削除後 (keep='last') ---
注文ID 顧客名 商品 価格
1 102 鈴木 商品B 1500
3 101 佐藤 商品A 1000 <-- インデックス0の代わりにこちらが残る
4 104 田中 商品B 1500
5 105 伊藤 商品A 1000
6 103 高橋 商品C 2000 <-- インデックス2の代わりにこちらが残る
今度は、最初に出現したインデックス0
と2
の行が削除され、最後に出現したインデックス3
と6
の行が残りました。
keep=False
の場合:
# 重複している行を全て削除 df_dropped_all_duplicates = df_dup.drop_duplicates(keep=False) print("--- 重複行削除後 (keep=False) ---") print(df_dropped_all_duplicates)
実行結果:
--- 重複行削除後 (keep=False) ---
注文ID 顧客名 商品 価格
1 102 鈴木 商品B 1500
4 104 田中 商品B 1500
5 105 伊藤 商品A 1000
重複していた組(インデックス0と3、インデックス2と6)が全て削除され、完全にユニークな行だけが残りました。
特定の列だけで重複を判断して削除:subset引数
.duplicated()
と同様に、subset
引数を使って、特定の列の値が一致する場合に重複とみなし、削除することができます。
例:「顧客名」と「商品」の組み合わせが重複している場合、最初の行を残して削除
# '顧客名'と'商品'の組み合わせで重複を判断し、最初の行を残す df_dropped_subset = df_dup.drop_duplicates(subset=['顧客名', '商品'], keep='first') print("--- subset=['顧客名', '商品'] で重複削除後 (keep='first') ---") print(df_dropped_subset)
実行結果:
--- subset=['顧客名', '商品'] で重複削除後 (keep='first') ---
注文ID 顧客名 商品 価格
0 101 佐藤 商品A 1000
1 102 鈴木 商品B 1500
2 103 高橋 商品C 2000
4 104 田中 商品B 1500
5 105 伊藤 商品A 1000
subset
を指定せずに全列で重複削除した場合と同じ結果になりましたが、これは重複の判断基準が「顧客名」と「商品」の組み合わせだけで行われている点が異なります。
.drop_duplicates()の注意点:inplace=True
.dropna()
と同様に、.drop_duplicates()
もデフォルトでは元のDataFrameを変更せず、処理結果を反映した新しいDataFrameを返します。元のDataFrameを直接変更したい場合は、inplace=True
引数を指定します(ただし、使用には注意が必要です)。
# inplace=True を使って元のDataFrameを直接変更する例(実行注意) # df_copy = df_dup.copy() # print("--- inplace=True 実行前 ---") # print(df_copy) # df_copy.drop_duplicates(inplace=True) # print("--- inplace=True 実行後 ---") # print(df_copy)
重複処理の際の考えどころ
重複データ処理は単純に見えますが、実際に適用する際にはいくつか考えるべき点があります。
- 何を「重複」とみなすか?: 全ての列が一致する場合のみか、それとも特定のキーとなる列(例:顧客ID、注文IDなど)が一致すれば重複とみなすか?
subset
引数を適切に設定する必要があります。 - どの行を残すか?: 重複があった場合、最初に見つかった行、最後に見つかった行、あるいは全て削除するか?
keep
引数の選択が重要です。例えば、最新の情報を残したい場合はkeep='last'
、データ入力の最初の記録を残したい場合はkeep='first'
、重複自体がエラーである場合はkeep=False
で全て削除して原因を調査する、といった使い分けが考えられます。 - 本当に削除して良いのか?: 一見重複に見えても、実は意味のあるデータである可能性はないか? 例えば、同じ顧客が同じ商品を複数回購入した場合、それは正当な購買履歴かもしれません。単純に
drop_duplicates()
を適用する前に、なぜ重複が発生しているのか、その重複がデータとして妥当なのかを検討することが重要です。
機械的に重複削除を行うのではなく、データの意味を考えながら適切な処理を選択することが、質の高い分析につながります。
まとめ
今回は、Pandas DataFrameにおける重複データの確認と削除の方法について学びました。
- 重複データの問題点: 集計ミスや分析結果の偏り、モデル性能低下などを引き起こす可能性があるため、対処が必要。
- 重複行の確認 (
.duplicated()
):- 各行がそれ以前の行と重複しているかを
True
/False
で返す。 keep='first'
(デフォルト),'last'
,False
でどの行を重複とみなすか制御できる。subset=[列名リスト]
で特定の列だけで重複を判断できる。.duplicated().sum()
で重複行数をカウントできる。
- 各行がそれ以前の行と重複しているかを
- 重複行の削除 (
.drop_duplicates()
):- 重複行を削除した新しいDataFrameを返す(デフォルトでは元のDataFrameは変更されない)。
keep
引数で残す行(最初、最後、全て削除)を指定できる。subset
引数で重複判断の基準となる列を指定できる。inplace=True
で元のDataFrameを直接変更できる(注意が必要)。
- 重複処理の考えどころ: 何を重複とみなし、どの行を残すか、本当に削除して良いデータかを慎重に検討することが重要。
欠損値処理と重複データ処理は、データクリーニングにおける二大基本作業と言えます。これらのスキルを身につけることで、より信頼性の高いデータ分析や機械学習モデル構築への道が開けます。
次回予告
データのお掃除(欠損値処理、重複処理)が一段落しました。データが少しきれいになったところで、次はいよいよデータの「中身」をより深く理解するためのステップに進みましょう。
次回は、データをグループ化して集計する「グループ集計」について解説します。例えば、「部署ごとに平均年齢や評価スコアを集計したい」「商品ごとに販売数をカウントしたい」といった、データを特定のカテゴリでまとめて分析する強力なテクニックです。Pandasの.groupby()
メソッドの使い方をマスターしましょう!お楽しみに!