了解pandas.read_csv()浮点解析

时间:2017-05-29 17:07:35

标签: python pandas floating-accuracy

我在使用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可用于选择“C引擎应该使用哪个转换器用于浮点值”,并将其设置为{ {1}}确实解决了我的问题。

但是,我想了解默认行为:

  1. 我在哪里可以找到默认浮点转换器的源代码?
  2. 哪里可以找到有关默认浮点转换器的预期行为的文档以及其他可能的选择?
  3. 为什么最不重要位置的单个数字变化会跳过一个值?
  4. 为什么这种行为完全不是单调的?
  5. 关于“重复问题”的修改:这不是重复。我知道浮点数学的局限性。我特别询问Pandas中的默认解析机制,因为内置'high'没有显示此行为:

    float

    ...和我找不到文档。

2 个答案:

答案 0 :(得分:2)

如果您想了解它的工作原理,请查看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

Source code for xstrtod

Source code for precise_xstrtod

答案 1 :(得分:2)

@MaxU已经显示了解析器和相关标记化器xstrtod的源代码,因此我将专注于“为什么”部分:

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

它再现了你看到的“问题”:

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

完全代表输入!