为啥熊猫这么快?如何定义这样的功能?

Posted

技术标签:

【中文标题】为啥熊猫这么快?如何定义这样的功能?【英文标题】:Why is Pandas so madly fast? How to define such functions?为什么熊猫这么快?如何定义这样的功能? 【发布时间】:2019-11-22 21:40:14 【问题描述】: 我尝试比较 Pandas传统循环 的性能。我意识到,与传统循环相比,相同的输入和输出Pandas 的计算速度非常快

我的代码:

#df_1h has been imported before

import time

n = 14
pd.options.display.max_columns = 8
display("df_1h's Shape  rows x  columns".format(df_1h.shape[0], df_1h.shape[1]))

close = df_1h['close']

start = time.time()
df_1h['sma_14_pandas'] = close.rolling(14).mean()
end = time.time()
display('pandas: '.format(end - start))

start = time.time()
df_1h['sma_14_loop'] = np.nan
for i in range(n-1, df_1h.shape[0]):
    df_1h['sma_14_loop'][i] = close[i-n+1:i+1].mean()
end = time.time()
display('loop: '.format(end - start))

display(df_1h.tail())

输出:

"df_1h's Shape 16598 rows x 15 columns"
'pandas: 0.0030088424682617188'
'loop: 7.2529966831207275'
        open_time       open        high        low         ... ignore  rsi_14  sma_14_pandas   sma_14_loop
16593   1.562980e+12    11707.39    11739.90    11606.04    ... 0.0 51.813151   11646.625714    11646.625714
16594   1.562983e+12    11664.32    11712.61    11625.00    ... 0.0 49.952679   11646.834286    11646.834286
16595   1.562987e+12    11632.64    11686.47    11510.00    ... 0.0 47.583619   11643.321429    11643.321429
16596   1.562990e+12    11582.06    11624.04    11500.00    ... 0.0 48.725262   11644.912857    11644.912857
16597   1.562994e+12    11604.96    11660.00    11588.16    ... 0.0 50.797087   11656.723571    11656.723571
5 rows × 15 columns
Pandas 几乎快于 2.5k 倍!!!

我的问题:

我的代码错误吗? 如果我的代码是正确的,为什么 Pandas 这么快? 如何为 Pandas 定义运行如此之快的自定义函数

【问题讨论】:

Pandas 的滚动功能特别非常快,比您的时间显示的循环快得多 附带说明,您应该避免使用链式索引,而是使用 panda 的 lociloc 索引器,查找更多 here @yatu 我完全支持你的建议,但我注意到将df_1h['SMA_14_loop'][i] =... 替换为df_1h.loc[i,'SMA_14_loop'] = ... 将循环算法的执行时间增加了30%(在描述的测试中从7.6 秒到10.1 秒)我的答案)。你知道任何类似的比较吗?或者你有解释吗? 【参考方案1】:

关于你的三个问题:

    您的代码是正确的,因为它产生了正确的结果。通常,显式迭代数据帧的行是一项规则,但在性能方面并不是一个好主意。大多数情况下,通过 pandas 方法可以更有效地实现相同的结果(正如您自己演示的那样)。 Pandas 如此之快是因为它在后台使用了 numpy。 Numpy 实现了高效的数组操作。此外,熊猫的原创者韦斯·麦金尼 (Wes McKinney) 有点痴迷于效率和速度。 使用 numpy 或其他优化的库。我建议阅读 pandas 文档的 Enhancing performance 部分。如果您不能使用内置的 pandas 方法,如果检索数据框或系列的 numpy 表示通常是有意义的(使用 value 属性或 to_numpy() 方法),则对 numpy 数组进行所有计算,并且只然后将结果存储回数据框或系列。

为什么循环算法这么慢?

在您的循环算法中,mean 被计算了 16500 次,每次加起来 14 个元素以求均值。 Pandas 的rolling 方法使用了更复杂的方法,大大减少了算术运算的数量。

如果您在 numpy 中进行计算,您可以获得与 pandas 相似(实际上大约 3 倍)的性能。这在以下示例中进行了说明:

import pandas as pd
import numpy as np
import time

data = np.random.uniform(10000,15000,16598)
df_1h = pd.DataFrame(data, columns=['Close'])
close = df_1h['Close']
n = 14
print("df_1h's Shape  rows x  columns".format(df_1h.shape[0], df_1h.shape[1]))

start = time.time()
df_1h['SMA_14_pandas'] = close.rolling(14).mean()
print('pandas: '.format(time.time() - start))

start = time.time()
df_1h['SMA_14_loop'] = np.nan
for i in range(n-1, df_1h.shape[0]):
    df_1h['SMA_14_loop'][i] = close[i-n+1:i+1].mean()
print('loop:   '.format(time.time() - start))

def np_sma(a, n=14) :
    ret = np.cumsum(a)
    ret[n:] = ret[n:] - ret[:-n]
    return np.append([np.nan]*(n-1), ret[n-1:] / n)

start = time.time()
df_1h['SMA_14_np'] = np_sma(close.values)
print('np:     '.format(time.time() - start))

assert np.allclose(df_1h.SMA_14_loop.values, df_1h.SMA_14_pandas.values, equal_nan=True)
assert np.allclose(df_1h.SMA_14_loop.values, df_1h.SMA_14_np.values, equal_nan=True)

输出:

df_1h's Shape 16598 rows x 1 columns
pandas: 0.0031278133392333984
loop:   7.605962753295898
np:     0.0010571479797363281

【讨论】:

感谢您的精彩回答。请你再看看这里的一个。 ***.com/q/57006437/6452246

以上是关于为啥熊猫这么快?如何定义这样的功能?的主要内容,如果未能解决你的问题,请参考以下文章

为啥当我将熊猫数据框用作具有多处理功能的函数的输入时它不会改变

ServiceContract 对命名空间的定义如何(为啥)影响功能

Elasticsearch为啥这么快

Elasticsearch为啥这么快

在熊猫滚动中应用自定义窗口功能

如何正确使用带有应用功能的熊猫 groupby 来解决副作用? (第一组申请两次)