TL;DR¶
- pandasではmissin valueを含むboolean型はobject型になってしまう
- pandas.assignやpandas.joinなどでいつの間にかobject型になっているので注意!
- object型をbool型にキャストすると、bool(np.nan)==True なので注意!
bool(np.nan)==True¶
np.nanとの比較は常にFalseとなる.
False == np.nan
True == np.nan
しかし(?)np.nanをboolにキャストするとTrueになる
bool(np.nan)
一方intへのキャストはエラーとなる
int(np.nan)
np.nanの比較はややこしいので、np.isnanやpd.isnaをつかおう
assert (np.nan == np.nan) == False
assert (np.nan is np.nan) == True
assert (np.nan == float('nan')) == False
assert (float('nan') is float('nan')) == False
まとめると、
s = [True,False,np.nan,1,0,'1','0']
df = pd.DataFrame().assign(tfn=s, typ=[type(a) for a in s])
df.assign(**{
'astype(bool)' : df.tfn.astype(bool),
'astype(float)' : df.tfn.astype(float),
'==True' : df.tfn==True,
'==False' : df.tfn==False,
'==1' : df.tfn==1,
'==0' : df.tfn==0,
'is np.nan' : df.tfn is np.nan,
'isna' : pd.isna(df.tfn),
})
import struct
hex(struct.unpack('<I', struct.pack('<f', np.nan))[0])
struct.unpack('!f', bytes.fromhex('7fc00000'))[0]==np.nan
この辺の挙動がよくわからんが、とにかくnp.isnanやpd.isnaを使おう
pandasでは、missing valueがあるとboolはobjectにupcastされる¶
pandas DataFrameにおけるbooleanの計算は基本的にnumpyと同じ動きになっている。 boolean型はTrueもしくはFalseであり、NaN (np.nan) をとることができない。 そのため、pandas は missing valueを含むboolean型は、object型にupcastして管理する。
例として、pandas.assignの場合
series = pd.Series([True,False], index=[1,2])
display(series)
df = pd.DataFrame().assign(iroha=list('イロハニ'))
display(df)
ここで、seriesをdfにassignすると、missing valueに対して np.nan が割り振られる。bool型はnp.nanを保持できないため、object型にアップキャストされる
dfa = df.assign(b = series)
dfa
dfa.b.dtype
assignの他に、merge/joinなどでも同様である
それを防ぐには, join後に fillna() してから astype(bool)
(ただしmissing valueの情報を保持しい場合は、floatに変換したほうがいい場合もある.)
def join_bool(left_db, right_db, fillna=False):
def get_bool_columns(db):
return [c for c in db.columns if db[c].dtype.name=='bool']
bool_cols = set(get_bool_columns(left_db) + get_bool_columns(right_db))
ret = left_db.join(right_db, how='outer')
for col in bool_cols:
ret[col] = ret[col].fillna(fillna).astype(bool)
return ret
join_bool(df, series.to_frame())
pandasにおけるbool型の扱い¶
pandasではbool型に対する計算は、True=1.0, False=0.0 の様に扱ってくれるので、sumやmeanが直感的で便利
pd.Series([np.nan, True, False], dtype='object').sum()
pd.Series([np.nan, True, False], dtype='object').mean()
pd.Series([np.nan], dtype='object').mean()
ただ、こういうことがしたいのであれば、boolをfloatに変換するほうがよい
object型のboolはすぐに泥沼にはまる
pd.Series([np.nan], dtype='object').sum()
pd.Series([True],dtype='object').sum()
pd.Series([True, np.nan],dtype='object').sum()
pd.Series([False],dtype='object').sum()
pd.Series([False, np.nan],dtype='object').sum()
結果として、groupby-sum とかが大体こける。rowsが大きいとすごく時間もかかる
df = pd.DataFrame().assign(name=list('ABBCCCD'))
df = df.assign(boolean = pd.Series([True,True,False,True,False]))
df
df.groupby('name').sum()
object型のboolは大抵利点がほとんど無い。かといってすべてfillna().astype(bool)しても欠損値の情報が抜ける.
まとめ¶
missing valueを含む真偽値は、カラムの特性に応じて適宜変換してから使う
- missing valueを考慮しながらTrueの割合など計算する目的であれば、floatへ変換する => x.astype(float)
- Trueのものに意味があるのであれば、=> x.fillna(False).astype(bool)
- x.astype(bool) は、x.fillna(True).astype(bool) の意味になる
ちなみに次回pandasには、NaNを含むintがサポートされるようだ
http://pandas-docs.github.io/pandas-docs-travis/whatsnew.html#optional-integer-na-support
Comments
comments powered by Disqus