【R】無名関数とmap系関数について

Rにおける無名関数と、map()across()などの関数と組み合わせた使用例についてまとめます。

R
データ処理
公開

2025年11月6日

はじめに

Rでは、関数を定義する際に名前を付けることが一般的ですが、特定の場面では名前を持たない無名関数(匿名関数)を使用することがあります。無名関数は、一時的に使用される関数や、他の関数に引数として渡される関数を定義する際に便利です。

今回は、Rにおける無名関数の使い方についてまとめます。

無名関数とは?

無名関数は名前を持たない関数のことで、以下のように定義します。

# 無名関数の例
result <- (function(x) {
  x^2
})(5)

この関数はxを引数として受け取り、その2乗を返します。関数定義の直後に(5)と続けることで、5を引数として渡し、結果をresultに格納しています。

また、この例においてxを2乗する関数には名前(mutateselectなど)が付いていません。名前を付ける場合であれば、

# 名前付き関数の例
square_function <- function(x) {
  x^2
}
result <- square_function(5)

となります。square_functionという名前で関数を定義し、その後で呼び出しています。square_function(5)square_functionの部分にfunction(x) { x^2 }を代入すると、無名関数の例に戻ることがおわかりいただけると思います。

これらはともに同じことをしていますが、無名関数は一時的に使用する場合に使い捨てのように扱えるので便利です。

また、R 4.1.0以降では、\(x) x^2のように短縮記法も使用できます。

# 短縮記法の無名関数の例
result <- (\(x) x^2)(5)

function(x)\(x)に置き換えられていますが、機能は同じです。

無名関数はどんな時に必要?

無名関数は、以下のような場面で役立ちます。

  • 一時的な処理: 一度だけ使用する関数を定義する場合。
  • 関数の引数として渡す: 他の関数に処理を渡す際に、簡潔に記述できる。
  • コードの可読性向上: 簡単な処理を行う場合に、コードを短く保つ。

これだけではよくわからないと思いますので、実例をいくつか示します。

無名関数の例

無名関数は、map()across()などの関数と組み合わせて使用されることが多いです。先ほど簡単な例を挙げましたが、少し実用性を意識した例を示します。

どちらの関数も複数の値や列に対して同じ処理を適用するために使用され、それぞれの処理の内容を無名関数で定義することができます。
例えば、全ての列に対して2乗して100を加える処理を行う、みたいな場合には既存の関数では対応できないため、自分で使い捨ての関数を定義して渡すことで処理します。

library(tidyverse)

df <- tibble(a = 1:5, b = 6:10) |>
  mutate(across(everything(), \(x) x^2 + 100))
print(df)
# A tibble: 5 × 2
      a     b
  <dbl> <dbl>
1   101   136
2   104   149
3   109   164
4   116   181
5   125   200

そもそもですが…

  • across()は第一引数に対象の列を指定し、第二引数に適用する関数を指定します。
  • everything()は全ての列を選択するためのヘルパー関数です。
    • 他の例で言えばstarts_with("a")ends_with("b")など、特定の文字列で始まったり終わったりする列を一括選択するための関数もあります。

\(x) x^2 + 100の部分が無名関数で、xを引数として受け取り、x^2 + 100を返します。xである必要はありませんが、一般的です。

もっと複雑な例で言うと、map()関数を使ってリスト内の各要素に対して無名関数を適用することもできます。

numbers <- list(1:5, 6:10, 11:15, 16:20)
squared_plus_ten <- map(numbers, \(x) x^2 + 10)
print(squared_plus_ten)
[[1]]
[1] 11 14 19 26 35

[[2]]
[1]  46  59  74  91 110

[[3]]
[1] 131 154 179 206 235

[[4]]
[1] 266 299 334 371 410

map()関数はリストの各要素に対して無名関数を適用し、その結果を新しいリストとして返します。numberesリストは4つの整数ベクトルを含んでおり、各ベクトルに対して2乗して10を加える処理が行われているのが確認できます。

実はmap系関数には他にも色々なバリエーションがあります。

map系関数

map()関数には、返り値の型に応じて様々なバリエーションがあります。以下に代表的なものを示します。

  • map(): 常にリストを返す(最も汎用的)。
  • map_lgl(): 論理値(TRUE/FALSE)のベクトルを返す。
  • map_int(): 整数のベクトルを返す。
  • map_dbl(): 数値のベクトルを返す。
  • map_chr(): 文字列のベクトルを返す。
  • map_dfr(): 行方向に結合したデータフレームを返す。
  • map_dfc(): 列方向に結合したデータフレームを返す。

これらの関数を使うことで、返り値の型を明示的に指定でき、コードの意図がより明確になります。

今回はこの中で割と使用頻度が高いmap_dfr()について簡単に説明します。

map_dfr()

map_dfr()関数は、リストの各要素に対して無名関数を適用し、その結果を行方向に結合したデータフレームとして返します。例えば、複数のデータフレームを一括で処理し、結果を一つのデータフレームにまとめたい場合に便利です。

