## このノートの目的 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 形式に変換してみよう]]