StateSpaceGenerator lets you specify
the transition and observation dynamics directly to build custom linear
systems.
The model xtโ=Fxtโ1โ+wtโ,wtโโผ(0,Q),ytโ=Hxtโ+vtโ,vtโโผN(0,R) A latent state evolves by a linear transition with process noise, and is mapped to the observed series by an observation equation with measurement noise. Choosing these dynamics reproduces local-level, local-trend, and other structural time-series models.
import numpy as np
import polars as pl
import matplotlib.pyplot as plt
from synforecast.generators import StateSpaceGenerator
1. Basic local level model (random walk with noise)
The simplest state space model: a hidden random walk observed with measurement noise.local_level_params = {
"min_length": 200,
"max_length": 200,
"freq": "D",
"state_dim": 1,
"obs_dim": 1,
"seed": 42,
}
local_level_gen = StateSpaceGenerator(engine="polars", **local_level_params)
local_level_df = local_level_gen.generate(n_series=1)
print(f"Generated {len(local_level_df)} observations from local level model")
print(
f"Statistics: Mean={local_level_df['y'].mean():.4f}, "
f"Std={local_level_df['y'].std():.4f}"
)
local_level_df.head(10)
Generated 200 observations from local level model
Statistics: Mean=-0.0235, Std=0.4556
| unique_id | ds | y |
|---|---|---|
| cat | datetime[ns] | f64 |
| โ0โ | 2000-01-01 00:00:00 | -1.477252 |
| โ0โ | 2000-01-02 00:00:00 | 0.466308 |
| โ0โ | 2000-01-03 00:00:00 | -0.351479 |
| โ0โ | 2000-01-04 00:00:00 | -0.460238 |
| โ0โ | 2000-01-05 00:00:00 | -0.088038 |
| โ0โ | 2000-01-06 00:00:00 | -0.199117 |
| โ0โ | 2000-01-07 00:00:00 | 0.23307 |
| โ0โ | 2000-01-08 00:00:00 | -0.331227 |
| โ0โ | 2000-01-09 00:00:00 | -0.634035 |
| โ0โ | 2000-01-10 00:00:00 | -0.79934 |
fig, ax = plt.subplots(figsize=(12, 4))
ax.plot(local_level_df["ds"].to_list(), local_level_df["y"].to_list(), alpha=0.8)
ax.set_xlabel("Timestamp")
ax.set_ylabel("Value")
ax.set_title("Local level model (random walk with noise)")
plt.tight_layout()
plt.show()

2. 2D state space model (level + trend)
A two-dimensional state captures both the level and its trend (rate of change).two_dim_params = {
"min_length": 200,
"max_length": 200,
"freq": "D",
"state_dim": 2,
"obs_dim": 1,
"seed": 42,
}
two_dim_gen = StateSpaceGenerator(engine="polars", **two_dim_params)
two_dim_df = two_dim_gen.generate(n_series=1)
print(f"Generated {len(two_dim_df)} observations from 2D state space model")
print(
f"Statistics: Mean={two_dim_df['y'].mean():.4f}, "
f"Std={two_dim_df['y'].std():.4f}"
)
two_dim_df.head(10)
Generated 200 observations from 2D state space model
Statistics: Mean=-0.0315, Std=0.4513
| unique_id | ds | y |
|---|---|---|
| cat | datetime[ns] | f64 |
| โ0โ | 2000-01-01 00:00:00 | 2.035213 |
| โ0โ | 2000-01-02 00:00:00 | 0.342724 |
| โ0โ | 2000-01-03 00:00:00 | -0.130302 |
| โ0โ | 2000-01-04 00:00:00 | -0.445831 |
| โ0โ | 2000-01-05 00:00:00 | 0.159239 |
| โ0โ | 2000-01-06 00:00:00 | -0.206335 |
| โ0โ | 2000-01-07 00:00:00 | 0.401635 |
| โ0โ | 2000-01-08 00:00:00 | -0.350341 |
| โ0โ | 2000-01-09 00:00:00 | 0.077724 |
| โ0โ | 2000-01-10 00:00:00 | -0.512018 |
fig, ax = plt.subplots(figsize=(12, 4))
ax.plot(two_dim_df["ds"].to_list(), two_dim_df["y"].to_list(), alpha=0.8, color="C1")
ax.set_xlabel("Timestamp")
ax.set_ylabel("Value")
ax.set_title("2D state space model (level + trend)")
plt.tight_layout()
plt.show()

