Pandas/PythonのDataFrameで出てくるデータ型と型変換

# はじめに
PandasのDFでデータを扱うとき。最初はあまり意識しないが、すぐにデータ型という概念にぶつかる。
というのも、例えばread_csv等でデータを読み込む際、pandas側でいいように型を設定してくれるのだが、
ふとした拍子で意外な型になっており、そのデータを扱う際にエラーになったりすることがある。

なのでデータ型の変更や、データ型毎の代表的な処理をまとめてみた。

# 存在するデータ型
pandasのdocにきれいにまとまっていればいいのだけれど、見当たらず。
以下以外にもあるようだが、主要どころを理解するという意味では以下を理解すればよさそう。

| 用途 | Pandas | Python |
|:-:|:-:|:-:|
| 文字列<br>複数データ混合  | object | (内容による) |
| 整数 | int64 | int |
| 浮動小数 | float64 | float |
| 真/偽 | bool | bool |
| 日付、時刻 | datetime64 | - |
| 2つの時刻の差 | timedelta[ns] | - |
| カテゴリ | category | - |

上記以外にも、pandasの型として以下のようなものもある。
必要な時が来たら追加で整理したい。

* int8、int16、int32：それぞれ末尾の数字＝ビット数　※int以外にも同様の概念あり
* unit64：int64と同様に整数だが、unit64の場合符号なし。
* complex128：複素数。実部と虚部がそれぞれfloat64で、全体として128
* category：カテゴリデータ。性別を0や1で示すような、「数値＋NaN」で扱うが、足し算引き算等をする意味のない値。int64と明確に区別するとき定義する
* string：文字列。厄介なことに昔のpandasにはなく、Ver1.0から導入されたらしい。
　　　　後方互換のためread時にはobject形で読み込まれるため、string型を意識的に使う人が少ない。


pandasのString型については以下記載あり。
objectでなく最初からStringで実装していればよかったなという雰囲気を感じる、、

https://pandas.pydata.org/pandas-docs/stable/user_guide/text.html#text-types


# 実験用データの定義

```python:Python
import pandas as pd
import numpy as np

df = pd.DataFrame({"index":[0, 1, 2, 3],
                   "id":[1, 2, 3, 4],
                   "F-Name":['aa', 'bb', 'cc', 'dd'],
                   "L-Name":['AA', 'BB', 'CC', 'DD'],
                   "point1":[63, 37, 20, 90],
                   "point2":[31, 41, 66, np.nan],
                   "height":[165.3, 143.2, 174.2, 158.0],
                   "weight":[51, 47, 65, 56],
                   "sex":[1, 0, 1, 9],
                   "birth":['1992-12-01', '1995-03-20', '1986-10-09', '1998-07-14'],
                   "add_date":['2018年5月3日', '2019年10月18日', '2020年1月17日', '2021年9月13日'],
                   "upd_date":[20210103, 20210304, 20220404, 20211225],
                   "active":[True, False, True, True]}).set_index("index")

# 		id	F-Name	L-Name	point1	point2	height	weight	sex	birth		add_date		upd_date	active
# index										
# 0		1	aa		AA		63		31.0	165.3	51		1	1992-12-01	2018年5月3日		20210103	True
# 1		2	bb		BB		37		41.0	143.2	47		0	1995-03-20	2019年10月18日	20210304	False
# 2		3	cc		CC		20		66.0	174.2	65		1	1986-10-09	2020年1月17日	20220404	True
# 3		4	dd		DD		90		NaN		158.0	56		9	1998-07-14	2021年9月13日	20211225	True
```

# データ型の表示：dtypes
```python:Python
df.dtypes

# id            int64
# F-Name       object
# L-Name       object
# point1        int64
# point2      float64
# height      float64
# sex           int64
# birth        object
# add_date     object
# upd_date      int64
# active         bool
# dtype: object
```

* 整数はint64、文字列はobject、小数はfloat64、真偽はboolとなる

:::note info
* NaNはpythonではfloatのみ、intでは存在しない概念のため、整数の中にNaNが含まれるとその列はfloat64になる
* 日付型も最初はobject（文字列）
* 意図する型と必ずしも一致しないケースがあり得る（例：weightはたまたま整数しかなかったからint64となっているが、本来小数も許容してほしい）
* 上記ではないが、一見数字でも、文字列として元データが定義していると、objectとして取り込まれることがある
:::



# データ型の変換：astype
```python:Python
# pandasの型名称で指定する場合は''で囲う
# pythonの型名称で指定する場合は''が不要
df = df.astype({'point1': float, 'weight': 'float64', 'sex': 'category'}) 
df.dtypes

# id             int64
# F-Name        object
# L-Name        object
# point1       float64
# point2       float64
# height       float64
# weight       float64
# sex         category
# birth         object
# add_date      object
# upd_date       int64
# active          bool
# dtype: object
```


# データ型の変換（日付）：to_datetime
```python:Python
df['birth'] = pd.to_datetime(df['birth'])
```

:::note info
* to_datetimeで変換できる日付文字列は以下
    * 日付

    | 表記法 | 例 |
    |:-:|:-:|
    | 区切りなし（※） | 20230228 |
    | ハイフン | 2023-02-28 |
    | スラッシュ | 2023/02/28 |
    | MM-DD-YYYY | 02-28-2023 |

    :::note warn
    区切りなしは扱いに注意。単に8文字の数字が並んだ文字列をto_datetimeにかけると、ミリ秒として処理される
    結果、「1970-01-01 00:00:00.020210103」みたいな表記となってしまう。
    これを回避する場合以下表記が必要
    ```python:Python 
    # to_datetimeは文字列に対する処理のため、int64をobjectに変換
    df = df.astype({'upd_date': 'object'}) 
    # ミリ秒として扱われないように、formatで形式を指定
    df['upd_date']  = pd.to_datetime(df['upd_date'], format='%Y%m%d')
    ```
    :::
:::

:::note info
* to_datetimeで変換できる日付時刻文字列は以下
    * 日付＋時刻
    日付部は上記の表の形式であればどれでも可
    以下は区切りなしケースで例を記載
    MM、SS部分を記載しない場合、00で補完される

    | 表記法 | 例 |
    |:-:|:-:|
    | 日付T時刻 | 20230228T14:21:31 |
    | 日付(スペース)時刻| 20230228 14:21:31 |
    | AM、PM付き | 20230228 02:21:31 PM<br>20230228T02:21:31 PM<br>20230228 02:21:31 AM<br>20230228T02:21:31 PM |
    | 時刻区切りなし | 20230228T142131 |
:::

:::note info
to_datetimeで変換できない形式の文字列は、formatを用いて変換を行う

```python:Python
df["add_date"] = pd.to_datetime(df["add_date"], format="%Y年%m月%d日")
```

formatで指定できるコードは以下の通り
https://docs.python.org/3/library/datetime.html#strftime-and-strptime-format-codes

よく使うものを抜粋

| フォーマットコード | 指定内容 | 例 |
|:-:|:-:|:-:|
| %Y | 4桁の年 | 2023 |
| %m | 2桁の月 | 02 |
| %d | 2桁の日 | 28 |
| %H | 時間（24時間） | 13 |
| %M | 分 | 26 |
| %S | 秒 | 49 |
| %y | 2桁の年 | 23 |
| %B | 英語月 | February |
| %b | 英語月（短縮） | Feb |
| %X | HH:MM:SS | 13:26:49 |