我有一个大型DataFrame(大约4e + 07行)。
总结时,我得到 2显着不同的结果我是否在列选择之前或之后总和。 此外,类型从float32更改为float64,即使总数都低于2 ** 31
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
我显然错过了什么,有没有人有同样的问题?
感谢您的帮助。
答案 0 :(得分:4)
要了解这里发生了什么,你需要了解熊猫正在做什么。我要简化一下,因为有很多花里胡哨和特殊情况要考虑,但大致看起来像这样:
假设您有一个带有各种数字列的Pandas DataFrame
对象df
(我们将忽略日期时间列,分类列等)。当你计算df.sum()
时,Pandas:
sum
function到具有axis=0
的二维数组,以计算列总和。这是重要的第一步。 DataFrame
的列可能具有不同的dtypes,但是2d NumPy数组只能有一个dtype。如果df
混合了float32
和int32
列(例如),Pandas必须同时选择适合两个列的单个dtype,在这种情况下,它会选择{{1 }}。因此,当计算总和时,它使用双精度算法在双精度值上计算。这是你的第二个例子中发生的事情。
另一方面,如果您首先减少到float64
列,那么Pandas可以并将使用float32
dtype作为2d NumPy数组,因此{{ 1}}计算以单精度执行。这就是你的第一个例子中发生的事情。
以下是一个显示此操作的简单示例:我们将分别设置一个包含1百万行和三列dtypes float32
,sum
和float32
的DataFrame。所有的值都是:
float32
现在,当我们直接计算总和时,Pandas首先将所有内容都转换为int32
s。对于所有三列,使用>>> 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
类型也进行了计算,我们得到了准确的答案。
float64
但是,如果我们首先将数据帧减少到float64
列,那么>>> df.sum()
A 100000000.0
B 100000000.0
C 100000000.0
dtype: float64
- 算术用于总和,我们得到的答案非常差。
float32
不准确当然是由于使用的dtype对于有问题的任务没有足够的精确度:在求和的某个时刻,我们最终反复将float32
添加到>>> df[['A', 'B']].sum()
A 16777216.0
B 16777216.0
dtype: float32
,每次都获得1.0
,感谢usual floating-point problems。解决方案是在进行计算之前自己明确地转换为16777216.0
。
然而,这并不是熊猫为我们带来惊喜的结束。使用与上面相同的数据框,让我们尝试只计算列16777216.0
的总和:
float64
突然间我们再次获得完全准确!发生什么了?这与dtypes几乎没有关系:我们仍在使用"A"
进行求和。它现在是第二个步骤(NumPy求和),它负责区别。发生的事情是,NumPy可以(有时会)使用更准确的求和算法(称为pairwise summation)和>>> df[['A']].sum()
A 100000000.0
dtype: float32
dtype以及我们正在使用的大小数组,这种准确性可以产生非常重要的意义与最终结果的差异。但是,它只在沿着阵列的变化最快的轴求和时才使用该算法;有关相关讨论,请参阅this NumPy issue。在我们计算列float32
和列float32
的总和的情况下,我们最终会得到一个形状为"A"
的值数组。变化最快的轴是轴1,我们计算沿轴0的和,所以使用了朴素求和算法,结果很差。但是如果我们只询问列"B"
的总和,我们就会得到准确的求和结果,使用成对求和来计算。
总而言之,当使用这个大小的DataFrame时,你要小心(a)尽可能使用双精度而不是单精度,并且(b)为NumPy做出不同的输出结果的差异做好准备算法选择。
答案 1 :(得分:1)
np.float32
相对于np.float64
np.finfo(np.float32)
finfo(resolution=1e-06, min=-3.4028235e+38, max=3.4028235e+38, dtype=float32)
和
np.finfo(np.float64)
finfo(resolution=1e-15, min=-1.7976931348623157e+308, max=1.7976931348623157e+308, dtype=float64)
一个人为的例子
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