1. ホーム
  2. r

[解決済み】なぜlapplyの代わりにpurrr::mapを使うのですか?

2022-04-11 15:17:31

質問

を使用しなければならない理由はあるのでしょうか?

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 は、パフォーマンスや例外処理などの面で優れているのでしょうか?以下のコメントから、そうではないと思われますが、どなたかもう少し詳しく教えていただけませんか?

どのように解決するのですか?

purrrから使用している唯一の関数が map() であれば の利点はあまりありません。Rich Paulooが指摘するように、主な の利点は map() は、コンパクトな記述を可能にするヘルパーです。 のコードで、よくある特殊なケースに対応できます。

  • ~ . + 1 は、次のものと同等です。 function(x) x + 1 (そして \(x) x + 1 R-4.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() を作成することを期待します。 無名関数です。マップ関数では、定数引数は常に を関数名の後に追加します。

  • ほとんどすべてのPurr関数は型安定です。 の出力型は、もっぱら関数名から得られます。ただし sapply() または mapply() . はい、あります。 vapply() しかし に相当します。 mapply() .

このような細かい区別はすべて重要でないと思うかもしれませんが (を超えるstringrの利点はないと考える人がいるように)。 しかし、私の経験では、これらは不要な正規表現の原因となります。 プログラミングの際の摩擦(引数の順序の違いはいつも また、関数型プログラミングのテクニックを難しくしています。 というのも、大きなアイデアだけでなく、多くのことを学ばなければならないからです。 のような、付随的な細部も含めて。

また、Purrrは、基本的なRにはない便利なマップの変種を補います。

  • modify() を使用してデータの型を保持します。 [[<- を変更することができます。 place"を修正します。と連携して _if このバリアントによって、(IMO のようなコードです。 modify_if(df, is.factor, as.character)

  • 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