我有一个大的DataFrame(大约4e + 07行)。
求和时,无论在列选择之前还是之后进行求和,我都会得到2个明显不同的结果。
另外,即使总数都在2 ** 31以下,类型也会从float32变为float64
df[[col1, col2, col3]].sum()
Out[1]:
col1 9.36e+07
col2 1.39e+09
col3 6.37e+08
dtype: float32
df.sum()[[col1, col2, col3]]
Out[2]:
col1 1.21e+08
col2 1.70e+09
col3 7.32e+08
dtype: float64
Run Code Online (Sandbox Code Playgroud)
我显然缺少了什么,有人遇到过同样的问题吗?
谢谢你的帮助。
要了解这里发生的事情,您需要了解Pandas在后台进行的操作。我将简化一下,因为有很多风吹草打和特殊情况需要考虑,但大致看起来像这样:
假设您有一个带有各种数字列的Pandas DataFrame对象df(我们将忽略datetime列,categorical列等)。当您计算时df.sum(),Pandas:
sum函数应用于该2d数组,axis=0以计算列总和。这是重要的第一步。a的列DataFrame可能具有不同的dtype,但是2d NumPy数组只能具有单个dtype。如果df具有的混合物float32和int32列(例如),熊猫有选择单一D型细胞,对于两列同时是适当,并且在这种情况下,它选择float64。因此,计算总和时,将使用双精度算术对双精度值进行计算。这就是第二个示例中发生的情况。
另一方面,如果首先减少到仅float32几列,则Pandas可以并且将对float322d NumPy数组使用dtype,因此sum计算以单精度执行。这就是您的第一个示例中发生的情况。
这里的显示操作一个简单的例子:我们将建立一个数据帧100万行三列,dtypes的float32,float32并int32分别。所有值均为1:
>>> import numpy as np, pandas as pd
>>> s = np.ones(10**8, dtype=np.float32)
>>> t = np.ones(10**8, dtype=np.int32)
>>> df = pd.DataFrame(dict(A=s, B=s, C=t))
>>> df.head()
A B C
0 1.0 1.0 1
1 1.0 1.0 1
2 1.0 1.0 1
3 1.0 1.0 1
4 1.0 1.0 1
>>> df.dtypes
A float32
B float32
C int32
dtype: object
Run Code Online (Sandbox Code Playgroud)
现在,当我们直接计算总和时,Pandas首先将所有内容转换为float64s。float64对于所有三列,也都使用该类型进行了计算,我们得到了准确的答案。
>>> df.sum()
A 100000000.0
B 100000000.0
C 100000000.0
dtype: float64
Run Code Online (Sandbox Code Playgroud)
但是,如果我们首先将数据框缩减为仅float32列,则将float32-arithmetic用于求和,并且得到的答案很差。
>>> df[['A', 'B']].sum()
A 16777216.0
B 16777216.0
dtype: float32
Run Code Online (Sandbox Code Playgroud)
这种不准确性当然是由于使用不具有足够的精度有问题的任务D型:在求和的某个时刻,我们最终反复添加1.0到16777216.0,并获得16777216.0每一次回来,多亏了平时浮点问题。解决方案是float64在进行计算之前将其显式转换为您自己。
但是,这并不是熊猫给我们带来的惊喜的终结。使用与上述相同的数据框,让我们尝试仅计算column的总和"A":
>>> df[['A']].sum()
A 100000000.0
dtype: float32
Run Code Online (Sandbox Code Playgroud)
突然,我们又获得了完全的准确性!发生什么了?这与dtypes无关:我们仍在使用float32求和。这是现在的第二个步骤(在NumPy的总和),这是负责的差异。发生的事情是,NumPy可以(有时确实)使用更精确的求和算法,称为成对求和,并且使用float32dtype和我们正在使用的大小数组,该精度可以对最终结果产生巨大的影响。但是,仅在沿数组的最快变化轴求和时才使用该算法。请参阅此NumPy问题进行相关讨论。在我们计算列"A" 和列之和的情况下"B",最后得到一个shape的values数组(100000000, 2)。最快变化的轴是轴1,我们正在计算沿轴0的总和,因此使用了朴素的求和算法,结果很差。但是,如果我们只要求column的总和"A",我们将获得准确的总和结果,该结果是使用成对求和计算的。
总而言之,当使用这种大小的DataFrame时,您要小心(a)尽可能以双精度而不是单精度工作,并且(b)由于NumPy选择不同的算法而准备输出结果的差异。
您可能会失去np.float32相对精度np.float64
np.finfo(np.float32)
finfo(resolution=1e-06, min=-3.4028235e+38, max=3.4028235e+38, dtype=float32)
Run Code Online (Sandbox Code Playgroud)
和
np.finfo(np.float64)
finfo(resolution=1e-15, min=-1.7976931348623157e+308, max=1.7976931348623157e+308, dtype=float64)
Run Code Online (Sandbox Code Playgroud)
一个人为的例子
df = pd.DataFrame(dict(
x=[-60499999.315, 60500002.685] * int(2e7),
y=[-60499999.315, 60500002.685] * int(2e7),
z=[-60499999.315, 60500002.685] * int(2e7),
)).astype(dict(x=np.float64, y=np.float32, z=np.float32))
print(df.sum()[['y', 'z']], df[['y', 'z']].sum(), sep='\n\n')
y 80000000.0
z 80000000.0
dtype: float64
y 67108864.0
z 67108864.0
dtype: float32
Run Code Online (Sandbox Code Playgroud)