実際のデータで分析を行うとデータが不完全で欠損値が含まれていることがあります。

欠損値の扱い方が変わるだけで分析の結果が変化する場合もあります。

そこで本記事では欠損値の処理をすることができるように

  • 簡単な欠損値の確かめ方
  • 欠損値を削除する方法
  • 欠損値を穴埋めする方法

の3つについて解説していきます。

簡単な欠損値の確かめ方

とりあえず各列に欠損値があるかどうかを知りたい、というときはisnull関数とany関数の組み合わせとnotnull関数とall関数の組み合わせがあります。

前者の組み合わせのときは欠損値のある列にTrueが返され、後者の組み合わせのときは欠損値のある列にFalseが返されます。

以下のように確かめることができます。

In [1]: import pandas as pd

In [2]: import numpy as np

In [3]: data = np.random.randn(5,5)

In [4]: data[1,3] = np.nan # 欠損値を入れる

In [5]: data[2,0] = np.nan # 欠損値2つ目

In [6]: data
Out[6]:
array([[ 2.10851627,  1.07282648, -0.97971334,  0.02266813, -0.38525149],
       [-0.48737955,  0.11157585, -0.76305474,         nan, -0.07192401],
       [        nan, -0.97189693, -0.31556478,  0.33463695,  0.70125536],
       [-0.07861912, -0.19900086, -0.01112946,  0.56708296,  0.46685101],
       [-0.63178651,  1.65860916, -0.36124422, -0.00691114, -0.73076744]])

In [7]: df = pd.DataFrame(data, columns=['A','B','C','D','E']) # DataFrameの作成
   ...:

In [8]: df
Out[8]:
          A         B         C         D         E
0  2.108516  1.072826 -0.979713  0.022668 -0.385251
1 -0.487380  0.111576 -0.763055       NaN -0.071924
2       NaN -0.971897 -0.315565  0.334637  0.701255
3 -0.078619 -0.199001 -0.011129  0.567083  0.466851
4 -0.631787  1.658609 -0.361244 -0.006911 -0.730767

In [9]: df.isnull().any()
Out[9]:
A     True
B    False
C    False
D     True
E    False
dtype: bool

In [10]: df.notnull().all()
Out[10]:
A    False
B     True
C     True
D    False
E     True
dtype: bool

欠損値を削除する方法

では、欠損値の削除の仕方を確認してみましょう。

欠損値を削除するにはdropna関数を使います。読んで字のごとく、NaN値(欠損値)をドロップ(除外)します。

1つ注意ですが、inplace=Trueにしないと、元のデータに変更は反映されないので注意しましょう。

以下のデータを使います。

In [11]: df = pd.Series([1, 2, 3, np.nan, 0, None], index=['A','B','C','D','E','F']) # 欠損値を含むデータを作成

In [12]: df
Out[12]:
A    1.0
B    2.0
C    3.0
D    NaN
E    0.0
F    NaN
dtype: float64

基本的な使い方

では先ほど作成したSeriesにdropna関数を適用させます。すると、NaN値を含むところが切り落とされる結果になります。

In [13]: df.dropna()
Out[13]:
A    1.0
B    2.0
C    3.0
E    0.0
dtype: float64

DataFrameだとデフォルトの設定でdropnaを適用すると欠損値を1つでも含む行は削除されます。

In [14]: df_2 = pd.DataFrame({'A':[0, 1, np.nan, 2],'B':[np.nan,2, 3, 4]})

In [15]: df_2
Out[15]:
     A    B
0  0.0  NaN
1  1.0  2.0
2  NaN  3.0
3  2.0  4.0

In [16]: df_2.dropna() # 0,2行目が削除される
Out[16]:
     A    B
1  1.0  2.0
3  2.0  4.0

全てが欠損値の行を削除する

how='all'にすると全ての値が欠損値になっている行のみを削除します。

In [17]: df = pd.DataFrame({'A': [1, np.nan, 2, np.nan, 2, np.nan],
    ...:                    'B': [np.nan, np.nan, 3, 4, 5, 6]})
    ...:                    

In [18]: df
Out[18]:
     A    B
0  1.0  NaN
1  NaN  NaN
2  2.0  3.0
3  NaN  4.0
4  2.0  5.0
5  NaN  6.0

In [19]: df.dropna(how='all') # 両方とも欠損値の1行目だけが消去される
Out[19]:
     A    B
0  1.0  NaN
2  2.0  3.0
3  NaN  4.0
4  2.0  5.0
5  NaN  6.0

削除したい列を指定する

