File: statespace_fixed_params.py

package info (click to toggle)
statsmodels 0.14.6%2Bdfsg-1
  • links: PTS, VCS
  • area: main
  • in suites: sid
  • size: 49,956 kB
  • sloc: python: 254,365; f90: 612; sh: 560; javascript: 337; asm: 156; makefile: 145; ansic: 32; xml: 9
file content (204 lines) | stat: -rw-r--r-- 7,305 bytes parent folder | download | duplicates (3)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
#!/usr/bin/env python
# coding: utf-8

# DO NOT EDIT
# Autogenerated from the notebook statespace_fixed_params.ipynb.
# Edit the notebook and then sync the output with this file.
#
# flake8: noqa
# DO NOT EDIT

# ## Estimating or specifying parameters in state space models
#
# In this notebook we show how to fix specific values of certain
# parameters in statsmodels' state space models while estimating others.
#
# In general, state space models allow users to:
#
# 1. Estimate all parameters by maximum likelihood
# 2. Fix some parameters and estimate the rest
# 3. Fix all parameters (so that no parameters are estimated)
#

from importlib import reload
import numpy as np
import pandas as pd
import statsmodels.api as sm
import matplotlib.pyplot as plt

from pandas_datareader.data import DataReader

# To illustrate, we will use the Consumer Price Index for Apparel, which
# has a time-varying level and a strong seasonal component.

endog = DataReader('CPIAPPNS', 'fred', start='1980').asfreq('MS')
endog.plot(figsize=(15, 3))

# It is well known (e.g. Harvey and Jaeger [1993]) that the HP filter
# output can be generated by an unobserved components model given certain
# restrictions on the parameters.
#
# The unobserved components model is:
#
# $$
# \begin{aligned}
# y_t & = \mu_t + \varepsilon_t & \varepsilon_t \sim N(0,
# \sigma_\varepsilon^2) \\
# \mu_t &= \mu_{t-1} + \beta_{t-1} + \eta_t & \eta_t \sim N(0,
# \sigma_\eta^2) \\
# \beta_t &= \beta_{t-1} + \zeta_t & \zeta_t \sim N(0, \sigma_\zeta^2) \\
# \end{aligned}
# $$
#
# For the trend to match the output of the HP filter, the parameters must
# be set as follows:
#
# $$
# \begin{aligned}
# \frac{\sigma_\varepsilon^2}{\sigma_\zeta^2} & = \lambda \\
# \sigma_\eta^2 & = 0
# \end{aligned}
# $$
#
# where $\lambda$ is the parameter of the associated HP filter. For the
# monthly data that we use here, it is usually recommended that $\lambda =
# 129600$.

# Run the HP filter with lambda = 129600
hp_cycle, hp_trend = sm.tsa.filters.hpfilter(endog, lamb=129600)

# The unobserved components model above is the local linear trend, or
# "lltrend", specification
mod = sm.tsa.UnobservedComponents(endog, 'lltrend')
print(mod.param_names)

# The parameters of the unobserved components model (UCM) are written as:
#
# - $\sigma_\varepsilon^2 = \text{sigma2.irregular}$
# - $\sigma_\eta^2 = \text{sigma2.level}$
# - $\sigma_\zeta^2 = \text{sigma2.trend}$
#
# To satisfy the above restrictions, we will set $(\sigma_\varepsilon^2,
# \sigma_\eta^2, \sigma_\zeta^2) = (1, 0, 1 / 129600)$.
#
# Since we are fixing all parameters here, we do not need to use the `fit`
# method at all, since that method is used to perform maximum likelihood
# estimation. Instead, we can directly run the Kalman filter and smoother at
# our chosen parameters using the `smooth` method.

res = mod.smooth([1., 0, 1. / 129600])
print(res.summary())

# The estimate that corresponds to the HP filter's trend estimate is given
# by the smoothed estimate of the `level` (which is $\mu_t$ in the notation
# above):

ucm_trend = pd.Series(res.level.smoothed, index=endog.index)

