Skip to contents

Model

The measurement model is given by π’ši,t=𝝂+𝚲𝜼i,t+𝜺i,t,with𝜺i,tβˆΌπ’©(𝟎,𝚯)\begin{equation} \mathbf{y}_{i, t} = \boldsymbol{\nu} + \boldsymbol{\Lambda} \boldsymbol{\eta}_{i, t} + \boldsymbol{\varepsilon}_{i, t}, \quad \mathrm{with} \quad \boldsymbol{\varepsilon}_{i, t} \sim \mathcal{N} \left( \mathbf{0}, \boldsymbol{\Theta} \right) \end{equation} where π’ši,t\mathbf{y}_{i, t}, 𝜼i,t\boldsymbol{\eta}_{i, t}, and 𝜺i,t\boldsymbol{\varepsilon}_{i, t} are random variables and 𝝂\boldsymbol{\nu}, 𝚲\boldsymbol{\Lambda}, and 𝚯\boldsymbol{\Theta} are model parameters. π’ši,t\mathbf{y}_{i, t} represents a vector of observed random variables, 𝜼i,t\boldsymbol{\eta}_{i, t} a vector of latent random variables, and 𝜺i,t\boldsymbol{\varepsilon}_{i, t} a vector of random measurement errors, at time tt and individual ii. 𝝂\boldsymbol{\nu} denotes a vector of intercepts, 𝚲\boldsymbol{\Lambda} a matrix of factor loadings, and 𝚯\boldsymbol{\Theta} the covariance matrix of 𝜺\boldsymbol{\varepsilon}.

An alternative representation of the measurement error is given by 𝜺i,t=𝚯12𝒛i,t,with𝒛i,tβˆΌπ’©(𝟎,𝑰)\begin{equation} \boldsymbol{\varepsilon}_{i, t} = \boldsymbol{\Theta}^{\frac{1}{2}} \mathbf{z}_{i, t}, \quad \mathrm{with} \quad \mathbf{z}_{i, t} \sim \mathcal{N} \left( \mathbf{0}, \mathbf{I} \right) \end{equation} where 𝒛i,t\mathbf{z}_{i, t} is a vector of independent standard normal random variables and (𝚯12)(𝚯12)β€²=𝚯\left( \boldsymbol{\Theta}^{\frac{1}{2}} \right) \left( \boldsymbol{\Theta}^{\frac{1}{2}} \right)^{\prime} = \boldsymbol{\Theta} .

The dynamic structure is given by d𝜼i,t=𝚽(𝜼i,tβˆ’π)dt+𝚺12d𝑾i,t\begin{equation} \mathrm{d} \boldsymbol{\eta}_{i, t} = \boldsymbol{\Phi} \left( \boldsymbol{\eta}_{i, t} - \boldsymbol{\mu} \right) \mathrm{d}t + \boldsymbol{\Sigma}^{\frac{1}{2}} \mathrm{d} \mathbf{W}_{i, t} \end{equation} where 𝝁\boldsymbol{\mu} is the long-term mean or equilibrium level, 𝚽\boldsymbol{\Phi} is the rate of mean reversion, determining how quickly the variable returns to its mean, 𝚺\boldsymbol{\Sigma} is the matrix of volatility or randomness in the process, and d𝑾\mathrm{d}\boldsymbol{W} is a Wiener process or Brownian motion, which represents random fluctuations.

Data Generation

Notation

Let t=1000t = 1000 be the number of time points and n=100n = 100 be the number of individuals.

Let the measurement model intecept vector 𝝂\boldsymbol{\nu} be given by

𝝂=(000).\begin{equation} \boldsymbol{\nu} = \left( \begin{array}{c} 0 \\ 0 \\ 0 \\ \end{array} \right) . \end{equation}

Let the factor loadings matrix 𝚲\boldsymbol{\Lambda} be given by

𝚲=(100010001).\begin{equation} \boldsymbol{\Lambda} = \left( \begin{array}{ccc} 1 & 0 & 0 \\ 0 & 1 & 0 \\ 0 & 0 & 1 \\ \end{array} \right) . \end{equation}

