時系列データの前処理（Python 編）

# はじめに
Python を使って時系列データの前処理を行った際、使ったコードをつらつらと書き足していく。全く同じことをRでも行っているのでそれを読みたい方は[こちら](https://qiita.com/ryamamoto0406/items/4c4a2869a468bc75fdb3)。

開発環境

- python 3.8.5
- pandas 1.1.3
- numpy 1.19.2
- jupyter lab 1.2.6


更新履歴

- 2021/02/05：初投稿。データの読み込みから欠損の確認まで。


参考にした記事

- https://qiita.com/mynkit/items/6e8ddfa1c73363f6117e


# モジュールのインポートとデータの読み込み
データは時系列かつ多変量である[Water Treatment Plant](https://archive.ics.uci.edu/ml/datasets/water+treatment+plant)を利用した。

必要なモジュールをimport し、データを読み込む。

```python:
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt

df = pd.read_csv("./water-treatment.data", header=None, index_col=0)
df
```
![wtd_01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1077734/5bb3b7c4-bbd6-32e4-57b5-b3adfed29b9b.png)

後々のために日付のカラムをindexとして指定している。

# データの結合（複数ある場合）
データのcsvファイルが複数ある場合は、それを結合し一つのDataFrameとして扱いたい。各列の要素が同じであることをチェックしたら、

```python:
df = pd.concat([df0, df1, df2])
```
で結合すればよい。#今回の場合は1ファイルなので不要。

# index の datetime化と時刻でのソート
index に時刻がD-日付/月/年という形式で入っているので、それをdatetime型に直す。pandas では、`to_datetime`という便利な関数があるので、それを使用する。

```python:
df.index = pd.to_datetime(df.index, format='D-%d/%m/%y') #datetimeに変換
df = df.sort_index() #時刻でソートする
df
```
![wtd_02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1077734/bb7b8790-8ba1-3687-e888-d531bdfe8434.png)

# データの重複の確認
各列のデータに対して重複が無いかをチェックする。列を直接比較する方法は難しそうなので、一旦転置し重複行を探して削除し、再度転置して戻すという方法を取る。

```python:
df.T.duplicated() #重複の確認
```

今回のデータの場合全て`False`なので重複は無し。重複があった場合は、

```python:
df = df.T.drop_duplicates().T
```
で重複を削除できる。（参考記事１）


因みに要素ではなく列名で重複を確認する場合は以下に示す。

```python:
print(df.columns.duplicated()) #重複があればnp.array にTrueが入る
df = df.loc[:,~df.columns.duplicated()] #重複していない項目だけでdfを再構成
```

# データのサンプル間隔を調べる
よくよくデータを見てみると等サンプルのデータではなさそうなので、サンプリング間隔のヒストグラムを作る。日付の配列`df.index`を一つずらした配列を作り順次引き算していくことでデータのサンプル間隔を調べる。

```python:
flag = 0 #最初の値を回避する用
dt_day = [] #サンプル間隔を入れる配列

for now, pre in zip(df.index, np.roll(df.index, 1)): 
    if flag == 0:
        flag = 1
        continue
    _tmp = ((now - pre).total_seconds())/(60*60*24) #サンプル間隔を日に変換
    dt_day.append(_tmp)

plt.hist(dt_day, bins=range(0,33))
plt.ylabel("Count")
plt.xlabel("Sample interval [days]")
plt.show()
```
![wt_hist.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1077734/b3f6866a-c39a-93dc-3b0f-fe15369ab77b.png)

見難いので対数で取ったヒストグラムも作成する。

```python:
plt.hist(dt_day, bins=range(0,33), log=True) #y軸をlogにする。
plt.ylabel("Count")
plt.xlabel("Sample interval [days]")
plt.show()
```
![wt_hist_log.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1077734/66824abd-6f4e-1bb8-f6a4-6a56d8c28cef.png)

ほとんどが一日間のサンプリングデータだが1/5程度は二日間、ごくまれに三日間、1データだけ一か月くらい（三十二日間）データが飛んでいる箇所があるよう。

これを「等サンプリングに直す」のは次回。

# データの欠損の確認
このデータは欠損を含んでいる（欠損は?で表されている。）ので、その可視化を行う。列ごとに欠損の数を数え、その割合を棒グラフにする。

```python:
df = df.replace("?", np.nan) #?をnp.nanに変換
nan_count = df.isnull().sum() #NaNの数を数える
nan_per = nan_count/len(df)*100

plt.bar(df.columns, nan_per)
plt.ylabel("Percentage of NaNs [%]")
plt.xlabel("Columns")
plt.show()
```
![wt_bar2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1077734/cd61c6cc-d706-ea0e-2e53-454f4a72f53c.png)

ほとんどのデータは10%以下、多くても12%程度の欠損であることが分かった。
欠損をどう埋めるかは次回。

# おわりに
以上、データの下準備とちょっとした分析を行った結果を示した。もし加えて何かをした場合は記事に追記していきたい。