今回は pandas を使っているときに二つの DataFrame を pd.concat()
で連結したところ int のカラムが float になって驚いた、という話。
先に結論から書いてしまうと、これは片方の DataFrame に存在しないカラムがあったとき、それが全て NaN 扱いになることで発生する。
NaN は浮動小数点数型にしか存在しない概念なので、それが元で整数型と浮動小数点数型の演算になりキャストされてしまった。
使った環境は次の通り。
$ sw_vers ProductName: Mac OS X ProductVersion: 10.13.4 BuildVersion: 17E202 $ python -V Python 3.6.5
下準備
まずは下準備として pandas と numpy をインストールしておく。
$ pip install pandas numpy
インストールできたら Python の REPL を起動する。
$ python
これで下準備はおわり。
問題の起こらないパターン
それでは早速 pandas.concat()
を使って二つの DataFrame を連結してみよう。
最初は問題の起こらないパターンについて概要を説明する。
まずは pandas をインポートしておく。
>>> import pandas as pd
続いて、次のように同じカラムを持った DataFrame を二つ用意する。 カラム名は両者で順番が異なっている。 最初の DataFrame はユーザ名、年齢の順になっているが、二つ目は年齢、ユーザ名の順になっている。
>>> users_df1 = pd.DataFrame([ ... ['alice', 10], ... ['bob', 20], ... ['carol', 30] ... ], columns=['name', 'age']) >>> users_df2 = pd.DataFrame([ ... [40, 'daniel'], ... [50, 'emily'], ... [60, 'frank'], ... ], columns=['age', 'name'])
それぞれの DataFrame に含まれる年齢 (age) の dtype は int64 になっている。
>>> users_df1.age.dtype dtype('int64') >>> users_df2.age.dtype dtype('int64')
それでは pandas.concat()
関数を使って上記二つの DataFrame を縦に連結してみよう。
この関数は、カラム名さえ一致すれば順番が異なっていても上手いこと連結してくれることが分かる。
>>> concat_df = pd.concat([users_df1, users_df2]) >>> concat_df age name 0 10 alice 1 20 bob 2 30 carol 0 40 daniel 1 50 emily 2 60 frank
カラムの dtype も、ちゃんと int64 のままだ。
>>> concat_df.age.dtype
dtype('int64')
問題の起こるパターン
では、続いて問題の起こるパターンについて見てみよう。
連結に使う DataFrame は次の通り。
先ほどとほとんど同じだが users_df2
に関しては user_id
というカラムが追加されている。
そしてポイントは user_id
というカラムが users_df1
には存在しないことだ。
>>> users_df1 = pd.DataFrame([ ... ['alice', 10], ... ['bob', 20], ... ['carol', 30] ... ], columns=['name', 'age']) >>> users_df2 = pd.DataFrame([ ... [10, 40, 'daniel'], ... [20, 50, 'emily'], ... [30, 60, 'frank'], ... ], columns=['user_id', 'age', 'name'])
追加された users_df2
の user_id
カラムの dtype は int64 になっている。
>>> users_df2.user_id.dtype
dtype('int64')
それでは二つの DataFrame を結合してみよう。
>>> concat_df = pd.concat([users_df1, users_df2])
結合された結果を確認してみよう。
すると user_id
カラムについては一部に NaN
が入っており、入っていないものについては小数点以下が表示されている。
>>> concat_df age name user_id 0 10 alice NaN 1 20 bob NaN 2 30 carol NaN 0 40 daniel 10.0 1 50 emily 20.0 2 60 frank 30.0
おや、と思いながら dtype を確認すると、結合前の int64
が結合後には float64
に変換されている。
>>> concat_df.user_id.dtype
dtype('float64')
何が起こったのか
NumPy をインポートして numpy.nan
を参照すると、たしかに型は float
になっている。
>>> import numpy as np >>> type(np.nan) <class 'float'>
NaN
を整数型にキャストしようとしても、上手くいかない。
そもそも、整数型には NaN
という概念が存在しないため。
>>> np.int64(np.nan) Traceback (most recent call last): File "<stdin>", line 1, in <module> ValueError: cannot convert float NaN to integer
で、よくよく考えるとそもそも整数型と浮動小数点型の演算結果は浮動小数点型に合わせて出てくるものだった。
>>> 1 + 1.0 2.0 >>> type(1 + 1.0) <class 'float'>
つまり、最初にやっていたことをよりシンプルな形で表すと、次のようになる。
まず、numpy.int64
と numpy.float64
の pandas.Series
を用意する。
>>> s1 = pd.Series([0, 1, 2], dtype=np.int64) >>> s2 = pd.Series([np.nan, np.nan, np.nan], dtype=np.float64)
そして、それらを結合する。 結果は浮動小数点型に合わせられる。
>>> pd.concat([s1, s2]) 0 0.0 1 1.0 2 2.0 0 NaN 1 NaN 2 NaN dtype: float64
なるほど、これなら違和感なし。
じゃあ、どうすれば良いのか?
これを解決するためのアプローチとしては次の二つの方法になると思う。 - あらかじめ存在しないカラムを追加しておく - 結合した上で値を置換、キャストする
最初の方法は自明として、二つ目に関して補足する。 まず、さきほどの DataFrame があったとする。
>>> concat_df age name user_id 0 10 alice NaN 1 20 bob NaN 2 30 carol NaN 0 40 daniel 10.0 1 50 emily 20.0 2 60 frank 30.0
浮動小数点になっちゃったカラムについて NaN
になっているところを Series#fillna()
を使って適当な値に置換する。
DataFrame.fillna()
するとカラムに関係なく埋められてしまう点に注意する。
>>> concat_df.user_id.fillna(-1, inplace=True) >>> concat_df age name user_id 0 10 alice -1.0 1 20 bob -1.0 2 30 carol -1.0 0 40 daniel 10.0 1 50 emily 20.0 2 60 frank 30.0
あとは Series#astype()
で元々の型にキャストする。
>>> df.assign(user_id = df.user_id.astype(np.int64)) age name user_id 0 10 alice -1 1 20 bob -1 2 30 carol -1 0 40 daniel 10 1 50 emily 20 2 60 frank 30
いじょう。
めでたしめでたし。
Pythonによるデータ分析入門 ―NumPy、pandasを使ったデータ処理
- 作者: Wes McKinney,小林儀匡,鈴木宏尚,瀬戸山雅人,滝口開資,野上大介
- 出版社/メーカー: オライリージャパン
- 発売日: 2013/12/26
- メディア: 大型本
- この商品を含むブログ (19件) を見る
スマートPythonプログラミング: Pythonのより良い書き方を学ぶ
- 作者: もみじあめ
- 発売日: 2016/03/12
- メディア: Kindle版
- この商品を含むブログ (1件) を見る