使用不可观察的组件模型模拟时间序列

时间:2018-08-16 16:00:59

标签: python scipy statsmodels

使用UnobservedComponents中的statsmodels拟合局部模型后,我们正在尝试寻找方法来模拟带有结果的新时间序列。像这样:

import numpy as np
import statsmodels as sm
from statsmodels.tsa.statespace.structural import UnobservedComponents

np.random.seed(12345)
ar = np.r_[1, 0.9]
ma = np.array([1])
arma_process = sm.tsa.arima_process.ArmaProcess(ar, ma)

X = 100 + arma_process.generate_sample(nsample=100)
y = 1.2 * x + np.random.normal(size=100)
y[70:] += 10

plt.plot(X, label='X')
plt.plot(y, label='y')
plt.axvline(69, linestyle='--', color='k')
plt.legend();

time series example

ss = {}
ss["endog"] = y[:70]
ss["level"] = "llevel"
ss["exog"] = X[:70]

model = UnobservedComponents(**ss)
trained_model = model.fit()

在给定外生变量trained_model的情况下,是否可以使用X[70:]模拟新的时间序列?正如我们拥有arma_process.generate_sample(nsample=100)一样,我们想知道是否可以做类似的事情:

trained_model.generate_random_series(nsample=100, exog=X[70:])

其背后的动机是,我们可以计算出与观察到的y[70:]一样极端的时间序列的概率(用于识别响应的p值大于预测的值)。

[编辑]

在阅读约瑟夫(Josef)和克夫顿(cfulton)的评论后,我尝试实现以下内容:

mod1 = UnobservedComponents(np.zeros(y_post), 'llevel', exog=X_post)
mod1.simulate(f_model.params, len(X_post))

但这导致模拟似乎无法跟踪predicted_mean预测的X_post。这是一个示例:

enter image description here

尽管y_post徘徊在100左右,但模拟值为-400。这种方法总是导致p_value为50%。

因此,当我尝试使用initial_sate=0和随机冲击时,结果如下:

enter image description here

现在看来,模拟遵循的是预测的均值及其95%可信区间(如下文cfulton所述,这实际上是错误的方法,并且它替代了训练模型的水平方差)。

我尝试使用这种方法只是为了观察我观察到的p值。这是我计算p值的方法:

samples = 1000
r = 0
y_post_sum = y_post.sum()
for _ in range(samples):
    sim = mod1.simulate(f_model.params, len(X_post), initial_state=0, state_shocks=np.random.normal(size=len(X_post)))
    r += sim.sum() >= y_post_sum
print(r / samples)

就上下文而言,这是Google开发的Causal Impact模型。由于已在R中实现,因此我们一直在尝试使用statsmodels作为处理时间序列的核心在Python中复制实现。

我们已经有了一个很酷的WIP implementation,但是我们仍然需要具有p值才能知道实际上什么时候我们产生的影响不能仅仅由随机性来解释(模拟序列和对序列进行计数的方法)。 Google的模型中也实现了总和超过y_post.sum()的广告。

在我的示例中,我使用了y[70:] + =10。如果我只添加一个而不是十,则Google的p值计算将返回0.001(在y中有影响),而在Python的方法返回0.247(无影响)。

仅当我向y_post添加+5时,该模型返回的p_value值为0.02并且低于0.05,我们认为y_post中会有影响。

我正在使用python3,statsmodels版本为0.9.0

[EDIT2]

阅读cfulton的评论后,我决定完全调试代码以查看发生了什么。这是我发现的:

当我们创建类型为UnobservedComponents的对象时,最终将启动卡尔曼滤波器的表示。默认情况下,它receives the parameter initial_variance为1e6,它设置了对象的same property

当我们运行simulate方法时,initial_state_covis created使用相同的值:

initial_state_cov = (
        np.eye(self.k_states, dtype=self.ssm.transition.dtype) *
        self.ssm.initial_variance
    )

最后,使用相同的值来查找initial_state

initial_state = np.random.multivariate_normal(
    self._initial_state, self._initial_state_cov)

这将导致标准差为1e6的正态分布。

然后我尝试运行以下内容:

mod1 = UnobservedComponents(np.zeros(len(X_post)), level='llevel', exog=X_post, initial_variance=1)
sim = mod1.simulate(f_model.params, len(X_post))
plt.plot(sim, label='simul')
plt.plot(y_post, label='y')
plt.legend();
print(sim.sum() > y_post.sum())

导致的结果:

enter image description here

然后,我测试了p值,最后测试了y_post中+1的变化,该模型现在可以正确识别添加的信号。

不过,当我使用R的Google软件包中的相同数据进行测试时,p值仍处于关闭状态。也许需要进一步调整输入以提高其准确性。

1 个答案:

答案 0 :(得分:5)

@Josef是正确的,并且您通过以下方式做了正确的事情:

mod1 = UnobservedComponents(np.zeros(y_post), 'llevel', exog=X_post)
mod1.simulate(f_model.params, len(X_post))

simulate方法根据所讨论的模型来模拟数据,这就是为什么当您有外生变量时不能直接使用trained_model进行模拟的原因。

  

但是由于某些原因,模拟结果总是低于y_post。

我认为这应该是可以预期的-运行您的示例并查看估计的系数,我们得到:

                       coef    std err          z      P>|z|      [0.025      0.975]
------------------------------------------------------------------------------------
sigma2.irregular     0.9278      0.194      4.794      0.000       0.548       1.307
sigma2.level         0.0021      0.008      0.270      0.787      -0.013       0.018
beta.x1              1.1882      0.058     20.347      0.000       1.074       1.303

级别的差异非常小,这意味着根据您指定的模型,级别极端不太可能在单个期间内上升近10%。

使用时:

mod1.simulate(f_model.params, len(X_post), initial_state=0, state_shocks=np.random.normal(size=len(X_post))

发生的事情是,这里的水平项是唯一未观察到的状态,并且通过为自己的冲击提供方差等于1,实际上可以覆盖模型实际估算的水平方差。 我认为在这里将初始状态设置为0不会产生太大影响。(请参见编辑)。

您写:

  

p值计算更接近,但仍然不正确。

我不确定这意味着什么-您为什么期望模型认为可能会发生这种跳跃?您期望达到什么p值?

修改

感谢进一步调查(在编辑2中)。首先,我认为您应该做的是:

mod1 = UnobservedComponents(np.zeros(y_post), 'llevel', exog=X_post)
initial_state = np.random.multivariate_normal(
    f_model.predicted_state[..., -1], f_model.predicted_state_cov[..., -1])
mod1.simulate(f_model.params, len(X_post), initial_state=initial_state)

现在,说明:

在Statsmodels 0.9中,我们尚未使用弥散初始化对状态进行精确处理(不过,自那时以来,它已被合并,这是我在测试之前无法复制您的结果的原因之一。您使用0.9代码库的示例)。这些“最初分散的”状态并不意味着我们可以解决(例如随机游走过程)的长期运行,而在本地级别的情况下,该状态就是这样的状态。

“近似”扩散初始化涉及将初始状态均值设置为零,并将初始状态方差设置为大数(如您所发现的那样)。

对于仿真,默认情况下,初始状态是从给定的初始状态分布中采样的。由于此模型是使用近似扩散初始化进行初始化的,因此可以解释为什么您的过程会围绕某个随机数进行初始化。

您的解决方案是一个很好的补丁,但是它不是最佳的,因为它没有将模拟周期的初始状态基于估计的模型/数据的最后状态。这些值由f_model.predicted_state[..., -1]f_model.predicted_state_cov[..., -1]给出。