Let the measurement error covariance matrix 𝚯\boldsymbol{\Theta} be given by

𝚯=(0.20000.20000.2).\begin{equation} \boldsymbol{\Theta} = \left( \begin{array}{ccc} 0.2 & 0 & 0 \\ 0 & 0.2 & 0 \\ 0 & 0 & 0.2 \\ \end{array} \right) . \end{equation}

Let the initial condition 𝜼0\boldsymbol{\eta}_{0} be given by

𝜼0βˆΌπ’©(𝝁𝜼∣0,𝚺𝜼∣0)\begin{equation} \boldsymbol{\eta}_{0} \sim \mathcal{N} \left( \boldsymbol{\mu}_{\boldsymbol{\eta} \mid 0}, \boldsymbol{\Sigma}_{\boldsymbol{\eta} \mid 0} \right) \end{equation}

𝝁𝜼∣0=(000)\begin{equation} \boldsymbol{\mu}_{\boldsymbol{\eta} \mid 0} = \left( \begin{array}{c} 0 \\ 0 \\ 0 \\ \end{array} \right) \end{equation}

𝚺𝜼∣0=(0.34251480.32960230.03438170.32960230.56646250.25459550.03438170.25459550.2999909).\begin{equation} \boldsymbol{\Sigma}_{\boldsymbol{\eta} \mid 0} = \left( \begin{array}{ccc} 0.3425148 & 0.3296023 & 0.0343817 \\ 0.3296023 & 0.5664625 & 0.2545955 \\ 0.0343817 & 0.2545955 & 0.2999909 \\ \end{array} \right) . \end{equation}

Let the long-term mean vector 𝝁\boldsymbol{\mu} be given by

𝝁=(000).\begin{equation} \boldsymbol{\mu} = \left( \begin{array}{c} 0 \\ 0 \\ 0 \\ \end{array} \right) . \end{equation}

Let the rate of mean reversion matrix 𝚽\boldsymbol{\Phi} be given by

𝚽=(βˆ’0.357000.771βˆ’0.5110βˆ’0.450.729βˆ’0.693).\begin{equation} \boldsymbol{\Phi} = \left( \begin{array}{ccc} -0.357 & 0 & 0 \\ 0.771 & -0.511 & 0 \\ -0.45 & 0.729 & -0.693 \\ \end{array} \right) . \end{equation}

Let the dynamic process noise covariance matrix 𝚺\boldsymbol{\Sigma} be given by

𝚺=(0.24455560.0220159βˆ’0.05004760.02201590.0706780.0153946βˆ’0.05004760.01539460.0755306).\begin{equation} \boldsymbol{\Sigma} = \left( \begin{array}{ccc} 0.2445556 & 0.0220159 & -0.0500476 \\ 0.0220159 & 0.070678 & 0.0153946 \\ -0.0500476 & 0.0153946 & 0.0755306 \\ \end{array} \right) . \end{equation}

Let Ξ”t=0.1\Delta t = 0.1.

R Function Arguments

