実行環境
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
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()
で調べます。
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 |
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 のデータ型と真理値。
nan_age = df.loc[888, 'Age']
print(nan_age)
print(type(nan_age))
print(bool(nan_age))
nan
<class 'numpy.float64'>
True
つづいて Cabin 列の NaN のデータ型と真理値。
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.
引用元
pd.DataFrame の真理値判定について
「ある列の値がすべて有効値である(=欠損値が 含まれていない)」ことを確認するため方法はいくつかありますが、例えば以下の方法だと期待の結果に なりません。
print(df['Age'].all()) # 期待の結果とならない判定方法
True
先にみたように Age 列には NaN が含まれますが、NaN が True 判定のため、.all()
をそのまま使うと Age 列の判定が True となり、欠損値が含まれていないという望まない結果になってしまいます。
正しくは、例えば以下のようにします。
print(df['Age'].notna().all())
False
少なくとも1つは欠損値が含まれるということで、期待の結果になります。
.notna()
で Age 列の各値が欠損値 ではない かをまず判定し、その結果がすべて True かどうかを .all()
で判定する方法です。
同様の判定を pd.DataFrame 全体にするときは .all()
を重ねます。
print(df.notna().all().all())
False
.notna() について
pandas の .notna()
は、pd.Series や pd.DataFrame の中の値に対して、欠損値ではない値を True にして、同じ行列サイズで返します。
.notna()
先の例でいうと、
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
pandas の欠損値と真理値の対応表
ここまで、欠損値として NaN を取り上げて説明をしてきましたが、NaN 以外にも欠損値として扱われる値があります。
逆に、欠損値のようにみえて欠損値ではない値もあります。
いくつか確認してみます。
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 という値が使えるようになっていますが、試験的なデータなようなので扱いませんでした。
気になる方はこちらから。