[pandas超入門]欠損値とその処理Pythonデータ処理入門

Pandasでデータを処理する際には避けては通れない欠損値。その概要と欠損値かどうかの判定方法、欠損値が行や列に含まれているかを確認する方法、それら数をカウントする方法、欠損値を含む行や列を削除したり置き換えたりする方法を紹介します。

» 2024年08月23日 05時00分 公開
[かわさきしんじDeep Insider編集部]
「Pythonデータ処理入門」のインデックス

連載目次

本シリーズと本連載について

 本シリーズ「Pythonデータ処理入門」は、Pythonの基礎をマスターした人を対象に以下のような、Pythonを使ってデータを処理しようというときに便利に使えるツールやライブラリ、フレームワークの使い方の基礎を説明するものです。

  • NumPy(「NumPy超入門」の目次はこちら
  • pandas(本連載)
  • Matplotlib

 なお、本連載では以下のバージョンを使用しています。

  • Python 3.12
  • pandas 2.2.1

 前回はpandasのDataFrameオブジェクトをNumPyの多次元配列やPythonのリスト、辞書、JSON形式のオブジェクトに変換する方法を紹介しました。今回は、pandasでデータを処理する際には必ずと言っていいほど目にする「欠損値」とその扱いについて見ていきます。

欠損値とは

 欠損値とは、取得したデータに含まれる「何らかの理由で存在していないデータ」のことです。何かのデータを計測しているときに本来は得られるはずの値がセンサーの誤動作により1つだけ取得できなかったとか、データの伝送時にノイズが入ったためにデータがきちんと読み取れなかったとか、理由はいろいろと考えられますが、あるべきデータがないという状況はよくあり、データを処理する際にはそれらについても考慮する必要があります。

 ここでは簡単な例として、次のコードでCSVファイルを作成し、それをDataFrameオブジェクトに読み込んで、欠損値の基本的な扱いを見てみます。

l = [['0', '1', '2'], ['3', '', '5'], ['', '7', '8']]

with open('test.csv', 'w') as f:
    for row in l:
        f.write(','.join(row) + '\n')

with open('test.csv') as f:
    print(f.read())
# 出力結果:
#0,1,2
#3,,5
#,7,8

欠損値を含むCSVファイルの作成

 出力結果を見ると、第1行の第1列(カンマ「,」が連続している部分)と第2行の第0列(「,7」とあるカンマの左側)にはデータが存在していないようです。このCSVファイルをpandasのread_csv関数で読み込んでみるとどうなるでしょうか。

import pandas as pd

df = pd.read_csv('test.csv', header=None# ヘッダー行がないのでheader=Noneを指定
print(df)
# 出力結果:
#     0    1  2
#0  0.0  1.0  2
#1  3.0  NaN  5
#2  NaN  7.0  8

欠損値を含むCSVファイルからDataFrameオブジェクトを生成

 上で述べた通り、第1行の第1列と第2行の第0列には「NaN」と表示されました。「NaN」とは「Not a Number」(非数)を表す表記のことで、DataFrameオブジェクトの要素としてこれが現れた場合、一般的にはそこにはデータが存在していないことを示すマーカーとして機能します。ここで作成したDataFrameオブジェクトの概要をinfoメソッドで調べてみましょう。

df.info()
# 出力結果:
#<class 'pandas.core.frame.DataFrame'>
#RangeIndex: 3 entries, 0 to 2
#Data columns (total 3 columns):
# #   Column  Non-Null Count  Dtype 
#---  ------  --------------  ----- 
# 0   0       2 non-null      float64
# 1   1       2 non-null      float64
# 2   2       3 non-null      int64 
#dtypes: float64(2), int64(1)
#memory usage: 204.0 bytes

作成したDataFrameオブジェクトの概要

 上記の出力結果の「Non-Null Count」という部分(強調書体で表示)を見ると、NaN値を含んでいる第0列と第1列は非Null値の数が「2」になっています。このことからも、これらの列には欠損値が含まれていることが確認できます。

 ここでNaN値のデータ型を調べてみましょう。

print(type(df.iloc[1, 1]))  # <class 'numpy.float64'>

NaNはnumpy.float64型

 NumPyが提供するnumpy.float64型であることが分かりました。しかし、Pythonでは「float('nan')」などとすることでもNaN値を求められます。実際のところ、pandasでは以下を欠損値として扱えます。

  • float('nan')
  • numpy.nan
  • pandas.NA
  • pandas.NaT
  • None

 最初の2つについては既に述べた通りです。また、pandas.NAの「NA」は「Not Available」(使えない)を意味していると思われます。pandasが標準で提供しているという点では、これを欠損値として使うのが一番適切かもしれません。次のpandas.NaTの「NaT」は「Not a Time」つまり「時間や日付ではない」ことを意味し、日付や時刻を表すデータにおける欠損値として使います。最後にPythonに組み込みのNone値も欠損値として扱えます。

欠損値かどうかの判定

 実際に試してみましょう。pandasにはisna関数とisnull関数があります。これらの関数にスカラー値(単一の値)やSeriesオブジェクト、DataFrameオブジェクトを渡すと、その値または一次元配列や二次元配列の要素が欠損値かどうかを判断できます。

pandas.isna関数/pandas.isnull関数

pandas.isna(obj)
pandas.isnull(obj)

pandas.isna関数/pandas.isnull関数

 引数として与えたobj(スカラー値)またはobj(配列)の要素が欠損値であるかどうかを判定する。なお、pandas.isnull関数はpandas.isna関数の別名である(「pd.isna is pd.isnull」の結果がTrueとなる)。


 では、上で紹介した欠損値として扱えるオブジェクトと空文字列、文字列の'NaN'、それから偽値であるFalseをここではpandas.isna関数に渡してみましょう。

import numpy as np

print(pd.isna(float('nan')))  # True
print(pd.isna(np.nan))  # True
print(pd.isna(pd.NA))  # True
print(pd.isna(pd.NaT))  # True
print(pd.isna(None))  # True
print(pd.isna(''))  # False
print(pd.isna('NaN'))  # False
print(pd.isna(False))  # False

空文字列や文字列の'NaN'、偽値であるFalseは欠損値ではない

 すると、上で述べた値についてはisna関数の戻り値はTrueになりますが、NaN値を文字列として表現した'NaN'や、条件式では偽値として取り扱われる空文字列やFalseを渡したときにはその戻り値はFalseになりました。後者の3つは欠損値ではないことには注意してください(同様にfloat('inf')で生成できるinf値も欠損値としては扱われません)。

 なお、if文などの条件式で何かの値が欠損値であるかどうかを調べるときには==演算子などを使ってもうまく比較できません。

x = np.nan
if x == pd.NA:  # TypeError: boolean value of NA is ambiguous
    print('x == pd.NA')
else:
    print('x != pd.NA')


if x == np.nan:  # TypeError: boolean value of NA is ambiguous
    print('x == np.nan')
else:
    print('x != np.nan')
# 出力結果:
# x != np.nan

==演算子による比較

 最初の例ではTypeError例外が発生しています。次の例ではnp.nanとnp.nanを==演算子で比較しているのにelse節が実行されています。欠損値かどうかの判定にはpd.isna関数などを使うようにしましょう。

 isna関数とは逆の振る舞いをするpandas.notna関数とpandas.notnull関数もあります(後者は前者の別名)。

pandas.notna関数/pandas.notnull関数

pandas.notna(obj)
pandas.notnull(obj)

pandas.notna関数/pandas.notnull関数

 引数として与えたobj(スカラー値)またはobj(配列)の要素が欠損値でないかどうかを判定する。スカラー値が欠損値でなければTrueが返され、配列については欠損値に対応する要素の値がFalseで、それ以外の要素の値がTrueなDataFrameオブジェクトが返される。


 以下に使用例を示します。

print(df)
# 出力結果:
#     0    1  2
#0  0.0  1.0  2
#1  3.0  NaN  5
#2  NaN  7.0  8

result = pd.notna(df)
print(result)
# 出力結果:
#       0      1     2
#0   True   True  True
#1   True  False  True
#2  False   True  True

pandas.notna関数の使用例

 ここでは先ほど作成したDataFrameオブジェクトをpandas.notna関数に渡しています。そのため、NaN値が格納されている要素に対応する第1行第1列と第2行第0列の要素がFalse、それら以外の要素がTrueとなるDataFrameオブジェクトが返されました。

 ここまではpandasモジュールが提供するisna関数などを見てきましたが、DataFrameオブジェクト(およびSeriesオブジェクト)には同様な処理を行うisnaメソッド/isnullメソッドがあります(以下ではDataFrameオブジェクトのメソッドを紹介します)。

DataFrame.isnaメソッド/DataFrame.isnullメソッド

pandas.DataFrame.isna()
pandas.DataFrame.isnull()

pandas.DataFrame.isnaメソッド/pandas.DataFrame.isnullメソッド

 メソッドの呼び出しに使用したDataFrameオブジェクトの要素と同じ形状のDataFrameオブジェクトを返す。ただし、その要素の値は全てブーリアン値であり、元のDataFrameオブジェクトの要素のうち欠損値と判定されたものに対応する要素の値はTrue、それ以外の要素の値はFalseとなる。pandas.DataFrame.isnullメソッドはpandas.DataFrame.isnaメソッドの別名。


 以下に使用例を示します。先ほどと同様、最初に作成したDataFrameオブジェクトを使って、欠損値があるかどうかを調べてみましょう。

print(df)
# 出力結果:
#     0    1  2
#0  0.0  1.0  2
#1  3.0  NaN  5
#2  NaN  7.0  8

result = df.isna()
print(result)
# 出力結果:
#       0      1      2
#0  False  False  False
#1  False   True  False
#2   True  False  False

DataFrame.isnaメソッドの使用例

 ご覧の通り、NaN値が格納されている第1行第1列と第2行第0列の要素がTrueになり、それら以外はFalseとなりました。

 もちろん、pandas.notna関数やpandas.notnull関数に対応するpandas.DataFrame.notnaメソッドやpandas.DataFrame.notnullメソッドもあります。以下に例を示します。

result = df.notna()
print(result)
# 出力結果:
#       0      1     2
#0   True   True  True
#1   True  False  True
#2  False   True  True

pandas.DataFrame.notnaメソッドの使用例

行や列に欠損値が含まれているかどうかを確認

 DataFrameオブジェクトにはallメソッドとanyメソッドがあります。これらはPythonに組み込みのall関数やany関数と似た振る舞いをします。

print(all([0, 1, 2]))  # False
print(any([0, 1, 2]))  # True

組み込みのall関数とany関数

 all関数は与えられた反復可能オブジェクトの要素が全て真ならTrueを、そうでなければFalseを返し、any関数は与えられた反復可能オブジェクトの要素が1つでも真ならTrueを、そうでなければFalseを返します。allメソッドとanyメソッドはこれをDataFrameオブジェクト(2次元の配列)に拡張したものだと考えられます。よって、呼び出し時には行方向に見るか列方向に見るか、その軸を指定する必要がある点には注意が必要です(デフォルトでは各列の要素が全て真かどうか、あるいはいずれかが真かどうかを判定します)。

pandas.DataFrame.allメソッド/pandas.DataFrame.anyメソッド

pandas.DataFrame.all(*, axis=0)
pandas.DataFrame.any(*, axis=0)

pandas.DataFrame.allメソッド/pandas.DataFrame.anyメソッドの構文(一部)

 allメソッドは、指定された軸に沿って、その列または行の要素が全て真であればTrueを、そうでなければFalseを格納するSeriesオブジェクトを返す。anyメソッドは、指定された軸に沿って、その列または行のいずれかの要素が真であればTrueを、全ての要素がFalseであればFalseを格納するSeriesオブジェクトを返す。以下ではaxisパラメーターだけを紹介する。全パラメーターについてはpandasのドキュメント「pandas.DataFrame.all」および「pandas.DataFrame.any」を参照のこと。

  • axis:列と行のどちらに沿って要素をチェックするかの指定。0を指定すると各列の要素が真かどうかを基に判定され、1を指定すると各行の要素が真かどうかを基に判定される。省略時のデフォルト値は0

 allメソッドの簡単な使い方の例を以下に示します。

test = pd.DataFrame([[0, 1, 2], [0, 4, 5], [6, 7, 8]])
test.columns = ['A', 'B', 'C']
test.index = ['X', 'Y', 'Z']

result = test.all()
print(result)
# 出力結果:
#A    False
#B     True
#C     True
#dtype: bool

result = test.all(axis=1)
print(result)
# 出力結果:
#X    False
#Y    False
#Z     True
#dtype: bool

DataFrame.allメソッドの使用例

 ここでは第0行('X'行)と第1行('Y'行)の第0列('A'列)が0となっている(その他の要素は0以外の)DataFrameオブジェクトを作成しています。そして、それに対してallメソッドを呼び出しています。最初の「test.all()」では軸を指定していないので列方向に各要素を調べるので、第0列('A'列)については全ての要素が真とはいえません(Pythonでは0は偽)。よって、戻り値であるSeriesオブジェクトでは、この列に関していえば結果はFalseで、他の列については結果はTrueになります。axis=1を指定した場合は同様なことを行方向に調べます。そのため、第0行('X'行)と第1行('Y'行)についての結果を格納する要素の値はFalseに、第2行('Z'行)に対応する要素の値はTrueになります。

 anyメソッドも同様なので、ここでは例は省略します。

 ここで重要なのは、上で見たisna/isnull/notna/notnullなどの関数/メソッドは真偽値を含むDataFrameオブジェクトを返すことです。つまり、これらの関数/メソッドの戻り値に対してallメソッドを使えば、特定の行や列に欠損値が含まれているかどうかを確認できるということです。

df.columns = ['col0', 'col1', 'col2']
df.index = ['row0', 'row1', 'row2']
print(df)
# 出力結果:
#      col0  col1  col2
#row0   0.0   1.0     2
#row1   3.0   NaN     5
#row2   NaN   7.0     8

test = df.notna()
print(test)
# 出力結果:
#       col0   col1  col2
#row0   True   True  True
#row1   True  False  True
#row2  False   True  True

result = test.all()  # result = df.notna().all()
print(result)
# 出力結果:
#col0    False
#col1    False
#col2     True
#dtype: bool

欠損値を含んでいない列に対応する要素がTrueとなるSeriesオブジェクトを作成

 この例ではDataFrame.notnaメソッドを呼び出して、欠損値ではない要素についてはTrue、欠損値である要素についてはFalseとなるようなDataFrameオブジェクトを得た後、allメソッドを呼び出しています。結果、欠損値を全く含んでいない'col2'列に対応する要素がTrueとなるSeriesオブジェクトが得られました。

 逆に欠損値を含んでいる列に対応する要素がTrueとなるようなSeriesオブジェクトが欲しければ次のようなコードになるでしょう。

test = df.isna()
print(test)
# 出力結果:
#       col0   col1   col2
#row0  False  False  False
#row1  False   True  False
#row2   True  False  False

result = test.any()  # result = df.isna().any()
print(result)
# 出力結果:
#col0     True
#col1     True
#col2    False
#dtype: bool

欠損値を含んでいる列に対応する要素がTrueとなるSeriesオブジェクトを作成

欠損値を含む行/列のカウント

 上で見たように、allメソッドやanyメソッドは行や列に欠損値があるかどうかを調べるのに使えますが、その数を知りたいときにはDataFrameオブジェクトのsumメソッドを使えます。

pandas.DataFrame.sumメソッド

pandas.DataFrame.sum(axis=0)

pandas.DataFrame.sumメソッド

 指定された軸に沿って、行または列の全要素の値を合計した結果を格納するSeriesオブジェクトを返す。以下ではaxisパラメーターのみを紹介する。全てのパラメーターについてはpandasのドキュメント「pandas.DataFrame.sum」を参照のこと。

  • axis:値を合計する軸を指定する。0を指定すると各列の値が合計され、1を指定する各行の値が合計される。省略時のデフォルト値は0

 以下に使用例を示します。

test = pd.DataFrame([[0, 1, 2], [3, 4, 5], [6, 7, 8]])
test.columns = ['A', 'B', 'C']
test.index = ['X', 'Y', 'Z']

result = test.sum()
print(result)
# 出力結果:
#A     9
#B    12
#C    15
#dtype: int64

sumメソッドの使用例

 ここでは3行3列のDataFrameオブジェクトを作成し、各列の要素を合計した値を要素とするSeriesオブジェクトを求めています。

 そして、Pythonにおける真偽値が0と1として扱えることを使い、isnaなどのメソッドで元のDataFrameオブジェクトの要素の値を基に真偽値を格納するDataFrameオブジェクトを作成して、sumメソッドを呼び出すことで、欠損値がある行や列の数をカウントします。以下はその例です。

print(df)
# 出力結果:
#      col0  col1  col2
#row0   0.0   1.0     2
#row1   3.0   NaN     5
#row2   NaN   7.0     8

result = df.isna()
print(result)
# 出力結果:
#       col0   col1   col2
#row0  False  False  False
#row1  False   True  False
#row2   True  False  False

cnt = result.sum()  # cnt = df.isna().sum()
print(cnt)
# 出力結果:
#col0    1
#col1    1
#col2    0
#dtype: int64

sumメソッドを使って欠損値を含む行数をカウント

 ここで使用しているDataFrameオブジェクトは第0行と第1列にNaN値が含まれています(最初の出力結果を参照)。このDataFrameオブジェクトでisnaメソッドを呼び出すと、対応する要素がTrueとなるDataFrameオブジェクトが得られます。後はこれに対してsumメソッドを呼び出すだけです。ここでは軸の指定を省略しているので列方向に合計が計算され、その結果、第0列('col0'列)と第1列('col1'列)の合計が1となります。これが意味するのは、これらの列には欠損値を含む行が1つずつ存在するということです。

 もちろん、これは最初の方で見たinfoメソッドでも調べられます。が、infoメソッドはコンソールやノートブックに概要を出力するためのものです。得られた行数や列数を使って何らかの条件分岐や処理を行いたいのであれば、こちらのやり方が便利かもしれませんね。

欠損値を含む行/列を削除する

 ここまでは欠損値があるかどうかの判定や、その行数や列数をカウントする方法などについて見てきました。しかし、実際に欠損値がDataFrameオブジェクトに含まれていたとしたらどうすればよいのでしょう。

 実は欠損値といってもMCAR(Missing Completely At Random)やMAR(Missing At Random)、MNAR(Missing Not At Random)のように幾つかの種類があります。ただし、これらの種類については、実際にデータ処理を行ってみる段階でまたお話しすることにしましょう。いずれにせよ、pandasで欠損値を含む行や列を扱うときには次のような方法を採ることになるでしょう。

  • 欠損値を含む行または列を削除してしまう
  • 欠損値を何らかの値で置き換える

 欠損値を含む行や列(主に行でしょう)を削除するのに使えるのがDataFrameオブジェクトのdropnaメソッドです。

pandas.DataFrame.dropnaメソッド

pandas.DataFrame.dropna(*, axis=0, how='any', ignore_index=False)

pandas.DataFrame.dropnaメソッドの書式(一部)

 欠損値を(含む行や列を)削除する。以下では一部のパラメーターだけを紹介する。全てのパラメーターについてはpandasのドキュメント「pandas.DataFrame.dropna」を参照のこと。

  • axis:欠損値がある要素を行と列のどちらを削除するかの指定。0を指定すると行を、1を指定すると列を削除する。省略時のデフォルト値は0
  • how:指定された行または列の全ての要素がNaNのときにそれを削除するか('all')、それとも1つでもNaNのときにそれを削除するか('any')の指定。省略時は'any'が指定されたものと見なされる
  • ignore_index:行インデックスを振り直すかどうかの指定。Trueを指定すると振り直す。Falseを指定すると振り直さない。省略時のデフォルト値はFalse

 ここではサンプルとして以下のようなDataFrameオブジェクトを作成しましょう。

kawasaki = {'name': 'kawasaki', 'age': 98, 'height': 162, 'weight': np.nan}
isshiki = {'name': 'isshiki', 'age': 45, 'height': 168, 'weight': 65}
endo = {'name': 'endo', 'age': 54, 'height': 175, 'weight': 70}
ogawa = {'name': 'ogawa', 'age': np.nan, 'height': 180, 'weight': 75}

df = pd.DataFrame([kawasaki, isshiki, endo, ogawa])
print(df)
# 出力結果:
#       name   age  height  weight
#0  kawasaki  98.0     162     NaN
#1   isshiki  45.0     168    65.0
#2      endo  54.0     175    70.0
#3     ogawa   NaN     180    75.0

サンプルのDataFrameオブジェクト

 簡単なdropnaメソッドの使用例を以下に示します。

result = df.dropna()
print(result)
# 出力結果:
#      name   age  height  weight
#1  isshiki  45.0     168    65.0
#2     endo  54.0     175    70.0

result = df.dropna(axis=1)
print(result)
# 出力結果:
#       name  height
#0  kawasaki     162
#1   isshiki     168
#2      endo     175
#3     ogawa     180

dropnaメソッドの使用例

 最初の例では軸を指定していません。そのため、欠損値を含む2つの行(インデックス0と3)が削除されました。次の例ではaxisパラメーターに1を指定しているので、今度は欠損値を含む2つの列(列ラベルが'age'と'weight')が削除されています。

 上記のデータは列には1件のデータが含むさまざまなデータが並べられ、行が1件のデータを表すようになっています。このようなときに、列を削除することはあまりないでしょう(行と列が入れ替わっている場合には、axisパラメーターの指定が必要になるでしょう)。

 howパラメーターには行または列の全ての要素が欠損値であるときに削除するか、それともどれか1つでも欠損があれば削除するか('any')を指定します。省略すると'any'が指定されたものと見なされます。以下に例を示します。

tmp_df = pd.DataFrame({'name': np.nan, 'age': np.nan,
                       'height': np.nan, 'weight': np.nan}, index=[4])
tmp = pd.concat([df, tmp_df])
print(tmp)
# 出力結果:
#       name   age  height  weight
#0  kawasaki  98.0   162.0     NaN
#1   isshiki  45.0   168.0    65.0
#2      endo  54.0   175.0    70.0
#3     ogawa   NaN   180.0    75.0
#4       NaN   NaN     NaN     NaN

result = tmp.dropna(how='all')
print(result)
# 出力結果:
#       name   age  height  weight
#0  kawasaki  98.0   162.0     NaN
#1   isshiki  45.0   168.0    65.0
#2      endo  54.0   175.0    70.0
#3     ogawa   NaN   180.0    75.0

result = tmp.dropna(how='any')
print(result)
# 出力結果:
#      name   age  height  weight
#1  isshiki  45.0   168.0    65.0
#2     endo  54.0   175.0    70.0

dropnaメソッドでhowパラメーターを指定する例

 ここではhowパラメーターに'all'を指定したときの振る舞いを確認するために、全ての値が欠損している行を元のDataFrameオブジェクトに追加したものを一時的に使用しています。「result = tmp.dropna(how='all')」行を実行することで、その行が削除されたことを確認してください。また、「how='any'」を指定した場合は、このパラメーターを指定しなかったときと同じ結果になっています。

 最後のignore_indexパラメーターにTrueを指定した場合の例は省略します(予想通りの結果になるはずです)。

欠損値を別の値に置き換える

 上で見たdropnaメソッドは欠損値を含む行や列を削除するものでした。欠損値を別の値で補完するには、DataFrameオブジェクトのfillnaメソッドを使います。

pandas.DataFrame.fillnaメソッド

pandas.DataFrame.fillna(value=None)

pandas.DataFrame.fillnaメソッドの書式(一部)

 欠損値を別の値に置き換える。以下ではvalueパラメーターのみを紹介する。全パラメーターについてはpandasのドキュメント「pandas.DataFrame.fillna」を参照のこと。

  • value:置き換えに使う値を指定する。スカラー値を指定するのが一般的だが、DataFrameオブジェクトを指定したり、辞書を指定したりすることも可能。省略時のデフォルト値はNone

 ここでも先ほどと同様なDataFrameオブジェクトを作成して、それを使ってfillnaメソッドを試してみましょう。

kawasaki = {'name': 'kawasaki', 'age': 98, 'height': 162, 'weight': np.nan}
isshiki = {'name': 'isshiki', 'age': 45, 'height': 168, 'weight': 65}
endo = {'name': 'endo', 'age': 54, 'height': 175, 'weight': 70}
ogawa = {'name': 'ogawa', 'age': np.nan, 'height': 180, 'weight': 75}

df = pd.DataFrame([kawasaki, isshiki, endo, ogawa])
print(df)
# 出力結果:
#       name   age  height  weight
#0  kawasaki  98.0     162     NaN
#1   isshiki  45.0     168    65.0
#2      endo  54.0     175    70.0
#3     ogawa   NaN     180    75.0

サンプルのDataFrameオブジェクト

 まずはvalueパラメーターにスカラー値を指定する例です。

result = df.fillna(-1)
print(result)
# 出力結果:
#       name   age  height  weight
#0  kawasaki  98.0     162    -1.0
#1   isshiki  45.0     168    65.0
#2      endo  54.0     175    70.0
#3     ogawa  -1.0     180    75.0

欠損値を-1で置き換えた

 valueパラメーターに-1を指定したので、2つの欠損値がその値に置き換えられています(浮動小数点数値に変換されている点にも注意)。ただし、これは欠損値があったことのマーカーとしては機能するでしょうが、実際にデータを分析する際には明らかに場違いな値になりそうです。

 そこで、 'age'列についてはその列の全要素の平均値を、'weight'列についても全要素の平均値を計算して、それらを使って置き換えてみましょう。このとき、valueパラメーターには「{'age': xxx, 'weight': yyy}」のように置き換える列のラベルと置き換え後の値を辞書として渡すのが簡単です。

age_mean = df['age'].mean()
print(age_mean)  # 65.66666666666667
weight_mean = df['weight'].mean()
print(weight_mean)  # 70.0

result = df.fillna({'age': age_mean, 'weight': weight_mean})
print(result)
# 出力結果:
#       name        age  height  weight
#0  kawasaki  98.000000     162    70.0
#1   isshiki  45.000000     168    65.0
#2      endo  54.000000     175    70.0
#3     ogawa  65.666667     180    75.0

各列の平均値で欠損値を置き換え

 それらしいデータになったように見えます。が、これはあくまでも平均値で穴埋めをしただけで、実際のデータとは乖離(かいり)している可能性もあります。この点には注意が必要です。もっとよい置き換え方法があれば、それを検討すべきでしょう。

 最後にDataFrameオブジェクトをvalueパラメーターに渡す例です。

shape = df.shape
tmp = pd.DataFrame(np.zeros(shape), columns=df.columns)
print(tmp)
# 出力結果:
#   name  age  height  weight
#0   0.0  0.0     0.0     0.0
#1   0.0  0.0     0.0     0.0
#2   0.0  0.0     0.0     0.0
#3   0.0  0.0     0.0     0.0

result = df.fillna(tmp)
print(result)
# 出力結果:
#       name   age  height  weight
#0  kawasaki  98.0     162     0.0
#1   isshiki  45.0     168    65.0
#2      endo  54.0     175    70.0
#3     ogawa   0.0     180    75.0

tmp = pd.DataFrame(np.zeros((4, 3)), columns=['name', 'age', 'height'])
result = df.fillna(tmp)
print(result)
# 出力結果:
#       name   age  height  weight
#0  kawasaki  98.0     162     NaN
#1   isshiki  45.0     168    65.0
#2      endo  54.0     175    70.0
#3     ogawa   0.0     180    75.0

DataFrameオブジェクトで欠損値を置き換える

 最初にnumpy.zeros関数を使って、元のDataFrameオブジェクトと同じ形状の多次元配列(要素の値は全て0.0)を作成して、それを基に置き換え後の値を含んだDataFrameオブジェクトを作成しています(列ラベルも基と同じものにしている点に注意)。

 そして、それをfillnaメソッドに渡してやれば、欠損値があれば、それに対応する要素の値で置き換えられます。なお、最後の例ではわざと1列少ないDataFrameオブジェクトを作成して、それをfillnaメソッドに渡しています。このときには、欠損値があっても、対応する要素が置き換え後の値を格納しているDataFrameオブジェクトにないので置き換えが行われません。


 少し長くなりましたが、今回は欠損値の概要とDataFrameオブジェクトに欠損値が含まれているかどうかを確認したり、その数をカウントしたり、欠損値を含む行や列を削除したり、欠損値を別の値で置き換えたりする方法を見てきました。次回からはもうちょっと高度な話題に触れていく予定です。

「Pythonデータ処理入門」のインデックス

Pythonデータ処理入門

Copyright© Digital Advantage Corp. All Rights Reserved.

スポンサーからのお知らせPR

注目のテーマ

Microsoft & Windows最前線2025
AI for エンジニアリング
ローコード/ノーコード セントラル by @IT - ITエンジニアがビジネスの中心で活躍する組織へ
Cloud Native Central by @IT - スケーラブルな能力を組織に
システム開発ノウハウ 【発注ナビ】PR
あなたにおすすめの記事PR

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。