pandas の NaN は True

**実行環境**
Windows11
python 3.10.12
numpy 1.25.2
pandas 2.1.0

## はじめに
pandas の 欠損値のひとつに **NaN** (=Not a Number) という値がありますが、NaN は真理値判定（Truth Value Testing）で False ではなく **True** の扱いとなります。

pandas で欠損値を落としたり埋めたりするときは `.dropna()` や `.fillna()` などのメソッドで処理をするので、普段は NaN の真理値は気になりません。

一方で、 NaN があるかないかという判定をするときは `.all()` や `.any()` などを使うことになるため、NaN そのものが True であることを知っていなければいけません。

ここでは、 pandas の NaN について調べたことや pd.DataFrame の真理値判定の方法などについて書きます。

## サンプルデータ
おなじみ titanic.csv を使います。

**pandas のチュートリアル等で提供している titanic.csv**

https://raw.githubusercontent.com/pandas-dev/pandas/main/doc/data/titanic.csv

```py
import numpy as np
import pandas as pd

url = 'https://raw.githubusercontent.com/pandas-dev/pandas/main/doc/data/titanic.csv'

df = pd.read_csv(url)
```


## Nan について
データフレームの中で欠損値のある列を `.info()` で調べます。

```py
print(df.info())
```

```
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 891 entries, 0 to 890
Data columns (total 12 columns):
 #   Column       Non-Null Count  Dtype
---  ------       --------------  -----
 0   PassengerId  891 non-null    int64
 1   Survived     891 non-null    int64
 2   Pclass       891 non-null    int64
 3   Name         891 non-null    object
 4   Sex          891 non-null    object
 5   Age          714 non-null    float64
 6   SibSp        891 non-null    int64
 7   Parch        891 non-null    int64
 8   Ticket       891 non-null    object
 9   Fare         891 non-null    float64
 10  Cabin        204 non-null    object
 11  Embarked     889 non-null    object
dtypes: float64(2), int64(5), object(5)
memory usage: 83.7+ KB
None
```

Non-Null Count が全数 891 に満たない Age、Cabin、Embarked の Column のうち、Dtype が異なる Age と Cabin の欠損値について見てみることします。

| Column | Dtype   |
|:------:|:-------:|
| Age    | Float64 |
| Cabin  | object  |

```py
print(df[['Age', 'Cabin']])
```

```
      Age Cabin
0    22.0   NaN
1    38.0   C85
2    26.0   NaN
3    35.0  C123
4    35.0   NaN
..    ...   ...
886  27.0   NaN
887  19.0   B42
888   NaN   NaN
889  26.0  C148
890  32.0   NaN

[891 rows x 2 columns]
```

では、各列に含まれるの NaN のデータ型と真理値を調べてみます。

Age 列の NaN のデータ型と真理値。

```py
nan_age = df.loc[888, 'Age']
print(nan_age)
print(type(nan_age))
print(bool(nan_age))
```

```
nan
<class 'numpy.float64'>
True
```

つづいて Cabin 列の NaN のデータ型と真理値。

```py
nan_cabin = df.loc[2, 'Cabin']
print(nan_cabin)
print(type(nan_cabin))
print(bool(nan_cabin))
```

```
nan
<class 'float'>
True
```

見ての通り、どちらの Column の NaN もデータ型が（numpy か組み込みかの違いはあるものの）float、真理値が True となりました。

繰り返しになりますが、**NaN は欠損値**でも **True 判定** されます。

| Column | Dtype   | NaN の型      | NaN の真理値  |
|:------:|:-------:|:-------------:|:------------:|
| Age    | Float64 | numpy.float64 | True         |
| Cabin  | object  | float         | True         |

なお pandas が 欠損値に NaN を使っている理由としては、ユーザーズガイドによると、単純さとパフォーマンスのためということです。

> The choice of using NaN internally to denote missing data was largely for simplicity and performance reasons.

**引用元**

https://pandas.pydata.org/pandas-docs/stable/user_guide/missing_data.html#working-with-missing-data

## pd.DataFrame の真理値判定について
「ある列の値がすべて有効値である（=欠損値が **含まれていない**）」ことを確認するため方法はいくつかありますが、例えば以下の方法だと期待の結果に **なりません。**

```py
print(df['Age'].all())  # 期待の結果とならない判定方法
```

```
True
```

先にみたように Age 列には NaN が含まれますが、NaN が True 判定のため、`.all()` をそのまま使うと Age 列の判定が True となり、欠損値が含まれていないという望まない結果になってしまいます。