3. Custom transition matrix (AR-like behavior)
Specify a custom state transition matrix to create AR-like dynamics in the hidden state.transition_matrix = np.array([[0.9, 0.1], [0.0, 0.8]])
custom_transition_params = {
"min_length": 200,
"max_length": 200,
"freq": "D",
"state_dim": 2,
"obs_dim": 1,
"transition_matrix": transition_matrix.tolist(),
"seed": 42,
}
custom_transition_gen = StateSpaceGenerator(engine="polars", **custom_transition_params)
custom_transition_df = custom_transition_gen.generate(n_series=1)
print(f"Generated {len(custom_transition_df)} observations with custom transition matrix")
print(
f"Statistics: Mean={custom_transition_df['y'].mean():.4f}, "
f"Std={custom_transition_df['y'].std():.4f}"
)
custom_transition_df.head(10)
Generated 200 observations with custom transition matrix
Statistics: Mean=0.2258, Std=0.7764
| unique_id | ds | y |
|---|---|---|
| cat | datetime[ns] | f64 |
| โ0โ | 2000-01-01 00:00:00 | -1.099579 |
| โ0โ | 2000-01-02 00:00:00 | -1.454542 |
| โ0โ | 2000-01-03 00:00:00 | -0.964621 |
| โ0โ | 2000-01-04 00:00:00 | -1.002322 |
| โ0โ | 2000-01-05 00:00:00 | -1.355461 |
| โ0โ | 2000-01-06 00:00:00 | -0.768567 |
| โ0โ | 2000-01-07 00:00:00 | -1.013438 |
| โ0โ | 2000-01-08 00:00:00 | -0.786117 |
| โ0โ | 2000-01-09 00:00:00 | 0.361217 |
| โ0โ | 2000-01-10 00:00:00 | -0.043312 |
fig, ax = plt.subplots(figsize=(12, 4))
ax.plot(custom_transition_df["ds"].to_list(), custom_transition_df["y"].to_list(), alpha=0.8, color="C2")
ax.set_xlabel("Timestamp")
ax.set_ylabel("Value")
ax.set_title("Custom transition matrix (AR-like behavior)")
plt.tight_layout()
plt.show()

4. Custom observation matrix (weighted state observation)
Observe a weighted combination of the hidden states.observation_matrix = np.array([[1.0, 0.5]])
custom_obs_params = {
"min_length": 200,
"max_length": 200,
"freq": "D",
"state_dim": 2,
"obs_dim": 1,
"observation_matrix": observation_matrix.tolist(),
"seed": 42,
}
custom_obs_gen = StateSpaceGenerator(engine="polars", **custom_obs_params)
custom_obs_df = custom_obs_gen.generate(n_series=1)
print(f"Generated {len(custom_obs_df)} observations with custom observation matrix")
print(
f"Statistics: Mean={custom_obs_df['y'].mean():.4f}, "
f"Std={custom_obs_df['y'].std():.4f}"
)
custom_obs_df.head(10)
Generated 200 observations with custom observation matrix
Statistics: Mean=-0.0163, Std=0.4940
| unique_id | ds | y |
|---|---|---|
| cat | datetime[ns] | f64 |
| โ0โ | 2000-01-01 00:00:00 | 2.24795 |
| โ0โ | 2000-01-02 00:00:00 | 0.496252 |
| โ0โ | 2000-01-03 00:00:00 | 0.135206 |
| โ0โ | 2000-01-04 00:00:00 | -0.172047 |
| โ0โ | 2000-01-05 00:00:00 | 0.132115 |
| โ0โ | 2000-01-06 00:00:00 | -0.300323 |
| โ0โ | 2000-01-07 00:00:00 | 0.495517 |
| โ0โ | 2000-01-08 00:00:00 | -0.444273 |
| โ0โ | 2000-01-09 00:00:00 | -0.004085 |
| โ0โ | 2000-01-10 00:00:00 | -0.554032 |
fig, ax = plt.subplots(figsize=(12, 4))
ax.plot(custom_obs_df["ds"].to_list(), custom_obs_df["y"].to_list(), alpha=0.8, color="C3")
ax.set_xlabel("Timestamp")
ax.set_ylabel("Value")
ax.set_title("Custom observation matrix (weighted state observation)")
plt.tight_layout()
plt.show()

