通常のクロスバリデーションでは乱数によって結果がかなり違うため、そこで今回は一つ抜き交差検証(Leave-One-Out Cross-Validation, LOOCV)の安定性のシミュレーションを行うことにした。
LOOCVの計算の方法はこちら。
また、結果にどのくらいの安定性があるか、計算のたびに異なる乱数を与え、LOOCVのLassoの計算を100回反復した。1) 独立変数側で選ばれた変数の回数、2)選ばれた時の推定値の平均とその標準偏差を計算した。
安定性のシミュレーション
# 必要なライブラリの読み込み
library(haven)
library(tidyr)
library(nestedcv)
library(doParallel)
# データの用意
auto <- haven::read_dta("http://www.stata-press.com/data/r9/auto.dta")
auto <- auto %>% drop_na() # 全カラムに対してNAがない行を抽出
n <- nrow(auto)
price <- auto$price
X <- auto[, c("mpg", "rep78", "headroom", "trunk", "weight", "length",
"turn", "displacement", "gear_ratio", "foreign")]
X$foreign <- as.integer(X$foreign)
X <- as.matrix(X)
# 乱数シードの設定
set.seed(123)
repeat_times <- 100 # 実際の実行時には100回に設定
seeds <- sample(1:10000, repeat_times) # 指定された回数の乱数シードを生成
# 結果を保存するリストを初期化
coefficients_list <- list()
# 並列処理
num_cores <- detectCores() - 2
# 推定を実行
for (i in 1:repeat_times) {
set.seed(seeds[i])
fit2 <- nestcv.glmnet(
y = price,
x = X,
family = "gaussian",
alphaSet = 1,
outer_method = "LOOCV", # LOOCVを指定
n_outer_folds = nrow(X), # データポイント数に一致させる
n_inner_folds = 10, # 内側のCVのfold数
cv.cores = num_cores # 並列処理のためのコア数
)
coefficients_list[[i]] <- fit2$final_coef
}
# 変数のリスト(全ての可能な変数名)
fixed_row_names <- c("(Intercept)", "foreign", "headroom", "rep78",
"displacement", "weight", "turn", "gear_ratio",
"length", "trunk", "mpg")
# 各リスト要素を処理し、欠けている行をNAで埋める関数
process_coefficients <- function(coef_list, fixed_row_names) {
df <- as.data.frame(coef_list)
df_fixed <- data.frame(matrix(NA, nrow = length(fixed_row_names), ncol = ncol(df)))
rownames(df_fixed) <- fixed_row_names
colnames(df_fixed) <- colnames(df)
df_fixed[rownames(df), ] <- df
return(df_fixed)
}
# coefficients_listを処理
processed_list <- lapply(coefficients_list, process_coefficients, fixed_row_names = fixed_row_names)
# リストの各要素を結合して1つのデータフレームにする
final_df <- do.call(cbind, lapply(processed_list, function(x) x["coef"]))
# 変数が選ばれた回数の計算
selected_counts <- apply(final_df, 1, function(row) sum(!is.na(row)))
# 選ばれた時の平均値とその標準偏差の計算
coef_stats <- t(apply(final_df, 1, function(row) {
valid_values <- row[!is.na(row)]
mean_value <- mean(valid_values)
sd_value <- sd(valid_values)
c(mean = mean_value, sd = sd_value)
}))
# Jaccard係数の計算関数
jaccard_index <- function(set1, set2) {
intersect_len <- length(intersect(set1, set2))
union_len <- length(union(set1, set2))
if (union_len == 0) return(NA)
return(intersect_len / union_len)
}
# Jaccard係数の計算
jaccard_indices <- combn(1:repeat_times, 2, function(index) {
set1 <- rownames(processed_list[[index[1]]])[!is.na(processed_list[[index[1]]]$coef)]
set2 <- rownames(processed_list[[index[2]]])[!is.na(processed_list[[index[2]]]$coef)]
jaccard_index(set1, set2)
})
mean_jaccard_index <- mean(jaccard_indices, na.rm = TRUE)
# 結果をデータフレームにまとめる
results_df <- data.frame(
Variable = rownames(final_df),
SelectedCount = selected_counts,
Mean = coef_stats[, "mean"],
SD = coef_stats[, "sd"]
)
# Jaccard係数の結果を表示
print(paste("Mean Jaccard Index:", mean_jaccard_index))
# 選ばれた回数と統計量の結果を表示
print(results_df)
Variable SelectedCount Mean SD (Intercept) (Intercept) 100 850.690282 3144.3525824 foreign foreign 100 2981.745823 181.9827829 headroom headroom 100 -462.979344 72.1114951 rep78 rep78 100 132.656012 20.0136089 displacement displacement 100 13.215741 0.6015858 weight weight 100 2.379150 0.7957266 turn turn 80 -61.469561 29.2796136 gear_ratio gear_ratio 62 -140.013670 110.0054683 length length 32 -39.067285 17.9979634 trunk trunk 26 31.002440 17.1044275 mpg mpg 24 -9.062924 5.2083239
Mean Jaccard Index: 0.798967598058507