n
#> [1] 100
time
#> [1] 1000
delta_t
#> [1] 0.1
mu0
#> [1] 0 0 0
sigma0
#>           [,1]      [,2]      [,3]
#> [1,] 0.3425148 0.3296023 0.0343817
#> [2,] 0.3296023 0.5664625 0.2545955
#> [3,] 0.0343817 0.2545955 0.2999909
sigma0_l # sigma0_l <- t(chol(sigma0))
#>            [,1]      [,2]      [,3]
#> [1,] 0.58524763 0.0000000 0.0000000
#> [2,] 0.56318429 0.4992855 0.0000000
#> [3,] 0.05874726 0.4436540 0.3157701
mu
#> [1] 0 0 0
phi
#>        [,1]   [,2]   [,3]
#> [1,] -0.357  0.000  0.000
#> [2,]  0.771 -0.511  0.000
#> [3,] -0.450  0.729 -0.693
sigma
#>             [,1]       [,2]        [,3]
#> [1,]  0.24455556 0.02201587 -0.05004762
#> [2,]  0.02201587 0.07067800  0.01539456
#> [3,] -0.05004762 0.01539456  0.07553061
sigma_l # sigma_l <- t(chol(sigma))
#>             [,1]      [,2]     [,3]
#> [1,]  0.49452559 0.0000000 0.000000
#> [2,]  0.04451917 0.2620993 0.000000
#> [3,] -0.10120330 0.0759256 0.243975
nu
#> [1] 0 0 0
lambda
#>      [,1] [,2] [,3]
#> [1,]    1    0    0
#> [2,]    0    1    0
#> [3,]    0    0    1
theta
#>      [,1] [,2] [,3]
#> [1,]  0.2  0.0  0.0
#> [2,]  0.0  0.2  0.0
#> [3,]  0.0  0.0  0.2
theta_l # theta_l <- t(chol(theta))
#>           [,1]      [,2]      [,3]
#> [1,] 0.4472136 0.0000000 0.0000000
#> [2,] 0.0000000 0.4472136 0.0000000
#> [3,] 0.0000000 0.0000000 0.4472136

Visualizing the Dynamics Without Measurement Error and Process Noise (n = 5 with Different Initial Condition)

Using the SimSSMOUFixed Function from the simStateSpace Package to Simulate Data

library(simStateSpace)
sim <- SimSSMOUFixed(
  n = n,
  time = time,
  delta_t = delta_t,
  mu0 = mu0,
  sigma0_l = sigma0_l,
  mu = mu,
  phi = phi,
  sigma_l = sigma_l,
  nu = nu,
  lambda = lambda,
  theta_l = theta_l,
  type = 0
)
data <- as.data.frame(sim)
head(data)
#>   id time          y1         y2         y3
#> 1  1  0.0  0.45047365 -1.2185550  0.6708638
#> 2  1  0.1 -0.84190461  0.1242594  0.1813493
#> 3  1  0.2  0.47289651 -0.2398710  0.5995107
#> 4  1  0.3  0.04089817 -0.6547109  0.4674929
#> 5  1  0.4 -1.37222946 -0.2010512 -0.3341885
#> 6  1  0.5 -0.62410106  0.5262251 -0.1892906
summary(data)
#>        id              time             y1                  y2           
#>  Min.   :  1.00   Min.   : 0.00   Min.   :-3.387237   Min.   :-3.561919  
#>  1st Qu.: 25.75   1st Qu.:24.98   1st Qu.:-0.497505   1st Qu.:-0.593119  
#>  Median : 50.50   Median :49.95   Median : 0.002308   Median : 0.002867  
#>  Mean   : 50.50   Mean   :49.95   Mean   : 0.002420   Mean   : 0.001311  
#>  3rd Qu.: 75.25   3rd Qu.:74.92   3rd Qu.: 0.507538   3rd Qu.: 0.600379  
#>  Max.   :100.00   Max.   :99.90   Max.   : 3.381345   Max.   : 3.415473  
#>        y3            
#>  Min.   :-2.9091510  
#>  1st Qu.:-0.4779403  
#>  Median : 0.0006008  
#>  Mean   : 0.0019631  
#>  3rd Qu.: 0.4815267  
#>  Max.   : 3.0074722
plot(sim)

Model Fitting

Prepare Data

dynr_data <- dynr::dynr.data(
  dataframe = data,
  id = "id",
  time = "time",
  observed = c("y1", "y2", "y3")
)

Prepare Initial Condition

dynr_initial <- dynr::prep.initial(
  values.inistate = mu0,
  params.inistate = c("mu0_1_1", "mu0_2_1", "mu0_3_1"),
  values.inicov = sigma0,
  params.inicov = matrix(
    data = c(
      "sigma0_1_1", "sigma0_2_1", "sigma0_3_1",
      "sigma0_2_1", "sigma0_2_2", "sigma0_3_2",
      "sigma0_3_1", "sigma0_3_2", "sigma0_3_3"
    ),
    nrow = 3
  )
)