5. High process noise (volatile state evolution)
Increase the process noise covariance to create more volatile hidden state dynamics.high_process_noise = np.array([[5.0, 0.0], [0.0, 5.0]])
high_noise_params = {
"min_length": 200,
"max_length": 200,
"freq": "D",
"state_dim": 2,
"obs_dim": 1,
"state_covariance": high_process_noise.tolist(),
"seed": 42,
}
high_noise_gen = StateSpaceGenerator(engine="polars", **high_noise_params)
high_noise_df = high_noise_gen.generate(n_series=1)
print(f"Generated {len(high_noise_df)} observations with high process noise")
print(
f"Statistics: Mean={high_noise_df['y'].mean():.4f}, "
f"Std={high_noise_df['y'].std():.4f}"
)
high_noise_df.head(10)
Generated 200 observations with high process noise
Statistics: Mean=-0.1518, Std=2.2105
| unique_id | ds | y |
|---|---|---|
| cat | datetime[ns] | f64 |
| โ0โ | 2000-01-01 00:00:00 | 2.035213 |
| โ0โ | 2000-01-02 00:00:00 | 2.980453 |
| โ0โ | 2000-01-03 00:00:00 | 2.307622 |
| โ0โ | 2000-01-04 00:00:00 | -1.815772 |
| โ0โ | 2000-01-05 00:00:00 | 1.32836 |
| โ0โ | 2000-01-06 00:00:00 | -2.890637 |
| โ0โ | 2000-01-07 00:00:00 | -0.526938 |
| โ0โ | 2000-01-08 00:00:00 | -2.710086 |
| โ0โ | 2000-01-09 00:00:00 | -1.37662 |
| โ0โ | 2000-01-10 00:00:00 | -1.535066 |
fig, ax = plt.subplots(figsize=(12, 4))
ax.plot(high_noise_df["ds"].to_list(), high_noise_df["y"].to_list(), alpha=0.8, color="C4")
ax.set_xlabel("Timestamp")
ax.set_ylabel("Value")
ax.set_title("High process noise (volatile state evolution)")
plt.tight_layout()
plt.show()

