一般原则
这是我的尝试。
我使用的函数几乎可以做到这一点:np.lib.stride_tricks.sliding_window
。
除了它显示前向值外,当您想要做的是后向值时(在 t 时,您希望将 t-1 处的值视为“预测 J+1”,将 t-2 处的值视为“预测 J+2”,等等。更一般地说,您需要最后 14 个值来构建预测)。
但这很容易解决:只需在开头添加一些虚拟 NaN 即可。因此,第一行中的“预测 J+1”为 NaN (您无法使用昨天的值来预测第一行的值,因为您没有昨天的任何值)
结果与您迄今为止得到的其他两个答案之一非常相似。相同的 2D 数据框。使用不同的格式(一种是使用日期和预测作为 2 个索引)和填充(一种是用今天的值填充未知值)
但时间安排并非如此。
Code
import pandas as pd
import numpy as np
import datetime
# Data generation
N=1000
dt=datetime.timedelta(days=1)
dates=[datetime.date(2012,1,1)+k*dt for k in range(N)]
temps=np.random.normal(100,15,(N,))
df = pd.DataFrame({'time':dates, 'temp':temps})
# slideWindowMethod
def mslide():
T=np.concatenate(([np.nan]*14, df['temp'].values))
TwithShift=np.lib.stride_tricks.sliding_window_view(T, 15)
return pd.concat([df, pd.DataFrame(TwithShift[:,:-1], columns=[f'Pred_J+{i}' for i in range(14,0,-1)])], axis=1)
dfWithPred=mslide()
(使用 arg less 函数只是因为它更容易使用 timeit。但是当然,df 和 14 应该是参数)
解释
主要思想在于这个sliding_window_view
功能。
M=np.arange(10)
np.lib.stride_tricks.sliding_window_view(M, 4)
⇒
array([[0, 1, 2, 3],
[1, 2, 3, 4],
[2, 3, 4, 5],
[3, 4, 5, 6],
[4, 5, 6, 7],
[5, 6, 7, 8],
[6, 7, 8, 9]])
请注意,它在以 6 开头的行处停止,因为我们没有其他行的未来值。没问题,稍后再看。除此之外,第一列是原始数组。
它的优点之一(尽管我们在这里不利用它,因为我们将在最后构建一个数组)是它只是一个视图。这里没有建立新的阵列。所以,即使数组 M 中有 1 亿个数字,你仍然可以得到sliding_window_view(M,1000)
包含 M 从 0 到 1000 的所有“移位”的数组,无需为 1000 亿个数字分配内存。
使用历史数据执行矢量化操作非常方便。
因为这里它显示未来值,而不是过去值(这就是您想要的:过去值,因为说“J+1 行的预测是今天的值”与说“今天的预测是 J-1 处的值”相同) ”,只是 J) 的含义不同,您可以在移位之前轻松地用 NaN 填充数组。
T=np.arange(10)
TwithNa=np.concatenate(([np.nan]*3, T))
np.lib.stride_tricks.sliding_window_view(TwithNa, 4)
⇒
array([[nan, nan, nan, 0.],
[nan, nan, 0., 1.],
[nan, 0., 1., 2.],
[ 0., 1., 2., 3.],
[ 1., 2., 3., 4.],
[ 2., 3., 4., 5.],
[ 3., 4., 5., 6.],
[ 4., 5., 6., 7.],
[ 5., 6., 7., 8.],
[ 6., 7., 8., 9.]])
相同的数组,但第一列之前有 3 个 NaN。因此我们有 10 行。通过这种移位,最后一列与原始数组相同。之前的列是前一个、前一个前一个等值。
休息只是用它填充数据框的问题。
Timings
但当然,它真正的魅力在于时机。这是我的 timeit 结果与我的方法,以及您到目前为止获得的其他两种方法
Method |
Time μs |
Naive for |
1022001.3829995878 |
Apply/join |
158593.13219989417 |
ffill |
67126.1526333789 |
Sliding window |
343.24235070089344 |
你看,这不仅仅是一个优化。它改变了游戏规则!
请注意,一般来说 apply 并不比使用简单的 for 循环访问数据帧好多少.iloc
。有时甚至更慢。大多数时候都是非常令人失望的。在这种情况下,情况还不错。 ×6的时间增益,往往远小于此。然而,与您没有的 ×3000 相比,这没什么可比的。所以当你说
但是,我读到建议不要迭代行,并且它
最好使用 pandas 之类的东西来“矢量化”数据
但我不知道该怎么做。
我显然同意第一部分。但我会放apply
与迭代行位于同一架子上。大多数时候都是一样的。尽量避免它总是更好。