正しくは、例えば以下のようにします。

```py
print(df['Age'].notna().all())
```

```
False
```
少なくとも１つは欠損値が含まれるということで、期待の結果になります。

`.notna()` で Age 列の各値が欠損値 **ではない** かをまず判定し、その結果がすべて True かどうかを `.all()` で判定する方法です。



同様の判定を pd.DataFrame 全体にするときは `.all()` を重ねます。

```py
print(df.notna().all().all())
```

```
False
```

## .notna() について
pandas の `.notna()` は、pd.Series や pd.DataFrame の中の値に対して、欠損値ではない値を True にして、同じ行列サイズで返します。

**.notna()**

https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.notna.html


先の例でいうと、

```py
print(df['Age'].notna())
```

```
0       True
1       True
2       True
3       True
4       True
       ...  
886     True
887     True
888    False
889     True
890     True
Name: Age, Length: 891, dtype: bool
```

欠損値 が False 、それ以外が True と判定されます。

`.notnull()` という似た名前のメソッドもありますが `.notna()` のエイリアスであるため、どちらを使っても構いません。

なお欠損値を True 判定したい場合は、`.isna()` または `.isnull()` を使います。

## ValueError: The truth value of a DataFrame is ambiguous.
よく見かけるエラーで以下のようなものがあります。

```
ValueError: The truth value of a DataFrame is ambiguous.
Use a.empty, a.bool(), a.item(), a.any() or a.all().
```

「そのデータフレームの真理値があいまいです云々」ということですが、つまり「判定方法によって True になったり False になったりするのでエラーにしました。.empty などの専用の方法があるのでそれを使って真理値判定をしてくださいね。」ということになります。

この ValueError は、 pd.DataFrame や pd.Series を if 文の条件に使うときや、and や or で論理演算をするときなどに発生します。

このエラーに対処するときのように `.any()` や `.all()` を使って真理値判定をする場面で、NaN が True であることを知っている必要があります。

参考に pandas のユーザーズガイドもご覧ください。

**Using if/truth statements with pandas**

https://pandas.pydata.org/pandas-docs/stable/user_guide/gotchas.html#gotchas-truth

## pandas の欠損値と真理値の対応表
ここまで、欠損値として NaN を取り上げて説明をしてきましたが、NaN 以外にも欠損値として扱われる値があります。

逆に、欠損値のようにみえて欠損値ではない値もあります。

いくつか確認してみます。

```py
import math

df_vals = pd.DataFrame(
    ['np.nan', 'float("nan")', 'math.nan', "''", "' '", '0', 'None', 'np.inf', 'pd.NaT'],
    columns=['str']
)

# 判定する値
df_vals['val'] = df_vals.apply(lambda row: eval(row['str']), axis=1)
# 真理値判定
df_vals['bool'] = df_vals.apply(lambda row: bool(row['val']), axis=1)
# 欠損値判定
df_vals['isna'] = df_vals['val'].isna()

print(df_vals)
```

```
            str   val   bool   isna
0        np.nan   NaN   True   True
1  float("nan")   NaN   True   True
2      math.nan   NaN   True   True
3            ''        False  False
4           ' '         True  False
5             0     0  False  False
6          None  None  False   True
7        np.inf   inf   True  False
8        pd.NaT   NaT   True   True
```

isna 列には、値が欠損値のときに True が入ります。

結果を日本語にすると、以下のようになります。

* `numpy の nan`、`float の nan`、`math の nan` は True だけど欠損値

* `空文字`は False だけど欠損値ではない、`スペース`は True で欠損値でもない

* `0` は False で欠損値ではない

* `None` は False で欠損値

* `numpy の inf` は True で欠損値でもない

* `pandas の NaT (Not-A-Time)` は True だけど欠損値

0 はデータ分析上有効であるため、欠損値でないことは当然ですね。

組み込みの None は直感的に分かりやすいです。

空文字やスペースは出力しても何も表示されませんが、どちらも欠損値ではないので注意が必要です。

## おわりに
pandas の NaN の説明や、pd.DataFrame の真理値判定の方法などについて書きました。

ときどきつまづくポイントなので、今回まとめられてよかったです。

なお pandas 1.0 から、欠損値を示す pd.NA という値が使えるようになっていますが、試験的なデータなようなので扱いませんでした。

気になる方はこちらから。

https://pandas.pydata.org/docs/user_guide/missing_data.html#experimental-na-scalar-to-denote-missing-values