Prepare Measurement Model

dynr_measurement <- dynr::prep.measurement(
  values.load = diag(3),
  params.load = matrix(data = "fixed", nrow = 3, ncol = 3),
  state.names = c("eta_1", "eta_2", "eta_3"),
  obs.names = c("y1", "y2", "y3")
)

Prepare Dynamic Process

dynr_dynamics <- dynr::prep.formulaDynamics(
  formula = list(  
    eta_1 ~ (phi_1_1 * (eta_1 - mu_1_1)) + (phi_1_2 * (eta_2 - mu_2_1)) + (phi_1_3 * (eta_3 - mu_3_1)),
    eta_2 ~ (phi_2_1 * (eta_1 - mu_1_1)) + (phi_2_2 * (eta_2 - mu_2_1)) + (phi_2_3 * (eta_3 - mu_3_1)),
    eta_3 ~ (phi_3_1 * (eta_1 - mu_1_1)) + (phi_3_2 * (eta_2 - mu_2_1)) + (phi_3_3 * (eta_3 - mu_3_1))
  ),
  startval = c(
    mu_1_1 = mu[1], mu_2_1 = mu[2], mu_3_1 = mu[3],
    phi_1_1 = phi[1, 1], phi_1_2 = phi[1, 2], phi_1_3 = phi[1, 3],
    phi_2_1 = phi[2, 1], phi_2_2 = phi[2, 2], phi_2_3 = phi[2, 3],
    phi_3_1 = phi[3, 1], phi_3_2 = phi[3, 2], phi_3_3 = phi[3, 3]
  ),
  isContinuousTime = TRUE
)

Prepare Process Noise

dynr_noise <- dynr::prep.noise(
  values.latent = sigma,
  params.latent = matrix(
    data = c(
      "sigma_1_1", "sigma_2_1", "sigma_3_1",
      "sigma_2_1", "sigma_2_2", "sigma_3_2",
      "sigma_3_1", "sigma_3_2", "sigma_3_3"
    ),
    nrow = 3
  ),
  values.observed = theta,
  params.observed = matrix(
    data = c(
      "theta_1_1", "fixed", "fixed",
      "fixed", "theta_2_2", "fixed",
      "fixed", "fixed", "theta_3_3"
    ),
    nrow = 3
  )
)

Prepare the Model

model <- dynr::dynr.model(
  data = dynr_data,
  initial = dynr_initial,
  measurement = dynr_measurement,
  dynamics = dynr_dynamics,
  noise = dynr_noise,
  outfile = "ou.c"
)

Add lower and upper bounds to aid in the optimization.

model$lb[
  c(
    "phi_1_1",
    "phi_1_2",
    "phi_1_3",
    "phi_2_1",
    "phi_2_2",
    "phi_2_3",
    "phi_3_1",
    "phi_3_2",
    "phi_3_3"
  )
] <- -1.5
model$ub[
  c(
    "phi_1_1",
    "phi_1_2",
    "phi_1_3",
    "phi_2_1",
    "phi_2_2",
    "phi_2_3",
    "phi_3_1",
    "phi_3_2",
    "phi_3_3"
  )
] <- +1.5

Fit the Model

