多重forが発生するケース
例えば3個のパラメータについて、いくつかのパターンの総組合せを試したいということ があるかと思います。
f <- function(x, y, z) { # x, y, zを使って何かするコード sprintf("x: %d, y: %d, z: %d", x, y, z) } x_list <- c(1, 2) y_list <- c(100, 50, 20) z_list <- c(1, 0) results<- list() for (x in x_list) { for (y in y_list) { for (z in z_list) { results <- c(results, f(x, y, z)) } } } head(results) ##> [[1]] ##> [1] "x: 1, y: 100, z: 1" ##> ##> [[2]] ##> [1] "x: 1, y: 100, z: 0" ##> ##> [[3]] ##> [1] "x: 1, y: 50, z: 1" ##> ##> [[4]] ##> [1] "x: 1, y: 50, z: 0" ##> ##> [[5]] ##> [1] "x: 1, y: 20, z: 1" ##> ##> [[6]] ##> [1] "x: 1, y: 20, z: 0"
このコードの嫌なところは例えば関数fに新しいパラメータw
を追加してw_list <- c("a", "b", "c")
というベクトルをパラメータの組合せに加えるということをしようとしたとき、変更しなければならない箇所が多いことが挙げられます。
また、見栄えも悪い気がします。
f <- function(x, y, z, w) { # x, y, z, wを使って何かするコード sprintf("x: %d, y: %d, z: %d, w: %s", x, y, z, w) } x_list <- c(1, 2) y_list <- c(100, 50, 20) z_list <- c(1, 0) w_list <- c("a", "b", "c") results<- list() for (x in x_list) { for (y in y_list) { for (z in z_list) { for (w in w_list) { results <- c(results, f(x, y, z, w)) } } } } head(results) ##> [[1]] ##> [1] "x: 1, y: 100, z: 1, w: a" ##> ##> [[2]] ##> [1] "x: 1, y: 100, z: 1, w: b" ##> ##> [[3]] ##> [1] "x: 1, y: 100, z: 1, w: c" ##> ##> [[4]] ##> [1] "x: 1, y: 100, z: 0, w: a" ##> ##> [[5]] ##> [1] "x: 1, y: 100, z: 0, w: b" ##> ##> [[6]] ##> [1] "x: 1, y: 100, z: 0, w: c"
そんなときに私ならどうするかを紹介しようと思います。
あくまで最近の私がどうしているかでこれが最善ではありません。
expand.gridとpurrrを使う
以下にコードを示します。もし関数f
の引数が増えたらその対応はwrapper_f
の内部とparam_list
の要素の追加になります。新しいコードブロックが増えるようなことはないので多少はシンプルかなと思っています。
欠点としては全組合せがいったんデータフレームに展開されるので組合せ数が爆発するとメモリに乗らなくなるかもしれないという危険があるので結局のところ一長一短ではあります。
f <- function(x, y, z, w) { # x, y, z, wを使って何かするコード sprintf("x: %d, y: %d, z: %d, w: %s", x, y, z, w) } wrapper_f <- function(p) { f(p$x, p$y, p$z, p$w) #### fの引数の数が変わったら変更すべき場所(1) ##### } param_list <- list( x = c(1, 2), y = c(100, 50, 20), z = c(1, 0), w = c("a", "b", "c") #### fの引数の数が変わったら変更すべき場所(2) ##### ) param_list |> expand.grid(stringsAsFactors = FALSE) |> # 全ての組合せを網羅するデータフレーム purrr::transpose() |> # データフレームを行ごとにイテレートする(直感的でないのがマイナスポイント) purrr::map(wrapper_f) |> head() ##> [[1]] ##> [1] "x: 1, y: 100, z: 1, w: a" ##> ##> [[2]] ##> [1] "x: 2, y: 100, z: 1, w: a" ##> ##> [[3]] ##> [1] "x: 1, y: 50, z: 1, w: a" ##> ##> [[4]] ##> [1] "x: 2, y: 50, z: 1, w: a" ##> ##> [[5]] ##> [1] "x: 1, y: 20, z: 1, w: a" ##> ##> [[6]] ##> [1] "x: 2, y: 20, z: 1, w: a"