[英]Most computational-time efficient/fastest way to compute rolling (linear) regression in Python (Numpy or Pandas)
我需要做非常非常快速和有效的滾動線性回歸的方法。 我查看了這兩個線程:
進行滾動線性回歸的有效方法Rolling linear regression
從他們那里,我推斷 numpy 是(計算上)最快的。 但是,使用我的(有限的)python 技能,我發現計算同一組滾動數據的時間是***相同的***。
有沒有比我在下面發布的 3 種方法中的任何一種更快的計算方法? 我本以為 numpy 方式要快得多,但不幸的是,事實並非如此。
########## testing time for pd rolling vs numpy rolling
def fitcurve(x_pts):
poly = np.polyfit(np.arange(len(x_pts)), x_pts, 1)
return np.poly1d(poly)[1]
win_ = 30
# tmp_ = data_.Close
tmp_ = pd.Series(np.random.rand(10000))
s_time = time.time()
roll_pd = tmp_.rolling(win_).apply(lambda x: fitcurve(x)).to_numpy()
print('pandas rolling time is', time.time() - s_time)
plt.show()
pd.Series(roll_pd).plot()
########
s_time = time.time()
roll_np = np.empty(0)
for cnt_ in range(len(tmp_)-win_):
tmp1_ = tmp_[cnt_:cnt_+ win_]
grad_ = np.linalg.lstsq(np.vstack([np.arange(win_), np.ones(win_)]).T, tmp1_, rcond = None)[0][0]
roll_np = np.append(roll_np, grad_)
print('numpy rolling time is', time.time() - s_time)
plt.show()
pd.Series(roll_np).plot()
#################
s_time = time.time()
roll_st = np.empty(0)
from scipy import stats
for cnt_ in range(len(tmp_)-win_):
slope, intercept, r_value, p_value, std_err = stats.linregress(np.arange(win_), tmp_[cnt_:cnt_ + win_])
roll_st = np.append(roll_st, slope)
print('stats rolling time is', time.time() - s_time)
plt.show()
pd.Series(roll_st).plot()
我的答案是
view = np.lib.stride_tricks.sliding_window_view(tmp_, (win_,))
xxx=np.vstack([np.arange(win_), np.ones(win_)]).T
roll_mat=(np.linalg.inv(xxx.T @ xxx) @ (xxx.T) @ view.T)[0]
計算時間為 1.2 毫秒,而 pandas 和 numpy 版本需要 2 秒,統計版本需要 3.5 秒。
一種方法可能是使用sliding_window_view
將您的tmp_
數組轉換為 window 的數組(假的:它只是一個視圖,而不是真正的 10000x30 數據數組。它只是tmp_
但視圖不同。因此_view
在function 姓名)。
沒有直接優勢。 但是,從那里,您可以嘗試利用矢量化。
我用兩種不同的方式來做:一種簡單的,一種需要花一分鍾的時間思考。 由於我將最佳答案放在第一位,因此此消息的 rest 可能會按時間順序出現不一致(當上一個答案稍后出現時,我會說“在我之前的答案中”之類的話),但我試圖一致地編輯兩個答案。
這樣做的一種方法(因為lstsq
是罕見的 numpy 方法,不會自然地這樣做)是 go 回到lstsq(X,Y)
在現實中所做的事情:它計算(XᵀX)⁻¹Xᵀ Y
所以讓我們這樣做吧。 在 python 中, xxx
是 X 數組(在您的示例中是 arange 和 1),並將 windows 的數組view
到您的數據(即view[i]
是tmp_[i:i+win_]
),這將是np.linalg.inv(xxx.T@xxx)@xxx.T@view[i]
因為我是每一行。 我們可以使用np.vectorize
對該操作進行矢量化以避免迭代i
,就像我在第一個解決方案中所做的那樣(見下文)。 但問題是,我們不需要。 那只是一個矩陣乘以一個向量。 並且為向量數組中的每個向量計算矩陣乘以向量的操作只是矩陣乘法!
因此我的第二個(也可能是最后一個)答案
view = np.lib.stride_tricks.sliding_window_view(tmp_, (win_,))
xxx=np.vstack([np.arange(win_), np.ones(win_)]).T
roll_mat=(np.linalg.inv(xxx.T @ xxx) @ (xxx.T) @ view.T)[0]
roll_mat
仍然與 roll_np 相同(多了一行,因為你的roll_np
比最后一行停止了一行)(請參閱下面的圖形證明和我的第一個答案。我可以為這個提供一個新圖像,但它與我已經用過的那個)。 如此相同的結果(毫不奇怪我應該說......但有時當事情完全按照理論所說的那樣工作時仍然令人驚訝)
但時機,是另一回事。 正如承諾的那樣,與真正的矢量化可以做的相比,我之前的因子 4 微不足道。 請參閱更新的時間表:
方法 | 時間 |
---|---|
pandas | 2.10 秒 |
numpy卷 | 2.03 秒 |
狀態 | 3.58 秒 |
numpy 查看/矢量化(見下文) | 0.46 秒 |
numpy 查看/matmult | 1.2 毫秒 |
與其他“s”相比,重要的部分是“ms”。 所以,這個時間因子是 1700 !
一個蹩腳的方法,一旦我們有了這個view
,就可以從那里使用np.vectorize
。 我稱之為lame
的,因為vectorize
不應該是有效的。 它只是一個用另一個名字調用的 for 循環。 官方文檔明確表示“不用於性能”。 然而,這將是您代碼的改進
view = np.lib.stride_tricks.sliding_window_view(tmp_, (win_,))
xxx=np.vstack([np.arange(win_), np.ones(win_)]).T
f = np.vectorize(lambda y: np.linalg.lstsq(xxx,y,rcond=None)[0][0], signature='(n)->()')
roll_vectorize=f(view)
首先讓我們驗證結果
plt.scatter(f(view)[:-1], roll_np))
因此,很明顯,結果與roll_np
相同(我以相同的方式檢查過,與其他兩個結果相同。索引的變化也相同,因為所有 3 種方法的邊界策略都不相同)
有趣的部分,時間:
方法 | 時間 |
---|---|
pandas | 2.10 秒 |
numpy卷 | 2.03 秒 |
狀態 | 3.58 秒 |
numpy 查看/矢量化 | 0.46 秒 |
所以,你看,它不應該是為了性能,然而,我用它獲得了 x4 倍的收益。
我很確定一個更矢量化的方法(唉,lstsq 不允許直接使用它,不像大多數 numpy 函數)會更快。
首先,如果您需要一些優化 python 代碼的技巧,我相信這個播放列表可能會對您有所幫助。
為了讓它更快; “追加”從來都不是一個好方法,你把它想象成 memory,每次你 append 時,python 可能會創建一個更大尺寸的全新列表(可能是 n+1;其中 n 是舊尺寸)並復制最后一個項目(這將是 n 個地方),最后一個項目將被添加到最后一個地方。
所以當我把它改成如下
########## testing time for pd rolling vs numpy rolling
def fitcurve(x_pts):
poly = np.polyfit(np.arange(len(x_pts)), x_pts, 1)
return np.poly1d(poly)[1]
win_ = 30
# tmp_ = data_.Close
tmp_ = pd.Series(np.random.rand(10000))
s_time = time.time()
roll_pd = tmp_.rolling(win_).apply(lambda x: fitcurve(x)).to_numpy()
print('pandas rolling time is', time.time() - s_time)
plt.show()
pd.Series(roll_pd).plot()
########
s_time = time.time()
roll_np = np.zeros(len(tmp_)-win_) ### Change
for cnt_ in range(len(tmp_)-win_):
tmp1_ = tmp_[cnt_:cnt_+ win_]
grad_ = np.linalg.lstsq(np.vstack([np.arange(win_), np.ones(win_)]).T, tmp1_, rcond = None)[0][0]
roll_np[cnt_] = grad_ ### Change
# roll_np = np.append(roll_np, grad_) ### Change
print('numpy rolling time is', time.time() - s_time)
plt.show()
pd.Series(roll_np).plot()
#################
s_time = time.time()
roll_st = np.empty(0)
from scipy import stats
for cnt_ in range(len(tmp_)-win_):
slope, intercept, r_value, p_value, std_err = stats.linregress(np.arange(win_), tmp_[cnt_:cnt_ + win_])
roll_st = np.append(roll_st, slope)
print('stats rolling time is', time.time() - s_time)
plt.show()
pd.Series(roll_st).plot()
我從第一個地方開始初始化數組,其大小為(len(tmp_)-win_ in range),稍后為其分配值,速度更快。
還有一些其他的技巧你可以做,Python 是解釋性語言,意思是每次它需要一行,將其轉換為機器代碼,然后執行它,它對每一行都這樣做。 這意味着如果你可以在一行中做多件事,這意味着它們將一次轉換為機器代碼,它應該更快,例如,考慮列表理解。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.