理解 pandas.read_csv() 浮点解析
Understanding pandas.read_csv() float parsing
我在使用 pandas.read_csv
从 CSV 读取概率时遇到问题;一些值被读取为带有 > 1.0
.
的浮点数
具体来说,我对以下行为感到困惑:
>>> pandas.read_csv(io.StringIO("column\n0.99999999999999998"))["column"][0]
1.0
>>> pandas.read_csv(io.StringIO("column\n0.99999999999999999"))["column"][0]
1.0000000000000002
>>> pandas.read_csv(io.StringIO("column\n1.00000000000000000"))["column"][0]
1.0
>>> pandas.read_csv(io.StringIO("column\n1.00000000000000001"))["column"][0]
1.0
>>> pandas.read_csv(io.StringIO("column\n1.00000000000000008"))["column"][0]
1.0
>>> pandas.read_csv(io.StringIO("column\n1.00000000000000009"))["column"][0]
1.0000000000000002
默认的浮点解析行为似乎是非单调的,尤其是一些以 0.9...
开头的值被转换为严格大于 1.0
的浮点数,从而导致问题,例如将它们喂入 sklearn.metrics
.
时
documentation 指出 read_csv
有一个参数 float_precision
可用于 select “C 引擎应该为浮点值使用哪个转换器” ,并将其设置为 'high'
确实解决了我的问题。
但是,我想了解默认行为:
- 在哪里可以找到默认浮点转换器的源代码?
- 在哪里可以找到有关默认浮点转换器和其他可能选择的预期行为的文档?
- 为什么最低位的单个数字变化会跳过一个值?
- 为什么这根本不是单调的?
关于“重复问题”的编辑:这不是重复问题。我知道浮点数学的局限性。我特别询问 Pandas 中的默认解析机制,因为内置 float
不显示此行为:
>>> float("0.99999999999999999")
1.0
...并且 我找不到文档。
如果您想了解其工作原理 - 请查看 source code - file "_libs/parsers.pyx" lines: 492-499 for Pandas 0.20.1:
self.parser.double_converter_nogil = xstrtod # <------- default converter
self.parser.double_converter_withgil = NULL
if float_precision == 'high':
self.parser.double_converter_nogil = precise_xstrtod # <------- 'high' converter
self.parser.double_converter_withgil = NULL
elif float_precision == 'round_trip': # avoid gh-15140
self.parser.double_converter_nogil = NULL
self.parser.double_converter_withgil = round_trip
@MaxU 已经展示了解析器和相关分词器的源代码 xstrtod
所以我将重点关注 "why" 部分:
xstrtod
的代码大致是这样(翻译成纯Python):
def xstrtod(p):
number = 0.
idx = 0
ndecimals = 0
while p[idx].isdigit():
number = number * 10. + int(p[idx])
idx += 1
idx += 1
while idx < len(p) and p[idx].isdigit():
number = number * 10. + int(p[idx])
idx += 1
ndecimals += 1
return number / 10**ndecimals
它再现了你看到的 "problem":
print(xstrtod('0.99999999999999997')) # 1.0
print(xstrtod('0.99999999999999998')) # 1.0
print(xstrtod('0.99999999999999999')) # 1.0000000000000002
print(xstrtod('1.00000000000000000')) # 1.0
print(xstrtod('1.00000000000000001')) # 1.0
print(xstrtod('1.00000000000000002')) # 1.0
print(xstrtod('1.00000000000000003')) # 1.0
print(xstrtod('1.00000000000000004')) # 1.0
print(xstrtod('1.00000000000000005')) # 1.0
print(xstrtod('1.00000000000000006')) # 1.0
print(xstrtod('1.00000000000000007')) # 1.0
print(xstrtod('1.00000000000000008')) # 1.0
print(xstrtod('1.00000000000000009')) # 1.0000000000000002
print(xstrtod('1.00000000000000019')) # 1.0000000000000002
问题似乎是最后一个地方的 9
改变了结果。所以它的浮点精度:
>>> float('100000000000000008')
1e+17
>>> float('100000000000000009')
1.0000000000000002e+17
最后一个 9
导致结果偏斜。
如果您想要高精度,您可以定义自己的转换器或使用 python 提供的转换器,即 decimal.Decimal
如果您想要任意精度:
>>> import pandas
>>> import decimal
>>> converter = {0: decimal.Decimal} # parse column 0 as decimals
>>> import io
>>> def parse(string):
... return '{:.30f}'.format(pd.read_csv(io.StringIO(string), converters=converter)["column"][0])
>>> print(parse("column\n0.99999999999999998"))
>>> print(parse("column\n0.99999999999999999"))
>>> print(parse("column\n1.00000000000000000"))
>>> print(parse("column\n1.00000000000000001"))
>>> print(parse("column\n1.00000000000000008"))
>>> print(parse("column\n1.00000000000000009"))
打印:
0.999999999999999980000000000000
0.999999999999999990000000000000
1.000000000000000000000000000000
1.000000000000000010000000000000
1.000000000000000080000000000000
1.000000000000000090000000000000
完全代表输入!
我在使用 pandas.read_csv
从 CSV 读取概率时遇到问题;一些值被读取为带有 > 1.0
.
具体来说,我对以下行为感到困惑:
>>> pandas.read_csv(io.StringIO("column\n0.99999999999999998"))["column"][0]
1.0
>>> pandas.read_csv(io.StringIO("column\n0.99999999999999999"))["column"][0]
1.0000000000000002
>>> pandas.read_csv(io.StringIO("column\n1.00000000000000000"))["column"][0]
1.0
>>> pandas.read_csv(io.StringIO("column\n1.00000000000000001"))["column"][0]
1.0
>>> pandas.read_csv(io.StringIO("column\n1.00000000000000008"))["column"][0]
1.0
>>> pandas.read_csv(io.StringIO("column\n1.00000000000000009"))["column"][0]
1.0000000000000002
默认的浮点解析行为似乎是非单调的,尤其是一些以 0.9...
开头的值被转换为严格大于 1.0
的浮点数,从而导致问题,例如将它们喂入 sklearn.metrics
.
documentation 指出 read_csv
有一个参数 float_precision
可用于 select “C 引擎应该为浮点值使用哪个转换器” ,并将其设置为 'high'
确实解决了我的问题。
但是,我想了解默认行为:
- 在哪里可以找到默认浮点转换器的源代码?
- 在哪里可以找到有关默认浮点转换器和其他可能选择的预期行为的文档?
- 为什么最低位的单个数字变化会跳过一个值?
- 为什么这根本不是单调的?
关于“重复问题”的编辑:这不是重复问题。我知道浮点数学的局限性。我特别询问 Pandas 中的默认解析机制,因为内置 float
不显示此行为:
>>> float("0.99999999999999999")
1.0
...并且 我找不到文档。
如果您想了解其工作原理 - 请查看 source code - file "_libs/parsers.pyx" lines: 492-499 for Pandas 0.20.1:
self.parser.double_converter_nogil = xstrtod # <------- default converter
self.parser.double_converter_withgil = NULL
if float_precision == 'high':
self.parser.double_converter_nogil = precise_xstrtod # <------- 'high' converter
self.parser.double_converter_withgil = NULL
elif float_precision == 'round_trip': # avoid gh-15140
self.parser.double_converter_nogil = NULL
self.parser.double_converter_withgil = round_trip
@MaxU 已经展示了解析器和相关分词器的源代码 xstrtod
所以我将重点关注 "why" 部分:
xstrtod
的代码大致是这样(翻译成纯Python):
def xstrtod(p):
number = 0.
idx = 0
ndecimals = 0
while p[idx].isdigit():
number = number * 10. + int(p[idx])
idx += 1
idx += 1
while idx < len(p) and p[idx].isdigit():
number = number * 10. + int(p[idx])
idx += 1
ndecimals += 1
return number / 10**ndecimals
它再现了你看到的 "problem":
print(xstrtod('0.99999999999999997')) # 1.0
print(xstrtod('0.99999999999999998')) # 1.0
print(xstrtod('0.99999999999999999')) # 1.0000000000000002
print(xstrtod('1.00000000000000000')) # 1.0
print(xstrtod('1.00000000000000001')) # 1.0
print(xstrtod('1.00000000000000002')) # 1.0
print(xstrtod('1.00000000000000003')) # 1.0
print(xstrtod('1.00000000000000004')) # 1.0
print(xstrtod('1.00000000000000005')) # 1.0
print(xstrtod('1.00000000000000006')) # 1.0
print(xstrtod('1.00000000000000007')) # 1.0
print(xstrtod('1.00000000000000008')) # 1.0
print(xstrtod('1.00000000000000009')) # 1.0000000000000002
print(xstrtod('1.00000000000000019')) # 1.0000000000000002
问题似乎是最后一个地方的 9
改变了结果。所以它的浮点精度:
>>> float('100000000000000008')
1e+17
>>> float('100000000000000009')
1.0000000000000002e+17
最后一个 9
导致结果偏斜。
如果您想要高精度,您可以定义自己的转换器或使用 python 提供的转换器,即 decimal.Decimal
如果您想要任意精度:
>>> import pandas
>>> import decimal
>>> converter = {0: decimal.Decimal} # parse column 0 as decimals
>>> import io
>>> def parse(string):
... return '{:.30f}'.format(pd.read_csv(io.StringIO(string), converters=converter)["column"][0])
>>> print(parse("column\n0.99999999999999998"))
>>> print(parse("column\n0.99999999999999999"))
>>> print(parse("column\n1.00000000000000000"))
>>> print(parse("column\n1.00000000000000001"))
>>> print(parse("column\n1.00000000000000008"))
>>> print(parse("column\n1.00000000000000009"))
打印:
0.999999999999999980000000000000
0.999999999999999990000000000000
1.000000000000000000000000000000
1.000000000000000010000000000000
1.000000000000000080000000000000
1.000000000000000090000000000000
完全代表输入!