6. High observation noise (noisy measurements)
Increase the observation noise to simulate noisy measurement conditions.high_obs_noise = np.array([[10.0]])
noisy_obs_params = {
"min_length": 200,
"max_length": 200,
"freq": "D",
"state_dim": 2,
"obs_dim": 1,
"obs_covariance": high_obs_noise.tolist(),
"seed": 42,
}
noisy_obs_gen = StateSpaceGenerator(engine="polars", **noisy_obs_params)
noisy_obs_df = noisy_obs_gen.generate(n_series=1)
print(f"Generated {len(noisy_obs_df)} observations with high observation noise")
print(
f"Statistics: Mean={noisy_obs_df['y'].mean():.4f}, "
f"Std={noisy_obs_df['y'].std():.4f}"
)
noisy_obs_df.head(10)
Generated 200 observations with high observation noise
Statistics: Mean=-0.2227, Std=2.9604
| unique_id | ds | y |
|---|---|---|
| cat | datetime[ns] | f64 |
| โ0โ | 2000-01-01 00:00:00 | 5.679936 |
| โ0โ | 2000-01-02 00:00:00 | -2.754326 |
| โ0โ | 2000-01-03 00:00:00 | -5.158032 |
| โ0โ | 2000-01-04 00:00:00 | -2.433461 |
| โ0โ | 2000-01-05 00:00:00 | -0.135867 |
| โ0โ | 2000-01-06 00:00:00 | 1.917522 |
| โ0โ | 2000-01-07 00:00:00 | 5.393208 |
| โ0โ | 2000-01-08 00:00:00 | -0.005179 |
| โ0โ | 2000-01-09 00:00:00 | 2.933222 |
| โ0โ | 2000-01-10 00:00:00 | -3.603567 |
fig, ax = plt.subplots(figsize=(12, 4))
ax.plot(noisy_obs_df["ds"].to_list(), noisy_obs_df["y"].to_list(), alpha=0.8, color="C5")
ax.set_xlabel("Timestamp")
ax.set_ylabel("Value")
ax.set_title("High observation noise (noisy measurements)")
plt.tight_layout()
plt.show()

7. Generate with hidden states
Return both observations and hidden states to inspect the latent dynamics.with_states_params = {
"min_length": 100,
"max_length": 100,
"freq": "D",
"state_dim": 2,
"obs_dim": 1,
"seed": 42,
}
with_states_gen = StateSpaceGenerator(engine="polars", **with_states_params)
obs_df, states_df = with_states_gen.generate_with_states(n_series=1)
print(f"Generated observations and hidden states")
print(
f"Observation Statistics: Mean={obs_df['y'].mean():.4f}, "
f"Std={obs_df['y'].std():.4f}"
)
print("\nObservations:")
print(obs_df.head(10))
print("\nHidden States:")
print(states_df.head(10))
Generated observations and hidden states
Observation Statistics: Mean=-0.0286, Std=0.4412
Observations:
shape: (10, 3)
โโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโ
โ unique_id โ ds โ y โ
โ --- โ --- โ --- โ
โ cat โ datetime[ns] โ f64 โ
โโโโโโโโโโโโโชโโโโโโโโโโโโโโโโโโโโโโชโโโโโโโโโโโโก
โ 0 โ 2000-01-01 00:00:00 โ -1.910609 โ
โ 0 โ 2000-01-02 00:00:00 โ -0.642705 โ
โ 0 โ 2000-01-03 00:00:00 โ 0.258913 โ
โ 0 โ 2000-01-04 00:00:00 โ 0.120297 โ
โ 0 โ 2000-01-05 00:00:00 โ 0.451117 โ
โ 0 โ 2000-01-06 00:00:00 โ -0.195514 โ
โ 0 โ 2000-01-07 00:00:00 โ 0.256028 โ
โ 0 โ 2000-01-08 00:00:00 โ 0.070385 โ
โ 0 โ 2000-01-09 00:00:00 โ 0.791294 โ
โ 0 โ 2000-01-10 00:00:00 โ -0.372822 โ
โโโโโโโโโโโโโดโโโโโโโโโโโโโโโโโโโโโโดโโโโโโโโโโโโ
Hidden States:
shape: (10, 4)
โโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโฌโโโโโโโโโโโโ
โ unique_id โ ds โ state_0 โ state_1 โ
โ --- โ --- โ --- โ --- โ
โ cat โ datetime[ns] โ f64 โ f64 โ
โโโโโโโโโโโโโชโโโโโโโโโโโโโโโโโโโโโโชโโโโโโโโโโโโชโโโโโโโโโโโโก
โ 0 โ 2000-01-01 00:00:00 โ -1.951035 โ -1.30218 โ
โ 0 โ 2000-01-02 00:00:00 โ -0.372949 โ -0.5793 โ
โ 0 โ 2000-01-03 00:00:00 โ 0.238032 โ 0.097115 โ
โ 0 โ 2000-01-04 00:00:00 โ 0.39203 โ 0.210554 โ
โ 0 โ 2000-01-05 00:00:00 โ 0.173327 โ -0.193943 โ
โ 0 โ 2000-01-06 00:00:00 โ 0.019815 โ -0.044133 โ
โ 0 โ 2000-01-07 00:00:00 โ 0.391477 โ -0.049829 โ
โ 0 โ 2000-01-08 00:00:00 โ -0.045179 โ 0.24666 โ
โ 0 โ 2000-01-09 00:00:00 โ 0.114046 โ 0.155727 โ
โ 0 โ 2000-01-10 00:00:00 โ -0.115485 โ -0.119006 โ
โโโโโโโโโโโโโดโโโโโโโโโโโโโโโโโโโโโโดโโโโโโโโโโโโดโโโโโโโโโโโโ
fig, axes = plt.subplots(2, 1, figsize=(12, 6), sharex=True)
axes[0].plot(obs_df["ds"].to_list(), obs_df["y"].to_list(), alpha=0.8, label="Observations")
axes[0].set_ylabel("Observed Value")
axes[0].set_title("Observations vs hidden states")
axes[0].legend()
state_cols = [c for c in states_df.columns if c.startswith("state_")]
for col in state_cols:
axes[1].plot(states_df["ds"].to_list(), states_df[col].to_list(), alpha=0.8, label=col)
axes[1].set_xlabel("Timestamp")
axes[1].set_ylabel("State Value")
axes[1].legend()
plt.tight_layout()
plt.show()

