PandasのDataFrameやSeriesで重複要素を取り除く方法

データ解析とかをしていて、ちょくちょく出番があるので備忘録的に残しておく。

## 要素の抽出方法

要素の抽出自体は過去記事を参照
[DataFrameやSeriesで要素を抽出する方法](https://qiita.com/kira4845/items/1a532b58f26b0ac36601#%E3%83%87%E3%83%BC%E3%82%BF%E6%8A%BD%E5%87%BA)

## 重複要素を判定するメソッド

DataFrameやSeriesには`duplicated()`という重複を判定するメソッドがあるので、これを利用すると**重複のある要素**や**重複要素以外**を抽出することができる。

とりあえず、こんなDataFrameサンプルで試してみる。

```python
import pandas as pd

df = pd.DataFrame([[0,1,2],[0,2,4],[0,1,2]], columns=["A","B","C"])
```
![image.png](https://qiita-image-store.s3.amazonaws.com/0/291298/89fc8f2b-5a8e-248a-e694-2f10dfd2847a.png)

```python
df.duplicated()
```
![image.png](https://qiita-image-store.s3.amazonaws.com/0/291298/8689d75e-7a03-1df6-384b-cd559885451f.png)

DataFrameに対して`duplicated()`メソッドを呼び出すと、行ごとに各列要素すべてが重複するかを判定したSeriesを返してくれる。なお、重複していると判定されたものは`True`となる。
ちなみに、列ごとに判定したい時はDataFrameを`T`で転置してから`duplicated()`を呼び出せばよい。

```python
df.T.duplicated()
```

### 判定結果のルール指定

`duplicated()`メソッドには`keep`というオプションがあり、以下の3種類から判定ルールを選択できる。

|種類|説明|使用例|
|:--|:--|:--:|
|"first"(デフォルト)|重複要素の先頭のみFalseと判定する|![image.png](https://qiita-image-store.s3.amazonaws.com/0/291298/61c434c4-484a-0995-95f6-1de91ffad915.png)|
|"last"|重複要素の末尾のみFalseと判定する|![image.png](https://qiita-image-store.s3.amazonaws.com/0/291298/1396d164-779d-0146-1fc4-2d41b8bf37fd.png)|
|False|重複要素はすべてTrueと判定する|![image.png](https://qiita-image-store.s3.amazonaws.com/0/291298/eef27e22-42b5-db87-313c-132b3b46e40d.png)|

今のところ、仕事ではデフォルトのルール以外の出番はないが、そのうち出番はありそうな気がする。

### 特定列のみで判定

DataFrameの`duplicated()`メソッドには`subset`というオプションがあり、判定対象にしたい列名を与えると、その列だけで判定してくれる。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/291298/76b513d3-b07b-9bdd-17a4-50298e5c4e9b.png)
複数の列を指定したい時は、リストなどでくくって渡すこともできる。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/291298/c9b28ba0-2293-191e-8161-0db2a64d493c.png)
ちなみに、Seriesにも`duplicated()`メソッドがあるので、DataFrameから特定列のSeriesを作ってから`duplicated()`を呼び出す方法もある。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/291298/fd55f1c2-ed44-6596-dbd0-b6a6b0e4e99f.png)

時間を計ってみたら**Seriesにしてから判定する**ほうが、$\frac{1}{4}$くらい早かった。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/291298/e44ab696-06cf-1877-1f4f-af32a7ed2104.png)

## 重複要素を取り除く

前述の`duplicated()`メソッドを使うと重複要素を取り除ける。
`duplicated()`メソッドは**重複している要素が`True`**になるので、`~`で反転すれば**重複要素以外が`True`**になる。

```python
df[~df.duplicated()]
```
![image.png](https://qiita-image-store.s3.amazonaws.com/0/291298/1310246e-7713-0480-9c9a-14add4176f95.png)

重複する要素を完全に除外するなら、`duplicated()`の`keep`オプションに`False`を指定すればよい。

```python
df[~df.duplicated(keep=False)]
```
![image.png](https://qiita-image-store.s3.amazonaws.com/0/291298/2b70c773-b1b2-569f-c0df-fc12c35f962c.png)

### インデックスを込みで重複要素を取り除く

スクリプト内で作るデータだとどうにでもできるが、既にある対象データを読み込んで解析する時なんかは、インデックスにしている**同じIDに複数の要素に紐づいている**ときなどがある。
インデックスをリセットすれば一応ユニークなIDになるが、DataFrameをデータベース的に使うときなどは、自動で割り振られたインデックスでは使いにくいので、できればそのまま判定したい。

```python
df[~((df.index.duplicated()) & (df.duplicated()))]
```
![image.png](https://qiita-image-store.s3.amazonaws.com/0/291298/ab59c4da-fd22-4353-a66a-b0e4af6f35ec.png)

今のサンプルでは変化がわからないので、サンプルのインデックスを変更する。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/291298/040dd050-7915-9ed2-5823-40f821985f00.png)

ちなみに、重複要素を取り除くだけなら**`drop_duplicates()`というメソッドもある**。このメソッドは重複値を除いたDataFrameを返してくれる。
なお、`duplicated()`メソッドと同様に`subset`,`keep`オプションを持っていて判定ルールは同じように設定できて、加えて`inplace`というオプションに`True`を指定すると、元のDataFrame(Series)を直接変更する。
処理時間はどっちを使っても同じくらいらしい。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/291298/25326d95-56d3-f7e6-d442-ebf0bb3e9ef8.png)



## 終わりに

重複判定は結構出番があるのでまとめてみた。