python / Pandas : bug in formula evaluation in map/lambda?

标签 python dictionary pandas lambda

在数据帧上应用公式时,我在 python (2.7.6.2)/pandas(0.13,也在 0.18 中)遇到了一个奇怪的问题。显然,当使用 map/lambda 而不是直接应用于数字时,公式的结果是不同的。这对我来说似乎是一个错误,我很好奇分别是什么原因如何避免此类问题。

...

我现在以一种允许重现它的方式准备了案例,这使问题更加清晰:

data15min = [ 5.4753, 5.4863, 5.2497, 5.057, 5.0917, 5.3467, 5.7513, 5.6, 5.342 ]
index     = pd.date_range("2015-10-17 18:00:00", periods=9, freq='15T')
columns = ['v03']

df15 = pd.DataFrame(data15min, index=index, columns=columns)
df_h = df15.rolling(min_periods=4,window=4,center=False).mean()
df_m = df_h['v03'].map(lambda x: np.nan if np.isnan(x) else int(x*100.))

df_h 的最后一个值是计算错误的值。该值本身看起来不错(5.3467、5.7513、5.6、5.342 的平均值恰好是 5.51):

In [99]: df_h
Out[99]: 
v03
2015-10-17 18:00:00 NaN
2015-10-17 18:15:00 NaN
2015-10-17 18:30:00 NaN
2015-10-17 18:45:00 5.317075
2015-10-17 19:00:00 5.221175
2015-10-17 19:15:00 5.186275
2015-10-17 19:30:00 5.311675
2015-10-17 19:45:00 5.447425
2015-10-17 20:00:00 5.510000

在用 map 应用公式后,我得到了 550:

In [100]: df_m
Out[100]: 
2015-10-17 18:00:00      NaN
2015-10-17 18:15:00      NaN
2015-10-17 18:30:00      NaN
2015-10-17 18:45:00    531.0
2015-10-17 19:00:00    522.0
2015-10-17 19:15:00    518.0
2015-10-17 19:30:00    531.0
2015-10-17 19:45:00    544.0
2015-10-17 20:00:00    550.0
Freq: 15T, Name: v03, dtype: float64

我认为这是由于数字表示不准确造成的,但是当直接对数字应用公式时我得到了不同的行为:

In [103]: int(np.mean([5.3467, 5.7513, 5.6, 5.342])*100.)
Out[103]: 551

为了使混淆更加彻底,当具有相同相关值的数据帧稍短时,我也会使用 map 得到不同的结果:

data15min = [  5.3467, 5.7513, 5.6, 5.342 ]
index     = pd.date_range("2015-10-17 19:15:00", periods=4, freq='15T')
columns = ['v03']

df15 = pd.DataFrame(data15min, index=index, columns=columns)
df_h = df15.rolling(min_periods=4,window=4,center=False).mean()
df_m = df_h['v03'].map(lambda x: np.nan if np.isnan(x) else int(x*100.))

In [104]: df_m
Out[104]: 
2015-10-17 19:15:00 NaN
2015-10-17 19:30:00 NaN
2015-10-17 19:45:00 NaN
2015-10-17 20:00:00 551.0
Freq: 15T, Name: v03, dtype: float64

我很困惑,担心得到错误的结果。如果这与不准确的内部数字表示有关(如果此问题在所示案例中表现不同,那将是令人惊讶的),我真的很想知道如何避免从中得到错误的结果。

最佳答案

这是一个浮点精度问题。 df_h['v03'] 中的最后一个值实际上比 5.51 小一点:

x = df_h['v03'].iloc[-1]
print repr(x)
print repr(x * 100.)
print int(x * 100.)

将打印:

5.5099999999999989
550.99999999999989
550

当然这是错误的,因为你写的数字的实际平均值是 5.51,但这就是浮点运算的工作原理。

IIRC 你试图使用前三位数字作为字典中的键。只取值的 100 倍的整数部分是一种非常脆弱的方法,因为非常小的错误可能会改变结果。一个更稳健的方法是四舍五入到 3 位小数:

