PythonのNaN(非数)判定完全ガイド:math.isnan()と検出方法を徹底解説

NaN(非数)とは

NaN(Not a Number)は、数学的に定義されない演算の結果を表す特殊な浮動小数点値です。IEEE 754標準で定義され、「非数」や「数ではない」という意味を持ちます。

import math

# NaNの作成方法
nan1 = float('nan')
nan2 = math.nan
nan3 = 0.0 / 0.0  # ZeroDivisionErrorが発生する場合あり

print(f"float('nan'): {nan1}")
print(f"math.nan: {nan2}")
print(f"タイプ: {type(nan1)}")

NaNが生成される演算

基本的なNaN生成パターン

import math

# NaNが生成される典型的な演算
nan_operations = [
    float('nan'),           # 直接作成
    math.sqrt(-1),          # 負数の平方根
    math.log(-1),           # 負数の対数
    0.0 / 0.0,              # ゼロ除算(例外の場合もある)
    math.inf - math.inf,    # 無限大同士の減算
    math.inf / math.inf,    # 無限大同士の除算
    math.inf * 0,           # 無限大とゼロの乗算
]

print("NaNが生成される演算:")
for i, operation in enumerate(nan_operations[:-4]):  # エラーになるものは除く
    print(f"パターン{i+1}: {operation}")

# エラーハンドリングが必要な演算
try:
    sqrt_negative = math.sqrt(-1)
except ValueError as e:
    print(f"math.sqrt(-1): {e}")
    sqrt_negative = float('nan')

try:
    log_negative = math.log(-1)
except ValueError as e:
    print(f"math.log(-1): {e}")
    log_negative = float('nan')

print(f"エラー回避後のNaN: {sqrt_negative}, {log_negative}")

複素数演算での対処

import cmath
import math

# 実数では不可能な演算を複素数で
negative_sqrt_real = math.sqrt(-1) if -1 >= 0 else float('nan')
negative_sqrt_complex = cmath.sqrt(-1)

print(f"実数での負数平方根: {negative_sqrt_real}")
print(f"複素数での負数平方根: {negative_sqrt_complex}")

# NaN判定
print(f"実数結果はNaN: {math.isnan(negative_sqrt_real)}")
print(f"複素数結果の実部はNaN: {math.isnan(negative_sqrt_complex.real)}")

NaNの判定方法

math.isnan()による基本判定

import math

# 様々な値でのNaN判定
test_values = [
    1.0,
    0.0,
    math.inf,
    -math.inf,
    float('nan'),
    math.nan,
    float('inf') - float('inf')
]

print("NaN判定結果:")
for val in test_values:
    print(f"{val}: {math.isnan(val)}")

NaNの特殊な比較特性

import math

nan = float('nan')

# NaNの特殊な性質
print(f"nan == nan: {nan == nan}")        # False
print(f"nan != nan: {nan != nan}")        # True
print(f"nan < 1: {nan < 1}")              # False
print(f"nan > 1: {nan > 1}")              # False
print(f"nan == 1: {nan == 1}")            # False

# この特性を利用したNaN判定
def is_nan_alternative(x):
    """自己比較によるNaN判定"""
    return x != x

test_val = float('nan')
print(f"自己比較判定: {is_nan_alternative(test_val)}")
print(f"math.isnan()判定: {math.isnan(test_val)}")

包括的なNaN検出関数

単一値のNaN検出

import math

def comprehensive_nan_check(value):
    """包括的なNaN検出"""
    checks = {
        'math.isnan()': math.isnan(value),
        '自己不等価': value != value,
        'str判定': str(value).lower() == 'nan',
        '有限性判定': not (math.isfinite(value) or math.isinf(value))
    }
    
    return checks

# テスト
test_cases = [42, float('nan'), math.inf, "not a number"]
for case in test_cases:
    if isinstance(case, (int, float)):
        results = comprehensive_nan_check(case)
        print(f"{case}: {results}")

リストやデータ構造でのNaN検出

import math

