## このノートの目的
MMRM(mixed model for repeated measures, 反復測定混合モデル)のような縦断解析にかけるには、データが [[long形式とwide形式|long 形式]](1 行 = 1 人 1 時点の測定)になっている必要があります。ここでは、1 人あたり複数回測定した eGFR のデータから、各人の baseline(最初に測定できた日)を決め、baseline からの経過日数を time 変数として付与するまでの手順を整理します。
実データでよく起きる次の 4 つを想定して組み立てます。
- 測定値が欠測している行がある
- 測定日が時系列どおりに並んでいない
- 最初の受診日に測定できず、後の受診で初めて実測できる人がいる
- baseline となる測定が 1 度も取れていない人がいる
## MMRM と long 形式の関係
MMRM は Stata では `mixed` で実装します。投入するデータは long 形式で、最低限「個体 id」「時間」「アウトカム」の 3 列が要ります。
```
id days_from_start eGFR
1 0 60
1 395 50
1 791 45
```
生データ(測定日と測定値の羅列)を、このかたちに整えるのが今回の作業です。整備で決めることは 2 つあります。
1. 各人の baseline をどの行にするか
2. baseline を起点にした経過時間(time 変数)をどう作るか
## 変数の命名
この手順で作る変数は次のとおりです。役割が名前から読めるように、平易な単語で付けています。
| 変数名 | 中身 |
|---|---|
| `test_date` | Stata 日付に変換した測定日 |
| `has_egfr` | eGFR を測定できた行のフラグ(1/0) |
| `is_baseline` | その人の baseline 行の印(1/0) |
| `start_date` | その人の baseline 日(起点) |
| `days_from_start` | 起点からの経過日数(time 変数) |
| `flag_analysis` | MMRM に投入する行のフラグ(1/0) |
## サンプルデータ
`input` で練習用データを作ります。欠測・並び順・baseline なしを意図的に混ぜています。
```stata
clear all
input id str10 date_text eGFR
1 "2024-05-08" 60
1 "2025-06-07" 50
1 "2026-07-08" 45
2 "2022-04-03" 70
2 "2024-06-03" .
2 "2026-07-01" 65
3 "2027-08-08" .
3 "2024-04-01" 40
3 "2025-05-08" 30
4 "2025-05-08" .
5 "2023-01-10" .
5 "2024-02-15" 55
5 "2025-03-20" 48
end
```
各人の特徴は次のとおりです。
| id | 特徴 |
|---|---|
| 1 | 欠測なし、日付も昇順に並んだ基本形 |
| 2 | 途中(2 回目)が欠測 |
| 3 | 1 行目が欠測。しかも測定日が昇順に並んでいない(2027 → 2024 → 2025) |
| 4 | eGFR が 1 度も測れていない。baseline を決められない |
| 5 | 最初の受診(2023-01-10)で eGFR が欠測。2 回目が初めての実測 |
## 手順
方針は「日付順に並べてから、各人の最初の測定行を baseline にする」というものです。list には `abbreviate(16)` を付けて、変数名が省略表示されないようにしています。
### 1. 文字列の日付を Stata 日付に変換する
読み込んだ `date_text` は文字列なので、`date()` で数値の日付 `test_date` に変換します。詳しくは [[Stata - 日付の取扱]] を参照してください。
```stata
gen test_date = date(date_text, "YMD")
format test_date %tdCCYY-NN-DD
```
### 2. 測定日順に並べる
`gsort` で、id ごとに測定日の早い順へ並べ替えます。あとで「各人の最初の行」を baseline に使うので、先にこの並びを作っておきます。変数名の前の `+` は昇順の指定で、省略しても昇順になりますが、意図をはっきりさせるために付けています。並べ替えの詳細は [[Stata - 並べ替える gsort]] を参照してください。
```stata
gsort +id +test_date
list id date_text eGFR test_date, sepby(id) noobs abbreviate(16)
```
入力時は 2027 → 2024 → 2025 の順だった id 3 が、日付の昇順に並び直りました。欠測行(eGFR が `.`)は日付の位置に従って並びます。
```
+-------------------------------------+
| id date_text eGFR test_date |
|-------------------------------------|
| 1 2024-05-08 60 2024-05-08 |
| 1 2025-06-07 50 2025-06-07 |
| 1 2026-07-08 45 2026-07-08 |
|-------------------------------------|
| 2 2022-04-03 70 2022-04-03 |
| 2 2024-06-03 . 2024-06-03 |
| 2 2026-07-01 65 2026-07-01 |
|-------------------------------------|
| 3 2024-04-01 40 2024-04-01 |
| 3 2025-05-08 30 2025-05-08 |
| 3 2027-08-08 . 2027-08-08 |
|-------------------------------------|
| 4 2025-05-08 . 2025-05-08 |
|-------------------------------------|
| 5 2023-01-10 . 2023-01-10 |
| 5 2024-02-15 55 2024-02-15 |
| 5 2025-03-20 48 2025-03-20 |
+-------------------------------------+
```
### 3. eGFR を測定できた行にフラグを立てる
baseline は「測定できた行」の中から選びます。まず測定ありの行を印付けします。
```stata
gen has_egfr = !missing(eGFR)
```
`!missing(eGFR)` は eGFR が欠測でなければ 1、欠測なら 0 を返します。欠測行(id 2 の 2 行目、id 3 の 3 行目、id 4、id 5 の 1 行目)だけ 0 になります。欠測の扱いは [[Stata - 欠損値]] も参照してください。
### 4. 各人の最初の測定行に baseline の印を付ける
`egen ... = tag(id)` は、id ごとに 1 行だけ 1 を立てる関数です。`if has_egfr == 1` を付けると、測定できた行の中で各人の最初の行に 1 が立ちます。手順 2 で日付順に並べてあるので、この「最初の行」が最も早い測定日になります。`tag` に `if` を付けたときの挙動は [[Stata - egen tag に if をつけて対象を絞る]]、`tag` 自体の詳細は [[Stata - 同じIDの最初の行だけを取り出す]] を参照してください。
```stata
egen is_baseline = tag(id) if has_egfr == 1
list id test_date has_egfr is_baseline, sepby(id) noobs abbreviate(16)
```
```
+------------------------------------------+
| id test_date has_egfr is_baseline |
|------------------------------------------|
| 1 2024-05-08 1 1 |
| 1 2025-06-07 1 0 |
| 1 2026-07-08 1 0 |
|------------------------------------------|
| 2 2022-04-03 1 1 |
| 2 2024-06-03 0 0 |
| 2 2026-07-01 1 0 |
|------------------------------------------|
| 3 2024-04-01 1 1 |
| 3 2025-05-08 1 0 |
| 3 2027-08-08 0 0 |
|------------------------------------------|
| 4 2025-05-08 0 0 |
|------------------------------------------|
| 5 2023-01-10 0 0 |
| 5 2024-02-15 1 1 |
| 5 2025-03-20 1 0 |
+------------------------------------------+
```
`if has_egfr == 1` が効くのが id 5 です。最初の受診 2023-01-10 は欠測なので印は付かず、2 番目の 2024-02-15(初めての実測)に `is_baseline = 1` が立ちます。id 1〜3 は最も早い測定日の行に印が立ち、id 4 は測定できた行が無いのでどの行にも印が付きません。この人には baseline がありません。
### 5. baseline の日付を全行に配る
`is_baseline = 1` の行にしか baseline 日が無いので、これをその人の全行に配ります。2 つのコマンドに分けます。まず baseline 行の日付だけを別変数 `base_tmp` にコピーし(他の行は自動で欠測になります)、次に `egen ... max()` で id ごとの値を全行へ広げます。1 人につき値は 1 つだけなので、`max()` でその日付が全行に入ります。
```stata
gen base_tmp = test_date if is_baseline == 1
format base_tmp %tdCCYY-NN-DD
bysort id: egen start_date = max(base_tmp)
format start_date %tdCCYY-NN-DD
list id test_date is_baseline base_tmp start_date, sepby(id) noobs abbreviate(16)
drop base_tmp
```
```
+---------------------------------------------------------+
| id test_date is_baseline base_tmp start_date |
|---------------------------------------------------------|
| 1 2024-05-08 1 2024-05-08 2024-05-08 |
| 1 2025-06-07 0 . 2024-05-08 |
| 1 2026-07-08 0 . 2024-05-08 |
|---------------------------------------------------------|
| 2 2022-04-03 1 2022-04-03 2022-04-03 |
| 2 2024-06-03 0 . 2022-04-03 |
| 2 2026-07-01 0 . 2022-04-03 |
|---------------------------------------------------------|
| 3 2024-04-01 1 2024-04-01 2024-04-01 |
| 3 2025-05-08 0 . 2024-04-01 |
| 3 2027-08-08 0 . 2024-04-01 |
|---------------------------------------------------------|
| 4 2025-05-08 0 . . |
|---------------------------------------------------------|
| 5 2023-01-10 0 . 2024-02-15 |
| 5 2024-02-15 1 2024-02-15 2024-02-15 |
| 5 2025-03-20 0 . 2024-02-15 |
+---------------------------------------------------------+
```
`start_date` が各人の全行に入りました。id 5 は初回の欠測受診も含めて全行に 2024-02-15 が配られます。id 4 は baseline 行が無いので `start_date` は欠測のままです。`base_tmp` は役目を終えたので `drop` で消します。
### 6. baseline からの経過日数(time 変数)を作る
起点 `start_date` から、各測定日までの経過日数 `days_from_start` を計算します。これが MMRM の time 変数になります。
```stata
gen days_from_start = test_date - start_date
list id test_date start_date days_from_start, sepby(id) noobs abbreviate(16)
```
起点当日は `days_from_start = 0`、以降は経過日数が入ります。起点が欠測の id 4 は `days_from_start` も欠測になります。
```
+------------------------------------------------+
| id test_date start_date days_from_start |
|------------------------------------------------|
| 1 2024-05-08 2024-05-08 0 |
| 1 2025-06-07 2024-05-08 395 |
| 1 2026-07-08 2024-05-08 791 |
|------------------------------------------------|
| 2 2022-04-03 2022-04-03 0 |
| 2 2024-06-03 2022-04-03 792 |
| 2 2026-07-01 2022-04-03 1550 |
|------------------------------------------------|
| 3 2024-04-01 2024-04-01 0 |
| 3 2025-05-08 2024-04-01 402 |
| 3 2027-08-08 2024-04-01 1224 |
|------------------------------------------------|
| 4 2025-05-08 . . |
|------------------------------------------------|
| 5 2023-01-10 2024-02-15 -401 |
| 5 2024-02-15 2024-02-15 0 |
| 5 2025-03-20 2024-02-15 399 |
+------------------------------------------------+
```
id 5 の 1 行目は baseline より前の受診なので、`days_from_start = -401` と負になります。ただしこの行は eGFR 欠測なので、次のステップで解析対象から外れます。id 3 の 3 行目(2027-08-08)も eGFR 欠測で、こちらは解析に入りません。
### 7. 解析対象行にフラグを立てる(行は削除しない)
eGFR が欠測の行や baseline を決められない人の行は、MMRM には投入しません。行を `drop` で消さず、フラグ `flag_analysis` で区別して元データを温存します。あとで「どの行をなぜ除外したか」を検証できます。
```stata
gen flag_analysis = has_egfr & !missing(start_date)
list id eGFR days_from_start has_egfr flag_analysis, sepby(id) noobs abbreviate(16)
```
解析時は `if flag_analysis == 1` で絞ります。
```
+--------------------------------------------------------+
| id eGFR days_from_start has_egfr flag_analysis |
|--------------------------------------------------------|
| 1 60 0 1 1 |
| 1 50 395 1 1 |
| 1 45 791 1 1 |
|--------------------------------------------------------|
| 2 70 0 1 1 |
| 2 . 792 0 0 |
| 2 65 1550 1 1 |
|--------------------------------------------------------|
| 3 40 0 1 1 |
| 3 30 402 1 1 |
| 3 . 1224 0 0 |
|--------------------------------------------------------|
| 4 . . 0 0 |
|--------------------------------------------------------|
| 5 . -401 0 0 |
| 5 55 0 1 1 |
| 5 48 399 1 1 |
+--------------------------------------------------------+
```
欠測行(id 2 の 2 行目、id 3 の 3 行目、id 4、id 5 の 1 行目)だけ `flag_analysis = 0` になり、MMRM に投入する 9 行と区別できました。id 5 の 1 行目は負の `days_from_start = -401` が付いていますが、`flag_analysis = 0` なので解析には入りません。
行数の内訳はログに残します。
```stata
tab flag_analysis
```
```
flag_analys |
is | Freq. Percent Cum.
------------+-----------------------------------
0 | 4 30.77 30.77
1 | 9 69.23 100.00
------------+-----------------------------------
Total | 13 100.00
```
13 行中 9 行が解析対象、4 行が対象外です。
## MMRM への投入
整備できたら `mixed` に渡します。経過日数を年単位(`years_from_start`)にしておくと、係数が「年あたりの eGFR 変化」として読めます。
```stata
gen years_from_start = days_from_start/365.25
mixed eGFR years_from_start || id: years_from_start if flag_analysis == 1
```
id をランダム切片、`years_from_start` をランダム傾きとした線形混合モデルで、`if flag_analysis == 1` により解析対象行だけを使います。
```
Mixed-effects ML regression Number of obs = 9
Group variable: id Number of groups = 4
------------------------------------------------------------------------------
eGFR | Coefficient Std. err. z P>|z| [95% conf. interval]
-------------+----------------------------------------------------------------
years_from~t | -5.548486 1.63239 -3.40 0.001 -8.747912 -2.349061
_cons | 55.85566 5.551352 10.06 0.000 44.97521 66.73611
------------------------------------------------------------------------------
```
`Number of obs = 9`、`Number of groups = 4` となり、`flag_analysis == 1` の 9 行だけが 4 人分使われました(測定ゼロの id 4 は除かれます)。傾き `years_from_start` は 1 年あたり約 −5.5 で、eGFR が経時的に低下する関連を示しています。傾きの解釈は [[Stata - 混合効果モデルの基礎]] を参照してください。
## まとめ
| ステップ | 作る変数 | コマンドの要点 | 目的 |
|---|---|---|---|
| 日付変換 | `test_date` | `date(date_text, "YMD")` | 文字列を計算できる日付にする |
| 並べ替え | — | `gsort +id +test_date` | 各人を測定日の早い順にする |
| 測定フラグ | `has_egfr` | `!missing(eGFR)` | baseline 候補行を印付けする |
| baseline 印 | `is_baseline` | `tag(id) if has_egfr==1` | 各人の最初の測定行に印を付ける |
| baseline 日 | `start_date` | `egen ... max()` で全行に配る | 起点をその人の全行に入れる |
| 経過日数 | `days_from_start` | `test_date - start_date` | time 変数を作る |
| 対象フラグ | `flag_analysis` | `gen flag_analysis = ...` | 行を消さず投入対象を絞る |
`gsort` で並べてから `tag` で最初の測定行を選ぶ流れなので、並べ替えを省くと baseline がずれます。手順 2 の `gsort` は省略しないでください。`if has_egfr == 1` を付けることで、最初の受診が欠測でも次の実測日を baseline にできます。不要行は削除せず `flag_analysis` で管理し、元データを温存します。
## Reference
- [[long形式とwide形式]]
- [[Stata - 混合効果モデルの基礎]]
- [[Stata - 同じIDの最初の行だけを取り出す]]
- [[Stata - egen tag に if をつけて対象を絞る]]
- [[Stata - 並べ替える gsort]]
- [[Stata - 日付の取扱]]
- [[Stata - 欠損値]]
- [[R - long 形式に変換してみよう]]