subset=[カラム名のリスト]とすることで欠損値を削除したい列を指定することができます。

In [22]: df
Out[22]:
     A    B
0  1.0  NaN
1  NaN  NaN
2  2.0  3.0
3  NaN  4.0
4  2.0  5.0
5  NaN  6.0

In [23]: df.dropna(subset=['A']) # A列に含まれる欠損値だけを削除する
Out[23]:
     A    B
0  1.0  NaN
2  2.0  3.0
4  2.0  5.0

変更を元のデータに反映させる

inplace=Trueにすれば変更を元のデータに反映させることが可能です。このとき、返り値はありません。

In [24]: df
Out[24]:
     A    B
0  1.0  NaN
1  NaN  NaN
2  2.0  3.0
3  NaN  4.0
4  2.0  5.0
5  NaN  6.0

In [25]: df_copy = df.copy()

In [26]: df_copy.dropna(inplace=True)

In [27]: df_copy
Out[27]:
     A    B
2  2.0  3.0
4  2.0  5.0

行あたりに残したいデータ数を指定

threshで行あたりにいくつ以上のデータが非欠損値であれば削除しないかを指定できます。

例えば、2個以上非欠損値があればその行を残したいときはthresh=2のように設定します。

In [28]: df = pd.DataFrame({'A':[0, np.nan, np.nan, 2, 3, 4],
    ...:                    'B':[np.nan, np.nan, 2, 3, 5, 6],
    ...:                    'C':[1, np.nan, np.nan, 3,5,np.nan]})
    ...:                    

In [29]: df
Out[29]:
     A    B    C
0  0.0  NaN  1.0
1  NaN  NaN  NaN
2  NaN  2.0  NaN
3  2.0  3.0  3.0
4  3.0  5.0  5.0
5  4.0  6.0  NaN

In [30]: df.dropna(thresh=2) # 欠損値でないところが2つ以上残っていれば削除しない
Out[30]:
     A    B    C
0  0.0  NaN  1.0
3  2.0  3.0  3.0
4  3.0  5.0  5.0
5  4.0  6.0  NaN

削除する方向を指定

axisで欠損値を含む行を削除するか、列を削除するかを指定できます。axis=1もしくはaxis='columns'で列を削除できます。

In [33]: df_t = df.T

In [34]: df_t
Out[34]:
     0   1    2    3    4    5
A  0.0 NaN  NaN  2.0  3.0  4.0
B  NaN NaN  2.0  3.0  5.0  6.0
C  1.0 NaN  NaN  3.0  5.0  NaN

In [35]: df_t.dropna(axis='columns')
Out[35]:
     3    4
A  2.0  3.0
B  3.0  5.0
C  3.0  5.0

欠損値を穴埋めする方法

欠損値を穴埋めするにはfillna関数を使います。

dropna関数と同様、元のデータに変更を反映させたいときはinplace=Trueを使います。

基本的な使い方

最初の引数で穴埋めする値を指定して使います。0で埋めたかったらdropna(0)と指定します。

In [37]: df
Out[37]:
     A    B    C
0  0.0  NaN  1.0
1  NaN  NaN  NaN
2  NaN  2.0  NaN
3  2.0  3.0  3.0
4  3.0  5.0  5.0
5  4.0  6.0  NaN

In [38]: df.fillna(0)
Out[38]:
     A    B    C
0  0.0  0.0  1.0
1  0.0  0.0  0.0
2  0.0  2.0  0.0
3  2.0  3.0  3.0
4  3.0  5.0  5.0
5  4.0  6.0  0.0

変則的ですが、文字列で埋めることも可能です。

In [39]: df.fillna('missing')
Out[39]:
         A        B        C
0        0  missing        1
1  missing  missing  missing
2  missing        2  missing
3        2        3        3
4        3        5        5
5        4        6  missing

列ごとに埋める値を変える

列ごとに穴埋めする値を変更するときは、辞書形式で指定します。

In [40]: df
Out[40]:
     A    B    C
0  0.0  NaN  1.0
1  NaN  NaN  NaN
2  NaN  2.0  NaN
3  2.0  3.0  3.0
4  3.0  5.0  5.0
5  4.0  6.0  NaN

In [41]: df.fillna({'A':-10, 'B': 0, 'C': 999}) # 列ごとに穴埋めする値を変える
Out[41]:
      A    B      C
0   0.0  0.0    1.0
1 -10.0  0.0  999.0
2 -10.0  2.0  999.0
3   2.0  3.0    3.0
4   3.0  5.0    5.0
5   4.0  6.0  999.0

