注目キーワード
  1. Python
  2. コンペ

【pandas】重複行の削除とカウント完全ガイド|drop_duplicatesの使い方とベストプラクティス

  • 2023年8月30日
  • 2025年8月23日
  • Python
  • 7864回
  • 0件

Pandasでデータ分析を行う際、重複データの処理は避けて通れない重要な前処理作業です。データの品質を保ち、正確な分析結果を得るためには、重複行の適切な処理が不可欠です。

この記事では、Pandasにおける重複行の削除とカウントの全手法を、実務で即戦力となるコード例とともに詳しく解説します。初心者から上級者まで、レベルに応じて活用できる内容となっています。

サンプルデータの準備

まず、重複行を含むサンプルデータを作成して、各手法を実際に試してみましょう。

import pandas as pd

# 重複行を含むサンプルデータを作成
df = pd.DataFrame({
    'ID': [1, 2, 3, 2, 4, 4, 5],
    'Name': ['田中', '佐藤', '鈴木', '佐藤', '高橋', '高橋', '山田'],
    'Age': [25, 30, 35, 30, 28, 28, 32],
    'Department': ['営業', '開発', '総務', '開発', '営業', '営業', '総務']
})

print("元のデータ:")
print(df)
   ID Name  Age Department
0   1   田中   25        営業
1   2   佐藤   30        開発
2   3   鈴木   35        総務
3   2   佐藤   30        開発  # 1行目と重複
4   4   高橋   28        営業
5   4   高橋   28        営業  # 4行目と重複
6   5   山田   32        総務

1. 重複行の削除(drop_duplicates)

基本的な削除方法

drop_duplicates()メソッドを使用して、重複行を削除します。デフォルトでは、すべての列が同じ値を持つ行を重複とみなします。

# 基本的な重複削除
df_unique = df.drop_duplicates()
print("重複削除後:")
print(df_unique)
print(f"\n元の行数: {len(df)}, 削除後の行数: {len(df_unique)}")
   ID Name  Age Department
0   1   田中   25        営業
1   2   佐藤   30        開発
2   3   鈴木   35        総務
4   4   高橋   28        営業
6   5   山田   32        総務

元の行数: 7, 削除後の行数: 5

keepパラメータの使い方

keepパラメータで、重複行のうちどれを残すかを制御できます。

# 最初の行を残す(デフォルト)
df_keep_first = df.drop_duplicates(keep='first')
print("keep='first' (最初の行を残す):")
print(df_keep_first)

# 最後の行を残す
df_keep_last = df.drop_duplicates(keep='last')
print("\nkeep='last' (最後の行を残す):")
print(df_keep_last)

# すべての重複行を削除
df_keep_false = df.drop_duplicates(keep=False)
print("\nkeep=False (重複行をすべて削除):")
print(df_keep_false)
keep='first' (最初の行を残す):
   ID Name  Age Department
0   1   田中   25        営業
1   2   佐藤   30        開発
2   3   鈴木   35        総務
4   4   高橋   28        営業
6   5   山田   32        総務

keep='last' (最後の行を残す):
   ID Name  Age Department
0   1   田中   25        営業
2   3   鈴木   35        総務
3   2   佐藤   30        開発
5   4   高橋   28        営業
6   5   山田   32        総務

keep=False (重複行をすべて削除):
   ID Name  Age Department
0   1   田中   25        営業
2   3   鈴木   35        総務
6   5   山田   32        総務

特定列での削除

subsetパラメータを使用して、特定の列のみを基準に重複を判定できます。

# ID列のみを基準に重複削除
df_unique_id = df.drop_duplicates(subset=['ID'])
print("ID列を基準とした重複削除:")
print(df_unique_id)

# 複数列を基準に重複削除(名前と年齢の組み合わせ)
df_unique_name_age = df.drop_duplicates(subset=['Name', 'Age'])
print("\n名前と年齢を基準とした重複削除:")
print(df_unique_name_age)
ID列を基準とした重複削除:
   ID Name  Age Department
0   1   田中   25        営業
1   2   佐藤   30        開発
2   3   鈴木   35        総務
4   4   高橋   28        営業
6   5   山田   32        総務

名前と年齢を基準とした重複削除:
   ID Name  Age Department
0   1   田中   25        営業
1   2   佐藤   30        開発
2   3   鈴木   35        総務
4   4   高橋   28        営業
6   5   山田   32        総務

2. 重複行の判定とカウント(duplicated)

基本的な重複判定

duplicated()メソッドは、各行が重複しているかをTrue/Falseで返します。

# 重複行の判定
is_duplicate = df.duplicated()
print("重複判定結果:")
print(is_duplicate)

# 重複行のみを表示
print("\n重複している行:")
print(df[is_duplicate])
重複判定結果:
0    False
1    False
2    False
3     True
4    False
5     True
6    False
dtype: bool

重複している行:
   ID Name  Age Department
3   2   佐藤   30        開発
5   4   高橋   28        営業

重複行のカウント方法

重複行の数を様々な方法でカウントできます。

# 1. 基本的なカウント
total_duplicates = df.duplicated().sum()
print(f"全体の重複行数: {total_duplicates}")