def find_nan_positions(data):
    """リスト内のNaNの位置を特定"""
    nan_positions = []
    for i, value in enumerate(data):
        if isinstance(value, (int, float)) and math.isnan(value):
            nan_positions.append(i)
    return nan_positions

def count_nan(data):
    """データ内のNaN数をカウント"""
    count = 0
    for value in data:
        if isinstance(value, (int, float)) and math.isnan(value):
            count += 1
    return count

# テストデータ
mixed_data = [1, 2.5, float('nan'), 4, math.inf, float('nan'), 7]
print(f"データ: {mixed_data}")
print(f"NaN位置: {find_nan_positions(mixed_data)}")
print(f"NaN数: {count_nan(mixed_data)}")

データクリーニングでのNaN処理

NaNの除去

import math

def remove_nan(data):
    """リストからNaNを除去"""
    return [x for x in data if not (isinstance(x, float) and math.isnan(x))]

def replace_nan(data, replacement=0):
    """NaNを指定値で置換"""
    result = []
    for x in data:
        if isinstance(x, float) and math.isnan(x):
            result.append(replacement)
        else:
            result.append(x)
    return result

# テスト
original_data = [1, 2, float('nan'), 4, float('nan'), 6]
print(f"元データ: {original_data}")
print(f"NaN除去: {remove_nan(original_data)}")
print(f"NaN置換(0): {replace_nan(original_data, 0)}")
print(f"NaN置換(平均): {replace_nan(original_data, sum(remove_nan(original_data))/len(remove_nan(original_data)))}")

統計計算でのNaN対応

import math

def safe_mean(data):
    """NaNを除外した平均値計算"""
    valid_data = [x for x in data if not math.isnan(x)]
    return sum(valid_data) / len(valid_data) if valid_data else float('nan')

def safe_max(data):
    """NaNを除外した最大値計算"""
    valid_data = [x for x in data if not math.isnan(x)]
    return max(valid_data) if valid_data else float('nan')

def safe_min(data):
    """NaNを除外した最小値計算"""
    valid_data = [x for x in data if not math.isnan(x)]
    return min(valid_data) if valid_data else float('nan')

# テストデータ
test_data = [1, 2, float('nan'), 4, 5, float('nan')]
print(f"データ: {test_data}")
print(f"安全な平均: {safe_mean(test_data)}")
print(f"安全な最大: {safe_max(test_data)}")
print(f"安全な最小: {safe_min(test_data)}")

# 全てNaNの場合
all_nan = [float('nan')] * 3
print(f"全NaNデータ: {all_nan}")
print(f"全NaN平均: {safe_mean(all_nan)}")

ファイル読み込み時のNaN処理

CSV読み込みでのNaN検出

import math

def parse_numeric_field(field_str):
    """文字列フィールドを数値に変換(NaN対応)"""
    field_str = field_str.strip().lower()
    
    # 空文字列や特定の文字列をNaNとして扱う
    nan_indicators = ['', 'na', 'nan', 'null', 'none', '#n/a']
    if field_str in nan_indicators:
        return float('nan')
    
    try:
        return float(field_str)
    except ValueError:
        return float('nan')

# CSVライクなデータの処理例
csv_like_data = [
    "1.5", "2.0", "", "4.5", "NA", "6.0", "invalid"
]

parsed_data = [parse_numeric_field(field) for field in csv_like_data]
print(f"元データ: {csv_like_data}")
print(f"変換後: {parsed_data}")
print(f"NaN数: {sum(1 for x in parsed_data if math.isnan(x))}")

NaNの伝播と計算への影響

計算でのNaN伝播

import math

def demonstrate_nan_propagation():
    """NaNの伝播を実演"""
    nan = float('nan')
    
    # 算術演算でのNaN伝播
    operations = [
        ('加算', nan + 5),
        ('減算', 10 - nan),
        ('乗算', nan * 3),
        ('除算', nan / 2),
        ('べき乗', nan ** 2)
    ]
    
    print("NaN伝播の例:")
    for op_name, result in operations:
        print(f"{op_name}: {result} (isnan: {math.isnan(result)})")