df_h['v03'].round(3).map(lambda x: np.nan if np.isnan(x) else int(x*100.))

2015-10-17 18:00:00      NaN
2015-10-17 18:15:00      NaN
2015-10-17 18:30:00      NaN
2015-10-17 18:45:00    531.0
2015-10-17 19:00:00    522.0
2015-10-17 19:15:00    518.0
2015-10-17 19:30:00    531.0
2015-10-17 19:45:00    544.0
2015-10-17 20:00:00    551.0
Freq: 15T, Name: v03, dtype: float64

我猜它在某些极端情况下也会失败。

关于所谓的不确定行为,有几种算法可以计算平均值,您不应该假设正在使用 numpy.mean()。其实看起来不是你的情况

print(x == np.mean([5.3467, 5.7513, 5.6, 5.342]))

False

但是你可以告诉 pandas 使用它:

df_h = df15.rolling(min_periods=4, window=4, center=False).apply(np.mean)
x = df_h['v03'].iloc[-1]
print(repr(x))
print(x == np.mean([5.3467, 5.7513, 5.6, 5.342]))

5.5099999999999998
True

虽然 Rolling.mean() 的结果存在实际的不一致:

for i in range(6):
    df_h = df15[i:].rolling(min_periods=4, window=4, center=False).mean()
    x = df_h['v03'].iloc[-1]
    print(repr(x))

5.5099999999999989
5.5099999999999989
5.5099999999999989
5.5099999999999989
5.5100000000000007
5.5099999999999998

如果你使用 numpy.mean() 就不会发生这种情况:

for i in range(6):
    df_h = df15[i:].rolling(min_periods=4, window=4, center=False).apply(np.mean)
    x = df_h['v03'].iloc[-1]
    print(repr(x))

5.5099999999999998
5.5099999999999998
5.5099999999999998
5.5099999999999998
5.5099999999999998
5.5099999999999998

我猜 Rolling.mean() 使用了一些优化(可能重复使用从一个窗口到下一个窗口的计算),这些优化引入了进一步的舍入误差并且不与 .apply()< 一起使用。它实际上比应用 numpy 版本快得多:

def test1(s):
    return s.rolling(min_periods=4, window=4, center=False).mean()

def test2(s):
    return s.rolling(min_periods=4, window=4, center=False).apply(np.mean)

s = pd.Series(np.random.randn(10000))

%timeit test1(s)

1000 loops, best of 3: 316 µs per loop

%timeit test2(s)

10 loops, best of 3: 84.9 ms per loop

这可能是由于使用 .apply() 的开销所致。我真的不太了解它的内部结构。

关于使用 float (或从 float 派生的值)作为查找键,请尽可能避免使用它。测试它们是否相等很容易出错。

如果您真的需要它,您可以四舍五入到允许您区分不同数字的最低小数位(在您的情况下是 2 位小数?),并使用四舍五入的值作为键。如果您将错误保持在很小的范围内,您就不应该出现虚假匹配/不匹配。

请记住,还有几个轮函数,特别是 math.round() 在 python2 和 python3 中表现不同。我认为这不会影响 numpy 或 pandas 中的 round(),但无论如何请确保在创建键和查找键时以相同的方式进行舍入。

关于 python / Pandas : bug in formula evaluation in map/lambda?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/35950171/

相关文章:

python - 在python中合并2个字典

python - 类型错误 : cannot convert dictionary update sequence element #0 to a sequence - Odoo v10 community

python - 如何在 Python 中使用 sklearn 对模型进行单一预测?

Python pandas 数据帧输出格式

python - 从文件中提取行的子集

python - 在 Python 中使用带有 R 语法的 statsmodels.api 进行逻辑回归

python - 创建一个包含文件每一行的列表

javascript将嵌套字典转换为树状数组结构

python - 显示整个数据集,Python

python - Python 条形图中的斜条