# 2. 列別重複カウント
print("\n列別重複カウント:")
for col in df.columns:
    count = df.duplicated(subset=[col]).sum()
    print(f"{col}列の重複数: {count}")

# 3. 複数列の組み合わせでカウント
multi_col_count = df.duplicated(subset=['Name', 'Age']).sum()
print(f"\n名前・年齢組み合わせの重複数: {multi_col_count}")

# 4. 重複率の計算
duplicate_rate = (df.duplicated().sum() / len(df)) * 100
print(f"重複率: {duplicate_rate:.2f}%")

# 5. keepパラメータ別のカウント
print(f"\nkeep='first'での重複数: {df.duplicated(keep='first').sum()}")
print(f"keep='last'での重複数: {df.duplicated(keep='last').sum()}")
print(f"keep=Falseでの重複数: {df.duplicated(keep=False).sum()}")
全体の重複行数: 2

列別重複カウント:
ID列の重複数: 2
Name列の重複数: 2
Age列の重複数: 2
Department列の重複数: 3

名前・年齢組み合わせの重複数: 2

重複率: 28.57%

keep='first'での重複数: 2
keep='last'での重複数: 2
keep=Falseでの重複数: 4

特定列での重複判定

# ID列のみで重複判定
id_duplicates = df.duplicated(subset=['ID'])
print("ID列での重複判定:")
print(df[id_duplicates])

# 部門別の重複をカウント
dept_duplicates = df.groupby('Department').apply(lambda x: x.duplicated().sum())
print("\n部門別重複カウント:")
print(dept_duplicates)
ID列での重複判定:
   ID Name  Age Department
3   2   佐藤   30        開発
5   4   高橋   28        営業

部門別重複カウント:
Department
営業    1
開発    1
総務    0
dtype: int64

3. drop_duplicatesとduplicatedの違いまとめ

項目 duplicated() drop_duplicates()
戻り値 Boolean Series DataFrame
用途 重複判定・カウント 重複削除
元データ 変更されない 変更されない(新しいDFを返す)
パフォーマンス 高速 やや低速
主な使用場面 重複の確認、統計情報取得 データクリーニング

実用例での違い

print("=== duplicated()の場合 ===")
result_duplicated = df.duplicated()
print(f"戻り値の型: {type(result_duplicated)}")
print(f"重複行数: {result_duplicated.sum()}")
print(result_duplicated)

print("\n=== drop_duplicates()の場合 ===")
result_drop = df.drop_duplicates()
print(f"戻り値の型: {type(result_drop)}")
print(f"残った行数: {len(result_drop)}")
print(result_drop)

4. 重複行の結合とグループ化

重複データを削除する代わりに、グループ化して集計することも可能です。

groupbyを使った集計

# 重複するIDをグループ化して集計
grouped_basic = df.groupby('ID').agg({
    'Name': 'first',  # 最初の名前を取得
    'Age': 'mean',    # 年齢の平均
    'Department': lambda x: ', '.join(x.unique())  # 部門の一意な値を結合
}).reset_index()

print("ID別グループ集計:")
print(grouped_basic)

# より複雑な集計
grouped_advanced = df.groupby(['Name', 'Age']).agg({
    'ID': ['first', 'count'],
    'Department': lambda x: ', '.join(x.unique())
}).reset_index()

print("\n名前・年齢別グループ集計:")
print(grouped_advanced)

重複フラグの追加

# 重複行をフラグとしてマーク
df_with_flag = df.copy()
df_with_flag['is_duplicate'] = df.duplicated()
df_with_flag['duplicate_count'] = df.groupby(df.columns.tolist()).cumcount() + 1

print("重複フラグ付きデータ:")
print(df_with_flag)

5. 実務でのベストプラクティス

推奨ワークフロー

def analyze_duplicates(df):
    """重複データの分析を行う関数"""
    print("=== 重複データ分析レポート ===")
    
    # 1. 基本統計
    total_rows = len(df)
    duplicate_rows = df.duplicated().sum()
    unique_rows = total_rows - duplicate_rows
    duplicate_rate = (duplicate_rows / total_rows) * 100
    
    print(f"総行数: {total_rows}")
    print(f"重複行数: {duplicate_rows}")
    print(f"ユニーク行数: {unique_rows}")
    print(f"重複率: {duplicate_rate:.2f}%")
    
    # 2. 列別重複分析
    print("\n=== 列別重複分析 ===")
    for col in df.columns:
        col_duplicates = df.duplicated(subset=[col]).sum()
        col_unique = df[col].nunique()
        print(f"{col}: 重複{col_duplicates}行, ユニーク値{col_unique}個")
    
    # 3. 重複パターン分析
    if duplicate_rows > 0:
        print("\n=== 重複パターン ===")
        duplicate_patterns = df[df.duplicated(keep=False)].groupby(df.columns.tolist()).size()
        print(duplicate_patterns)
    
    return {
        'total_rows': total_rows,
        'duplicate_rows': duplicate_rows,
        'duplicate_rate': duplicate_rate
    }

# 使用例
analysis_result = analyze_duplicates(df)

