web-dev-qa-db-ja.com

なぜlapplyの代わりにpurrr :: mapを使うのですか?

私が使うべき理由はありますか

map(<list-like-object>, function(x) <do stuff>)

の代わりに

lapply(<list-like-object>, function(x) <do stuff>)

出力は同じでなければならず、私が行ったベンチマークはlapplyがわずかに速いことを示しているようです(mapはすべての非標準評価入力を評価する必要があるため)。

それで、そのような単純なケースのために私が実際にpurrr::mapに切り替えることを考慮すべきである理由は何ですか?ここでは、構文、purrrなどが提供する他の機能についての好き嫌いについては尋ねていませんが、purrr::maplapplyとの比較については、厳密に言うと標準的な評価、つまりmap(<list-like-object>, function(x) <do stuff>)を使用しています。パフォーマンス、例外処理などの面でpurrr::mapが持つ利点はありますか?以下のコメントはそうではないことを示唆していますが、誰かがもう少し詳しく説明することができるでしょうか。

136
Tim

あなたがpurrrから使っている唯一の関数がmap()であるならば、いいえ、利点は実質的ではありません。 Rich Pauloo氏が指摘しているように、map()の主な利点は、一般的な特別な場合のためにコンパクトなコードを書くことを可能にするヘルパーです。

  • ~ . + 1function(x) x + 1と同等です

  • list("x", 1)function(x) x[["x"]][[1]]と同等です。これらのヘルパーは[[より少し一般的です - 詳細については?pluckを参照してください。 データ矩形化 の場合、.default引数が特に役立ちます。

しかし、ほとんどの場合、単一の*apply()/map()関数を使用しているのではなく、それらの束を使用しています。そして、purrrの利点は、関数間の一貫性がはるかに高いことです。例えば:

  • lapply()の最初の引数はデータです。 mapply()の最初の引数は関数です。すべてのマップ関数の最初の引数は常にデータです。

  • vapply()sapply()、およびmapply()を使用すると、USE.NAMES = FALSEを使用して出力上の名前を抑制することを選択できます。しかしlapply()はその引数を持ちません。

  • 一貫した引数をマッパー関数に渡すための一貫した方法はありません。ほとんどの関数は...を使用しますが、mapply()MoreArgs(これはMORE.ARGSと呼ばれることを期待しています)を使用し、Map()Filter()およびReduce()は新しい無名関数を作成することを期待します。マップ関数では、定数引数は常に関数名の後に来ます。

  • ほとんどすべてのpurrr関数は型安定しています。関数名からのみ出力型を予測できます。これはsapply()またはmapply()には当てはまりません。はい、vapply()があります。しかし、mapply()に相当するものはありません。

あなたはこれらのマイナーな区別のすべてが重要ではないと思うかもしれません(base Rの正規表現よりもstringrには利点がないと考える人もいます)が、私の経験ではプログラミング時に不必要な摩擦を引き起こします大きなアイデアと同様に、あなたはまた、付随する詳細の束を学ぶ必要があるので、それらは関数型プログラミング技術を学ぶことを難しくします。

Purrrはbase Rには存在しない便利なマップの変種も埋めます。

  • modify()[[<-を使用して「その場で」変更することでデータの種類を保存します。 _ifの変形と組み合わせて、これはmodify_if(df, is.factor, as.character)のような(IMO美しい)コードを可能にします。

  • map2()を使用すると、xyに同時にマッピングできます。これにより、map2(models, datasets, predict)のようなアイデアを表現しやすくなります。

  • imap()を使うと、xとそのインデックス(名前または位置)に同時にマッピングできます。これにより、ディレクトリにすべてのcsvファイルを簡単にロードし、それぞれにfilename列を追加することができます。

    dir("\\.csv$") %>%
      set_names() %>%
      map(read.csv) %>%
      imap(~ transform(.x, filename = .y))
    
  • walk()は入力を見えなく返します。そして、あなたがその副作用のために関数を呼んでいるとき(すなわち、ファイルをディスクに書き込むとき)に役に立ちます。

safely()partial()のような他のヘルパーは言うまでもありません。

個人的には、purrrを使用すると、摩擦が少なく簡単に機能コードを書くことができます。それはアイデアを考え出すこととそれを実行することの間のギャップを減らします。しかし、あなたの走行距離は変わるかもしれません。それが実際にあなたを助けない限り、purrrを使用する必要はありません。

マイクロベンチマーク

はい、map()lapply()より少し遅くなります。ただし、map()またはlapply()を使用するコストは、ループの実行によるオーバーヘッドではなく、マッピングしている内容によって決まります。以下のマイクロベンチマークは、map()と比較してlapply()のコストが1要素あたり約40nsであることを示唆しています。これは、ほとんどのRコードに重大な影響を及ぼす可能性は低いと思われます。

library(purrr)
n <- 1e4
x <- 1:n
f <- function(x) NULL

mb <- microbenchmark::microbenchmark(
  lapply = lapply(x, f),
  map = map(x, f)
)
summary(mb, unit = "ns")$median / n
#> [1] 490.343 546.880
197
hadley

このオンラインpurrrチュートリアル は、purrrを使用するときに無名関数を明示的に記述する必要がない便利さを強調します。特定のマップ機能はそれを非常に機能的にします。

1. purrr::mapはlapplyより構文的にずっと便利です

リストの2番目の要素を抽出する

map(list, 2)  # and it's done like magic

これは@Fです。 Privéが指摘したのと同じである:

map(list, function(x) x[[2]])

lapply

lapply(list, 2) # doesn't work

それに無名関数を渡す必要があります

lapply(list, function(x) x[[2]])  # now it works

または@RichScrivenが指摘したように、単に[[を引数としてlapplyに渡すことができます

lapply(list, `[[`, 2)  # a bit more simple syntantically

バックグラウンドでは、purrは引数として数値または文字ベクトルを取り、それをサブセットとして使用します。 lapplyを使っている多くのリスト、そしてカスタム関数を定義するか匿名関数を書くことに疲れている場合、便利さがpurrrに移行する理由の1つです。

2.型固有のマップは、単に多くのコード行を機能させます。

  • map_chr()
  • map_lgl()
  • map_int()
  • map_dbl()
  • map_df() - 私のお気に入りは、データフレームを返します。

これらの型固有のマップ関数はそれぞれ、map()およびlapply()が自動的に返すリストではなく、アトミックリストを返します。アトミックベクトルを含むネストされたリストを処理している場合は、これらの型固有のマップ関数を使用してベクトルを直接取り出すか、またはベクトルをint、dbl、chrベクトルに強制変換できます。便利さと機能性のためのもう一つのポイント。

3.利便性はさておき、lapplymapより速いです。

purrrの便利な関数を@Fとして使用します。 Privé氏は、処理が少し遅くなると指摘した。上記で提示した4つの各ケースをレースしましょう。

# devtools::install_github("jennybc/repurrrsive")
library(repurrrsive)
library(purrr)
library(microbenchmark)
library(ggplot2)

mbm <- microbenchmark(
lapply       = lapply(got_chars[1:4], function(x) x[[2]]),
lapply_2     = lapply(got_chars[1:4], `[[`, 2),
map_shortcut = map(got_chars[1:4], 2),
map          = map(got_chars[1:4], function(x) x[[2]]),
times        = 100
)
autoplot(mbm)

enter image description here

そして勝者は....

lapply(list, `[[`, 2)

要するに、速度があなたの望むものであれば、base::lapply

単純な構文があなたの渋滞の場合:purrr::map

44
Rich Pauloo

嗜好の側面(そうでなければこの質問は閉じるべきです)や構文の一貫性、スタイルなどを考慮しない場合、答えはノーです。mapまたはその他のapplyファミリーの派生形の代わりにlapplyを使用する特別な理由はありません。 vapply

追伸:無償で率直に投票している人々には、OPが書いたことを忘れないでください。

構文、purrrが提供する他の機能性などについての自分の好き嫌いについてはここでは尋ねませんが、厳密には標準評価を使用していると仮定してpurrr :: mapを比較してください。

purrrの構文やその他の機能を考慮しないのであれば、mapを使用する特別な理由はありません。私はpurrrを使用し、Hadleyの答えには問題ありません。尋ねていませんでした。

28
Carlos Cinelli