Numpy.float64 在写入 Excel (.xlsx) 时发生变化
Numpy.float64 changes when writing to Excel (.xlsx)
我注意到,当某些 Numpy float64 值被保存为 Excel 文件(通过 Pandas DataFrame)时,它们会发生变化。首先我认为这与 Excel 中的一些不精确有关,但是 Excel 似乎将浮点数编码为双精度,所以我对这个观察有点困惑。
>>> import numpy as np
>>> import pandas as pd
# Create a floating point number that exhibits the problem.
>>> ba = bytearray(['\x53', '\x2a', '\xb0', '\x49', '\xf3', '\x79', '\x90', '\x40'])
>>> ba
bytearray(b'S*\xb0I\xf3y\x90@')
>>> f = np.frombuffer(ba)
>>> f[0]
1054.4875857854684
# Write to dataframe to save as Excel file.
>>> df = pd.DataFrame({'a': f})
>>> df.to_excel('test.xlsx', engine='xlsxwriter')
# Read excel file (when viewing the file in LibreOffice, the
# value isn't 1054.4875857854684 any more).
>>> df2 = pd.read_excel('test.xlsx')
>>> df2.ix[0,'a']
1054.4875857854699
>>> df2.ix[0,'a'] == f[0]
False
为什么无法从之前写入的 Excel 读回相同的 float64?
我也尝试过使用 Openpyxl
(.xlsx 格式)和 Xlwt
(.xls 格式)作为引擎。虽然前者产生与 xlsxwriter
相同的错误结果,但 Xlwt
实际上按预期工作并根据确切的变量值写入浮点数。对于 .xlsx
格式编写器引擎,我可能错过了一个参数吗?
# this uses the xlwt engine
>>> df.to_excel('test.xls')
>>> df2 = pd.read_excel('test.xls')
>>> df2.ix[0,'a'] == f[0]
True
I also tried this with Openpyxl (.xlsx format) and Xlwt (.xls format) as engines. While the former produced the same erroneous result as xlsxwriter, Xlwt was actually working as expected and wrote the float according to the exact variable value.
不同之处在于 .xls 是一种二进制文件格式,IEEE 754 双精度的 64 位表示形式被准确地写入文件,并且可以读回相同的 64 位。
然而,.xlsx 文件格式是 zip 容器中文本 XML 文件的集合。因此,double 被写为 double 的字符串表示形式(使用 '%.16g'
之类的格式),并通过将该字符串表示形式转换回 double 来读入。对于双打来说,这本质上是一个有损过程,因为绝大多数 IEEE 754 数字都没有精确的字符串表示。
例如,如果您在示例中采用 numpy 数字并以不同的精度对其进行格式化,您将得到不同的表示形式:
>>> '%.16g' % f[0]
'1054.487585785468'
>>> '%.17g' % f[0]
'1054.4875857854684'
>>> '%.18g' % f[0]
'1054.48758578546835'
您也可以通过将 1054.4875857854684
粘贴到 Excel 中的单元格中,保存文件并检查输出来自己演示:
所以对于这样的文件:
你会得到这样的东西:
$ unzip numpy.xlsx -d numpy
$ xmllint --format numpy/xl/worksheets/sheet1.xml | grep 1054
<v>1054.4875857854599</v>
这或多或少是您使用 Pandas.
读回文件时看到的内容
在 Pandas 和 XlsxWriter 中进行一些挖掘之后,我基本上发现了从 numpy.float64
到 .xlsx 文件的两个转换步骤:
1) numpy.float64
=> float
(无保真度损失)在 pandas/io/excel.py
def _conv_value(val):
# Convert numpy types to Python types for the Excel writers.
if com.is_integer(val):
val = int(val)
elif com.is_float(val):
val = float(val)
elif com.is_bool(val):
val = bool(val)
elif isinstance(val, Period):
val = "%s" % val
elif com.is_list_like(val):
val = str(val)
return val
2) float
=> string
(attr += ' %s="%s"' % (key, value)
)。这是更改精度的地方(xlswriter/xmlwriter.py
)
def _xml_number_element(self, number, attributes=[]):
# Optimised tag writer for <c> cell number elements in the inner loop.
attr = ''
for key, value in attributes:
value = self._escape_attributes(value)
attr += ' %s="%s"' % (key, value)
self.fh.write("""<c%s><v>%.15g</v></c>""" % (attr, number))
所以序列化(第 2 步)是更改精度的地方。我猜,因为xls是二进制格式,所以直接写float,不用转换。
我注意到,当某些 Numpy float64 值被保存为 Excel 文件(通过 Pandas DataFrame)时,它们会发生变化。首先我认为这与 Excel 中的一些不精确有关,但是 Excel 似乎将浮点数编码为双精度,所以我对这个观察有点困惑。
>>> import numpy as np
>>> import pandas as pd
# Create a floating point number that exhibits the problem.
>>> ba = bytearray(['\x53', '\x2a', '\xb0', '\x49', '\xf3', '\x79', '\x90', '\x40'])
>>> ba
bytearray(b'S*\xb0I\xf3y\x90@')
>>> f = np.frombuffer(ba)
>>> f[0]
1054.4875857854684
# Write to dataframe to save as Excel file.
>>> df = pd.DataFrame({'a': f})
>>> df.to_excel('test.xlsx', engine='xlsxwriter')
# Read excel file (when viewing the file in LibreOffice, the
# value isn't 1054.4875857854684 any more).
>>> df2 = pd.read_excel('test.xlsx')
>>> df2.ix[0,'a']
1054.4875857854699
>>> df2.ix[0,'a'] == f[0]
False
为什么无法从之前写入的 Excel 读回相同的 float64?
我也尝试过使用 Openpyxl
(.xlsx 格式)和 Xlwt
(.xls 格式)作为引擎。虽然前者产生与 xlsxwriter
相同的错误结果,但 Xlwt
实际上按预期工作并根据确切的变量值写入浮点数。对于 .xlsx
格式编写器引擎,我可能错过了一个参数吗?
# this uses the xlwt engine
>>> df.to_excel('test.xls')
>>> df2 = pd.read_excel('test.xls')
>>> df2.ix[0,'a'] == f[0]
True
I also tried this with Openpyxl (.xlsx format) and Xlwt (.xls format) as engines. While the former produced the same erroneous result as xlsxwriter, Xlwt was actually working as expected and wrote the float according to the exact variable value.
不同之处在于 .xls 是一种二进制文件格式,IEEE 754 双精度的 64 位表示形式被准确地写入文件,并且可以读回相同的 64 位。
然而,.xlsx 文件格式是 zip 容器中文本 XML 文件的集合。因此,double 被写为 double 的字符串表示形式(使用 '%.16g'
之类的格式),并通过将该字符串表示形式转换回 double 来读入。对于双打来说,这本质上是一个有损过程,因为绝大多数 IEEE 754 数字都没有精确的字符串表示。
例如,如果您在示例中采用 numpy 数字并以不同的精度对其进行格式化,您将得到不同的表示形式:
>>> '%.16g' % f[0]
'1054.487585785468'
>>> '%.17g' % f[0]
'1054.4875857854684'
>>> '%.18g' % f[0]
'1054.48758578546835'
您也可以通过将 1054.4875857854684
粘贴到 Excel 中的单元格中,保存文件并检查输出来自己演示:
所以对于这样的文件:
你会得到这样的东西:
$ unzip numpy.xlsx -d numpy
$ xmllint --format numpy/xl/worksheets/sheet1.xml | grep 1054
<v>1054.4875857854599</v>
这或多或少是您使用 Pandas.
读回文件时看到的内容在 Pandas 和 XlsxWriter 中进行一些挖掘之后,我基本上发现了从 numpy.float64
到 .xlsx 文件的两个转换步骤:
1) numpy.float64
=> float
(无保真度损失)在 pandas/io/excel.py
def _conv_value(val):
# Convert numpy types to Python types for the Excel writers.
if com.is_integer(val):
val = int(val)
elif com.is_float(val):
val = float(val)
elif com.is_bool(val):
val = bool(val)
elif isinstance(val, Period):
val = "%s" % val
elif com.is_list_like(val):
val = str(val)
return val
2) float
=> string
(attr += ' %s="%s"' % (key, value)
)。这是更改精度的地方(xlswriter/xmlwriter.py
)
def _xml_number_element(self, number, attributes=[]):
# Optimised tag writer for <c> cell number elements in the inner loop.
attr = ''
for key, value in attributes:
value = self._escape_attributes(value)
attr += ' %s="%s"' % (key, value)
self.fh.write("""<c%s><v>%.15g</v></c>""" % (attr, number))
所以序列化(第 2 步)是更改精度的地方。我猜,因为xls是二进制格式,所以直接写float,不用转换。