pandasのDataFrameで整数型に欠損値を追加したくて〜2022年冬〜
認めたくないものですが12月がやって来てしまいましたね。
▲ 私はスプラトゥーン3の新シーズンで12月を受け入れることにしました
こんにちは。データアナリティクス事業本部 インテグレーション部 機械学習チームのShirotaです。
今月も元気に(元気に?)日々学んだことをブログにしていこうと思います。
本日は、Pythonのデータ解析用のライブラリでお馴染み「pandas」の話をしていこうと思います。
データの前処理で楽しようとしたらやらかした話
「整数型(以下 int型)でNaNを使うぞ!」が今回の本題になっております。一旦心の隅に記録しておいてくれると嬉しいです。
ここに至るまでの経緯を簡単に説明していきます。
二つのデータを結合したら列のデータ型が変わってしまった
機械学習用の訓練データとテストデータの両方に、pandasで前処理を実施しようとしていました。
その際、両方のデータに同じ処理を別々にかけるのは面倒だと思い、一旦両データを結合しようと思いました。
訓練データとテストデータに存在するデータ型は以下のようになっていました。
id object source object text object label int64 dtype: object
id object source object text object dtype: object
訓練データには、テストデータに存在しないlabel
というint型の列が存在しました。
label
列は、今回目的変数であるためテストデータには存在していなかったのです。
訓練データとテストデータは一時的に結合する予定なので、TrainFlag
という列を追加して後でデータを元通り分離できるようにしてからこの二つのデータを結合して、データ型を確認してみました。
id object source object text object label float64 TrainFlag bool dtype: object
label
列が浮動小数点数型(以下 float型)になってしまっていました。
なぜこのようなことが起こったのか
二つのデータを結合したことによって、訓練データにしか存在しなかったlabel
列が自動補完されました。
その際、テストデータはlabel
列の値を持っていなかったため、欠損値としてNaNが補完されました。
NaN とは 何 なのかと言いますと、NaNは 非数(数ではない値) を表現するものであり、主にfloat型の演算で用いられる値です。
Pythonにおいても、NaNはfloat型にのみ存在する概念となっています。
Wikipediaより、NaNの説明を引用しておきます。
NaN(Not a Number、非数、ナン)は、コンピュータにおいて、主に浮動小数点演算の結果として、不正なオペランドを与えられたために生じた結果を表す値またはシンボルである。
欠損値をどう扱うか
では、欠損値はどう扱えば良いのでしょうか。
例えば、fillna
メソッドを用いて欠損値を何らかの定数(平均値や中央値・最大値や最小値・その他固有値)で埋めるという手段があります。
しかし前述した通り、今回欠損値となっているところはテストデータの目的変数の値であるためこの手法は取らないこととします。
今回の場合、int型を保持したまま欠損値を扱いたいです。
厳密には違いますが、「 int型でNaNを使いたい 」という表現がしっくりくるかもしれません。
本題に辿り着いたので、ここから実際にint型でNaNを使いたいという夢を叶えていきましょう。
pandasを使ってint型で欠損値を扱っていく
PythonではNaNはfloat型でしか扱えません。どうしましょうか。
実は、pandas 1.0から NA
というさまざまなデータ型で使える欠損値が利用できるようになりました。
以下、pandasの公式ドキュメントとDevelopersIOのブログを参考資料として紹介させて頂きます。
上記資料にも記載がありますが、現状、NA
は 実験的に採用されている(pandas 1.5.2 時点) ようなので、今後予告なしに変更が入る可能性はあります。この点は注意して下さい。
今回私はGoogle Colaboratory(以下 Colab)上で以下作業を実施しており、Colab上で最新のバージョンであるpandas 1.3.5(2022年12月1日現在)を利用しました。
では早速、テストデータに空列のlabel
列を追加していきます。
data_test["label"] = pd.NA
上記を実行後のデータセットは以下のようになりました。
id | source | text | label | |
---|---|---|---|---|
0 | 0238492 | hoge | 今日ずっと隣で猫が丸まって寝ていてかわいい | <NA> |
1 | 03942829 | hogefuga | ヒラメのショクワンも夢があるな | <NA> |
2 | 29342930 | fuga | ノヴァブラスターアドベントカレンダー開催します | <NA> |
3 | 375938 | fuga | 猫の寝息かと思ったら給水器の音だった | <NA> |
4 | 13858035 | hoge | よく見たら黒筐体だった | <NA> |
欠損値NA
が入ったことが確認できました。
ここで、データ型を見てみます。
id object source object text object label object dtype: object
object
型になってしまいました。
なので、データ型をint
型に変更します。
data_test["label"] = data_test["label"].astype("Int64")
この際、 Int64 と Iを大文字にしないといけない ところに注意してください。
int64と指定すると、NAが利用できないデータ型として以下のようなエラーが出ます。
ValueError: cannot convert to 'int64'-dtype NumPy array with missing values. Specify an appropriate 'na_value' for this dtype.
上記を実行してからデータ型を確認すると、以下のようになります。
id object source object text object label Int64 dtype: object
これで、int型でも無事欠損値が利用できました!
オマケ:int64とInt64のデータ型を結合するとどうなるか
NA
を使えないint64とNA
を使えるInt64、この二つのデータ型を結合するとどうなるでしょうか。
少しの間、考えてみてください。
考えましたか?
答えはこちらです。
id object source object text object label object dtype: object
int型を維持したい場合は、int64(iが小文字)のデータ型をInt64(Iが大文字)のデータ型に変換してから二つのデータを結合するようにしましょう。
最後にもう一度だけお話ししておきますが、NA
は 現状実験的に採用されている(pandas 1.5.2 時点) ため、本番環境での利用は避け一時的なデータ処理や検証などで利用するようにしましょう。