データ分析をおこなうにあたり、まずはデータベースなどで収集したデータをプログラムで扱いやすいよう整形する必要がある。

そしてデータ分析では、この前処理に多くの時間を費やすことになる。

例えば、欠損値や重複値の処理、その他文字列操作などが必要な前処理として挙げられる。

今回はデータセットの前処理のうち、欠損値の取り扱いについて詳しく取り上げていこう。

サンプルデータセットの用意

まずはサンプルとなるデータセット(シリーズ)を用意しよう。

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