Pandas read_csv读取含NaN大整数时精度丢失,求更优解决方案
问题描述
我有一个包含大整数值的CSV文件,需对这些值执行算术运算,部分字段可能包含NaN值。使用Pandas的read_csv方法加载数据时,若不存在NaN值,这些字段会被加载为int类型,精度正常;但存在NaN值时,会被转换为float类型,出现精度丢失。
示例CSV文件:
,epoch_1,epoch_2 0,1665045912937687151,1665045912937689151 1,,
加载后表现:
[1] df = pd.read_csv('sample.csv', index_col=0) [2] df epoch_1 epoch_2 0 1.665046e+18 1.665046e+18 1 NaN NaN [3] df['diff'] = df['epoch_2'] - df['epoch_1'] [4] df epoch_1 epoch_2 diff 0 1.665046e+18 1.665046e+18 2048.0 1 NaN NaN NaN
可见第三列计算结果错误,正确值应为2000。若不存在NaN值,计算结果正确。
已尝试的方案
尝试在加载数据时指定dtype为Int64:
[1] df = pd.read_csv('sample.csv', index_col=0, dtype={'epoch_1': pd.Int64Dtype(), 'epoch_2': pd.Int64Dtype()}) [2] df epoch_1 epoch_2 0 1665045912937687296 1665045912937689088 1 <NA> <NA> [3] df['diff'] = df['epoch_2'] - df['epoch_1'] [4] df epoch_1 epoch_2 diff 0 1665045912937687296 1665045912937689088 1792 1 <NA> <NA> <NA>
结果仍出现精度丢失,计算错误。
不愿采用的临时方案
可以将数据加载为字符串类型,移除含NaN的行,再转换为int64类型计算,结果正确:
[1] df = pd.read_csv('sample.csv', index_col=0, dtype={'epoch_1': str, 'epoch_2': str}) [2] df epoch_1 epoch_2 0 1665045912937687151 1665045912937689151 1 NaN NaN [3] df = df[~df['epoch_1'].isna()] [4] df['diff'] = df['epoch_2'].astype(int) - df['epoch_1'].astype(int) [5] df epoch_1 epoch_2 diff 0 1665045912937687151 1665045912937689151 2000
但需要保留含NaN的条目,因此需重新添加这些行,该方法涉及大量类型转换,在数据量较大时会成为性能瓶颈,且不够简洁,故寻求更优方案。
更新尝试
另一种可行方法:
[1] df = pd.read_csv('sample.csv', index_col=0, dtype=str) [2] df epoch_1 epoch_2 0 1665045912937687151 1665045912937689151 1 NaN NaN [3] df['diff'] = df['epoch_2'].astype('Int64') - df['epoch_1'].astype('Int64') [4] df epoch_1 epoch_2 diff 0 1665045912937687151 1665045912937689151 2000 1 NaN NaN <NA>
该方法比删除再添加行更优,但仍需在运算前进行类型转换,希望能避免此操作。
同时产生疑问:为何在read_csv中指定dtype为Int64会丢失精度,而加载为字符串后转换为Int64却能正常工作?是否read_csv内部先将数据加载为float64再转换为指定类型?
解决方案与疑问解答
最优加载方式
直接使用read_csv的converters参数,指定列转换为Int64类型,跳过中间浮点转换步骤:
df = pd.read_csv( 'sample.csv', index_col=0, converters={ 'epoch_1': pd.Int64Dtype().construct_array_type()._from_sequence, 'epoch_2': pd.Int64Dtype().construct_array_type()._from_sequence } ) # 计算差值 df['diff'] = df['epoch_2'] - df['epoch_1']
这样加载后的数据能完整保留大整数精度,计算结果正确,且无需后续类型转换。
精度丢失原因解析
你的猜测完全正确:当在read_csv中直接指定dtype=pd.Int64Dtype()时,Pandas内部会先将CSV中的数值解析为float64(因为存在空值,默认解析逻辑会优先用浮点类型兼容NaN),再转换为Int64。而float64只能精确表示到2^53(约9e15),你的数值是1.6e18,远超这个范围,因此转换时会丢失精度。
而先加载为字符串再转换为Int64,是直接从字符串解析为整数,跳过了浮点转换的步骤,因此能完整保留大整数的精度。
替代方案:使用dtype_backend='numpy_nullable'(Pandas 2.0+)
如果你的Pandas版本在2.0及以上,可以使用dtype_backend参数,让read_csv直接用nullable整数类型解析,避免浮点转换:
df = pd.read_csv( 'sample.csv', index_col=0, dtype={'epoch_1': 'Int64', 'epoch_2': 'Int64'}, dtype_backend='numpy_nullable' ) df['diff'] = df['epoch_2'] - df['epoch_1']
这个参数会改变Pandas的类型解析逻辑,直接用对应的nullable类型读取数据,不会经过浮点类型中转,因此精度不会丢失。
内容的提问来源于stack exchange,提问作者K S