条件付き重複処理

def conditional_dedup(df, condition_col, condition_value, subset_cols=None):
    """条件を満たすデータのみ重複削除を行う"""
    # 条件を満たす行のマスク
    mask = df[condition_col] == condition_value
    
    # 条件を満たす行で重複削除
    if subset_cols:
        duplicate_mask = mask & df.duplicated(subset=subset_cols)
    else:
        duplicate_mask = mask & df.duplicated()
    
    # 重複行を除いたデータフレームを返す
    return df[~duplicate_mask]

# 使用例:営業部のデータのみID重複削除
df_cleaned = conditional_dedup(df, 'Department', '営業', ['ID'])
print("営業部のID重複削除後:")
print(df_cleaned)

パフォーマンス最適化

# 大規模データでのパフォーマンス最適化
def efficient_dedup(df, subset_cols=None):
    """効率的な重複削除"""
    if subset_cols is None:
        subset_cols = df.columns.tolist()
    
    # メモリ使用量を最小化
    return df.drop_duplicates(subset=subset_cols).reset_index(drop=True)

# データ型最適化と組み合わせ
def optimize_and_dedup(df):
    """データ型最適化と重複削除を同時実行"""
    # 数値列の最適化
    for col in df.select_dtypes(include=['int64']).columns:
        df[col] = pd.to_numeric(df[col], downcast='integer')
    
    # 文字列列の最適化
    for col in df.select_dtypes(include=['object']).columns:
        df[col] = df[col].astype('category')
    
    # 重複削除
    return df.drop_duplicates().reset_index(drop=True)

6. よくある問題と解決方法(FAQ)

Q1. drop_duplicatesが効かない場合

# 問題:NaNが含まれる場合の対処法
df_with_nan = pd.DataFrame({
    'A': [1, 2, None, 2, None], 
    'B': [1, 2, 3, 2, 3]
})

print("NaN含みデータ:")
print(df_with_nan)

# 解決策1: NaNも重複として扱う
result1 = df_with_nan.drop_duplicates()
print("\nNaNを考慮した重複削除:")
print(result1)

# 解決策2: NaNを特定の値で置換してから重複削除
result2 = df_with_nan.fillna('MISSING').drop_duplicates()
print("\nNaN置換後の重複削除:")
print(result2)

Q2. データ型が異なる場合の問題

# 問題:データ型が混在している場合
df_mixed = pd.DataFrame({
    'ID': ['1', 1, '2', 2],  # 文字列と数値が混在
    'Value': [100, 100, 200, 200]
})

print("データ型混在:")
print(df_mixed.dtypes)
print(df_mixed)

# 解決策:データ型を統一してから処理
df_mixed['ID'] = df_mixed['ID'].astype(str)
result = df_mixed.drop_duplicates()
print("\nデータ型統一後:")
print(result)

Q3. 特定の条件でのみ重複削除したい

# 解決策:条件付き重複削除
df_sample = pd.DataFrame({
    'Category': ['A', 'A', 'B', 'B', 'C'],
    'Value': [1, 1, 2, 3, 4],
    'Status': ['active', 'active', 'inactive', 'active', 'active']
})

# Activeなデータのみで重複削除
active_mask = df_sample['Status'] == 'active'
duplicate_mask = active_mask & df_sample.duplicated(subset=['Category', 'Value'])

result = df_sample[~duplicate_mask]
print("条件付き重複削除結果:")
print(result)

Q4. 重複判定が正しく動作しない場合

# 問題:浮動小数点の精度問題
df_float = pd.DataFrame({
    'A': [1.0, 1.0000000001, 2.0],
    'B': [1, 1, 2]
})

print("浮動小数点精度問題:")
print(df_float.duplicated())

# 解決策:四捨五入してから比較
df_rounded = df_float.round(5)
print("\n四捨五入後の重複判定:")
print(df_rounded.duplicated())

重要なポイント

重複処理を行う前に、必ずデータの特性とビジネスロジックを確認しましょう。機械的に重複削除を行うのではなく、なぜ重複が発生しているのか、その重複が意味のあるデータなのかを検討することが重要です。

まとめ

Pandasにおける重複行処理の全手法を解説しました。重要なポイントを再確認しましょう:

主要メソッドの使い分け

  • duplicated(): 重複の確認・カウント・統計分析に使用
  • drop_duplicates(): 実際の重複削除・データクリーニングに使用

実務での推奨手順

  1. データ探索: df.duplicated().sum()で重複行数を確認
  2. 分析: 列別重複パターンを調査
  3. 判断: ビジネスロジックに基づき処理方針を決定
  4. 実行: 適切なパラメータで重複削除
  5. 検証: 処理後のデータ品質を確認

パフォーマンス向上のコツ

  • 大規模データではsubsetパラメータで対象列を限定
  • データ型の最適化と組み合わせ実行
  • 条件付き処理で不要な計算を削減

実践への活用

これらのテクニックをマスターすることで、データクリーニング作業が大幅に効率化され、より信頼性の高いデータ分析が可能になります。実際のプロジェクトでぜひ活用してください。

最新情報をチェックしよう!