前回、Excelで「関数型脳」を作る Vol.3:関数を生み出す工場 —— クロージャと部分適用で、「関数を生成する工場」を作り、_標準計算 や _軽減計算 という、設定済みの専用関数を作りました。
Vol.4では、テーブルの各レコードに対して、このような関数を適用する話しになります。
ありがちですが、次のようなユースケースを想定して話を進めます。
- テーブルから異常値を取り除き、
- OKのレコードに対して関数を適用する
VBAなら、For i = 1 To ... とでも書いて、IF文でOKレコードに対して関数を適用すればよいですが、Excelの数式(LAMBDA)の中には繰り返し構文がないので、どうしたものかなぁと。
1. OKデータの抽出:FILTER関数
例えば、手元に1,000行の「売上明細」があるとして、
- 「在庫が1以上、かつ東京都のデータだけ」を計算したい。
VBAなら If 文で判定しながらループしますが、関数型脳では FILTER関数 でまず「計算対象」を確定させます。
=LET(
_テーブル, A2:D1000,
// 1. まずは「面」で絞り込む(AND条件は * で繋ぐ)
_条件_数量あり, INDEX(_テーブル, , 3) > 0,
_条件_東京都, INDEX(_テーブル, , 4) = "東京都",
_計算対象, FILTER(_テーブル, _条件_数量あり * _条件_東京都),
2. 配列の各行に対して自前の関数を適用させる:MAP関数
絞り込んだレコードに対して、「単価 × 数量」関数を適用します。
これに MAP関数 を使います。MAP関数は、複数の列(配列)を同時に受け取り、関数を適用してくれます。
_商品名, INDEX(_計算対象, , 1),
_単価列, INDEX(_計算対象, , 2),
_数量列, INDEX(_計算対象, , 3),
// 2つの配列を渡すと、LAMBDAも引数を2つ受け取れます
_売上合計, MAP(_単価列, _数量列, LAMBDA(_単価, _数量, _単価 * _数量)),
_売上合計
)
「A列とB列を、1行ずつペアにして計算」しています。
これでもよいのですが、頭の中のイメージと実装が離れているので、しっくりこないです。
頭の中はテーブルをイメージしていますし、 レコード毎に関数を適用していく イメージで組み立てたいです。
3. 「行(レコード)」という塊で扱う:BYROW関数
例えば、「税区分が A なら標準、B なら軽減」といった具合に、行の中にある複数の値を見て判断する ことがよくあります。
MAP で引数を増やし続けるよりも、BYROW関数 を使って「行そのもの」を LAMBDA に渡す方が、頭の中のイメージにマッチします。
ここで、Vol.3 の「関数工場」とBYROWを使ってみます。
=LET(
// --- Vol.3の工場(クロージャ) ---
_税込計算工場, LAMBDA(_税率, LAMBDA(_価格, _価格 * (1 + _税率))),
_標準計算, _税込計算工場(0.1),
_軽減計算, _税込計算工場(0.08),
_データ, A2:C100, // [商品名, 単価, 税区分("通常" or "軽減")]
// 1. FILTERでノイズを除去
_対象, FILTER(_データ, INDEX(_データ,,2) > 0),
// 2. BYROWで「行(レコード)」として回す
_結果, BYROW(_対象, LAMBDA(_行,
LET(
_価格, INDEX(_行, 2),
_税区分, INDEX(_行, 3),
// 行の中身を見て、工場で作った「専用関数」を使い分ける
IF(_税区分 = "通常", _標準計算(_価格), _軽減計算(_価格))
)
)),
_結果
)
あらかじめ作っておいた標準税率と軽減税率の関数を、税区分を使ってテーブルの各レコードに関数を適用して計算しています。

4. MAPやBYROWってレコードを返せないの?!
ここまでの書き方だと1列だけの結果を返しているのですが、結果をレコードで返したくなります。で、試しに次のような関数を書きました。
=MAP(
SEQUENCE(5),
LAMBDA(x, HSTACK(x, x * 2))
)
これを実行すると、 #CALC! というエラーになります。MAPやBYROWは、次のような入れ子になっている配列は返せません。
{{1,2};{2,4};{3,6};{4,8};{5,10}}
うーん。あらたな問題発生。Excelだとだめなのか?!
5. まとめ
VBAの For ループがなくても、だいぶやりたいことがやれるようになってきました。
- FILTER: テーブルから処理対象レコードを抽出して、サブセットのテーブルを作る。
- MAP: 複数の列を並列に同期させて関数を適用する。
- BYROW: 行を「レコード」という塊で捉えて関数を適用する。
とここまでは良かったのですが、「配列のネスト」をどうするか?という新たな課題が出てきました...と思いましたが、Vol.2の関数も値として扱える考え方に従えば、問題は解けそうです。
次回は、配列の壁を破壊し、すべてのデータを一点に収束させ、あるいは自在に再構築する。
Excelで「関数型脳」を作る Vol.5:REDUCE(畳み込み)と THUNK —— 世界を一つにまとめ上げる。
です。
今日の気づき
-
MAPは便利だが、複雑な条件分岐が出てくるとBYROWで「レコード」として扱いたくなる。 - Vol.3で作った「専用関数」を
BYROWの中で使うと、数式が「ロジック(文章)」として読めるようになる。 -
BYROWの中でHSTACKなどを使って「配列」を返そうとすると#CALC!エラーになるが、関数も値として扱えると考えれば問題は解けそう。
