pandas.DataFrameの任意の列がユニークの行をいろいろやってみる

- 環境
    - macOS Catalina バージョン10.15.7
    - Python 3.8.5
    - pandas 1.1.3

サンプルデータはCSVから読み込んだこんなデータ。
このデータの[ID]列に着目してユニークの行をいろいろやってみる。

```py
if __name__ == '__main__':
    df = pandas.read_csv('CSV.csv')
    print(df)
#       ID     日付     開始     終了
# 0   20.0  10/14   9:00  18:00
# 1   16.0  10/15  10:00  19:00
# 2   39.0  10/16  11:00  20:00
# 3    NaN  10/17  12:00  21:00
# 4   47.0  10/18  20:00  22:00
# 5   29.0  10/19  11:00  12:00
# 6   62.0  10/20  15:00  17:00
# 7    NaN  10/21  16:00  18:00
# 8   16.0  10/22  17:00  19:00
# 9    3.0  10/23  18:00  20:00
# 10  16.0  10/24  19:00  21:00
# 11  20.0  10/25  20:00  21:00
```

|行<br/>番号|ID|日付|...|[ID]列が重複<br/>している行|
|:-:|:-:|:-:|:-:|:-:|
|0|20.0|10/14|...|o|
|1|16.0|10/15|...|o|
|2|39.0|10/16|...||
|3|NaN|10/17|...|o|
|4|47.0|10/18|...||
|5|29.0|10/19|...||
|6|62.0|10/20|...||
|7|NaN|10/21|...|o|
|8|16.0|10/22|...|o|
|9|3.0|10/23|...||
|10|16.0|10/24|...|o|
|11|20.0|10/25|...|o|


# 任意の列のユニークな件数を取得する
- 参考 : [pandasでユニークな要素の個数、頻度（出現回数）をカウント | note.nkmk.me](https://note.nkmk.me/python-pandas-value-counts/)

```py
# NaNを含む場合
print(df.nunique(dropna=False)['ID'])
# 8

# NaNを除いた場合
print(df.nunique()['ID'])
# 7
```

# 任意の列でユニークにしたときの値と件数を取得する
- 参考
    - [pandas.DataFrame.value_counts &#8212; pandas 1.1.3 documentation](https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.value_counts.html)
    - [pandas.DataFrame.groupby &#8212; pandas 1.1.3 documentation](https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.groupby.html)
    - [pandas.DataFrameの行を条件で抽出するquery | note.nkmk.me](https://note.nkmk.me/python-pandas-query/)

|違い|groupby|value_counts|備考|
|:-:|:-:|:-:|:-:|
|列見出し|あり|なし||
|ソート|列の値|頻度|sort=Falseにすれば並びは同じ|


```py
# NaNを含む場合
print(df.groupby('ID', dropna=False).size())
# ID
# 3.0     1
# 16.0    3
# 20.0    2
# 29.0    1
# 39.0    1
# 47.0    1
# 62.0    1
# NaN     2
# dtype: int64
print(df['ID'].value_counts(dropna=False))
# 16.0    3
# NaN     2
# 20.0    2
# 3.0     1
# 62.0    1
# 29.0    1
# 47.0    1
# 39.0    1
# Name: ID, dtype: int64

# NaNを除いた場合
print(df.groupby('ID', sort=False).size())
print(df['ID'].value_counts(sort=False))
# 20.0    2
# 16.0    3
# 39.0    1
# 47.0    1
# 29.0    1
# 62.0    1
# 3.0     1
# Name: ID, dtype: int64
```

## 重複している行の値と件数を取得する
```py
# NaNを含む場合
val_con = df.groupby('ID', dropna=False).size()
print(val_con[val_con >= 2])
# ID
# 16.0    3
# 20.0    2
# NaN     2
# dtype: int64

# NaNを除いた場合
val_con = df['ID'].value_counts()
print(val_con[val_con >= 2])
# 16.0    3
# 20.0    2
# Name: ID, dtype: int64
```

# 任意の列が重複している行を判定する
- 参考
    - [pandas.DataFrame.duplicated &#8212; pandas 1.1.3 documentation](https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.duplicated.html)
    - [pandas.DataFrameの行を条件で抽出するquery | note.nkmk.me](https://note.nkmk.me/python-pandas-query/)
    - [pandas.DataFrame, Seriesの重複した行を抽出・削除 | note.nkmk.me](https://note.nkmk.me/python-pandas-duplicated-drop-duplicates/)

```py
# NaNを含む場合
print(df.duplicated(subset=['ID']))
# 0     False
# 1     False
# 2     False
# 3     False
# 4     False
# 5     False
# 6     False
# 7      True
# 8      True
# 9     False
# 10     True
# 11     True
# dtype: bool

# NaNを除いた場合
print(df.dropna(subset=['ID']).duplicated(subset=['ID']))
# 0     False
# 1     False
# 2     False
# 4     False
# 5     False
# 6     False
# 8      True
# 9     False
# 10     True
# 11     True
# dtype: bool
```

`duplicated`は、デフォルトで重複した最初の行が`False`になる(`keep='first'`)。
keepに指定する値によって重複した行のどこが`False`になるかが変わる。

|keepの値|ID|keep='first'<br/>(デフォルト)|keep='last'|keep=False|
|:-:|:-:|:-:|:-:|:-:|
|0|20|False|True|True|
|1|16|False|True|True|
|2||False|False|False|
|3|NaN|False|True|True|
|4||False|False|False|
|5||False|False|False|
|6||False|False|False|
|7|NaN|True|False|True|
|8|16|True|True|True|
|9||False|False|False|
|10|16|True|False|True|
|11|20|True|False|True|

## 任意の列が重複しているか判定する
```py
# df.shape[0]で全件数が取得できる
if df.shape[0] == df.nunique()['ID']:
    print('ID列の値は一意')
else:
    print('ID列は一意ではない')
```

# 任意の列が重複している行を取得する
```py
# NaNを含む場合
print(df[df.duplicated(subset=['ID'])])
#       ID     日付     開始     終了
# 0   20.0  10/14   9:00  18:00
# 1   16.0  10/15  10:00  19:00
# 3    NaN  10/17  12:00  21:00
# 7    NaN  10/21  16:00  18:00
# 8   16.0  10/22  17:00  19:00
# 10  16.0  10/24  19:00  21:00
# 11  20.0  10/25  20:00  21:00

# NaNを除いた場合
df_dropna = df.dropna(subset=['ID'])
print(df_dropna[df_dropna.duplicated(subset=['ID'], keep=False)])
#       ID     日付     開始     終了
# 0   20.0  10/14   9:00  18:00
# 1   16.0  10/15  10:00  19:00
# 8   16.0  10/22  17:00  19:00
# 10  16.0  10/24  19:00  21:00
# 11  20.0  10/25  20:00  21:00
```