8. Multiple state space series
Generate multiple independent state space series.multi_params = {
"min_length": 150,
"max_length": 150,
"freq": "D",
"state_dim": 2,
"obs_dim": 1,
"seed": 42,
}
multi_gen = StateSpaceGenerator(engine="polars", **multi_params)
multi_df = multi_gen.generate(n_series=3)
print(f"Generated 3 series with {len(multi_df)} total observations")
print(
f"Overall Statistics: Mean={multi_df['y'].mean():.4f}, "
f"Std={multi_df['y'].std():.4f}"
)
multi_df.filter(pl.col("unique_id") == "0").head(10)
Generated 3 series with 450 total observations
Overall Statistics: Mean=-0.0026, Std=0.4818
| unique_id | ds | y |
|---|---|---|
| cat | datetime[ns] | f64 |
| โ0โ | 2000-01-01 00:00:00 | 2.035213 |
| โ0โ | 2000-01-02 00:00:00 | 0.342724 |
| โ0โ | 2000-01-03 00:00:00 | -0.130302 |
| โ0โ | 2000-01-04 00:00:00 | -0.445831 |
| โ0โ | 2000-01-05 00:00:00 | 0.159239 |
| โ0โ | 2000-01-06 00:00:00 | -0.206335 |
| โ0โ | 2000-01-07 00:00:00 | 0.401635 |
| โ0โ | 2000-01-08 00:00:00 | -0.350341 |
| โ0โ | 2000-01-09 00:00:00 | 0.077724 |
| โ0โ | 2000-01-10 00:00:00 | -0.512018 |
fig, ax = plt.subplots(figsize=(12, 4))
for uid in multi_df["unique_id"].unique().to_list():
series = multi_df.filter(pl.col("unique_id") == uid)
ax.plot(series["ds"].to_list(), series["y"].to_list(), label=uid, alpha=0.8)
ax.set_xlabel("Timestamp")
ax.set_ylabel("Value")
ax.set_title("Multiple state space series")
ax.legend()
plt.tight_layout()
plt.show()

Related generatorsFull parameters are in the generator reference.
- SARIMA and ETS โ specific state-space families.
- Chaotic system โ deterministic nonlinear dynamics.

