[解決!Python]NaN(非数)かどうかを判断したり、NaNの数をカウントしたりするには解決!Python

math.isnan関数やnumpy.isnan関数、pandas.DataFrame.isnaメソッド、numpy.sum関数、pandas.DataFrame.sumメソッドなどを使って、非数かどうかを判断したり、多次元配列やDataFrameに含まれるNaNの数をカウントしたりする方法を紹介する。

» 2024年04月16日 05時00分 公開
[かわさきしんじDeep Insider編集部]
「解決!Python」のインデックス

連載目次

# 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かどうかを調べる方法

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を含む四則演算の結果はNaNになる(ただし、NaNの0乗「NaN0」の結果は1.0)
  • NaNを含む比較演算の結果は「NaN != NaN」を除き全てFalse(「NaN != NaN」の結果はTrue)

 以下に例を示す。

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 != 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判定を行うが、ここでは例は省略する。

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を渡せばよい(例は省略)。

「解決!Python」のインデックス

解決!Python

Copyright© Digital Advantage Corp. All Rights Reserved.

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

注目のテーマ

AI for エンジニアリング
「サプライチェーン攻撃」対策
1P情シスのための脆弱性管理/対策の現実解
OSSのサプライチェーン管理、取るべきアクションとは
Microsoft & Windows最前線2024
システム開発ノウハウ 【発注ナビ】PR
あなたにおすすめの記事PR

RSSについて

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

メールマガジン登録

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