備忘録 as vet.

日々のアイデア、疑問など備忘録的に書きます。Scienceが好きです。

Rで柔軟にロジスティック回帰(MLE,lasso, ridge)のBias-corrected estimationを行う関数を自作してみた

Rで予測モデルの内的妥当性検証を行うためのパッケージはいくつか有るが、自分がほしい解析を一括で扱ってくれるいい感じのパッケージがなくて困っていた。

特に、c-statistics, calibration slope, calibration interceptのbias-corrected estimationを最尤推定法だけでなくshrinkage estimation (lasso, ridge)で評価できるパッケージは存在しない。

そこで、既存のパッケージと組み合わせていい感じのBias-corredted estimatonをしてくれる関数を自作してみた。

使用したパッケージは

pacman::p_load(
tidyverse, #データ処理で必須 
rms, #Harrellの回帰分析とにかく便利全部盛り
glmnet, #lasso, ridgeには必要不可欠
CalibrationCurves # 予測性能指標を一括して算出してくれる関数が入っている
)

まずは各種推定法における予測性能指標のデータフレームを算出する関数を自作する

MLE regression

MLE.pred<-
  function(var,data,newdata){ #var=variable, data= modeling data, newdata=predicted data

form<-as.formula(paste("death30~",paste(var,collapse="+"))) #death30の部分は二値アウトカムなので使うときに自由に設定してください
model<-lrm(form,data=data)

res<-valProbggplot(
  logit=predict(model,
                newdata=newdata),
  y=as.numeric(newdata$death30)-1,
  pl=T)

res<-
  res$stats %>%
  t() %>%
  as.data.frame()

return(res)
  } 

Lasso regression

lasso.pred<-
 function(var,data,newdata){ #var=variable, data= modeling data, newdata=predicted data

form<-as.formula(paste("death30~",paste(var,collapse="+")))
mat<-model.matrix(form, data=data)[,-1]
newmat<-model.matrix(form, data=newdata)[,-1]

#set.seed(1)
lasso.cv<-cv.glmnet(mat, data$death30, alpha = 1,family = "binomial")

res<-valProbggplot(
  p=predict(lasso.cv,
          newx=newmat,
          type="response",
          s="lambda.min"),
  y=as.numeric(newdata$death30)-1,
  pl=T)

res<-
  res$stats %>%
  t() %>%
  as.data.frame()

return(res)
}

ridge regression

ridge.pred<-
 function(var,data,newdata){ #var=variable, data= modeling data, newdata=predicted data

form<-as.formula(paste("death30~",paste(var,collapse="+")))
mat<-model.matrix(form, data=data)[,-1]
newmat<-model.matrix(form, data=newdata)[,-1]

#set.seed(1)
ridge.cv<-cv.glmnet(mat, data$death30, alpha = 0,family = "binomial")


res<-valProbggplot( 
  p=predict(ridge.cv,
          newx=newmat,
          type="response",
          s="lambda.min"),
  y=as.numeric(newdata$death30)-1,
  pl=T)

res<-
  res$stats %>%
  t() %>%
  as.data.frame()

 return(res)
}

ちなみにCalibrationCurvesパッケージのval.prob.ci.2を使っても予測性能指標は出るのだが、関数のエラーなのか、calibraiton plotの出力をoffにする(pl=F)にするとエラーが出てしまい、pl=TにするとBootstrap時にBootstrap回数枚のplot画像が出力されてしまうので、かわりにvalProbggplotを使用。

なぜかこちらだと結果を変数に格納すればplotが出ないので便利。各種指標の計算方法はval.prob.ci.2と一緒。

説明変数のベクトル、dataを入れてあげれば以下のようなデータフレームで出力される。 元々Bootstrapを想定してnewdataを変数に組み込んだ形の関数にしているが、apparent estimationをしたければ、data=newdataにしてあげればOK。

3つの推定方法(MLE, lasso, ridge)について、Bootstrap resampling法でBias-corrected estimateする自作関数