具体的には、国勢調査のメッシュデータなどの読み込みに役立ちます。メッシュデータは一定の範囲ごとに分割されているため、複数のファイルを一括で読み込み、結合する必要があります。
一応forループで読み込んで結合することもできますが、map_dfr()を使うとより簡潔に書けます。

ここではdata/census_2020フォルダに複数のテキストファイルがあると仮定し、それらを一括で読み込む例を示します。
ファイルのリストを作るためにfsパッケージも使用します。

library(tidyverse)
library(fs)

# ファイルパスのリストを取得
# glob引数で特定の拡張子のファイルのみを対象にする
file_paths <- dir_ls("data/census_2020", glob = "*.txt")

# 各ファイルを読み込み、行方向に結合
census_data <- map_dfr(file_paths, \(file) {
  read_csv(
    file,
    locale = locale(encoding = "SHIFT_JIS"),
    skip = 1
  ) |>
    select(
      1:4,
      " 人口(総数)",
      " 0~14歳人口 総数",
      " 15~64歳人口 総数",
      " 65歳以上人口 総数"
    ) |>
    filter(...2 == 0 | ...2 == 1) |>
    select(-c(...2, ...3, ...4)) |>
    rename(
      KEY_CODE = ...1,
      pop_total = " 人口(総数)",
      pop_young = " 0~14歳人口 総数",
      pop_working = " 15~64歳人口 総数",
      pop_old = " 65歳以上人口 総数"
    ) |>
    mutate(
      KEY_CODE = as.character(KEY_CODE),
      across(starts_with("pop_"), as.integer)
    )
})

このように、map_dfr()を使用することで、各ファイルを読み込み、必要な列を選択・フィルタリング・リネーム・型変換などの処理を一括で行い、最終的に一つのデータフレームとしてまとめることができます。file_paths\(file) { ... }の無名関数が適用され、各ファイルに対して同じ処理が実行されています。

内部的には最後にbind_rows()が呼ばれているので、ひと手間省けます。ただ、各ファイルで列の型が異なる1とエラーになるので注意してください。

ちなみに最後にacross(starts_with("pop_"), as.integer)としており、pop_で始まる列全てに対してas.integerを適用しています。処理内容が関数名で済む場合はこのように簡潔に書けるので便利です。
整数型にして1000で割りたい、みたいな場合には\(x) as.integer(x) / 1000のようにできますね。

さらに一例

パイプ処理の途中に無名関数を挟んで処理をしてみます。

先日仕事でこんな場面がありました。

「うわぁ、今処理しているところで一旦保存して、ここからフィルターかけたものをbind_rows()したいなあ」

いったいどんな場面だよと言う感じではありますが、事情はいろいろあって、実際もっと複雑ではありますがこんなシーンがあったんですね。

別に一旦オブジェクトを保存してから別で処理してもよいのですが、そんなにオブジェクトを増やしたくないということもあり、なんとかパイプで処理をすることにしました。

df <- tibble(
  group = rep(c("A", "B"), each = 5),
  value = 1:10
)
print(df)
# A tibble: 10 × 2
   group value
   <chr> <int>
 1 A         1
 2 A         2
 3 A         3
 4 A         4
 5 A         5
 6 B         6
 7 B         7
 8 B         8
 9 B         9
10 B        10

これを使って…

df <- df |>
  (\(data) {
    bind_rows(
      data,
      data |> filter(group == "A") |> mutate(value = value * 10)
    )
  })() |>
  arrange(group, value)
print(df)
# A tibble: 15 × 2
   group value
   <chr> <dbl>
 1 A         1
 2 A         2
 3 A         3
 4 A         4
 5 A         5
 6 A        10
 7 A        20
 8 A        30
 9 A        40
10 A        50
11 B         6
12 B         7
13 B         8
14 B         9
15 B        10

こう。

おわかりいただけますでしょうか。
パイプの途中で無名関数を定義し、dataという引数でパイプの直前のデータフレームを受け取っています。
その中でbind_rows()を使って、元のデータフレームと、groupAの行に対してvalueを10倍した行を結合しています。
最後に()を付けることで、無名関数を即座に実行しています。

dataの部分にはその直前間で処理していたデータフレームが入るので、無名関数の中でそれを利用してさらに処理を行うことができます。

最後に()を付けないとコードが途中と判断され実行されませんので、注意してください。

(今日の目的はこの処理方法について書いておきたかったということで、実はそれまでは前振りでした!)

まとめ

無名関数は、Rにおいて一時的な処理や関数の引数として使用される便利な機能です。map()across()などの関数と組み合わせることで、コードを簡潔かつ可読性の高いものにすることができます。

類似する変数がたくさんある場合や、複数のデータセットに同じ処理を適用したい場合などには、無名関数をmap()系関数やacross()などと組み合わせて使用していきましょう!

そして機会があれば、パイプの途中に無名関数を挟んで処理する方法も試してみてください。

  1. 例えばあるファイルは数値型、あるファイルは文字型など。特に全ての行が欠損していると勝手に型を判定されてエラーが起きる場合があるので、明示的に型を指定しておくとよいかもしれません。↩︎