Mplusで多数の時系列について隠れマルコフモデルを推定するとき、時点数はどのくらいまで増やせるのか

Author

小野滋 / ONO Shigeru

Modified

May 9, 2025

1 おまえはいったいなにをしたいのか

これから行うシミュレーションは切実な問題意識に由来しているのだけれど、 なかなか共感が得られないだろうと思う。 まず背景について説明したい。

データ解析の仕事をしていると、ときどき、たくさんの測定対象について時系列データを得た、こんな感じのモデルを組みたいんだけど、どうしよう? と困ることがある。

測定対象がひとつだったら、世間にあふれる時系列分析用のプログラムを使えばよろしい。いくら時点数が多くてもびびることはない。測定対象がたくさんあっても時点数が少ないならば、パネルデータであると考えれば、それはそれでいろんな分析手法がある。いくら測定対象が多くてもびびることはない。

問題は、測定対象が中途半端に多く(100とか200とか以上)、時点数も中途半端に多いとき(20とか30とか以上)である。最近ではこういうのを強縦断データなどというらしいですね。こういうデータに対する手立てはぐっと絞られる。さあどうするよ?

哀れな統計ソフトユーザたちは、多変量時系列分析だか階層混合モデリングだかのソフトウェアを探して暗闇をさまようことであろう。しかし自分が夢想するモデルを実現してくれるソフトウェアなんてそうそうは見つからない。小賢しいガキどもは、いまこそフルベイズ、僕ならStanで書きますね、などと得意げに嘯くだろう。シャラップ(お黙り)。おまえがもしデータ解析を愛しデータ解析に愛される真の漢(おとこ)ならば、ただひとつの選択肢がそこにある。Mplusを起動するのだ。(いったいなにいってんですかね)

Mplusとは、SEM(構造方程式モデリング)の世界の導師Muthen先生が率いるチームによって開発されているSEMのためのソフトウェアである。有償ソフトではあるが世界にユーザは数多い。幸いなことに、近年Mplus開発チームはSEMに時間を組み込んだモデルをDSEM(動的SEM)と呼び、さまざまな新機能を実装している。強縦断データ分析の増大を念頭に置いた展開である。

まあそんなこんなで、Mplusを使うとして… ちょっと困ったことが起きる。

多数の測定対象の時系列データを扱う際、Mplusではアプローチが2つある。

  • 測定対象がクラスタである階層データとみなして分析するアプローチ。たとえば「複数の学校の複数の生徒のデータ」が学校-生徒という階層構造をなしているのと同様に、「複数の測定対象の複数の時点のデータ」も測定対象-時点という階層構造をなしていると考えるわけだ。渡すデータ行列の各行はある測定対象のある時点となり、各時点で測定したある変数がひとつの列となる。Mplusに渡すデータは縦長になるのでlong型ということがある。
  • 各時点での測定を異なる変数として分析するアプローチ。たとえば、各測定対象について3つの指標を10時点で測定したならば、その測定対象は30個の変数を持っている、と考える。データの各行はある測定対象となり、ある時点で測定したある変数がひとつの列になる。横長のデータになるのでwide型ということがある。

Mplusの場合、DSEMのための機能が充実しているいまでは、long型のアプローチで分析するのが容易である。しかしある種のモデルは依然としてwide型のアプローチを必要とする。たとえば隠れマルコフモデルがそうである。ある測定対象について複数の時点で測定を得ているんだけど、それらの変数の背後には見えない離散的な状態があって、それは前の時点の状態のみに依存して変化している、と考えるモデルである。Mplusの観点からは、時点ごとに潜在カテゴリカル変数があり前の時点のそれとの間に依存関係がある、ということになるのだが、あいにくそういうモデルはいまのところlong型では扱えない。

じゃあwide型で分析すればいいじゃん? いや、そうなんだけど、ここで不安になるのはモデルの大きさである。Mplus開発チーム謹製のMplus User’s Guideではwide型で隠れマルコフモデルを扱う事例を紹介しているが(EXAMPLE 8.12)、時点数は4である。強縦断データではどうなるのか。時点数が50ならば、潜在カテゴリカル変数が50個あるモデルとなる。もし各時点の測定変数が3個あったら測定変数150個のモデルとなる。ちょっと待ってください、SEMですよ? 150変数のSEMなんてあんまり見かけないですよね? そんな巨大なモデル、ほんとに推定できるの?

というわけで、本稿では、多数の測定対象から得た時系列データについて、Mplusでwide型アプローチで隠れマルコフモデルを推定する際、時点数をどこまで増やせるか、実験で確かめる。

2 データ生成モデル

2.1 潜在状態

対象者\(i\)の時点\(t (=1, \ldots, T)\) における離散的潜在状態を\(C_{it}\)とする。状態数は3とする。たとえば、ある対象者はある時点で、状態1(ハッピー)、状態2(ニュートラル)、状態3(アンハッピー)、のいずれかである。ある時点の各対象者の状態は対象者によって異なる。

潜在状態は各対象者においてマルコフ過程に従う。遷移行列は共通であり、かつ定常分布を持つとする。 つまり、ある対象者がある時点である状態である確率は、対象者とも時間とも無関係に、ただその人の前の状態によって決まる。

分析者は知らないのだが、マルコフ過程の遷移行列は以下のとおりである(ChatGPTくんにお願いして適当につくってもらいました):\[ \left[ \begin{array}{c} 0.5 & 0.3 & 0.2 \\ 0.2 & 0.6 & 0.2 \\ 0.3 & 0.1 & 0.6 \end{array} \right]\] 行は現在の状態、列は前の時点の状態、値は前の時点の状態の下での現在の状態の条件付き確率を表している。たとえば、前の時点でハッピーだった人が(1列目)、いまハッピーである確率は0.5, ニュートラルである確率は0.3, アンハッピーである確率は0.2である。

このマルコフ過程は定常分布 \((11/33, 13/33, 9/33) = (0.33, 0.39, 0.27)\)を持つ。つまり、どの時点であっても、対象者がハッピーである確率は0.33, ニュートラルである確率は0.39, アンハッピーである確率は0.27である。

2.2 測定

二値指標がM個あって、各対象者の各時点の状態を測定している。たとえば「あなたはいま幸せですか」的なYes/No設問\(M\)個からなるアンケートがあり、全員が全時点で真面目に答えておるわけですね。対象者\(i\)の時点\(t\)における\(m\)番目の指標\(u_{itm}\)は、状態\(C_{it}=c\)のもとで\[ P(u_{itm} = 1 | C_{it} = c) = logit^{-1}(a_{mc}) \] とする。つまり時点と対象者を通じて測定不変である。

分析者は知らないのだが、すべての\(m\)について \(a_{m1} = +1, a_{m2} = 0, a_{m3} = -1\)である。

3 分析モデルとその推定

分析者は上記について知っているのだが、遷移行列と\(a_{mc}\)については知らない。

そこで、対象者数\(n\)のデータを得て(欠損はない)、データにモデルをあてはめ、時点1における潜在状態の確率分布、遷移行列、\(a_{mc}\)を最尤推定する。アルゴリズムはMplusでいうところのEMAとする。

パラメータ数は、時点1における潜在状態の確率分布が2個、遷移行列が6個、\(a_{mc}\)\(3M\)個である。

推定にあたっての初期値として、\(a_{mc}\)にはその真値を与え(ちょっとずるいけど、ま、調査項目からなんとなく見当がついたんでしょうね)、他のパラメータには0を与える。

3.1 MODELコマンドがちょっとわかりにくいので解説するぞ

Mplusで隠れマルコフモデルを推定する際、遷移行列のパラメータ化について理解しておく必要がある。

3.1.1 確率分布のパラメータ化