demonstrate_nan_propagation()

集計関数への影響

import math

def aggregation_with_nan():
    """集計関数でのNaN影響を確認"""
    data_with_nan = [1, 2, float('nan'), 4, 5]
    
    # 標準の集計関数
    try:
        total = sum(data_with_nan)
        average = total / len(data_with_nan)
        maximum = max(data_with_nan)
        minimum = min(data_with_nan)
        
        print(f"データ: {data_with_nan}")
        print(f"合計: {total} (isnan: {math.isnan(total)})")
        print(f"平均: {average} (isnan: {math.isnan(average)})")
        print(f"最大: {maximum} (isnan: {math.isnan(maximum)})")
        print(f"最小: {minimum} (isnan: {math.isnan(minimum)})")
        
    except Exception as e:
        print(f"エラー: {e}")

aggregation_with_nan()

デバッグとログ出力

NaNの詳細情報表示

import math

def debug_nan_info(value, var_name="value"):
    """NaNの詳細情報を表示"""
    if not isinstance(value, (int, float)):
        print(f"{var_name}: 数値型ではありません ({type(value)})")
        return
    
    info = {
        '値': value,
        'タイプ': type(value).__name__,
        'isnan': math.isnan(value),
        'isinf': math.isinf(value),
        'isfinite': math.isfinite(value),
        '文字列表現': str(value),
        '自己等価性': value == value
    }
    
    print(f"=== {var_name} の詳細情報 ===")
    for key, val in info.items():
        print(f"{key}: {val}")
    print()

# 様々な値でテスト
test_values = [
    (42, "正常な整数"),
    (3.14, "正常な浮動小数点"),
    (float('nan'), "NaN"),
    (math.inf, "正の無限大"),
    (-math.inf, "負の無限大")
]

for val, desc in test_values:
    debug_nan_info(val, desc)

パフォーマンス比較

NaN判定方法の速度比較

import math
import time

def benchmark_nan_detection():
    """NaN判定方法の性能比較"""
    test_value = float('nan')
    iterations = 1000000
    
    # math.isnan()
    start = time.time()
    for _ in range(iterations):
        result = math.isnan(test_value)
    isnan_time = time.time() - start
    
    # 自己比較
    start = time.time()
    for _ in range(iterations):
        result = test_value != test_value
    self_compare_time = time.time() - start
    
    # 文字列比較
    start = time.time()
    for _ in range(iterations):
        result = str(test_value).lower() == 'nan'
    str_compare_time = time.time() - start
    
    print("NaN判定方法の性能比較:")
    print(f"math.isnan(): {isnan_time:.6f}秒")
    print(f"自己比較: {self_compare_time:.6f}秒")
    print(f"文字列比較: {str_compare_time:.6f}秒")

benchmark_nan_detection()

まとめ

PythonにおけるNaN(非数)は、不正な数学演算の結果を表現するIEEE 754標準の特殊値です。math.isnan()が最も確実で高速な判定方法であり、NaN特有の「自分自身と等しくない」性質も判定に利用できます。データ処理において、NaNは計算結果に伝播するため、適切な検出と処理(除去、置換、エラーハンドリング)が重要です。科学計算やデータ分析では、NaNを含むデータの前処理が結果の正確性に直結するため、これらの手法を適切に使い分けることが重要です。

「らくらくPython塾」が切り開く「呪文コーディング」とは?

■プロンプトだけでオリジナルアプリを開発・公開してみた!!

■AI時代の第一歩!「AI駆動開発コース」はじめました!

テックジム東京本校で先行開始。

■テックジム東京本校

「武田塾」のプログラミング版といえば「テックジム」。
講義動画なし、教科書なし。「進捗管理とコーチング」で効率学習。
より早く、より安く、しかも対面型のプログラミングスクールです。

<短期講習>5日で5万円の「Pythonミニキャンプ」開催中。

<月1開催>放送作家による映像ディレクター養成講座

<オンライン無料>ゼロから始めるPython爆速講座