前後の値を使って穴埋めをする

欠損値の前後を使って穴埋めをすることができます。method='ffill'で直前の値が穴埋めの値に使われます。

ffillはforward fillの略称なので前方方向に向けて穴埋めするというイメージを持つとわかりやすいと思います。 一方、method='bfill'はbackward fillの略称となり後方方向に向けて穴埋めするということになります。

In [42]: df_method = df.copy()

In [43]: df_method.iloc[3:,1] = np.nan

In [44]: df_method
Out[44]:
     A    B    C
0  0.0  NaN  1.0
1  NaN  NaN  NaN
2  NaN  2.0  NaN
3  2.0  NaN  3.0
4  3.0  NaN  5.0
5  4.0  NaN  NaN

In [45]: df_method.fillna(method='ffill') # 直前の値を使って埋めていく
Out[45]:
     A    B    C
0  0.0  NaN  1.0
1  0.0  NaN  1.0
2  0.0  2.0  1.0
3  2.0  2.0  3.0
4  3.0  2.0  5.0
5  4.0  2.0  5.0

In [46]: df_method.fillna(method='bfill') # 直後の値を使って穴埋めをする
Out[46]:
     A    B    C
0  0.0  2.0  1.0
1  2.0  2.0  3.0
2  2.0  2.0  3.0
3  2.0  NaN  3.0
4  3.0  NaN  5.0
5  4.0  NaN  NaN

limit引数で値を指定することで連続した欠損値で何個まで穴埋めするかを指定できます。limit引数はmethodが指定されているときのみ有効となります。

ではmethod='ffill'limitを変更して見ます。

In [49]: df_method.fillna(method='ffill', limit=2) # 2個連続まで
Out[49]:
     A    B    C
0  0.0  NaN  1.0
1  0.0  NaN  1.0
2  0.0  2.0  1.0
3  2.0  2.0  3.0
4  3.0  2.0  5.0
5  4.0  NaN  5.0

In [50]: df_method.fillna(method='ffill', limit=1) # 1個連続まで
Out[50]:
     A    B    C
0  0.0  NaN  1.0
1  0.0  NaN  1.0
2  NaN  2.0  NaN
3  2.0  2.0  3.0
4  3.0  NaN  5.0
5  4.0  NaN  5.0

平均値や最頻値などで穴埋め

平均値や最頻値などで穴埋めすることもできます。列ごとの値を算出して穴埋めしてくれます。

In [51]: df
Out[51]:
     A    B    C
0  0.0  NaN  1.0
1  NaN  NaN  NaN
2  NaN  2.0  NaN
3  2.0  3.0  3.0
4  3.0  5.0  5.0
5  4.0  6.0  NaN

In [52]: df.fillna(df.mean()) # 平均値で穴埋め
Out[52]:
      A    B    C
0  0.00  4.0  1.0
1  2.25  4.0  3.0
2  2.25  2.0  3.0
3  2.00  3.0  3.0
4  3.00  5.0  5.0
5  4.00  6.0  3.0

In [53]: df.fillna(df.median()) # 中央値
Out[53]:
     A    B    C
0  0.0  4.0  1.0
1  2.5  4.0  3.0
2  2.5  2.0  3.0
3  2.0  3.0  3.0
4  3.0  5.0  5.0
5  4.0  6.0  3.0

In [54]: df.fillna(df.mode()) # 最頻値
Out[54]:
     A    B    C
0  0.0  2.0  1.0
1  2.0  3.0  3.0
2  3.0  2.0  5.0
3  2.0  3.0  3.0
4  3.0  5.0  5.0
5  4.0  6.0  3.0

個別に穴埋めする値を指定する

DataFrameを指定することで個別に穴埋めする値を指定することができます。

In [70]: fill_df = pd.DataFrame(np.arange(18).reshape(6,3),
    ...:                        columns=['A','B','C'])
    ...:                        

In [71]: df.fillna(fill_df)
Out[71]:
     A    B     C
0  0.0  1.0   1.0
1  3.0  4.0   5.0
2  6.0  2.0   8.0
3  2.0  3.0   3.0
4  3.0  5.0   5.0
5  4.0  6.0  17.0

まとめ

今回は欠損値を削除する方法と穴埋めする方法について解説しました。

欠損値を削除する場合はdropna、穴埋めする場合はfillna関数を使えば大抵の欠損値処理を行うことができます。

欠損値処理を確実にこなしておくと、次のデータ処理の段階でエラーが起きにくくなるのでしっかり把握しておくと良いでしょう。

参考