カテゴリ数\(K\)の潜在カテゴリカル変数\(C\)があるとしよう。凡人ならばパラメータ\(P(C=1), P(C=2), \ldots, P(C=K)\)があると考えるかもしれない。しかしMplusの世界に生きる真の漢たちはそうは考えない。確率分布を表すパラメータ\(\tau_1, \ldots, \tau_{K-1}\)と定数\(\tau_K=0\)があって  \[ P(C=i) = \frac{\exp(\tau_i)}{\sum_{j=1}^K \exp(\tau_j)}\] なのだと考える(正確に言うと、デフォルトのパラメータ化であるロジット・パラメータ化の場合にはこのように考える)。 たとえば\(K=3\)で、\(\tau_1 = 1, \tau_2 = -1\)だったら、 \[ P(C=1) = \frac{\exp(1)}{\exp(1)+\exp(-1)+\exp(0)} = 0.67 \] \[ P(C=2) = \frac{\exp(-1)}{\exp(1)+\exp(-1)+\exp(0)} = 0.09 \] \[ P(C=3) = \frac{\exp(0)}{\exp(1)+\exp(-1)+\exp(0)} = 0.24 \] となる。

Mplusでは、潜在カテゴリカル件数\(C\)c とと表記するとして、\(\tau_1, \tau_2\)はそれぞれ[c#1], [c#2]と表記される。真の漢たちはこれらをcの「切片」と呼ぶ。この例では不思議な呼び方だが、こう呼ぶ理由はあとでわかる。

3.1.2 依存性のパラメータ化

今度は、ふたつの潜在カテゴリカル変数\(C_1, C_2\) (クラス数\(K_1, K_2\))があって依存性があるとしよう。凡人ならば\(K_1 \times K_2\)の同時確率行列がパラメータなのだと考えるところだが、真の漢たちは次のように考える。 \[ P(C_1 = i) = \frac{\exp(\tau_i)}{\sum_{j=1}^{K_1} \exp(\tau_j)} \] \[ P(C_2 = j | C_1=i) = \frac{\exp(\tau_{j|i})}{\sum_{k=1}^{K_2} \exp(\tau_{k|i})} \] \[ \tau_{j|i} = \alpha_j + \beta_{j|i} \] ただし\(\alpha_{K_2} = 0\), すべての\(i\)について\(\beta_{K_2|i}=0\), すべての\(j\)について\(\beta_{j|K_1} = 0\)である。

自分でもなにいってんだかわかんなくなってきたので例を挙げよう。\(K_1=K_2=3\)とする。まずはこういう表を考えますね。\(C_1\)を列, \(C_2\)を行にとって \[ \begin{array}{c|c|c} P(C_2=1|C_1=1) & P(C_2=1|C_1=2) & P(C_2=1|C_1=3) \\ \hline P(C_2=2|C_1=1) & P(C_2=2|C_1=2) & P(C_2=2|C_1=3) \\ P(C_2=3|C_1=1) & P(C_2=3|C_1=2) & \hline P(C_2=3|C_1=3) \end{array}\] これに対応する切片を次のように表現する。 \[ \begin{array}{c|c|c} \tau_{1|1} = \alpha_1 + \beta_{1|1} & \tau_{1|2} = \alpha_1 + \beta_{1|2} & \tau_{1|3} = \alpha_1 \\ \hline \tau_{2|1} = \alpha_2 + \beta_{2|1} & \tau_{2|2} = \alpha_2 + \beta_{2|2} & \tau_{2|3} = \alpha_2 \\ \hline \tau_{3|1} = 0 & \tau_{3|2} = 0 & \tau_{3|3} = 0 \\ \end{array}\] この表の全要素を指数変換し、縦にシェアを求めたのが、上の\(P(C_2 = j | C_1=i)\)の表だと考えるわけだ。 \(C_1\)の各カテゴリの確率\(P(C_1=i)\)は、切片パラメータの値\(\tau_1, \tau_2\)からわりかしすぐに求められるが、\(C_2\)の各カテゴリの確率\(P(C_2=j)\)はもはや単純ではなく、パラメータ\(\tau_1, \tau_2, \alpha_1, \alpha_2, \beta_{1|1}, \beta_{1|2}, \beta_{2|1}, \beta_{2|2}\)を組み合わせないとわからない、という点に注意。

さてMplusでは、\(C_1, C_2\)c1, c2と表記するとして、依存関係を想定する場合、MODELコマンドでc2 on c1;と指定する。これは

c2#1 on c1#1; 
c2#1 on c1#2; 
c2#2 on c1#1; 
c2#2 on c1#2;

と指定したのと同じことである。上から順に\(\beta_{1|1}, \beta_{1|2}, \beta_{2|1}, \beta_{2|2}\)を表している。

このとき、Mplusがいう[c1#1], [c1#2]が表しているのは依然として\(\tau_1, \tau_2\)であり、ここから\(C_1\)の確率分布がわりかしすぐにわかる。しかし、[c2#1], [c2#2]が表しているのは\(C_2\)の確率分布に対応するなにかではなく、\(\alpha_1,\alpha_2\)のことなのである。 これが「切片」という呼び方をする理由である。

3.1.3 本実験ではどうなるか

2節で定義した遷移行列を持つマルコフ連鎖\(C_1, C_2, \ldots, C_T\)を上述の方法でパラメータ化してみよう。まず\(C_1\)については \(\tau_1 = \log(11/9) = 0.20, \tau_2 = \log(13/9) = 0.37\)とすればいいですね。\(C_2\)はちょっとややこしい。3列目から考えると、 \[ \tau_{1|3} = \alpha_1 = \log(0.2/0.6) = -1.10\] \[ \tau_{2|3} = \alpha_2 = \log(0.2/0.6) = -1.10\] ここから \[ \tau_{1|1} = \alpha_1 + \beta_{1|1} = \log(0.5/0.3) = 0.51, \ \ \beta_{1|1} = 0.51 + 1.10 = 1.61 \] \[ \tau_{2|1} = \alpha_2 + \beta_{2|1} = \log(0.2/0.3) = -0.41, \ \ \beta_{2|1} = -0.41 + 1.10 = 0.69 \] \[ \tau_{1|2} = \alpha_1 + \beta_{1|2} = \log(0.3/0.1) = 1.09, \ \ \beta_{1|2} = 1.09 + 1.10 = 2.19 \] \[ \tau_{2|2} = \alpha_2 + \beta_{2|2} = \log(0.6/0.1) = 1.79, \ \ \beta_{2|2} = 1.79 + 1.10 = 2.89 \] となる。\(C_3, \ldots, C_T\)\(C_2\)と同じでよい。

というわけで… 本実験におけるMODELコマンドは、もしパラメータが既知ならば下記となる。\(T=4\)として、

%overall%
[c1#1 @ 0.20];       ! \tau_1
[c1#2 @ 0.37];       ! \tau_2
[c2#1-c4#1 @ -1.10]; ! \alpha_1
[c2#2-c4#2 @ -1.10]; ! \alpha_2
c2#1 on c1#1 @ 1.61; ! \beta_{1|1}
c2#2 on c1#1 @ 0.69; ! \beta_{2|1}
c2#1 on c1#2 @ 2.19; ! \beta_{1|2}
c2#2 on c1#2 @ 2.89; ! \beta_{2|2}
c3#1 on c2#1 @ 1.61; ! \beta_{1|1}
c3#2 on c2#1 @ 0.69; ! \beta_{2|1}
c3#1 on c2#2 @ 2.19; ! \beta_{1|2}
c3#2 on c2#2 @ 2.89; ! \beta_{2|2}
c4#1 on c3#1 @ 1.61; ! \beta_{1|1}
c4#2 on c3#1 @ 0.69; ! \beta_{2|1}
c4#1 on c3#2 @ 2.19; ! \beta_{1|2}
c4#2 on c3#2 @ 2.89; ! \beta_{2|2}

パラメータが未知ならば下記のようになる。等値制約を掛けていることに注意。

[c1#1 * 0] ;          ! \tau_1
[c1#2 * 0] ;          ! \tau_2
[c2#1-c4#1 * 0]  (1); ! \alpha_1
[c2#2-c4#2 * 0]  (2); ! \alpha_2
c2#1 on c1#1 * 0 (3); ! \beta_{1|1}
c2#2 on c1#1 * 0 (4); ! \beta_{2|1}
c2#1 on c1#2 * 0 (5); ! \beta_{1|2}
c2#2 on c1#2 * 0 (6); ! \beta_{2|2}
c3#1 on c2#1 * 0 (3); ! \beta_{1|1}
c3#2 on c2#1 * 0 (4); ! \beta_{2|1}
c3#1 on c2#2 * 0 (5); ! \beta_{1|2}
c3#2 on c2#2 * 0 (6); ! \beta_{2|2}
c4#1 on c3#1 * 0 (3); ! \beta_{1|1}
c4#2 on c3#1 * 0 (4); ! \beta_{2|1}
c4#1 on c3#2 * 0 (5); ! \beta_{1|2}
c4#2 on c3#2 * 0 (6); ! \beta_{2|2}

4 実験1

各時点の指標数を\(M=3\)とする。よって推定すべきパラメータは17個である。対象者数を\(n=100\)とする。 時点数\(T\)について\(4,5,\ldots, 15\)を試す。

各時点数について架空データを25個生成し、それぞれについて推定して、パラメータそれぞれについて25個の推定値のバイアスと標準誤差を求める。Mplusはモンテカルロ・シミュレーションの機能を持っており、勝手にデータを生成して分析してくれる。

また、推定にかかった時間を求める。正確に言うと、25試行の実行にかかった時間を25で割る。データ生成にかかった時間も含まれてしまっているが、ま、細かいことは気にしない。

4.1 準備

Code
# ライブラリの準備
library(tidyverse)
── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
✔ dplyr     1.1.4     ✔ readr     2.1.5
✔ forcats   1.0.0     ✔ stringr   1.5.1
✔ ggplot2   3.5.2     ✔ tibble    3.2.1
✔ lubridate 1.9.4     ✔ tidyr     1.3.1
✔ purrr     1.0.4     
── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
✖ dplyr::filter() masks stats::filter()
✖ dplyr::lag()    masks stats::lag()
ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors
Code
library(MplusAutomation)
Version:  1.1.1
We work hard to write this free software. Please help us get credit by citing: 

Hallquist, M. N. & Wiley, J. F. (2018). MplusAutomation: An R Package for Facilitating Large-Scale Latent Variable Analyses in Mplus. Structural Equation Modeling, 25, 621-638. doi: 10.1080/10705511.2017.1402334.

-- see citation("MplusAutomation").
Code
# 作業フォルダ
WORKDIR <- "c:/work"

4.2 Mplusコードの生成

まずはMplusのコードを生成する関数を作る。

Code
sub_makeInp_1 <- function(nT, nN){
  # 時点数nTのinpファイルを生成する。指標数は3, 標本サイズはnN
  
  # TITLEコマンド
  sTitle = c(
    sprintf("TITLE: HMM Model (base on Example 8.12 in Mplus Users Guide), M=3, T=%d;", nT), 
    ""
  )
  # MONTECARLOコマンド
  sMontecarlo = c(
    "montecarlo:",
    "  names = ", 
    sapply(1:nT, function(t) sprintf("    u%d1-u%d3", t, t)),
    "  ;", 
    "  generate = ", 
    sapply(1:nT, function(t) sprintf("    u%d1-u%d3 (1)", t, t)), 
    "  ;", 
    "  categorical =", 
    sapply(1:nT, function(t) sprintf("    u%d1-u%d3", t, t)), 
    "  ;", 
    "  genclasses = ", 
    sapply(1:nT, function(t) sprintf("    c%d(3)", t)), 
    "  ;", 
    "  classes = ", 
    sapply(1:nT, function(t) sprintf("    c%d(3)", t)), 
    "  ;", 
    paste0("  nobs = ", nN, ";"),
    "  nrep = 25;",
    "  seed = 12345;",
    ""
  )
  # ANALYSISコマンド
  sAnalysis = c(
    "ANALYSIS:", 
    "  type = mixture;", 
    "  estimator = ML;",
    "  algorithm = EMA;",
    ""
  )
  # MODEL POLULATIONコマンド(クラス別の部分を含む)
  sModelPopulation = c(
    "MODEL POPULATION:", 
    "  %overall%",
    "  [c1#1@0.20];", 
    "  [c1#2@0.37];", 
    sprintf("  [c2#1-c%d#1 @ -1.10];", nT), 
    sprintf("  [c2#2-c%d#2 @ -1.10];", nT), 
    sapply(2:nT, function(t){
      c(
        sprintf("  c%d#1 on c%d#1 @ 1.61;", t, t-1),
        sprintf("  c%d#2 on c%d#1 @ 0.69;", t, t-1),
        sprintf("  c%d#1 on c%d#2 @ 2.19;", t, t-1),
        sprintf("  c%d#2 on c%d#2 @ 2.89;", t, t-1)
      )
    }), 
    "",
    sapply(1:nT, function(t){
      c(
        sprintf("MODEL POPULATION-c%d:", t), 
        sprintf("  %%c%d#1%%", t),
        sprintf("  [u%d1$1 @ 1];", t),
        sprintf("  [u%d2$1 @ 1];", t),
        sprintf("  [u%d3$1 @ 1];", t),
        sprintf("  %%c%d#2%%", t),
        sprintf("  [u%d1$1 @ 0];", t),
        sprintf("  [u%d2$1 @ 0];", t),
        sprintf("  [u%d3$1 @ 0];", t),
        sprintf("  %%c%d#3%%", t),
        sprintf("  [u%d1$1 @ -1];", t),
        sprintf("  [u%d2$1 @ -1];", t),
        sprintf("  [u%d3$1 @ -1];", t),
        ""
      )
    })
  )
  # MODEL コマンド(クラス別の部分を含む)
  sModel = c(
    "MODEL:", 
    "  %overall%",
    "  [c1#1 * 0];", 
    "  [c1#2 * 0];", 
    sprintf("  [c2#1-c%d#1 * 0] (1);", nT), 
    sprintf("  [c2#2-c%d#2 * 0] (2);", nT), 
    sapply(2:nT, function(t){
      c(
        sprintf("  c%d#1 on c%d#1 * 0 (3);", t, t-1),
        sprintf("  c%d#2 on c%d#1 * 0 (4);", t, t-1),
        sprintf("  c%d#1 on c%d#2 * 0 (5);", t, t-1),
        sprintf("  c%d#2 on c%d#2 * 0 (6);", t, t-1)
      )
    }), 
    "",
    sapply(1:nT, function(t){
      c(
        sprintf("MODEL c%d:", t), 
        sprintf("  %%c%d#1%%", t),
        sprintf("  [u%d1$1 * 1] (p7);", t),
        sprintf("  [u%d2$1 * 1] (p8);", t),
        sprintf("  [u%d3$1 * 1] (p9);", t),
        sprintf("  %%c%d#2%%", t),
        sprintf("  [u%d1$1 * 0] (p10);", t),
        sprintf("  [u%d2$1 * 0] (p11);", t),
        sprintf("  [u%d3$1 * 0] (p12);", t),
        sprintf("  %%c%d#3%%", t),
        sprintf("  [u%d1$1 * -1] (p13);", t),
        sprintf("  [u%d2$1 * -1] (p14);", t),
        sprintf("  [u%d3$1 * -1] (p15);", t),
        ""
      )
    })
  )
  # 出力
  out <- c(
    sTitle, sMontecarlo, sAnalysis, sModelPopulation, sModel
  )
  return(out)
}

この関数をコールして、結果をファイルに保存する。

Code
# 生成する時点
anT <- c(4:15)

for (nT in anT){
  write_lines(sub_makeInp_1(nT, 100), file = paste0(WORKDIR, "/HMM_1_", nT, ".inp"))
}

うまく書けたかな? 時点数4の入力ファイルをみてみよう。

Code
asLine <- read_lines(paste0(WORKDIR, "/HMM_1_4.inp"))
print(asLine)
  [1] "TITLE: HMM Model (base on Example 8.12 in Mplus Users Guide), M=3, T=4;"
  [2] ""                                                                       
  [3] "montecarlo:"                                                            
  [4] "  names = "                                                             
  [5] "    u11-u13"                                                            
  [6] "    u21-u23"                                                            
  [7] "    u31-u33"                                                            
  [8] "    u41-u43"                                                            
  [9] "  ;"                                                                    
 [10] "  generate = "                                                          
 [11] "    u11-u13 (1)"                                                        
 [12] "    u21-u23 (1)"                                                        
 [13] "    u31-u33 (1)"                                                        
 [14] "    u41-u43 (1)"                                                        
 [15] "  ;"                                                                    
 [16] "  categorical ="                                                        
 [17] "    u11-u13"                                                            
 [18] "    u21-u23"                                                            
 [19] "    u31-u33"                                                            
 [20] "    u41-u43"                                                            
 [21] "  ;"                                                                    
 [22] "  genclasses = "                                                        
 [23] "    c1(3)"                                                              
 [24] "    c2(3)"                                                              
 [25] "    c3(3)"                                                              
 [26] "    c4(3)"                                                              
 [27] "  ;"                                                                    
 [28] "  classes = "                                                           
 [29] "    c1(3)"                                                              
 [30] "    c2(3)"                                                              
 [31] "    c3(3)"                                                              
 [32] "    c4(3)"                                                              
 [33] "  ;"                                                                    
 [34] "  nobs = 100;"                                                          
 [35] "  nrep = 25;"                                                           
 [36] "  seed = 12345;"                                                        
 [37] ""                                                                       
 [38] "ANALYSIS:"                                                              
 [39] "  type = mixture;"                                                      
 [40] "  estimator = ML;"                                                      
 [41] "  algorithm = EMA;"                                                     
 [42] ""                                                                       
 [43] "MODEL POPULATION:"                                                      
 [44] "  %overall%"                                                            
 [45] "  [c1#1@0.20];"                                                         
 [46] "  [c1#2@0.37];"                                                         
 [47] "  [c2#1-c4#1 @ -1.10];"                                                 
 [48] "  [c2#2-c4#2 @ -1.10];"                                                 
 [49] "  c2#1 on c1#1 @ 1.61;"                                                 
 [50] "  c2#2 on c1#1 @ 0.69;"                                                 
 [51] "  c2#1 on c1#2 @ 2.19;"                                                 
 [52] "  c2#2 on c1#2 @ 2.89;"                                                 
 [53] "  c3#1 on c2#1 @ 1.61;"                                                 
 [54] "  c3#2 on c2#1 @ 0.69;"                                                 
 [55] "  c3#1 on c2#2 @ 2.19;"                                                 
 [56] "  c3#2 on c2#2 @ 2.89;"                                                 
 [57] "  c4#1 on c3#1 @ 1.61;"                                                 
 [58] "  c4#2 on c3#1 @ 0.69;"                                                 
 [59] "  c4#1 on c3#2 @ 2.19;"                                                 
 [60] "  c4#2 on c3#2 @ 2.89;"                                                 
 [61] ""                                                                       
 [62] "MODEL POPULATION-c1:"                                                   
 [63] "  %c1#1%"                                                               
 [64] "  [u11$1 @ 1];"                                                         
 [65] "  [u12$1 @ 1];"                                                         
 [66] "  [u13$1 @ 1];"                                                         
 [67] "  %c1#2%"                                                               
 [68] "  [u11$1 @ 0];"                                                         
 [69] "  [u12$1 @ 0];"                                                         
 [70] "  [u13$1 @ 0];"                                                         
 [71] "  %c1#3%"                                                               
 [72] "  [u11$1 @ -1];"                                                        
 [73] "  [u12$1 @ -1];"                                                        
 [74] "  [u13$1 @ -1];"                                                        
 [75] ""                                                                       
 [76] "MODEL POPULATION-c2:"                                                   
 [77] "  %c2#1%"                                                               
 [78] "  [u21$1 @ 1];"                                                         
 [79] "  [u22$1 @ 1];"                                                         
 [80] "  [u23$1 @ 1];"                                                         
 [81] "  %c2#2%"                                                               
 [82] "  [u21$1 @ 0];"                                                         
 [83] "  [u22$1 @ 0];"                                                         
 [84] "  [u23$1 @ 0];"                                                         
 [85] "  %c2#3%"                                                               
 [86] "  [u21$1 @ -1];"                                                        
 [87] "  [u22$1 @ -1];"                                                        
 [88] "  [u23$1 @ -1];"                                                        
 [89] ""                                                                       
 [90] "MODEL POPULATION-c3:"                                                   
 [91] "  %c3#1%"                                                               
 [92] "  [u31$1 @ 1];"                                                         
 [93] "  [u32$1 @ 1];"                                                         
 [94] "  [u33$1 @ 1];"                                                         
 [95] "  %c3#2%"                                                               
 [96] "  [u31$1 @ 0];"                                                         
 [97] "  [u32$1 @ 0];"                                                         
 [98] "  [u33$1 @ 0];"                                                         
 [99] "  %c3#3%"                                                               
[100] "  [u31$1 @ -1];"                                                        
[101] "  [u32$1 @ -1];"                                                        
[102] "  [u33$1 @ -1];"                                                        
[103] ""                                                                       
[104] "MODEL POPULATION-c4:"                                                   
[105] "  %c4#1%"                                                               
[106] "  [u41$1 @ 1];"                                                         
[107] "  [u42$1 @ 1];"                                                         
[108] "  [u43$1 @ 1];"                                                         
[109] "  %c4#2%"                                                               
[110] "  [u41$1 @ 0];"                                                         
[111] "  [u42$1 @ 0];"                                                         
[112] "  [u43$1 @ 0];"                                                         
[113] "  %c4#3%"                                                               
[114] "  [u41$1 @ -1];"                                                        
[115] "  [u42$1 @ -1];"                                                        
[116] "  [u43$1 @ -1];"                                                        
[117] ""                                                                       
[118] "MODEL:"                                                                 
[119] "  %overall%"                                                            
[120] "  [c1#1 * 0];"                                                          
[121] "  [c1#2 * 0];"                                                          
[122] "  [c2#1-c4#1 * 0] (1);"                                                 
[123] "  [c2#2-c4#2 * 0] (2);"                                                 
[124] "  c2#1 on c1#1 * 0 (3);"                                                
[125] "  c2#2 on c1#1 * 0 (4);"                                                
[126] "  c2#1 on c1#2 * 0 (5);"                                                
[127] "  c2#2 on c1#2 * 0 (6);"                                                
[128] "  c3#1 on c2#1 * 0 (3);"                                                
[129] "  c3#2 on c2#1 * 0 (4);"                                                
[130] "  c3#1 on c2#2 * 0 (5);"                                                
[131] "  c3#2 on c2#2 * 0 (6);"                                                
[132] "  c4#1 on c3#1 * 0 (3);"                                                
[133] "  c4#2 on c3#1 * 0 (4);"                                                
[134] "  c4#1 on c3#2 * 0 (5);"                                                
[135] "  c4#2 on c3#2 * 0 (6);"                                                
[136] ""                                                                       
[137] "MODEL c1:"                                                              
[138] "  %c1#1%"                                                               
[139] "  [u11$1 * 1] (p7);"                                                    
[140] "  [u12$1 * 1] (p8);"                                                    
[141] "  [u13$1 * 1] (p9);"                                                    
[142] "  %c1#2%"                                                               
[143] "  [u11$1 * 0] (p10);"                                                   
[144] "  [u12$1 * 0] (p11);"                                                   
[145] "  [u13$1 * 0] (p12);"                                                   
[146] "  %c1#3%"                                                               
[147] "  [u11$1 * -1] (p13);"                                                  
[148] "  [u12$1 * -1] (p14);"                                                  
[149] "  [u13$1 * -1] (p15);"                                                  
[150] ""                                                                       
[151] "MODEL c2:"                                                              
[152] "  %c2#1%"                                                               
[153] "  [u21$1 * 1] (p7);"                                                    
[154] "  [u22$1 * 1] (p8);"                                                    
[155] "  [u23$1 * 1] (p9);"                                                    
[156] "  %c2#2%"                                                               
[157] "  [u21$1 * 0] (p10);"                                                   
[158] "  [u22$1 * 0] (p11);"                                                   
[159] "  [u23$1 * 0] (p12);"                                                   
[160] "  %c2#3%"                                                               
[161] "  [u21$1 * -1] (p13);"                                                  
[162] "  [u22$1 * -1] (p14);"                                                  
[163] "  [u23$1 * -1] (p15);"                                                  
[164] ""                                                                       
[165] "MODEL c3:"                                                              
[166] "  %c3#1%"                                                               
[167] "  [u31$1 * 1] (p7);"                                                    
[168] "  [u32$1 * 1] (p8);"                                                    
[169] "  [u33$1 * 1] (p9);"                                                    
[170] "  %c3#2%"                                                               
[171] "  [u31$1 * 0] (p10);"                                                   
[172] "  [u32$1 * 0] (p11);"                                                   
[173] "  [u33$1 * 0] (p12);"                                                   
[174] "  %c3#3%"                                                               
[175] "  [u31$1 * -1] (p13);"                                                  
[176] "  [u32$1 * -1] (p14);"                                                  
[177] "  [u33$1 * -1] (p15);"                                                  
[178] ""                                                                       
[179] "MODEL c4:"                                                              
[180] "  %c4#1%"                                                               
[181] "  [u41$1 * 1] (p7);"                                                    
[182] "  [u42$1 * 1] (p8);"                                                    
[183] "  [u43$1 * 1] (p9);"                                                    
[184] "  %c4#2%"                                                               
[185] "  [u41$1 * 0] (p10);"                                                   
[186] "  [u42$1 * 0] (p11);"                                                   
[187] "  [u43$1 * 0] (p12);"                                                   
[188] "  %c4#3%"                                                               
[189] "  [u41$1 * -1] (p13);"                                                  
[190] "  [u42$1 * -1] (p14);"                                                  
[191] "  [u43$1 * -1] (p15);"                                                  
[192] ""                                                                       

5 実験2

あとで紹介しますが、実験1は途中であえなく失敗しました。メモリ不足。辛い。

もしかすると、変数の数、つまり時点数x指標数が大きすぎるのかもしれない。 そこで今度は、指標数を\(M=1\)に減らして再挑戦する。推定すべきパラメータは12個となる。 他の設定は実験1と同様。

5.1 Mplusコードの生成

Mplusコードは実験1と似ているのだが、面倒なので、生成用の関数を新たに作り直した。

Code
sub_makeInp_2 <- function(nT, nN){
  # 時点数nTのinpファイルを生成する。指標数1, 標本サイズnN
  
  # TITLEコマンド
  sTitle = c(
    sprintf("TITLE: HMM Model (base on Example 8.12 in Mplus Users Guide), M=1, T=%d;", nT), 
    ""
  )
  # MONTECARLOコマンド
  sMontecarlo = c(
    "montecarlo:",
    "  names = ", 
    sapply(1:nT, function(t) sprintf("    u%d1", t)),
    "  ;", 
    "  generate = ", 
    sapply(1:nT, function(t) sprintf("    u%d1 (1)", t)), 
    "  ;", 
    "  categorical =", 
    sapply(1:nT, function(t) sprintf("    u%d1", t)), 
    "  ;", 
    "  genclasses = ", 
    sapply(1:nT, function(t) sprintf("    c%d(3)", t)), 
    "  ;", 
    "  classes = ", 
    sapply(1:nT, function(t) sprintf("    c%d(3)", t)), 
    "  ;", 
    paste0("  nobs =", nN, ";"),
    "  nrep = 25;",
    "  seed = 12345;",
    ""
  )
  # ANALYSISコマンド
  sAnalysis = c(
    "ANALYSIS:", 
    "  type = mixture;", 
    "  estimator = ML;",
    "  algorithm = EMA;",
    ""
  )
  # MODEL POLULATIONコマンド(クラス別の部分を含む)
  sModelPopulation = c(
    "MODEL POPULATION:", 
    "  %overall%",
    "  [c1#1@0.20];", 
    "  [c1#2@0.37];", 
    sprintf("  [c2#1-c%d#1 @ -1.10];", nT), 
    sprintf("  [c2#2-c%d#2 @ -1.10];", nT), 
    sapply(2:nT, function(t){
      c(
        sprintf("  c%d#1 on c%d#1 @ 1.61;", t, t-1),
        sprintf("  c%d#2 on c%d#1 @ 0.69;", t, t-1),
        sprintf("  c%d#1 on c%d#2 @ 2.19;", t, t-1),
        sprintf("  c%d#2 on c%d#2 @ 2.89;", t, t-1)
      )
    }), 
    "",
    sapply(1:nT, function(t){
      c(
        sprintf("MODEL POPULATION-c%d:", t), 
        sprintf("  %%c%d#1%%", t),
        sprintf("  [u%d1$1 @ 1];", t),
        sprintf("  %%c%d#2%%", t),
        sprintf("  [u%d1$1 @ 0];", t),
        sprintf("  %%c%d#3%%", t),
        sprintf("  [u%d1$1 @ -1];", t),
        ""
      )
    })
  )
  # MODEL コマンド(クラス別の部分を含む)
  sModel = c(
    "MODEL:", 
    "  %overall%",
    "  [c1#1 * 0];", 
    "  [c1#2 * 0];", 
    sprintf("  [c2#1-c%d#1 * 0] (1);", nT), 
    sprintf("  [c2#2-c%d#2 * 0] (2);", nT), 
    sapply(2:nT, function(t){
      c(
        sprintf("  c%d#1 on c%d#1 * 0 (3);", t, t-1),
        sprintf("  c%d#2 on c%d#1 * 0 (4);", t, t-1),
        sprintf("  c%d#1 on c%d#2 * 0 (5);", t, t-1),
        sprintf("  c%d#2 on c%d#2 * 0 (6);", t, t-1)
      )
    }), 
    "",
    sapply(1:nT, function(t){
      c(
        sprintf("MODEL c%d:", t), 
        sprintf("  %%c%d#1%%", t),
        sprintf("  [u%d1$1 * 1] (p7);", t),
        sprintf("  %%c%d#2%%", t),
        sprintf("  [u%d1$1 * 0] (p10);", t),
        sprintf("  %%c%d#3%%", t),
        sprintf("  [u%d1$1 * -1] (p13);", t),
        ""
      )
    })
  )
  # 出力
  out <- c(
    sTitle, sMontecarlo, sAnalysis, sModelPopulation, sModel
  )
  return(out)
}

この関数をコールして、結果をファイルに保存する。

Code
# 生成する時点
anT <- c(4:15)

for (nT in anT){
  write_lines(sub_makeInp_2(nT, 100), file = paste0(WORKDIR, "/HMM_2_", nT, ".inp"))
}

うまく書けたかな? 時点数4のinpファイルをみてみよう。

Code
asLine <- read_lines(paste0(WORKDIR, "/HMM_2_4.inp"))
print(asLine)
  [1] "TITLE: HMM Model (base on Example 8.12 in Mplus Users Guide), M=1, T=4;"
  [2] ""                                                                       
  [3] "montecarlo:"                                                            
  [4] "  names = "                                                             
  [5] "    u11"                                                                
  [6] "    u21"                                                                
  [7] "    u31"                                                                
  [8] "    u41"                                                                
  [9] "  ;"                                                                    
 [10] "  generate = "                                                          
 [11] "    u11 (1)"                                                            
 [12] "    u21 (1)"                                                            
 [13] "    u31 (1)"                                                            
 [14] "    u41 (1)"                                                            
 [15] "  ;"                                                                    
 [16] "  categorical ="                                                        
 [17] "    u11"                                                                
 [18] "    u21"                                                                
 [19] "    u31"                                                                
 [20] "    u41"                                                                
 [21] "  ;"                                                                    
 [22] "  genclasses = "                                                        
 [23] "    c1(3)"                                                              
 [24] "    c2(3)"                                                              
 [25] "    c3(3)"                                                              
 [26] "    c4(3)"                                                              
 [27] "  ;"                                                                    
 [28] "  classes = "                                                           
 [29] "    c1(3)"                                                              
 [30] "    c2(3)"                                                              
 [31] "    c3(3)"                                                              
 [32] "    c4(3)"                                                              
 [33] "  ;"                                                                    
 [34] "  nobs =100;"                                                           
 [35] "  nrep = 25;"                                                           
 [36] "  seed = 12345;"                                                        
 [37] ""                                                                       
 [38] "ANALYSIS:"                                                              
 [39] "  type = mixture;"                                                      
 [40] "  estimator = ML;"                                                      
 [41] "  algorithm = EMA;"                                                     
 [42] ""                                                                       
 [43] "MODEL POPULATION:"                                                      
 [44] "  %overall%"                                                            
 [45] "  [c1#1@0.20];"                                                         
 [46] "  [c1#2@0.37];"                                                         
 [47] "  [c2#1-c4#1 @ -1.10];"                                                 
 [48] "  [c2#2-c4#2 @ -1.10];"                                                 
 [49] "  c2#1 on c1#1 @ 1.61;"                                                 
 [50] "  c2#2 on c1#1 @ 0.69;"                                                 
 [51] "  c2#1 on c1#2 @ 2.19;"                                                 
 [52] "  c2#2 on c1#2 @ 2.89;"                                                 
 [53] "  c3#1 on c2#1 @ 1.61;"                                                 
 [54] "  c3#2 on c2#1 @ 0.69;"                                                 
 [55] "  c3#1 on c2#2 @ 2.19;"                                                 
 [56] "  c3#2 on c2#2 @ 2.89;"                                                 
 [57] "  c4#1 on c3#1 @ 1.61;"                                                 
 [58] "  c4#2 on c3#1 @ 0.69;"                                                 
 [59] "  c4#1 on c3#2 @ 2.19;"                                                 
 [60] "  c4#2 on c3#2 @ 2.89;"                                                 
 [61] ""                                                                       
 [62] "MODEL POPULATION-c1:"                                                   
 [63] "  %c1#1%"                                                               
 [64] "  [u11$1 @ 1];"                                                         
 [65] "  %c1#2%"                                                               
 [66] "  [u11$1 @ 0];"                                                         
 [67] "  %c1#3%"                                                               
 [68] "  [u11$1 @ -1];"                                                        
 [69] ""                                                                       
 [70] "MODEL POPULATION-c2:"                                                   
 [71] "  %c2#1%"                                                               
 [72] "  [u21$1 @ 1];"                                                         
 [73] "  %c2#2%"                                                               
 [74] "  [u21$1 @ 0];"                                                         
 [75] "  %c2#3%"                                                               
 [76] "  [u21$1 @ -1];"                                                        
 [77] ""                                                                       
 [78] "MODEL POPULATION-c3:"                                                   
 [79] "  %c3#1%"                                                               
 [80] "  [u31$1 @ 1];"                                                         
 [81] "  %c3#2%"                                                               
 [82] "  [u31$1 @ 0];"                                                         
 [83] "  %c3#3%"                                                               
 [84] "  [u31$1 @ -1];"                                                        
 [85] ""                                                                       
 [86] "MODEL POPULATION-c4:"                                                   
 [87] "  %c4#1%"                                                               
 [88] "  [u41$1 @ 1];"                                                         
 [89] "  %c4#2%"                                                               
 [90] "  [u41$1 @ 0];"                                                         
 [91] "  %c4#3%"                                                               
 [92] "  [u41$1 @ -1];"                                                        
 [93] ""                                                                       
 [94] "MODEL:"                                                                 
 [95] "  %overall%"                                                            
 [96] "  [c1#1 * 0];"                                                          
 [97] "  [c1#2 * 0];"                                                          
 [98] "  [c2#1-c4#1 * 0] (1);"                                                 
 [99] "  [c2#2-c4#2 * 0] (2);"                                                 
[100] "  c2#1 on c1#1 * 0 (3);"                                                
[101] "  c2#2 on c1#1 * 0 (4);"                                                
[102] "  c2#1 on c1#2 * 0 (5);"                                                
[103] "  c2#2 on c1#2 * 0 (6);"                                                
[104] "  c3#1 on c2#1 * 0 (3);"                                                
[105] "  c3#2 on c2#1 * 0 (4);"                                                
[106] "  c3#1 on c2#2 * 0 (5);"                                                
[107] "  c3#2 on c2#2 * 0 (6);"                                                
[108] "  c4#1 on c3#1 * 0 (3);"                                                
[109] "  c4#2 on c3#1 * 0 (4);"                                                
[110] "  c4#1 on c3#2 * 0 (5);"                                                
[111] "  c4#2 on c3#2 * 0 (6);"                                                
[112] ""                                                                       
[113] "MODEL c1:"                                                              
[114] "  %c1#1%"                                                               
[115] "  [u11$1 * 1] (p7);"                                                    
[116] "  %c1#2%"                                                               
[117] "  [u11$1 * 0] (p10);"                                                   
[118] "  %c1#3%"                                                               
[119] "  [u11$1 * -1] (p13);"                                                  
[120] ""                                                                       
[121] "MODEL c2:"                                                              
[122] "  %c2#1%"                                                               
[123] "  [u21$1 * 1] (p7);"                                                    
[124] "  %c2#2%"                                                               
[125] "  [u21$1 * 0] (p10);"                                                   
[126] "  %c2#3%"                                                               
[127] "  [u21$1 * -1] (p13);"                                                  
[128] ""                                                                       
[129] "MODEL c3:"                                                              
[130] "  %c3#1%"                                                               
[131] "  [u31$1 * 1] (p7);"                                                    
[132] "  %c3#2%"                                                               
[133] "  [u31$1 * 0] (p10);"                                                   
[134] "  %c3#3%"                                                               
[135] "  [u31$1 * -1] (p13);"                                                  
[136] ""                                                                       
[137] "MODEL c4:"                                                              
[138] "  %c4#1%"                                                               
[139] "  [u41$1 * 1] (p7);"                                                    
[140] "  %c4#2%"                                                               
[141] "  [u41$1 * 0] (p10);"                                                   
[142] "  %c4#3%"                                                               
[143] "  [u41$1 * -1] (p13);"                                                  
[144] ""                                                                       

6 実験1&2の結果

Inpファイルの数は、実験1, 2それぞれ12ファイル。 MplusAutomationパッケージでバッチ処理してもいいんだけど、ひとつづつ手で実行した。

では、結果をご報告いたします。

6.1 推定できるか

もっとも気になるのは、そもそも推定できるのかどうか。残念ながら、実験1, 2ともに、推定できたのは 時点数9までであった。 時点10以上の場合、メモリ不足でエラーとなってしまう。か、悲しい… 64G積んでいるのに…

実験1, 2で変わらないということは、おそらく、変数の数ではなくて 潜在カテゴリカル変数の数が問題なのであろう。 実際、Mplusの出力にはすべての潜在カテゴリカル変数の組み合わせについての 統計量が出力されるのだが、10変数だと\(3^{10} = 59049\)件の組み合わせが生じてしまう。 推定の時に何をどうしてんのかよくわかんないけど、なんだか無理そうな雰囲気ではある。

推定に成功した出力ファイルを読み込みます。出力ファイルが超デカいので時間がかかる。

Code
# 推定に成功したファイル名
asOutput <- c(
  paste0("HMM_1_", 4:9, ".out"), 
  paste0("HMM_2_", 4:9, ".out")
)

# 読み込み
# lResult <- lapply(
#   asOutput,
#   function(sOutput){
#     readModels(target = paste0(WORKDIR, "/", sOutput))
#   }
# )
# names(lResult) <- asOutput
# write_rds(lResult, file = "lResult.rds")
lResult <- read_rds(file = "lResult.rds")

# MplusAutomation::readModels()で読み込んだ出力には
# 実行時間が入っていないようなので、実行時間の行だけ別途読み直す
# dfTime <- tibble(
#   sOutput = asOutput, 
#   sTime = sapply(
#     asOutput,
#     function(sOutput){
#       asLine <- read_lines(paste0(WORKDIR, "/", sOutput))
#       sTime <- grep("Elapsed Time:", asLine, value = TRUE)
#       stopifnot(length(sTime) == 1)
#       return(sTime)
#     }
#   )
# ) |> 
#   separate(sTime, c("sTime1", "sTime2"), sep = ": ") |>
#   separate(sOutput, c("sDummy1", "nExperiment", "nNumTime", "sDummy2"), sep = "[_\\.]") |>
#   mutate(
#     gSecond = period_to_seconds(hms(sTime2))/25, 
#     nExperiment = as.integer(nExperiment), 
#     fExperiment = factor(nExperiment, levels=1:2, labels=c("実験1(指標数3)", "実験2(指標数1)")),
#     nNumTime = as.integer(nNumTime)
#   ) 
# write_rds(dfTime, file = "dfTime.rds")
dfTime <- read_rds(file = "dfTime.rds")

6.2 推定にかかる時間

まずは実行に要した時間から。

Code
g <- ggplot(data = dfTime, aes(x = nNumTime, y = gSecond, group = fExperiment, color = fExperiment))
g <- g + geom_point()
g <- g + geom_line()
g <- g + labs(x = "時点数", y = "1試行当たりの実行時間(秒) (対数目盛)", color = NULL)
g <- g + scale_y_log10()
g <- g + scale_x_continuous(breaks = anT)
g <- g + theme_bw()
print(g)

実行時間は時点の増大とともに指数的に増えることがわかる。仮に10時点以上のデータについて 推定できていたとしても、とても時間がかかったことだろう。指標数が少ないほうが時間が短いが、 たいした違いでない。

6.3 推定のバイアス

推定のバイアスはどうなっているだろうか。もっとも気になるのは、遷移行列を表す6個のパラメータである。 隠れマルコフモデルを推定する目的はさまざまなだろうけれど、 遷移行列に関心がないという場面はレアではないかとと思う。

まずはバイアスをみてみよう。

Code
lParam <- lapply(
  lResult, 
  function(lIn){
    lIn$parameters$unstandardized |> 
      dplyr::select(-mse) # なぜかしらんが型が異なる
  }
) 
dfParam <- bind_rows(lParam, .id = "sOutput") |> 
  separate(sOutput, c("sDummy1", "nExperiment", "nNumTime", "sDummy2"), sep = "[_\\.]") |>
  mutate(
    nExperiment = as.integer(nExperiment), 
    fExperiment = factor(nExperiment, levels=1:2, labels=c("実験1(指標数3)", "実験2(指標数1)")),
    nNumTime = as.integer(nNumTime)
  ) |> 
  dplyr::select(nExperiment, fExperiment, nNumTime, paramHeader, param, average, average_se)

dfTemplate <- tibble(
  paramHeader = c("C2#1.ON", "C2#1.ON", "C2#2.ON", "C2#2.ON", "Means", "Means"),
  param       = c("C1#1",    "C1#2",    "C1#1",    "C1#2",    "C2#1",  "C2#2"),
  sParam      = c("beta_1_given_1", "beta_1_given_2", "beta_2_given_1", "beta_2_given_2", "alpha_1", "alpha_2"),
  gTrue       = c(1.61, 2.19, 0.69, 2.89, -1.10, -1.10)
) |>
  mutate(fParam = factor(sParam, levels = sParam))

dfPlot <- dfParam |>
  inner_join(dfTemplate, by = c("paramHeader", "param")) |>
  mutate(gBias = average - gTrue) 
# print(dfPlot)

g <- ggplot(data = dfPlot, aes(x = nNumTime, y = gBias, group = fExperiment, color = fExperiment))
g <- g + geom_point()
g <- g + geom_line()
g <- g + geom_hline(yintercept = 0, color = "gray")
g <- g + labs(x = "時点数", y = "バイアス(=推定値の平均-真値)", color = NULL)
g <- g + facet_wrap(~ fParam)
g <- g + theme_bw()
print(g)

まずわかるのは、実験2 (指標数1) の推定が時点数8のときに失敗してしまっているということである。 指標が1つしかないのに潜在状態を推測したいだなんて、虫が良すぎるんでしょうね。

実験2の時点8を除いて描きなおすと…

Code
g <- ggplot(
  data = dfPlot |> 
    dplyr::filter(!(nExperiment == 2 & nNumTime == 8)),
  aes(x = nNumTime, y = gBias, group = fExperiment, color = fExperiment)
)
g <- g + geom_point()
g <- g + geom_line()
g <- g + geom_hline(yintercept = 0, color = "gray")
g <- g + labs(x = "時点数", y = "バイアス(=推定値の平均-真値)", color = NULL)
g <- g + facet_wrap(~ fParam)
g <- g + theme_bw()
print(g)

実験2の推定結果は他の箇所でも結構ひどい。実験1も、\(\beta_{1|1},\beta_{1|2}\)は過大に推定されている。 時点数が多いほどバイアスは減るかと思ったけど、そうでもないようだ。困っちゃうなあ。

パラメータ推定のバイアスがどのくらい深刻なのかを直感的に理解するために、 得られたパラメータ推定値を遷移行列に変換してみよう。

実験1, 時点数9のパラメータ推定値(25試行の平均値)を用いる。 今回の実験のなかではわりかしましなほうである。

推定された遷移行列はこちら。

Code
  dfTemp <- dfPlot |> dplyr::filter(nExperiment == 1, nNumTime == 9)

  # 真値をつかって計算方法を確認
  # agTrue <- dfTemp$gTrue
  # names(agTrue) <- dfTemp$sParam
  # 
  # agMatrix_True <- matrix(c(
  #   agTrue["alpha_1"] + agTrue["beta_1_given_1"], 
  #   agTrue["alpha_2"] + agTrue["beta_2_given_1"], 
  #   0, 
  #   agTrue["alpha_1"] + agTrue["beta_1_given_2"], 
  #   agTrue["alpha_2"] + agTrue["beta_2_given_2"], 
  #   0, 
  #   agTrue["alpha_1"], 
  #   agTrue["alpha_2"], 
  #   0
  # ), ncol = 3)
  # agMatrix_True <- exp(agMatrix_True)
  # agMatrix_True <- sweep(agMatrix_True, 2, colSums(agMatrix_True), "/")
  # print(agMatrix_True)

  agEst <- dfTemp$average
  names(agEst) <- dfTemp$sParam
  agMatrix_Est <- matrix(c(
    agEst["alpha_1"] + agEst["beta_1_given_1"],
    agEst["alpha_2"] + agEst["beta_2_given_1"],
    0,
    agEst["alpha_1"] + agEst["beta_1_given_2"],
    agEst["alpha_2"] + agEst["beta_2_given_2"],
    0,
    agEst["alpha_1"],
    agEst["alpha_2"],
    0
  ), ncol = 3)
  agMatrix_Est <- exp(agMatrix_Est)
  agMatrix_Est <- sweep(agMatrix_Est, 2, colSums(agMatrix_Est), "/")
  options(scipen=1)
  print(agMatrix_Est)
           [,1]      [,2]          [,3]
[1,] 0.87899473 0.7527283 0.00002716217
[2,] 0.05961331 0.1278687 0.24092118172
[3,] 0.06139196 0.1194030 0.75905165611

いっぽう、真の遷移行列は

\[ \left[ \begin{array}{c} 0.5 & 0.3 & 0.2 \\ 0.2 & 0.6 & 0.2 \\ 0.3 & 0.1 & 0.6 \end{array} \right]\]

やだもう、全然ちがうじゃん!

6.4 推定のばらつき

標準誤差はどうか。

Code
g <- ggplot(data = dfPlot, aes(x = nNumTime, y = average_se, group = fExperiment, color = fExperiment))
g <- g + geom_point()
g <- g + geom_line()
g <- g + geom_hline(yintercept = 0, color = "gray")
g <- g + labs(x = "時点数", y = "標準誤差の平均", color = NULL)
g <- g + facet_wrap(~ fParam)
g <- g + theme_bw()
print(g)

実験2の推定値はばらつきが大きい。これではちょっと怖くて使えない。 実験1では、時点数が小さいとき、推定値のばらつきはやや高いようだ。

7 実験3

バイアスも標準誤差も大きすぎる。これは標本サイズのせいだろうか? そこで、実験1の設定(指標数3)に戻り、対象者数\(n\)\(100, 200, \ldots, 1000\)としてやりなおす。 時点数は6に固定する。

Code
# # 生成する標本サイズ
# anN <- seq(100, 1000, 100)
# 
# for (nN in anN){
#   write_lines(sub_makeInp_1(6, nN), file = paste0(WORKDIR, "/HMM_3_", nN, ".inp"))
# }

8 実験3の結果

すべての標本サイズで推定できた。

推定に成功した出力ファイルを読み込みます。出力ファイルが超デカいので時間がかかる。

Code
# 推定に成功したファイル名
asOutput <- paste0("HMM_3_", seq(100, 1000, 100), ".out")

# 読み込み
# lResult_3 <- lapply(
#   asOutput,
#   function(sOutput){
#     readModels(target = paste0(WORKDIR, "/", sOutput))
#   }
# )
# names(lResult_3) <- asOutput
# write_rds(lResult_3, file = "lResult_3.rds")
lResult_3 <- read_rds(file = "lResult_3.rds")

# MplusAutomation::readModels()で読み込んだ出力には
# 実行時間が入っていないようなので、実行時間の行だけ別途読み直す
# dfTime_3 <- tibble(
#   sOutput = asOutput,
#   sTime = sapply(
#     asOutput,
#     function(sOutput){
#       asLine <- read_lines(paste0(WORKDIR, "/", sOutput))
#       sTime <- grep("Elapsed Time:", asLine, value = TRUE)
#       stopifnot(length(sTime) == 1)
#       return(sTime)
#     }
#   )
# ) |>
#   separate(sTime, c("sTime1", "sTime2"), sep = ": ") |>
#   separate(sOutput, c("sDummy1", "nExperiment", "nN", "sDummy2"), sep = "[_\\.]") |>
#   mutate(
#     gSecond = period_to_seconds(hms(sTime2))/25,
#     nN = as.integer(nN)
#   )
# write_rds(dfTime_3, file = "dfTime_3.rds")
dfTime_3 <- read_rds(file = "dfTime_3.rds")

8.1 推定にかかる時間

Code
g <- ggplot(data = dfTime_3, aes(x = nN, y = gSecond))
g <- g + geom_point()
g <- g + geom_line()
g <- g + labs(x = "標本サイズ", y = "1試行当たりの実行時間(秒) (対数目盛)", color = NULL)
# g <- g + scale_y_log10()
g <- g + scale_x_continuous(breaks = seq(100, 1000, 100))
g <- g + theme_bw()
print(g)

標本サイズが大きいほうが時間はかかるんだけど、そんなに明確な関係ではないし、700くらいから先はあまり 変わらないようにみえる。

8.2 推定のバイアス

Code
lParam <- lapply(
  lResult_3, 
  function(lIn){
    lIn$parameters$unstandardized |> 
      dplyr::select(-mse) # なぜかしらんが型が異なる
  }
) 
dfParam <- bind_rows(lParam, .id = "sOutput") |> 
  separate(sOutput, c("sDummy1", "nExperiment", "nN", "sDummy2"), sep = "[_\\.]") |>
  mutate(
    nN = as.integer(nN)
  ) |> 
  dplyr::select(nN, paramHeader, param, average, average_se)
# print(dfParam)
# stop()

dfTemplate <- tibble(
  paramHeader = c("C2#1.ON", "C2#1.ON", "C2#2.ON", "C2#2.ON", "Means", "Means"),
  param       = c("C1#1",    "C1#2",    "C1#1",    "C1#2",    "C2#1",  "C2#2"),
  sParam      = c("beta_1_given_1", "beta_1_given_2", "beta_2_given_1", "beta_2_given_2", "alpha_1", "alpha_2"),
  gTrue       = c(1.61, 2.19, 0.69, 2.89, -1.10, -1.10)
) |>
  mutate(fParam = factor(sParam, levels = sParam))

dfPlot <- dfParam |>
  inner_join(dfTemplate, by = c("paramHeader", "param")) |>
  mutate(gBias = average - gTrue) 
# print(dfPlot)

g <- ggplot(data = dfPlot, aes(x = nN, y = gBias))
g <- g + geom_point()
g <- g + geom_line()
g <- g + geom_hline(yintercept = 0, color = "gray")
g <- g + scale_x_continuous(breaks = seq(100, 1000, 100))
g <- g + labs(x = "標本サイズ", y = "バイアス(=推定値の平均-真値)", color = NULL)
g <- g + facet_wrap(~ fParam)
g <- g + theme_bw()
print(g)

うわあ… n=500, 800で滅茶苦茶な推定値になっている… (ドン引き)

この2点を抜いて描きなおしてみても、

Code
g <- ggplot(
  data = dfPlot |> dplyr::filter(!(nN %in% c(500, 800))),
  aes(x = nN, y = gBias)
)
g <- g + geom_point()
g <- g + geom_line()
g <- g + geom_hline(yintercept = 0, color = "gray")
g <- g + scale_x_continuous(breaks = seq(100, 1000, 100))
g <- g + labs(x = "標本サイズ", y = "バイアス(=推定値の平均-真値)", color = NULL)
g <- g + facet_wrap(~ fParam)
g <- g + theme_bw()
print(g)

バイアスは結構大きく、標本サイズとのあいだでの明確な関係はみられない。ええええ!?

8.3 推定のばらつき

標準誤差はどうか。

Code
g <- ggplot(data = dfPlot, aes(x = nN, y = average_se))
g <- g + geom_point()
g <- g + geom_line()
g <- g + geom_hline(yintercept = 0, color = "gray")
g <- g + scale_x_continuous(breaks = seq(100, 1000, 100))
g <- g + labs(x = "標本サイズ", y = "標準誤差の平均", color = NULL)
g <- g + facet_wrap(~ fParam)
g <- g + theme_bw()
print(g)

標準誤差は、標本サイズが小さいときにすごく大きい。しかし、十分なサイズがあっても依然として大きいことがある。標本サイズ1000のときの\(\beta_{2|1}\)の標準誤差がそうである。

9 まとめ

ふたつのことがわかった。

9.1 時点数はせいぜい9くらいまで

Mplusで隠れマルコフモデルを扱う場合、残念ながら、時点数はあまり多くできない。 本実験のごくシンプルな設定でさえ、時点数9が限界であった。状態数2だったらもう少し増えるかもしれないけれど。

うーん、どうしたらいいんですかね。 やはり、時点ごとの測定をすべて異なる変数とみるwide型のアプローチにはちょっと無理があり、 測定個体をクラスタとした2レベルモデル、つまりlong型のアプローチで分析できるソフトを 探したほうがよいのかもしれない。

実は、Mplusの開発者のひとりであるTihomir Asparouhovさんは、2016年の講義において、Mplusが近日 搭載するはずの機能のひとつとして潜在カテゴリカル変数があるDSEMを紹介し、その例として 2レベルモデルによる隠れマルコフモデルの推定を挙げている(講義資料”Latnt class …“のp.35をみよ)。どうやらこの時点では、Mplus Version 8 においてそういう機能が実装されるはずだったようだ。 残念ながらこの機能はVersion 8.11に至るまで実装されていないのだが、 そのうち実現することを祈りたい。

9.2 推定値はあてにならない

本実験の設定の場合、パラメータの推定誤差はかなり大きい。

\(n=100\)の場合、たとえ指標が3つあっても、時系列が9時点あっても、パラメータ推定値には大きなバイアスが生じた。 指標が1つの場合なんて、とてもじゃないけど使い物にならなかった。標本サイズを増やしても 事態はあまり改善しなかった。

ちょっとショッキングな結果でしたね。だって、たとえば100人だか100社だか100ブランドだかの9期分の縦断データがあったら、いっちょ隠れマルコフモデルをあてはめて隠れた変化パターンを抽出したろか、なんて思うじゃないですか。そういう研究って実際にあるんじゃないですかね。

隠れマルコフモデルを推定するためには、もっと多くの時点数が必要なのではないだろうか。

もっとも、本実験の結果はあくまで本実験の設定の下での話であって、別の設定ではちがうかもしれない。いずれにせよ、実データを分析して結果に一喜一憂する前に、Mplusのモンテカルロ・シミュレーション機能を活用し、自分が想定しているモデルについてのシミュレーションをやっておいたほうがよさそうだ。案外、推定値なんてぜんぜんあてにならないかもしれませんよ。

以上!