results <- dynr::dynr.cook(
  model,
  debug_flag = TRUE,
  verbose = FALSE
)
#> [1] "Get ready!!!!"
#> using C compiler: β€˜gcc (Ubuntu 13.3.0-6ubuntu2~24.04) 13.3.0’
#> Optimization function called.
#> Starting Hessian calculation ...
#> Finished Hessian calculation.
#> Original exit flag:  3 
#> Modified exit flag:  3 
#> Optimization terminated successfully: ftol_rel or ftol_abs was reached. 
#> Original fitted parameters:  -0.001486414 -0.003474606 0.001459749 -0.3876957 
#> 0.0497315 -0.05068424 0.7575202 -0.5020978 -0.001002782 -0.4483666 0.7166069 
#> -0.6816382 -1.397824 0.08915739 -0.2066158 -2.670695 0.287372 -2.880188 
#> -1.615787 -1.611792 -1.602987 0.01193792 -0.03514586 -0.0202138 -1.023004 
#> 0.9898615 0.1951522 -1.492855 0.876229 -2.109101 
#> 
#> Transformed fitted parameters:  -0.001486414 -0.003474606 0.001459749 
#> -0.3876957 0.0497315 -0.05068424 0.7575202 -0.5020978 -0.001002782 -0.4483666 
#> 0.7166069 -0.6816382 0.2471341 0.02203383 -0.05106181 0.07116856 0.01533478 
#> 0.07238944 0.1987341 0.1995297 0.2012944 0.01193792 -0.03514586 -0.0202138 
#> 0.3595134 0.3558685 0.07015983 0.5769907 0.2663636 0.3075816 
#> 
#> Doing end processing
#> Warning in sqrt(diag(iHess)): NaNs produced
#> Warning in sqrt(diag(x$inv.hessian)): NaNs produced
#> Warning: These parameters may have untrustworthy standard errors: sigma0_1_1,
#> sigma0_2_1.
#> Total Time: 38.78143 
#> Backend Time: 38.77473

Summary

summary(results)
#> Coefficients:
#>             Estimate Std. Error t value  ci.lower  ci.upper Pr(>|t|)    
#> mu_1_1     -0.001486   0.014463  -0.103 -0.029833  0.026860   0.4591    
#> mu_2_1     -0.003475   0.023224  -0.150 -0.048992  0.042043   0.4405    
#> mu_3_1      0.001460   0.014728   0.099 -0.027406  0.030326   0.4605    
#> phi_1_1    -0.387696   0.039562  -9.800 -0.465235 -0.310156   <2e-16 ***
#> phi_1_2     0.049731   0.034658   1.435 -0.018196  0.117659   0.0757 .  
#> phi_1_3    -0.050684   0.026390  -1.921 -0.102408  0.001040   0.0274 *  
#> phi_2_1     0.757520   0.024418  31.023  0.709662  0.805379   <2e-16 ***
#> phi_2_2    -0.502098   0.021619 -23.225 -0.544470 -0.459726   <2e-16 ***
#> phi_2_3    -0.001003   0.016417  -0.061 -0.033180  0.031174   0.4756    
#> phi_3_1    -0.448367   0.025634 -17.491 -0.498608 -0.398125   <2e-16 ***
#> phi_3_2     0.716607   0.022660  31.624  0.672194  0.761019   <2e-16 ***
#> phi_3_3    -0.681638   0.017286 -39.433 -0.715518 -0.647759   <2e-16 ***
#> sigma_1_1   0.247134   0.007041  35.098  0.233333  0.260935   <2e-16 ***
#> sigma_2_1   0.022034   0.002723   8.092  0.016697  0.027371   <2e-16 ***
#> sigma_3_1  -0.051062   0.002879 -17.733 -0.056706 -0.045418   <2e-16 ***
#> sigma_2_2   0.071169   0.001970  36.120  0.067307  0.075030   <2e-16 ***
#> sigma_3_2   0.015335   0.001384  11.076  0.012621  0.018048   <2e-16 ***
#> sigma_3_3   0.072389   0.002092  34.601  0.068289  0.076490   <2e-16 ***
#> theta_1_1   0.198734   0.001174 169.300  0.196433  0.201035   <2e-16 ***
#> theta_2_2   0.199530   0.001002 199.040  0.197565  0.201494   <2e-16 ***
#> theta_3_3   0.201294   0.001016 198.086  0.199303  0.203286   <2e-16 ***
#> mu0_1_1     0.011938   0.068751   0.174 -0.122812  0.146687   0.4311    
#> mu0_2_1    -0.035146   0.088382  -0.398 -0.208371  0.138079   0.3454    
#> mu0_3_1    -0.020214   0.064115  -0.315 -0.145876  0.105448   0.3763    
#> sigma0_1_1  0.359513        NaN      NA       NaN       NaN       NA    
#> sigma0_2_1  0.355868        NaN      NA       NaN       NaN       NA    
#> sigma0_3_1  0.070160   0.048955   1.433 -0.025791  0.166111   0.0759 .  
#> sigma0_2_2  0.576991   0.091692   6.293  0.397278  0.756704   <2e-16 ***
#> sigma0_3_2  0.266364   0.065448   4.070  0.138088  0.394639   <2e-16 ***
#> sigma0_3_3  0.307582   0.051535   5.968  0.206575  0.408589   <2e-16 ***
#> ---
#> Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
#> 
#> -2 log-likelihood value at convergence = 429162.30
#> AIC = 429222.30
#> BIC = 429507.68

