井出草平の研究ノート

ShahとSamworthによる安定性選択 Stability Selection for Lasso[R]

www.rdocumentation.org

データの準備

library(haven)
library(tidyr)
auto_data <- haven::read_dta("http://www.stata-press.com/data/r9/auto.dta")
auto_data <- auto_data %>% drop_na()  # 全カラムに対してNAがない行を抽出
auto_data$foreign <- as.integer(auto_data$foreign)

price_data <- auto_data$price
X_data <- auto_data[, c("mpg", "rep78", "headroom", "trunk", "weight", "length",
                        "turn", "displacement", "gear_ratio", "foreign")]
X_data <- as.matrix(X_data)  

安定性選択の実行

stabsel関数を使用する際に、特に次の3つの仮定(assumption)を指定する。

  • Unimodal: この仮定は、選択確率の分布が一峰性であると仮定する。つまり、選択確率が一つの山(モード)を持つ分布に従うことを前提とする。通常、選択確率が高い変数は、信頼性の高い変数とみなされる。
  • R-concave: この仮定は、選択確率の分布がr凹性であることを仮定する。これは、選択確率の分布が特定の凹状の形状を持つことを前提としている。R-concave仮定を使用することで、選択確率の分布がより一般的な形状を持つ場合にも適用可能である。
  • None: この仮定は、選択確率の分布に特定の形状を仮定しないことを意味する。つまり、分布がどのような形状であっても受け入れられる。この設定は、特にサンプリングタイプがMB(Meinshausen & Buehlmann, 2010)の場合に使用される。

ここではsampling.type = "SS"を使用する。SSは下記の論文での方法論である。

  • R.D. Shah and R.J. Samworth (2013), Variable selection with error control: another look at stability selection. Journal of the Royal Statistical Society, Series B, 75, 55--80.
library(glmnet)
library(stabs)
library(dplyr)

set.seed(123)  # 再現性のためのシード設定
# 安定性選択の実行
stab_glmnet_model <- stabsel(
  x = X_data,
  y = price_data,
  fitfun = glmnet.lasso,
  cutoff = 0.75,
  PFER = 1,
  B = 100,
  sampling.type = "SS",
  assumption = "r-concave"
)

# 結果の表示
print(stab_glmnet_model)
plot(stab_glmnet_model, main = "Stability Selection with Lasso (glmnet)")
 Stability Selection with r-concavity assumption

Selected variables:
      weight displacement      foreign 
           5            8           10 

Selection probabilities:
      length         turn        trunk   gear_ratio        rep78     headroom          mpg displacement       weight      foreign 
       0.015        0.030        0.070        0.085        0.350        0.370        0.395        0.765        0.780        0.915 

---
Cutoff: 0.75; q: 4; PFER (*):  0.845 
   (*) or expected number of low selection probability variables
PFER (specified upper bound):  1 
PFER corresponds to signif. level 0.0845 (without multiplicity adjustment)

Lasso回帰とRidge回帰を用いて推定

selected_indices <- stab_glmnet_model$selected
selected_variables <- colnames(X_data)[selected_indices]

if (length(selected_variables) == 0) {
  print("選ばれた変数がありません。")
} else {
  X_selected_data <- X_data[, selected_variables, drop = FALSE]
  
  if (ncol(X_selected_data) == 1) {
    X_selected_data <- as.matrix(X_selected_data)
    colnames(X_selected_data) <- selected_variables
  }

  # Lasso回帰
  lasso_cv_model <- cv.glmnet(X_selected_data, price_data, alpha = 1)
  best_lambda_lasso <- lasso_cv_model$lambda.min
  lasso_coefficients <- coef(lasso_cv_model, s = best_lambda_lasso)
  print("Lasso回帰の結果:")
  print(paste("Best Lambda: ", best_lambda_lasso))
    print(lasso_coefficients)

  # Ridge回帰
  ridge_cv_model <- cv.glmnet(X_selected_data, price_data, alpha = 0)
  best_lambda_ridge <- ridge_cv_model$lambda.min
  ridge_coefficients <- coef(ridge_cv_model, s = best_lambda_ridge)
    print("Ridge回帰の結果:")
  print(paste("Best Lambda: ", best_lambda_ridge))
  print(ridge_coefficients)
}
[1] "Lasso回帰の結果:"
[1] "Best Lambda:  5.96437147505912"
4 x 1 sparse Matrix of class "dgCMatrix"
                       s1
(Intercept)  -3520.061513
weight           1.894351
displacement    14.016316
foreign       3769.198043

[1] "Ridge回帰の結果:"
[1] "Best Lambda:  158.418942078602"
4 x 1 sparse Matrix of class "dgCMatrix"
                       s1
(Intercept)  -2677.702583
weight           1.701122
displacement    13.424760
foreign       3311.321977

ブートストラップを使ったRidge回帰での推定

各ブートストラップサンプルに対してモデルを構築し、その結果を集計することで、推定値の安定性を高められる。

X_selected_data <- X_data[, selected_variables, drop = FALSE]
  
