Pythonでいろんな形の日時データをdatetime型に変換する

#作業環境
Jupyter Notebook(6.1.4)を用いて作業を進めた。
各versionは`pandas`(1.1.3), `mojimoji`(0.0.11)である。

#やりたいこと
データ分析の学習の中で「2021/8/16」のような日時のデータを取り扱った。分析のためにまず`int`や`str`になっているデータ型を`datetime`型へ変換してから差分を計算するなどを行ったので、その変換方法をまとめておく。

#取り扱うデータ

まずはExcelで下のようなデータを準備してみた。

![スクリーンショット (8).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1851062/2359b13e-e321-fcc8-2745-e5759e505e33.png)

この時点で素直に表示設定を日付にしてから保存するのが間違いなく楽だが、今回はPythonを使って加工する方法を考えたいのでこのまま`date.csv`として保存した。Jupyter Notebookを開いてファイルを読み込み表示してみたところ、予想通り次のような形になった。

```python
import pandas as pd
df = pd.read_csv('date.csv', encoding='shift-jis')
print(df.dtypes)
#-->date     object
#   temp    float64
#   dtype: object
df
```

![スクリーンショット (10).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1851062/05211e68-9cd2-3c8f-d546-ce654e2d3d17.png)

このdate列を`2021-08-01`の形の`datetime`型に変換する。

#`datetime`型に変換

変換の方法は次の順番で行った。

1. 数値データから`datetime`型に変換する
2. 漢字入りの文字データから`datetime`型に変換する
3. 文字データから`datetime`型に変換する

##数値データから`datetime`型に変換する

`astype('str')`で`str`型に変換し、`isdigit()`で数値のみのフラグを立てて`to_timedelta`で`datetime`型における基準日からの増分へと変換、基準日を足して結果を確認する。

```python
flag_digit = df['date'].astype('str').str.isdigit()
pd.to_timedelta(df.loc[flag_digit, 'date'].astype('float'), unit='D') + pd.to_datetime('1900/01/01')
#---> 
#2   2021-08-05
#3   2021-08-06
#Name: date, dtype: datetime64[ns]
```

結果をみると、3,4行目が変換されているが、ExcelとPythonでは基準日からの数え方に違いがあるので2日分ずらさなければならない。コードに`-2`を加えて`Digit`として保存する。

```python
Digit = pd.to_timedelta(df.loc[flag_digit, 'date'].astype('float') - 2, unit='D') + pd.to_datetime('1900/01/01')
```

##漢字入りの文字データから`datetime`型に変換する

次に数値データ以外の部分を変換しようとすると次のような結果になった。

```python
Str = pd.to_datetime(df.loc[~flag_digit, 'date'])
#---> Unknown string format: ２０２１年　８月　５日
```

どうやら1,2行目はこれで変換できるようだが、5行目の全角・空白入り・漢字表記のコンボで引っかかってしまったらしい（ちなみに`pd.to_datetime(１９００/０１/０１)`は変換できたので全角が必ずしもだめなわけではなさそう）。まずは全角と空白入りを解決する。全角から半角への変換は[mojimoji](https://pypi.org/project/mojimoji/ "mojimoji")を使用した。

```python
import mojimoji
df['date'].apply(lambda s: mojimoji.zen_to_han(s))
#--->
#0     2021/08/01
#1     2021/08/02
#2          44411
#3          44412
#4    2021年 8月 5日
#5    2021年 8月 6日
#6      2021年8月7日
#7      2021年8月8日
#8     2021-08-09
#9     2021-08-10
Name: date, dtype: object
```

5,6行目が半角になっていることが確認できた。また`replace`を使ってスペースも削除しておく。

```python
df['date'] = df['date'].apply(lambda s: mojimoji.zen_to_han(s))
df['date'] = df['date'].str.replace(' ', '')
```

漢字表記については`to_datetime()`の第二引数に`format='%Y年%m月%d'`を指定することで変換ができるので、漢字部分にのみフラグを立てて変換を行いたい。これには正規表現を用いる。私があまり詳しくないので動作確認を行った。なお漢字の範囲はかなり広めにとっている。

```python
import re
#コンパイル
kanji_check = re.compile('[\u2E80-\u2FDF\u3005-\u3007\u3400-\u4DBF\u4E00-\u9FFF\uF900-\uFAFF\U00020000-\U0002EBEF]+')
print(kanji_check.search('これは漢字'))
#---> <re.Match object; span=(3, 5), match='漢字'>
#マッチングオブジェクトが返される。これはif文の中でbool値として判定される
if kanji_check.search('こんにちは'):
    print('漢字が入っている')
else:
    print('漢字が入っていない')
#---> 漢字が入っていない
if kanji_check.search('今日は'):
    print('漢字が入っている')
else:
    print('漢字が入っていない')
#---> 漢字が入っている
```

これを用いてフラグを作り変換、`Kanji`に保存した。

```python
flag_kanji = []
for date in df['date']:
    if kanji_check.search(date):
        flag_kanji.append(True)
    else:
        flag_kanji.append(False)
Kanji =  pd.to_datetime(df.loc[flag_kanji, 'date'], format='%Y年%m月%d日')
```

##文字データから`datetime`型に変換する

最後に残りの行を変換して`Str`に保存する。

```python
Str = pd.to_datetime(df.loc[~(flag_digit | flag_kanji), 'date'])
```

ここまでで保存したものを`concat`で結合して結果を確認する。

```python
df['date'] = pd.concat([Digit, Kanji, Str])
df
```

![スクリーンショット (12).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1851062/079d1331-a4b1-4309-c87d-939caebeed65.png)

無事に変換できた！