boot.bias.corr.estimation<-
  function(orig.data,var,B=5,method){
## Adjustment of the optimism by bootstrap


N <- dim(orig.data)[1] #patient number

res.i1 <- res.i2 <- list()

# MLE estimation
if (method=="MLE"){
  for(i in 1:B){
  
  #bootstrap sample
  set.seed(i)
  bs.i <- sample(1:N, N, replace=TRUE) 
  d.i <- orig.data[bs.i,]
  
  #bootstrap modelでのapparent prediction ability
  res.i1[[i]]<-MLE.pred(var,data=d.i,newdata=d.i)
  
  #bootstrap modelでoriginal dataのpredictionを外挿した結果
  res.i2[[i]]<-MLE.pred(var,data=d.i,newdata = orig.data)
  
  #print(paste(i,"th bootstrap iteration is completed.", sep=""))  
  
  }
}
  
  
# lasso estimation
else if (method=="lasso"){
  for(i in 1:B){
  
  #bootstrap sample
  set.seed(i)
  bs.i <- sample(1:N, N, replace=TRUE) 
  d.i <- orig.data[bs.i,]
  
  #bootstrap modelでのapparent prediction ability
  res.i1[[i]]<-lasso.pred(var,data=d.i,newdata=d.i)
  
  #bootstrap modelでoriginal dataのpredictionを外挿した結果
  res.i2[[i]]<-lasso.pred(var,data=d.i,newdata = orig.data)
  
  #print(paste(i,"th bootstrap iteration is completed.", sep=""))
  

  }
}

# ridge estimation
else if (method=="ridge"){
  for(i in 1:B){
    
    #bootstrap sample
    set.seed(i)
    bs.i <- sample(1:N, N, replace=TRUE) 
    d.i <- orig.data[bs.i,]
    
    #bootstrap modelでのapparent prediction ability
    res.i1[[i]]<-ridge.pred(var,data=d.i,newdata=d.i)
    
    #bootstrap modelでoriginal dataのpredictionを外挿した結果
    res.i2[[i]]<-ridge.pred(var,data=d.i,newdata = orig.data)
    
    #print(paste(i,"th bootstrap iteration is completed.", sep=""))
    
    
  }
  
}


res.i1.df <- do.call(rbind, res.i1)
res.i2.df <- do.call(rbind, res.i2)


# Extract the relevant metrics and compute the difference
opt1 <- list()

for (i in 1:B) {
  # Assuming the result data frames have columns named "AUC", "slope", and "intercept"
  auc_diff <- res.i1[[i]]$`C (ROC)` - res.i2[[i]]$`C (ROC)`
  slope_diff <- res.i1[[i]]$Slope - res.i2[[i]]$Slope
  intercept_diff <- res.i1[[i]]$Intercept - res.i2[[i]]$Intercept
  brier_diff <- res.i1[[i]]$Brier - res.i2[[i]]$Brier
  
  opt1[[i]] <- data.frame(AUC = auc_diff, slope = slope_diff, intercept = intercept_diff, brier = brier_diff)
}


opt1.df <- do.call(rbind, opt1)
save(opt1.df, file="opt1.df.RData")




# bias-corrected estimation
original.res<-
  if(method=="MLE"){
  MLE.pred(var,data=orig.data,newdata=orig.data) %>% 
  select("AUC"="C (ROC)","slope"="Slope","intercept"="Intercept","brier"="Brier") 
  }
  
  else if(method=="lasso"){
  lasso.pred(var,data=orig.data,newdata=orig.data) %>% 
  select("AUC"="C (ROC)","slope"="Slope","intercept"="Intercept","brier"="Brier") 
  }
  
  else if(method=="ridge"){
  ridge.pred(var,data=orig.data,newdata=orig.data) %>% 
  select("AUC"="C (ROC)","slope"="Slope","intercept"="Intercept","brier"="Brier") 
  }

opt1df.mean<-
  opt1.df %>%
  summarise_all(mean) 

bias.corrected.estimation<-
  original.res - opt1df.mean

result<-rbind(original.res, bias.corrected.estimation) %>% 
  mutate(rowname=c("original","bias_corrected"),.before=AUC)

return(list(optimism.data=opt1.df,
            optimisim.hist=hist(opt1.df),
            result=result))

}

以下のように、original data, 説明変数のベクトル(var)、Bootstrap回数(B)、推定方法(method)を定義すると、

apparent estimateとbias-corrected estimateが比較されたデータフレーム形式で算出される。今はc-statistics, calibration slope, intercept, brier scoreを算出しているが、内部コードをいじれば簡単にECIなども算出することはできる。

boot.bias.corr.estimation(orig.data=d,var=model1,B=500,method="MLE")

また、同時にoptimismの全データとヒストグラムも表示されるようにしたので、あとからbias-corrected estimateの95%信頼区間を算出することもできる。

色々Githubとか調べてみたけどニーズにハマるパッケージが見つけられず数日浪費していたが、最終的に自作するのが一番だと気づいた。これで予測モデル研究がだいぶはかどる。 もう少し頑張ればBootstrapしたCalibration plotの作成もできそうだけど、ひとまずはここまで。