# It is easy to see that the estimate of the smoothed level from the UCM
# is equal to the output of the HP filter:

fig, ax = plt.subplots(figsize=(15, 3))

ax.plot(hp_trend, label='HP estimate')
ax.plot(ucm_trend, label='UCM estimate')
ax.legend()

# ### Adding a seasonal component

# However, unobserved components models are more flexible than the HP
# filter. For example, the data shown above is clearly seasonal, but with
# time-varying seasonal effects (the seasonality is much weaker at the
# beginning than at the end). One of the benefits of the unobserved
# components framework is that we can add a stochastic seasonal component.
# In this case, we will estimate the variance of the seasonal component by
# maximum likelihood while still including the restriction on the parameters
# implied above so that the trend corresponds to the HP filter concept.
#
# Adding the stochastic seasonal component adds one new parameter,
# `sigma2.seasonal`.

# Construct a local linear trend model with a stochastic seasonal
# component of period 1 year
mod = sm.tsa.UnobservedComponents(endog,
                                  'lltrend',
                                  seasonal=12,
                                  stochastic_seasonal=True)
print(mod.param_names)

# In this case, we will continue to restrict the first three parameters as
# described above, but we want to estimate the value of `sigma2.seasonal` by
# maximum likelihood. Therefore, we will use the `fit` method along with the
# `fix_params` context manager.
#
# The `fix_params` method takes a dictionary of parameters names and
# associated values. Within the generated context, those parameters will be
# used in all cases. In the case of the `fit` method, only the parameters
# that were not fixed will be estimated.

# Here we restrict the first three parameters to specific values
with mod.fix_params({
        'sigma2.irregular': 1,
        'sigma2.level': 0,
        'sigma2.trend': 1. / 129600
}):
    # Now we fit any remaining parameters, which in this case
    # is just `sigma2.seasonal`
    res_restricted = mod.fit()

# Alternatively, we could have simply used the `fit_constrained` method,
# which also accepts a dictionary of constraints:

res_restricted = mod.fit_constrained({
    'sigma2.irregular': 1,
    'sigma2.level': 0,
    'sigma2.trend': 1. / 129600
})

# The summary output includes all parameters, but indicates that the first
# three were fixed (and so were not estimated).

print(res_restricted.summary())

# For comparison, we construct the unrestricted maximum likelihood
# estimates (MLE). In this case, the estimate of the level will no longer
# correspond to the HP filter concept.

res_unrestricted = mod.fit()

# Finally, we can retrieve the smoothed estimates of the trend and
# seasonal components.

# Construct the smoothed level estimates
unrestricted_trend = pd.Series(res_unrestricted.level.smoothed,
                               index=endog.index)
restricted_trend = pd.Series(res_restricted.level.smoothed, index=endog.index)

# Construct the smoothed estimates of the seasonal pattern
unrestricted_seasonal = pd.Series(res_unrestricted.seasonal.smoothed,
                                  index=endog.index)
restricted_seasonal = pd.Series(res_restricted.seasonal.smoothed,
                                index=endog.index)

# Comparing the estimated level, it is clear that the seasonal UCM with
# fixed parameters still produces a trend that corresponds very closely
# (although no longer exactly) to the HP filter output.
#
# Meanwhile, the estimated level from the model with no parameter
# restrictions (the MLE model) is much less smooth than these.

fig, ax = plt.subplots(figsize=(15, 3))

ax.plot(unrestricted_trend, label='MLE, with seasonal')
ax.plot(restricted_trend, label='Fixed parameters, with seasonal')
ax.plot(hp_trend, label='HP filter, no seasonal')
ax.legend()

# Finally, the UCM with the parameter restrictions is still able to pick
# up the time-varying seasonal component quite well.

fig, ax = plt.subplots(figsize=(15, 3))

ax.plot(unrestricted_seasonal, label='MLE')
ax.plot(restricted_seasonal, label='Fixed parameters')
ax.legend()