set.seed(123)
<- 240
N <- 10
mu <- 4
sigma <- 6
cens <- rnorm(N, mu, sigma) Y
TMBを使って、打ちきりデータへのモデルのあてはめをやってみます。Stan User’s GuideのCensored Dataの項の内容をTMBに移植したような内容になっています。
【2025-08-23 追記】Blueskayでコメントをいただきまして、誤りを修正しました。あと、最適化計算中にsigma
が0以下になる可能性を考慮して、念のため、モデルのパラメータとしてはlog_sigma
を使うようにしました。
データ
模擬データを生成します。平均10、標準偏差4の正規分布で、6以下の値が打ち切られて欠損値になっているという状況を想定しています。
平均と標準偏差を見てみます。
mean(Y)
## [1] 9.978479
sd(Y)
## [1] 3.753209
打ちきり閾値以下の値を欠損値にしたものをY2
とします。
<- ifelse(Y <= cens, NA, Y) Y2
欠損値の数は以下のようになっています。
length(Y[is.na(Y2)])
## [1] 37
打ちきりを考慮せず、単純に欠損値を除いた平均と標準偏差の値です。打ちきりにより、平均は大きく、標準偏差は小さくなっています。
mean(Y2, na.rm = TRUE)
## [1] 10.95027
sd(Y2, na.rm = TRUE)
## [1] 3.198584
ヒストグラム
ヒストグラムを見てみます。青緑が打ち切られた部分になります。
library(ggplot2)
data.frame(Y = Y, censored = (Y <= cens)) |>
ggplot(aes(x = Y, fill = censored)) +
geom_histogram(binwidth = 1, center = 0.5, na.rm = TRUE) +
theme_minimal()
モデル
TMBのC++のコードです。"models/censored.cpp"
に保存しておきます。TMBのpnorm
関数は、Rのpnorm
と同様に、正規分布の累積分布関数です。TMBのdnorm
には、対数で値を返すかどうかを指定するgive_log
引数があるのですが、pnorm
にはありません(ドキュメント)。試してみたところ、対数で値を返すようになっていました対数ではなく、確率スケールで値を返していました。
censored.cpp
// Censored data
#include <TMB.hpp>
template<class Type>
Type objective_function<Type>::operator() ()
{
DATA_VECTOR(Y_obs);
DATA_SCALAR(L);
DATA_INTEGER(N_cens);
PARAMETER(mu);
PARAMETER(log_sigma);
Type sigma = exp(log_sigma);
Type nll = 0;
nll += -sum(dnorm(Y_obs, mu, sigma, true));
nll += -N_cens * log(pnorm(L, mu, sigma));
return nll;
}
コンパイルと最適化
モデルをコンパイルして、できたライブラリをロードします。
library(TMB)
<- "censored"
model_name file.path("models", paste(model_name, "cpp", sep = ".")) |>
compile()
file.path("models", dynlib(model_name)) |>
dyn.load()
MakeADFun
関数で、最適化関数に渡すオブジェクトを作成して、nlminb
関数で最適化します。
<- list(Y_obs = Y2[!is.na(Y2)], L = cens,
data N_cens = sum(is.na(Y2)))
<- list(mu = 1, log_sigma = 0)
parameters <- MakeADFun(data, parameters, DLL = model_name)
obj ## Constructing atomic pnorm1
<- nlminb(obj$par, obj$fn, obj$gr)
opt ## outer mgc: 21962.24
## outer mgc: 2766.439
## outer mgc: 2015.442
## outer mgc: 627.6762
## outer mgc: 329.3835
## outer mgc: 51.0817
## outer mgc: 24.05909
## outer mgc: 28.33266
## outer mgc: 6.86779
## outer mgc: 4.248491
## outer mgc: 0.8034103
## outer mgc: 0.1025161
## outer mgc: 0.002068856
## outer mgc: 0.0004122294
## outer mgc: 1.15373e-05
結果
結果です。convergence
が0なので、収束しているようです。log_sigma
の最尤推定値が1.37なので、sigma
のスケールでは3.94くらいです。mu
もsigma
も、打ちきりを考慮しない場合よりも、元の値に近くなっていました。
print(opt)
## $par
## mu log_sigma
## 9.865439 1.372054
##
## $objective
## [1] 606.2088
##
## $convergence
## [1] 0
##
## $iterations
## [1] 14
##
## $evaluations
## function gradient
## 15 15
##
## $message
## [1] "relative convergence (4)"