if (ncol(X_selected_data) == 1) {
    X_selected_data <- as.matrix(X_selected_data)
    colnames(X_selected_data) <- selected_variables
}

# ブートストラップ法を用いて安定した推定値を計算
set.seed(123)
n_bootstrap <- 1000
bootstrap_coefficients <- matrix(NA, ncol = ncol(X_selected_data) + 1, nrow = n_bootstrap) # +1 for intercept
  
for (i in 1:n_bootstrap) {
  set.seed(i)
  bootstrap_indices <- sample(1:nrow(X_selected_data), replace = TRUE)
  X_bootstrap <- X_selected_data[bootstrap_indices, ]
  y_bootstrap <- price_data[bootstrap_indices]
  
  ridge_model_bootstrap <- glmnet(X_bootstrap, y_bootstrap, alpha = 0)
  cv_ridge_model_bootstrap <- cv.glmnet(X_bootstrap, y_bootstrap, alpha = 0)
  best_lambda_ridge_bootstrap <- cv_ridge_model_bootstrap$lambda.min
  ridge_coefficients_bootstrap <- coef(ridge_model_bootstrap, s = best_lambda_ridge_bootstrap)
    
  bootstrap_coefficients[i, ] <- as.vector(ridge_coefficients_bootstrap)
}
  
# 推定値の平均と標準偏差を計算
mean_coefficients <- colMeans(bootstrap_coefficients, na.rm = TRUE)
sd_coefficients <- apply(bootstrap_coefficients, 2, sd, na.rm = TRUE)

# 結果の表示
results_bootstrap <- data.frame(
  Variable = c("(Intercept)", selected_variables),
  Mean = mean_coefficients,
  SD = sd_coefficients
)
  
print(results_bootstrap)
      Variable         Mean           SD
1  (Intercept) -2703.937195 1387.1918186
2       weight     1.733702    0.5080322
3 displacement    12.966528    4.5688871
4      foreign  3299.952422  616.2677086
# 必要なライブラリの読み込み
library(ggplot2)

# グラフの作成
ggplot(results_bootstrap, aes(x = Variable, y = Mean)) +
  geom_bar(stat = "identity", fill = "skyblue", width = 0.5) +
  geom_errorbar(aes(ymin = Mean - SD, ymax = Mean + SD), width = 0.2, color = "blue") +
  labs(title = "Mean and Standard Deviation of Coefficients",
       x = "Variable",
       y = "Value") +
  theme_minimal()

異なる乱数シードを用いた反復計算

# 選ばれた変数のみを用いて新たにモデルを再構築
X_selected <- X_data[, selected_vars, drop = FALSE]

if (ncol(X_selected) == 1) {
  X_selected <- as.matrix(X_selected)
  colnames(X_selected) <- selected_vars
}
# 並列処理の設定
num_cores <- detectCores() - 2  # システム全体のコア数から2つを引いた数を使用
cl <- makeCluster(num_cores)
registerDoParallel(cl)

# 結果を格納するリストを初期化
coef_list <- list()
n_repeats <- 1000

# 1000回の反復計算
for (i in 1:n_repeats) {
  set.seed(i)  # 異なる乱数シードを設定
  
  # リッジ回帰のモデルの作成
  final_model <- glmnet(X_selected, price_data, alpha = 0)
  cv_final_model <- cv.glmnet(X_selected, price_data, alpha = 0)
  best_lambda_final <- cv_final_model$lambda.min
  final_coefs <- coef(final_model, s = best_lambda_final)
  
  # 係数のリストに追加
  coef_list[[i]] <- as.vector(final_coefs)
}

# 並行処理の停止
stopCluster(cl)

# すべての反復で同じ長さのベクトルを保持するための処理
max_length <- max(sapply(coef_list, length))
coef_matrix <- do.call(rbind, lapply(coef_list, function(x) {
  c(x, rep(NA, max_length - length(x)))
}))

# 推定値の平均と標準偏差を計算
mean_coefs <- colMeans(coef_matrix, na.rm = TRUE)
sd_coefs <- apply(coef_matrix, 2, sd, na.rm = TRUE)

# 結果をデータフレームにまとめる
results_random <- data.frame(
  Variable = c("(Intercept)", selected_vars),
  Mean = mean_coefs,
  SD = sd_coefs
)

# 結果の表示
print(results_random)
      Variable        Mean           SD
1  (Intercept) -2637.36681 108.08933510
2       weight     1.69288   0.02195923
3 displacement    13.38200   0.11660046
4      foreign  3288.71780  60.57435723
# 必要なライブラリの読み込み
library(ggplot2)

# グラフの作成
ggplot(results_random, aes(x = Variable, y = Mean)) +
  geom_bar(stat = "identity", fill = "skyblue", width = 0.5) +
  geom_errorbar(aes(ymin = Mean - SD, ymax = Mean + SD), width = 0.2, color = "blue") +
  labs(title = "Mean and Standard Deviation of Coefficients (Random Seeds)",
       x = "Variable",
       y = "Value") +
  theme_minimal()