math.isnan関数やnumpy.isnan関数、pandas.DataFrame.isnaメソッド、numpy.sum関数、pandas.DataFrame.sumメソッドなどを使って、非数かどうかを判断したり、多次元配列やDataFrameに含まれるNaNの数をカウントしたりする方法を紹介する。
# NaNは浮動小数点数値の演算結果を適切な数値として表現できないことを意味する
inf = float('inf')
print(inf * 0) # nan
print(inf - inf) # nan
print(inf / inf) # nan
print(inf + inf) # inf
# NaNはfloat('nan')やfloat('NaN')、numpy.nanで得られる
nan = float('nan')
print(nan) # nan
import numpy as np
print(np.nan) # nan
# データが欠損している箇所をNaNとして表現
from pathlib import Path
print(Path('test.csv').read_text())
# 出力結果:
#0,,2
#3,4,
a = np.genfromtxt('test.csv', delimiter=',')
print(a)
# 出力結果:
#[[ 0. nan 2.]
# [ 3. 4. nan]]
# NaNと数値の四則演算の結果は常にNaN
nan = float('NaN')
result = 1 + nan
print(result) # nan
result = nan ** 0
print(result) # 1
# NaNとNaNの比較演算では、「nan != nan」を除き、その結果は常にFalse
print(nan == nan) # False
print(nan > nan) # False
print(nan < nan) # False
print(nan != nan) # True
# 変数の値がNaNかどうかを調べるのに比較演算子は使えない
nan = float('NaN')
x = float('nan')
if x == nan:
print(f'{x} is not a number')
else:
print(f'{x} is a number')
# 出力結果:
#nan is a number
# mathモジュールのisnan関数を使う
from math import isnan
x = float('nan')
if isnan(x):
print(f'{x} is not a number')
else:
print(f'{x} is a number')
# 出力結果:
#nan is not a number
# math.isnanはコンテナオブジェクトには使えない
import numpy as np
a = np.array([[0.0, nan, 2.0], [3.0, 4.0, nan]])
print(a)
# 出力結果:
#[[ 0. nan 2.]
# [ 3. 4. nan]]
result = isnan(a) # TypeError
# numpy.isnan関数を使う
result = np.isnan(a) # OK
print(result)
# 出力結果:
#[[False True False]
# [False False True]]
result = isnan(np.nan)
print(result) # True
nan = float('nan')
result = np.isnan(nan)
print(result) # True
# pandas.DataFrame.isnaメソッドを使う
import pandas as pd
df = pd.DataFrame(a, columns=['col0', 'col1', 'col2'])
print(df)
# 出力結果:
# col0 col1 col2
#0 0.0 NaN 2.0
#1 3.0 4.0 NaN
result = df.isna()
print(result)
# 出力結果:
# col0 col1 col2
#0 False True False
#1 False False True
# NaNが幾つあるかを調べる
a = np.array([[0.0, nan, 2.0], [3.0, 4.0, nan]])
result = np.isnan(a) # OK
count = result.sum()
print(count) # 2(NaNの総数)
count = result.sum(axis=0)
print(count) # [0 1 1](列ごとのNaNの総数)
count = result.sum(axis=1)
print(count) # [1 1](行ごとのNaNの総数)
df = pd.DataFrame(a, columns=['col0', 'col1', 'col2'])
tmp = df.isna()
result = tmp.sum() # df.isna().sum()
print(result)
# 出力結果:
#col0 0
#col1 1
#col2 1
#dtype: int64
方法 | 説明 |
---|---|
mathモジュールのisnan関数 | Pythonに標準で付属。単一の値のみを対象とする |
NumPyのisnan関数 | 変数や多次元配列、リスト、DataFrameなどの要素にNaNがあるかどうかをまとめてチェックできる |
pandasのDataFrameやSeriesのisnaメソッド(isnullメソッド) | DataFrameやSeriesの要素にNaNがあるかどうかをまとめてチェックできる |
pandasのisna関数(isnull関数) | 変数やDataFrame、リスト、多次元配列などの要素にNaNがあるかどうかをまとめてチェックできる |
値がNaNかどうかを調べる方法 |
PythonやNumPyには非数(NaN:Not a Number)を表す値がある。NaNは浮動小数点数値の演算結果を適切な数値表現で表せないことを意味する。例えば、無限大を表すfloat('inf')に0を乗算した結果はNaNとなる。
inf = float('inf')
print(inf * 0) # nan
print(inf - inf) # nan
print(inf / inf) # nan
print(inf + inf) # inf
PythonではNaNはfloat関数に'nan'や'NaN'を渡すことで得られる。NumPyではnumpy.nanで非数を表す。
nan = float('nan')
print(nan) # nan
import numpy as np
print(np.nan) # nan
以下はデータを欠損しているCSVファイルからnumpy.genfromtxt関数を使ってデータを読み出すコード例だが、欠損している箇所が「nan」として表現されている点に注目されたい。このように数値演算の結果以外にもNaNが使われることがある。
from pathlib import Path
print(Path('test.csv').read_text())
# 出力結果:
#0,,2
#3,4,
a = np.genfromtxt('test.csv', delimiter=',')
print(a)
# 出力結果:
#[[ 0. nan 2.]
# [ 3. 4. nan]]
PythonではNaNを含む演算には次のような特徴がある。
以下に例を示す。
nan = float('NaN')
result = 1 + nan
print(result) # nan
result = nan ** 0
print(result) # 1
print(nan == nan) # False
print(nan > nan) # False
print(nan < nan) # False
print(nan != nan) # True
上で述べたようにNaNを含んだ比較演算では「NaN != NaN」以外の結果は全てFalseとなるので、ある変数が格納している値がNaNかどうかを調べるのに次のようなコードは書けない。
nan = float('NaN')
x = float('nan')
if x == nan:
print(f'{x} is not a number')
else:
print(f'{x} is a number')
# 出力結果:
#nan is a number
この例では変数xの値がNaNかどうかを==演算子でチェックしているが、NaNと他の値の比較結果はFalseとなるので、変数xの値がNaNであってもelse節が実行されてしまう。
変数(配列、DataFrameの要素を含む)の値がNaNかどうかを調べるには、幾つかの方法がある(以下は冒頭の表の再掲)。
方法 | 説明 |
---|---|
mathモジュールのisnan関数 | Pythonに標準で付属。単一の値のみを対象とする |
NumPyのisnan関数 | 変数や多次元配列、リスト、DataFrameなどの要素にNaNがあるかどうかをまとめてチェックできる |
pandasのDataFrameやSeriesのisnaメソッド(isnullメソッド) | DataFrameやSeriesの要素にNaNがあるかどうかをまとめてチェックできる |
pandasのisna関数(isnull関数) | 変数やDataFrame、リスト、多次元配列などの要素にNaNがあるかどうかをまとめてチェックできる |
値がNaNかどうかを調べる方法(再掲) |
以下ではこれらの方法の幾つかを見ていこう。
簡単なのはPythonに標準で付属のmathモジュールが提供するisnan関数を使うことだ。以下に例を示す。
from math import isnan
x = float('nan')
if isnan(x):
print(f'{x} is not a number')
else:
print(f'{x} is a number')
# 出力結果:
#nan is not a number
この例では変数xの値がNaNかどうかをmath.isnan関数でチェックしているが、先ほどの例とは異なり、ちゃんと値がNaNであると判断できている。
ただし、isnan関数は単一の値のみを受け取るので、リストやNumPyの多次元配列、pandasのDataFrameのような複数の要素を格納するオブジェクトに対しては使えない。
import numpy as np
a = np.array([[0.0, nan, 2.0], [3.0, 4.0, nan]])
print(a)
# 出力結果:
#[[ 0. nan 2.]
# [ 3. 4. nan]]
result = isnan(a) # TypeError
ここではNumPyの多次元配列を作成し、それをmath.isnan関数に渡しているがTypeError例外が発生している。
このようなときに使えるのが、NumPyのisnan関数だ。numpy.isnan関数は単一の値やリスト、多次元配列などを受け取り、単一の値であればそれがNaNかどうかを調べてTrueかFalseを返す。複数の値を格納するオブジェクトであれば、そこにNaNが含まれているかどうかをまとめて調べて、元のオブジェクトと同じ形状でNaNの箇所にはTrueを、それ以外の箇所にはFalseを格納したオブジェクトを返す。
以下に例を示す。
result = np.isnan(a) # OK
print(result)
# 出力結果:
#[[False True False]
# [False False True]]
先の例で作成した多次元配列では、第0行の第1列と、第1行の第2列にNaNが格納されていた。そのため、numpy.isnan関数にそれを渡すと、その位置だけがTrueとなる2次元配列が返送される。
math.isnan関数とnumpy.isnan関数に単一の値を渡したときの挙動を以下に示す。
result = isnan(np.nan)
print(result) # True
nan = float('nan')
result = np.isnan(nan)
print(result) # True
math.isnan関数にはnumpy.nanを、numpy.isnan関数にはfloat('nan')を渡しているが、どちらも正しくTrueと判定されている点に注目しよう。
pandasのDataFrameが持つisnaメソッドを使うと、同様にDataFrameに含まれるNaNをまとめてチェックできる。以下に例を示す(pandas.DataFrame.isnullメソッドはisnaメソッドの別名)。
import pandas as pd
df = pd.DataFrame(a, columns=['col0', 'col1', 'col2'])
print(df)
# 出力結果:
# col0 col1 col2
#0 0.0 NaN 2.0
#1 3.0 4.0 NaN
result = df.isna()
print(result)
# 出力結果:
# col0 col1 col2
#0 False True False
#1 False False True
この例では、先ほどの例で作成した多次元配列aを基にDataFrameを作成し(列名を明示して作成)、そのisnaメソッドを呼び出している。その結果はnumpy.isnan関数のものと同様に、元のDataFrameと同じ形状でNaNの箇所がTrue、他の箇所がFalseとなるDataFrameである。
pandas.isna関数(pandas.isnull関数)はnumpy.isnan関数と同様に、単一の値やリスト、多次元配列、DataFrameなどを受け取り、それらのNaN判定を行うが、ここでは例は省略する。
NumPyの例では以下のようにして多次元配列を作成して、numpy.isnan関数に渡していた。
a = np.array([[0.0, nan, 2.0], [3.0, 4.0, nan]])
result = np.isnan(a) # OK
この多次元配列に幾つのNaNが含まれているかを調べるにはnumpy.sum関数を使える。PythonではTrueは1と、Falseは0と解釈されることを利用して、Trueとなる要素が幾つあるかを計算している。
単純には次のように呼び出す。
count = result.sum()
print(count) # 2(NaNの総数)
そうではなく、列ごとにNaNの値を知りたければ、axisパラメーターに0を指定する。
count = result.sum(axis=0)
print(count) # [0 1 1](列ごとのNaNの総数)
すると、列ごとに和が計算され、上の例では[0 1 1]という結果になる。行ごとにNaNの数をカウントしたければ、axisパラメーターに1を指定する。
count = result.sum(axis=1)
print(count) # [1 1](行ごとのNaNの総数)
この場合は、各行に1つのNaNが含まれていたので[1 1]となる。
pandasのDataFrameにもsumメソッドがあり、numpy.sum関数と同様にNaNの数をカウントするのに使える。以下に例を示す。
df = pd.DataFrame(a, columns=['col0', 'col1', 'col2'])
tmp = df.isna()
result = tmp.sum() # df.isna().sum()
print(result)
# 出力結果:
#col0 0
#col1 1
#col2 1
#dtype: int64
この例ではaxisパラメーターに0を指定していないが列ごとに和が計算(NaNの数がカウント)されている。行ごとのNaNの数を知りたければ、axisパラメーターに1を渡せばよい(例は省略)。
Copyright© Digital Advantage Corp. All Rights Reserved.