map関数で使える無名関数

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!

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

コメント

コメントする

目次