此篇介绍今天遇到的一个有意思的bug
今天coding完毕,测试代码的时候,下面一段代码出现了错误。
amp_samples = value[value].index.tolist()not_amp_samples = value[~value].index.tolist()
value
是一个值为True
或False
的Series
,我想从中分别获取值为True
和False
的index
列表。
代码很简单,pandas
中使用~
进行条件取反。于是,利用value[value]
和value[~value]
即可进行筛选。
但是出错了。
IndexError Traceback (most recent call last)-38-e29c78a5ffe7> ----> 1 value[~value]IndexError: only integers, slices (`:`), ellipsis (`...`), numpy.newaxis (`None`) and integer or boolean arrays are valid indices
index
错误。而且很清楚的写明是value[~value]
而不是value[value]
错误。
第一反应就是~
条件取反是不是在Series
中不能用。
进入ipython进行测试。
import pandas as pdseries = pd.Series({'R1':True,'R2':False,'R3':False,'R4':True})print(series[series])print(series[~series])
out:
R1 TrueR4 Truedtype: boolR2 FalseR3 Falsedtype: bool
是正常的。
那么第二反应就是,前期的处理导致value
出现除了True
和False
的其他值。取正时非bool
值会自动转换,但是条件取反就会失败。
于是,在代码中加print
获取value
的值。
R1 TrueR2 FalseR3 FalseR4 Truedtype: object
看起来很正常啊。
A FEW MINUTES LATER。
我开始觉得是服务器上的pandas
和本地版本不同导致的。于是在服务器上写code,无法复现。
15 MINUTES LATER。
终于,我发现,有那么一丝丝不同。
不知道各位注意到没有,代码输出中的Value
最后的dtype
是object
,而我的测试代码中的dtype
是bool
。我们知道,object
是pandas
中的通用类型,虽然它通常用来存储字符串,但也可以存储列表、元组等元素。
print(pd.Series({'R1':'sssimon yang'}))print(pd.Series({'R1':(1,2,3)}))print(pd.Series({'R1':[1,2,3]}))
out:
R1 sssimon yangdtype: objectR1 (1, 2, 3)dtype: objectR1 [1, 2, 3]dtype: object
虽然目前不知道object
与bool
会不会影响条件取反,但是为什么代码中的value
是object
类型呢?
错误之前的代码是这样的。
df['count'] = np.sum(patient_cnv[samples_map.values()],axis=1) #samples_map.values()的结果类似['R1','R2','R3','R4']for index, value in df.iterrows(): value = value[list(samples_map.values())]
可以看出,之前是想算出所有正值的总count
,然后遍历每一行。
用示例看一下加一个数值会不会影响类型。
series = pd.Series({'R1':True,'R2':False,'R3':False,'R4':True})print(series)series['count'] = series.sum()print(series)
out:
R1 TrueR2 FalseR3 FalseR4 Truedtype: boolR1 1R2 0R3 0R4 1count 2dtype: int64
啧,直接从bool
变为了int
。那还是用dataframe
模拟吧。
samples = ['R1','R2','R3','R4']df = pd.DataFrame([[True,False,False,True]],columns=samples)print(df)print(df.iloc[0])df['count'] = df[samples].sum(axis=1)print(df)print(df.iloc[0])
out:
R1 R2 R3 R40 True False False TrueR1 TrueR2 FalseR3 FalseR4 TrueName: 0, dtype: bool R1 R2 R3 R4 count0 True False False True 2R1 TrueR2 FalseR3 FalseR4 Truecount 2Name: 0, dtype: object
确实是从bool
变为了object
,因为各列很有可能是不同类型的值,所以内部机制可能是在取行时倾向于使用object
。
那赶紧取反一下试试。
series = df.iloc[0]series = series[samples]print(series)print(series[series])print(series[~series])
out:
R1 TrueR2 FalseR3 FalseR4 TrueName: 0, dtype: objectR1 TrueR4 TrueName: 0, dtype: objectR2 FalseR3 FalseName: 0, dtype: object
咦,没有错误,不应该啊。
再看原来的代码,里面有个iterrows
,复现的不够像?
for index, series in df.iterrows(): series = series[samples] print(series) print(series[series]) print(series[~series])
out:
R1 TrueR2 FalseR3 FalseR4 TrueName: 0, dtype: objectR1 TrueR4 TrueName: 0, dtype: object---------------------------------------------------------------------------IndexError Traceback (most recent call last)-96-ba90254b282a> 3 print(series) 4 print(series[series])----> 5 print(series[~series])
绝了,iterrows
和iloc
取出的行类型都是object
,一个可以条件取反,一个不能?
知识盲区。
不过既然复现了,那就要看看这神奇的取反结果是什么。
index,series = list(df.iterrows())[0]series = series[samples]print(series)print(~series)
out:
R1 TrueR2 FalseR3 FalseR4 TrueName: 0, dtype: objectR1 -2R2 -1R3 -1R4 -2Name: 0, dtype: object
-2
,-1
,那看来是以True
为1
,False
为0
进行的按位取反。
再看iloc
中的。
series = df.iloc[0]series = series[samples]print(series)print(~series)
out:
R1 TrueR2 FalseR3 FalseR4 TrueName: 0, dtype: objectR1 FalseR2 TrueR3 TrueR4 FalseName: 0, dtype: object
正常的条件取反。
我们知道在raw python中存在按位取反~1
输出-2
,所以这种结果看起来是raw python与pandas
在~
使用上的冲突,在iloc
结果中还可以保持pandas
中的条件取反,但是在iterrows()
中,按位取反就被暴露出来了。
按照道理来讲这两个之前不应该有区别,所以我认为这是个pandas
中的bug,看能不能提个tissue。
解决起来当然就很简单了,强制转为bool类型就可以了。
value = value.astype(bool)
我是 SSSimon Yang,关注我,用code解读世界