library(tidyverse)
map関数の~{}の動きを眺めてみる。
注)2023年11月時点では、Rのパイプ関数は、%>%ではなく、|>が主流です。この記事は%>%での無名関数の利用方法について記載しているものになり、|>ではうまく動かないので注意が必要です。
map関数の関数部分について、~{}で記述する部分がなかなか理解が難しかった経験があるので、その動きを書いてみます。
ちなみに、~{}で各関数のことを、無名関数(anonymous function)とヘルプドキュメントに記載されています。
基本:パイプ(%>%)のデータの受け渡し
(ここからは「.」のことを、ドットと読んでください。)
この記事を読んでいただいている方にとって、Tidyverseでのデータの受け渡しで利用する、%>%記号は比較的慣れ親しんでいるものだと思います。
とりあえず、その動きの復習をしておきましょう。
まず、次のような関数を作ります。
print_dot <- function(passed_data){
print(str_c("Print Inside Function:", passed_data))
return(passed_data)
}
この関数は、単純に、中に入れたデータをPrint Inside FUnction: <中に入れたデータ>という形で出力するもので、
print_dot(2)
## [1] "Print Inside Function:2" ## [1] 2
こんな感じで実行されます。
簡単ですね?
で、パイプを利用すると、
5 %>% print_dot()
## [1] "Print Inside Function:5" ## [1] 5
10 %>% print_dot(.)
## [1] "Print Inside Function:10" ## [1] 10 こんな感じになりました。 関数のargumentの数が1個だと、ドットを書いても、書かなくても 実行結果はあんまり変わりません。
ただし、argumentの数が2個以上になってくると、ドットの書き方は実行結果に大きな影響を与えます。
print_dot2 <- function(data_one = 1, data_two = 2){
print( str_c("This is value of data_one: ", data_one))
print( str_c("This is value of data_two: ", data_two))
return(data_one + data_two)
}
こんな関数があったとして、
print_dot2()
## [1] "This is value of data_one: 1" ## [1] "This is value of data_two: 2" ## [1] 3
関数のargumentのデフォルト値がそれぞれ1と2になっているので、何もargumentを指定せずに実行すると上の形で出力がされます。
print_dot2(10,20)
## [1] "This is value of data_one: 10" ## [1] "This is value of data_two: 20" ## [1] 30 数字を書き換えるとこんな感じです。
で、ここでパイプを渡してあげると、
5 %>% print_dot2() #この場合、data_oneに5が代入されています
## [1] "This is value of data_one: 5" ## [1] "This is value of data_two: 2" ## [1] 7
6 %>% print_dot2(data_one = ., data_two = 2) #この場合も上と同様です
## [1] "This is value of data_one: 6" ## [1] "This is value of data_two: 2" ## [1] 8
7 %>% print_dot2(10) #このように引数をいれていても、やはり一つ目にデータがわたされています
## [1] "This is value of data_one: 7" ## [1] "This is value of data_two: 10" ## [1] 17
8 %>% print_dot2(1,.) #これだと、二つ目にデータを渡すことができています
## [1] "This is value of data_one: 1" ## [1] "This is value of data_two: 8" ## [1] 9
まとめると、「パイプの後の関数」には、ドットで、「パイプの前までの処理結果」を代入できるという感じです。

map、map2、pmapの「ドット」
では、ここから本題です。
map関数の無名関数
function_for_map <- function(x){
return(str_c("X: ", x))
}
こんな関数があるとすると、次の書き方はすべて同じ結果になります。(スペースの都合、map_chrとしていますが、通常のmapでも同じです)
map_chr(1:5, function_for_map)
## [1] "X: 1" "X: 2" "X: 3" "X: 4" "X: 5"
map_chr(1:5, ~{function_for_map(.)})
## [1] "X: 1" "X: 2" "X: 3" "X: 4" "X: 5"
map_chr(1:5, ~{str_c("X: ", .)})
## [1] "X: 1" "X: 2" "X: 3" "X: 4" "X: 5"

パイプと同様、「.」を使います。
map2の無名関数
map2もドットだとよいのですが、二種類のデータを表す必要があるため、ドットではなく、.xと.yを利用します。
function_for_map2 <- function(one,two){
return( str_c("X: ",one, " Y: ", two) )
}
map2_chr(letters[1:5], LETTERS[1:5], function_for_map2)
## [1] "X: a Y: A" "X: b Y: B" "X: c Y: C" "X: d Y: D" "X: e Y: E"
map2_chr(letters[1:5], LETTERS[1:5], ~{function_for_map2(.x, .y)})
## [1] "X: a Y: A" "X: b Y: B" "X: c Y: C" "X: d Y: D" "X: e Y: E"
map2_chr(letters[1:5], LETTERS[1:5], ~{str_c("X: ", .x, " Y: ", .y)})
## [1] "X: a Y: A" "X: b Y: B" "X: c Y: C" "X: d Y: D" "X: e Y: E"

pmapの無名関数
最後に、pmapでは..1 ..2 ..3 という風にして指定できます。
data_for_pmap <- list(
one = c("a1","b1","c1"),
two = c("a2","b2","c2"),
three = c("a3","b3","c3"),
four = c("a4","b4","c4"),
five = c("a5","b5","c5"),
six = c("a6","b6","c6")
)
function_for_pmap <- function(...){
str_c(..., collapse = "/")
}
pmap_chr(data_for_pmap, function_for_pmap)
## [1] "a1a2a3a4a5a6" "b1b2b3b4b5b6" "c1c2c3c4c5c6"
pmap_chr(data_for_pmap, ~{function_for_pmap(..1,..2,..3,..4,..5,..6)})
## [1] "a1a2a3a4a5a6" "b1b2b3b4b5b6" "c1c2c3c4c5c6"
pmap_chr(data_for_pmap, ~{str_c(..1,..2,..3,..4,..5,..6,collapse="/")})
## [1] "a1a2a3a4a5a6" "b1b2b3b4b5b6" "c1c2c3c4c5c6"

まとめ
ということで、map、map2、pmapの無名関数の指定方法について解説してみました。この内容そのものは、RStudioのチートシートに記載されている内容となりますが、イメージがつかめるまで、自分にとって謎の記載だったため、同じところで悩んでいる人の役に立てば幸いです。
Have a happy R life!
コメント