@@ -1071,6 +1071,12 @@ class TimeSeasonality(Component):
1071
1071
1072
1072
If None, states will be numbered ``[State_0, ..., State_s]``
1073
1073
1074
+ remove_first_state: bool, default True
1075
+ If True, the first state will be removed from the model. This is done because there are only n-1 degrees of
1076
+ freedom in the seasonal component, and one state is not identified. If False, the first state will be
1077
+ included in the model, but it will not be identified -- you will need to handle this in the priors (e.g. with
1078
+ ZeroSumNormal).
1079
+
1074
1080
Notes
1075
1081
-----
1076
1082
A seasonal effect is any pattern that repeats every fixed interval. Although there are many possible ways to
@@ -1163,7 +1169,7 @@ def __init__(
1163
1169
innovations : bool = True ,
1164
1170
name : str | None = None ,
1165
1171
state_names : list | None = None ,
1166
- pop_state : bool = True ,
1172
+ remove_first_state : bool = True ,
1167
1173
):
1168
1174
if name is None :
1169
1175
name = f"Seasonal[s={ season_length } ]"
@@ -1176,14 +1182,15 @@ def __init__(
1176
1182
)
1177
1183
state_names = state_names .copy ()
1178
1184
self .innovations = innovations
1179
- self .pop_state = pop_state
1185
+ self .remove_first_state = remove_first_state
1180
1186
1181
- if self .pop_state :
1187
+ if self .remove_first_state :
1182
1188
# In traditional models, the first state isn't identified, so we can help out the user by automatically
1183
1189
# discarding it.
1184
1190
# TODO: Can this be stashed and reconstructed automatically somehow?
1185
1191
state_names .pop (0 )
1186
- k_states = season_length - 1
1192
+
1193
+ k_states = season_length - int (self .remove_first_state )
1187
1194
1188
1195
super ().__init__ (
1189
1196
name = name ,
@@ -1218,8 +1225,16 @@ def populate_component_properties(self):
1218
1225
self .shock_names = [f"{ self .name } " ]
1219
1226
1220
1227
def make_symbolic_graph (self ) -> None :
1221
- T = np .eye (self .k_states , k = - 1 )
1222
- T [0 , :] = - 1
1228
+ if self .remove_first_state :
1229
+ # In this case, parameters are normalized to sum to zero, so the current state is the negative sum of
1230
+ # all previous states.
1231
+ T = np .eye (self .k_states , k = - 1 )
1232
+ T [0 , :] = - 1
1233
+ else :
1234
+ # In this case we assume the user to be responsible for ensuring the states sum to zero, so T is just a
1235
+ # circulant matrix that cycles between the states.
1236
+ T = np .eye (self .k_states , k = 1 )
1237
+ T [- 1 , 0 ] = 1
1223
1238
1224
1239
self .ssm ["transition" , :, :] = T
1225
1240
self .ssm ["design" , 0 , 0 ] = 1
0 commit comments