データ分析をおこなうにあたり、まずはデータベースなどで収集したデータをプログラムで扱いやすいよう整形する必要がある。
そしてデータ分析では、この前処理に多くの時間を費やすことになる。
例えば、欠損値や重複値の処理、その他文字列操作などが必要な前処理として挙げられる。
今回はデータセットの前処理のうち、欠損値の取り扱いについて詳しく取り上げていこう。
コンテンツ
サンプルデータセットの用意
まずはサンプルとなるデータセット(シリーズ)を用意しよう。
import pandas as pd
import numpy as np
string_data = pd.Series(['hoge', 'hooooge', np.nan, 'fugafuga'])
# 0 hoge
# 1 hooooge
# 2 NaN
# 3 fugafuga
# dtype: object
string_data.isnull()
# 0 False
# 1 False
# 2 True
# 3 False
# dtype: bool
pandasでは欠損値のことをNA(not available)と呼ぶ。
上記の例ではNumpyを利用して欠損値NAを設定したが、Python標準のNone値が配列に含まれている場合も欠損値として扱われる。
string_data[0] = None
string_data.isnull()
# 0 True
# 1 False
# 2 True
# 3 False
# dtype: bool
欠損値を削除するdropnaメソッド
データセットから欠損値を削除する方法はいくつも存在するが、dropnaメソッドを使うと簡単だ。
下記のように、シリーズに対しdropnaメソッドを実行すると、欠損値でないデータのみを持ったシリーズを返してくれる。
import pandas as pd
from numpy import nan as NA
series = pd.Series([1, NA, 2.5, NA, 8.9])
# 0 1.0
# 1 NaN
# 2 2.5
# 3 NaN
# 4 8.9
# dtype: float64
series.dropna()
# 0 1.0
# 2 2.5
# 4 8.9
# dtype: float64
データフレームに対しdropnaメソッドを実行する
データフレームに対しdropnaメソッドを使う場合は、少し複雑になる。
まずはdropnaの引数に何も指定しない例を見てみよう。
import pandas as pd
from numpy import nan as NA
df = pd.DataFrame([
[1, 2, 3], [1, NA, NA], [NA, NA, NA], [NA, 2, 3]
])
# 0 1 2
# 0 1.0 2.0 3.0
# 1 1.0 NaN NaN
# 2 NaN NaN NaN
# 3 NaN 2.0 3.0
cleaned = df.dropna()
# 0 1 2
# 0 1.0 2.0 3.0
上記のように、デフォルトでは欠損値を一つでも含む行を全て削除した結果が返る。
引数howにallを指定すると、全てのデータが欠損値である行のみを削除する。
cleaned = df.dropna(how='all')
# 0 1 2
# 0 1.0 2.0 3.0
# 1 1.0 NaN NaN
# 3 NaN 2.0 3.0
また、軸を指定することで列を削除することも可能だ。
df[4] = NA # 全て欠損値で構成された列を追加
cleaned = df.dropna(axis=1, how='all')
print(cleaned)
# 0 1 2
# 0 1.0 2.0 3.0
# 1 1.0 NaN NaN
# 2 NaN NaN NaN
# 3 NaN 2.0 3.0
欠損値を穴埋めするfillnaメソッド
dropnaメソッドでは欠損値が含まれる行を削除してしまうが、同じ行に必要な情報が保存されている場合、これでは都合が悪い。
欠損値を何か別の値で代入したいケースもあるだろう。
そこで有用なのがfillnaメソッドだ。
fillnaメソッドは、引数に任意の値を指定し、欠損値をその値で置き換えることができる。
import pandas as pd
from numpy import nan as NA
df = pd.DataFrame(np.random.randn(7, 3))
df.iloc[:4, 1] = NA
df.iloc[:2, 2] = NA
# 0 1 2
# 0 -0.832792 NaN NaN
# 1 -0.098271 NaN NaN
# 2 -1.118468 NaN -0.189909
# 3 -0.157277 NaN 0.367300
# 4 -0.638321 -0.143893 -0.897541
# 5 1.459028 2.842839 -0.123797
# 6 -0.175514 1.704813 -0.465991
cleaned = df.fillna(0)
# 0 1 2
# 0 -0.832792 0.000000 0.000000
# 1 -0.098271 0.000000 0.000000
# 2 -1.118468 0.000000 -0.189909
# 3 -0.157277 0.000000 0.367300
# 4 -0.638321 -0.143893 -0.897541
# 5 1.459028 2.842839 -0.123797
# 6 -0.175514 1.704813 -0.465991
列ごとに別の値で穴埋めをする
また、引数にディクショナリを指定すると、列ごとに別の値を代入することができる。
cleaned = df.fillna({1:0.5, 2:0})
# 0 1 2
# 0 0.849705 0.500000 0.000000
# 1 1.500298 0.500000 0.000000
# 2 0.339612 0.500000 -0.616841
# 3 1.801734 0.500000 -1.109708
# 4 -0.989622 1.321243 -0.394812
# 5 0.688982 0.321112 -2.229204
# 6 1.080932 0.654148 1.042735
既存オブジェクトを上書きする
fillnaメソッドを実行すると、デフォルトでは新しいオブジェクトを返すが、オプションのinplaceにTrueを指定することで、既存のオブジェクトを上書きすることもできる。
df.fillna(0, inplace=True)
print(df)
# 0 1 2
# 0 -0.043925 0.000000 0.000000
# 1 -1.144397 0.000000 0.000000
# 2 1.350779 0.000000 0.614622
# 3 -0.418694 0.000000 1.917690
# 4 0.186214 0.121632 -0.026854
# 5 0.186749 -1.062706 0.011198
# 6 -0.293070 -0.402799 0.601867