Parameter Estimates

mu_hat
#> [1] -0.001486414 -0.003474606  0.001459749
phi_hat
#>            [,1]       [,2]         [,3]
#> [1,] -0.3876957  0.0497315 -0.050684239
#> [2,]  0.7575202 -0.5020978 -0.001002782
#> [3,] -0.4483666  0.7166069 -0.681638168
sigma_hat
#>             [,1]       [,2]        [,3]
#> [1,]  0.24713412 0.02203383 -0.05106181
#> [2,]  0.02203383 0.07116856  0.01533478
#> [3,] -0.05106181 0.01533478  0.07238944
theta_hat
#>           [,1]      [,2]      [,3]
#> [1,] 0.1987341 0.0000000 0.0000000
#> [2,] 0.0000000 0.1995297 0.0000000
#> [3,] 0.0000000 0.0000000 0.2012944
mu0_hat
#> [1]  0.01193792 -0.03514586 -0.02021380
sigma0_hat
#>            [,1]      [,2]       [,3]
#> [1,] 0.35951339 0.3558685 0.07015983
#> [2,] 0.35586848 0.5769907 0.26636364
#> [3,] 0.07015983 0.2663636 0.30758162
beta_var1_hat <- expm::expm(phi_hat)
beta_var1_hat
#>            [,1]       [,2]        [,3]
#> [1,]  0.6952025 0.02134302 -0.03008700
#> [2,]  0.4901232 0.61421636 -0.01200378
#> [3,] -0.1042377 0.39410024  0.50935760

References

Chow, S.-M., Ho, M. R., Hamaker, E. L., & Dolan, C. V. (2010). Equivalence and differences between structural equation modeling and state-space modeling techniques. Structural Equation Modeling: A Multidisciplinary Journal, 17(2), 303–332. https://doi.org/10.1080/10705511003661553
Chow, S.-M., Losardo, D., Park, J., & Molenaar, P. C. M. (2023). Continuous-time dynamic models: Connections to structural equation models and other discrete-time models. In R. H. Hoyle (Ed.), Handbook of structural equation modeling (2nd ed.). The Guilford Press.
Deboeck, P. R., & Preacher, K. J. (2015). No need to be discrete: A method for continuous time mediation analysis. Structural Equation Modeling: A Multidisciplinary Journal, 23(1), 61–75. https://doi.org/10.1080/10705511.2014.973960
Oravecz, Z., Tuerlinckx, F., & Vandekerckhove, J. (2011). A hierarchical latent stochastic differential equation model for affective dynamics. Psychological Methods, 16(4), 468–490. https://doi.org/10.1037/a0024375
Ou, L., Hunter, M. D., & Chow, S.-M. (2019). What’s for dynr: A package for linear and nonlinear dynamic modeling in R. The R Journal, 11(1), 91. https://doi.org/10.32614/rj-2019-012
R Core Team. (2025). R: A language and environment for statistical computing. R Foundation for Statistical Computing. https://www.R-project.org/
Uhlenbeck, G. E., & Ornstein, L. S. (1930). On the theory of the brownian motion. Physical Review, 36(5), 823–841. https://doi.org/10.1103/physrev.36.823