パハットノート

※当サイトではGoogleアドセンス広告を利用しています

Pandas 欠損値の補完(fillna、pivot_table)





はじめに

機械学習を行うためにはデータセットが必要不可欠です。
しかし、場合によっては与えられたデータの中に一部欠損が場合もあります。
そのため、その欠損値を補完する方法をまとめました。



基本編

0. 使用するデータについて

実際に配布されているデータセットを用いて説明をしようとしたのですが、irisデータセットは欠損がなく、Kaggleのtitanicは登録しなければダウンロードできませんでした。
そこで、今回は私が作成したこちらのCSVファイルをデータとして使用しました。

名前,攻撃力,MP,属性
勇者,60,60,炎
魔法使い,20,400,水
ナイト,90,20,炎
ドクター,10,,
忍者,,60,炎
遊び人,,10,光



1. データの表示

まず、先ほどのCSVファイルを読み込んで表示します。

import pandas as pd

df = pd.read_csv('sample.csv')

df



実行結果は以下の様になり、

  • 「忍者」と「遊び人」の「攻撃力」
  • 「ドクター」の「MP」と「属性」

にデータが入っていないことが確認できます。



2. 平均値での補完

  • 「遊び人」の「攻撃力」を補完していきます。
  • 欠損値の補完はPandasに搭載されている、fillna()を使用するのが一般的です。
  • 今回は、遊び人の攻撃力 = 他のデータの攻撃力の平均値とします。
#攻撃力の平均値を取得
attackMean = df['攻撃力'].mean()

#補完した値を表示
df['攻撃力'].fillna(attackMean)



さて、この状態でdfを表示すると次のようになります。
「遊び人」の「攻撃力」が空のままです。何故こうなっているかというと、先ほどの状態ではdfの攻撃力の列をコピーしてから補完しているので、dfそのものは変更されていないからです。


変更を元のdfに反映させるためには、引数のinPlaceをTrueにすればいいです(デフォルトではFalseになっている)。

#攻撃力の平均値を取得
attackMean = df['攻撃力'].mean()

#補完
df['攻撃力'].fillna(attackMean, inplace=True)

df



ちなみに、元のdfを書き換えたくなければ、以下の様に一度コピーしてから、それを書き換えるようにすればいいです(結果は先ほどと同様なので省略)。

#コピー
df2 = df.copy()

#攻撃力の平均値を取得
attackMean = df2['攻撃力'].mean()

#補完
df2['攻撃力'].fillna(attackMean, inplace=True)

#表示
df2



3. 中央値での補完

次に「ドクター」のMPを補完しています。
他のデータではMPが100未満なのに、魔法使いだけ400と以上に高いため、仮に平均値で補完すると、ドクターのMPは100程度と高い値になってしまいます。このような飛び抜けた価は外れ値と言います。
そこで、今回は中央値を使って補完します。中央値での補完は外れ値がある場合に効果的です。


#MPの中央値を取得
mpMedian = df['MP'].median()

#補完した
df['MP'].fillna(mpMedian, inplace=True)

df




4. 最頻値での補完

  • 最後に「ドクター」の「属性」を補完します。
  • 属性については、数値でないので今までのように平均値や中央値での補完ができません。
  • そこで最も多く出ている属性で補完するため、最頻値を使います。
#属性の最頻値を取得
elementMode = df['属性'].mode()[0]

#補完した
df['属性'].fillna(elementMode, inplace=True)

df






発展(ピボットテーブルによる補完)

0. 使用するデータセット

ここでは、KaggleのTitanicのデータセットのtrain.csvを使用します。


1. データの読み込みと表示

import pandas as pd

train = pd.read_csv('train.csv')

train.head(3)


性別(Sex)、Pclass(階級)など様々な項目があることが分かります。また、index = 5のAgeがNaNと欠損していることが確認できます。



2. 欠損値の確認

train.isnull().sum()


  • Ageの欠損しているデータ数が多いのでこれの補完を考えます。
  • 全データの最頻値で補完するのもありですが、より正確に補完するためにはデータ群ごとに別の値で埋めるというのが有効です。
  • 今回は、性別(Sex)、Pclass(階級)の2つを使って補完を考えていきます。
  • そのためには、「Pclass=0, Sex=maleにおける平均値はxである」といったことを把握する必要があります。この時に便利なのがピボットテーブルです。



3. ピボットテーブルの作成

Pandas内にはピボットテーブルを作成するためにpivot_tableという関数が用意されています。この関数における引数を次の表に示します。

第1引数 データ
index 探索したい列名1
columns 探索したい列名2
values 表示したい列名
aggFunc valuesの何を表示するかを関数で指定。
例では、平均値の表示のために「np.mean」としているが、最大値を表示したいなら「np.max」とする。



import numpy as np

#Sexがindex(行名)、Pclassがcolumns(列名)として、テーブルを作成
pt = pd.pivot_table(train, index='Sex', columns='Pclass', values='Age', aggfunc=np.mean)

pt


表示すると次のようなDataFrameが作成されたことが確認できます。
男女ともにPclassの数値が小さいほど平均年齢は高いです。また、同じPclassなら女性より男性の方が平均年齢が高いことも確認できます。



4. 作成したピボットテーブルを使った補完

#trainを一度コピー
df = train.copy()

for i in pt.index:
    for c in pt.columns:
        #(1)Ageが欠損している、(2)Sex = i、(3)Pclass = c のすべてを満たす物に代入を行う
        df.loc[(df['Age'].isnull()) & (df['Sex'] == i) & (df['Pclass'] == c), 'Age'] = pt.loc[i, c]

df.head(6)


表示結果は以下の様になり、index = 5のAgeがNaNだったのが、Pclass = 3, Sex = maleなので、26.50759が代入されたことが分かります。