CUBE SUGAR CONTAINER

技術系のこと書きます。

Python: pandas でカラムの型を変換する

pandas はデータを読み込むとき、よきに計らってカラムに型を付与してくれる。 ただ、その内容が意図しない場合もある。 そんなとき、どうやってカラムの型を直すか、ということについて。

使った環境は次の通り。

$ sw_vers 
ProductName:    Mac OS X
ProductVersion: 10.12.6
BuildVersion:   16G29
$ python --version
Python 3.6.3

もくじ

下準備

まずは下準備として pandas をインストールしておく。

$ pip install pandas
$ pip list --format=columns | grep pandas
pandas          0.21.0 

サンプルデータ

続いてサンプルとなるデータフレームを次のように用意する。

>>> columns = ['name', 'innings']
>>> data = [
...   ['Sugano', 187.1],
...   ['Mikolas', 188],
...   ['Messenger', 143],
...   ['Nomura', 155.1],
...   ['Imanaga', 148],
... ]
>>> import pandas as pd
>>> df = pd.DataFrame(data, columns=columns)
>>> df
        name  innings
0     Sugano    187.1
1    Mikolas    188.0
2  Messenger    143.0
3     Nomura    155.1
4    Imanaga    148.0

カラムの型は DataFrame#dtype プロパティで確認できる。

>>> df['innings'].dtype
dtype('float64')

例えば、この innings カラムを整数型 (int64) に変換してみよう。

カラムの型を変換する

特定のカラム (Series) の型を変換するには Series#astype() メソッドを使う。 このメソッドを使うと同じ値で指定された型の Series オブジェクトができる。

>>> import numpy as np
>>> df['innings'].astype(np.int64)
0    187
1    188
2    143
3    155
4    148
Name: innings, dtype: int64

型を変換したカラムを非破壊的に追加する

元々のデータフレームに、型を変換したカラムを追加したものを作りたいときは DataFrame#assign() メソッドを使うと良い。

>>> df.assign(innings_int = df['innings'].astype(np.int64))
        name  innings  innings_int
0     Sugano    187.1          187
1    Mikolas    188.0          188
2  Messenger    143.0          143
3     Nomura    155.1          155
4    Imanaga    148.0          148

このメソッドは非破壊的な処理で、カラムを追加した新しいデータフレームを作って返す。

そのため、元々のデータフレームを確認してもカラムは追加されていない。

>>> df
        name  innings
0     Sugano    187.1
1    Mikolas    188.0
2  Messenger    143.0
3     Nomura    155.1
4    Imanaga    148.0

つまり、新しくできたデータフレームを別の変数とかに格納して使えば良い。

>>> df2 = df.assign(innings_int = df['innings'].astype(np.int64))

型を変換したカラムを破壊的に追加する

破壊的に追加したいときは、元々のデータフレームにブラケット演算子で名前を指定して代入すれば良い。

>>> df['innings_int'] = df['innings'].astype(np.int64)
>>> df
        name  innings  innings_int
0     Sugano    187.1          187
1    Mikolas    188.0          188
2  Messenger    143.0          143
3     Nomura    155.1          155
4    Imanaga    148.0          148

もっと柔軟に変換する

これまでの例のように浮動小数点数型を整数型にするくらいの処理ならいいんだけど、もっと柔軟に型を変換したいという場合もある。 例えば、小数点の端数を切り上げた上で整数型に変換したい、という場合を考えてみよう。

こういった場合には DataFrame#apply() メソッドを使って、その処理を一行ずつに適用していくことが考えられる。 ただし、このやり方はカラム単位ではなく行単位の処理になるので遅い。 これに限らず axis=1 となるような処理は、なるべく避けた方が良いと思う。

>>> import math
>>> df.apply(lambda x: math.ceil(x['innings']), axis=1)
0    188
1    188
2    143
3    156
4    148
dtype: int64

作った Series をデータフレームに追加する方法については先ほどと同じ。

>>> df.assign(innings_ceil = df.apply(lambda x: math.ceil(x['innings']), axis=1))
        name  innings  innings_ceil
0     Sugano    187.1           188
1    Mikolas    188.0           188
2  Messenger    143.0           143
3     Nomura    155.1           156
4    Imanaga    148.0           148

値として NaN が入っている場合

ちなみに、値として NaN が入っているときは、ちょっと話が変わってくる。

>>> columns = ['name', 'innings']
>>> data = [
...   ['Sugano', 187.1],
...   ['Mikolas', 188],
...   ['Messenger', 143],
...   ['Nomura', 155.1],
...   ['Imanaga', 148],
...   ['Sawamura', np.nan],
... ]
>>> import pandas as pd
>>> df = pd.DataFrame(data, columns=columns)

NaN が入っている場合に、最初の例のように Series#astype() メソッドを使うと ValueError 例外になってしまう。

>>> df['innings'].astype(np.int64)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
...(省略)...
ValueError: Cannot convert non-finite values (NA or inf) to integer

そういったときは Series#fillna() メソッドなどを使って NaN をそれ以外の値に置き換えてやる必要がある。 もちろん Series#dropna() などで、そもそも集計の対象外としてしまうことも考えられる。

>>> df['innings'].fillna(0.0)
0    187.1
1    188.0
2    143.0
3    155.1
4    148.0
5      0.0
Name: innings, dtype: float64

Series#fillna() メソッドなどを使って NaN さえ無くすことができれば、ちゃんと変換できる。

>>> df['innings'].fillna(0.0).astype(int)
0    187
1    188
2    143
3    155
4    148
5      0
Name: innings, dtype: int64

変換した結果を元のデータフレームに追加する方法については、これまでと変わらない。

>>> df.assign(innings_int = df['innings'].fillna(0.0).astype(np.int64))
        name  innings  innings_int
0     Sugano    187.1          187
1    Mikolas    188.0          188
2  Messenger    143.0          143
3     Nomura    155.1          155
4    Imanaga    148.0          148
5   Sawamura      NaN            0

めでたしめでたし。