可変長配列をVSTACKする4つの方法|REDUCE・Thunk・チャンク・再帰分割
Excelで TEXTSPLIT などを用いて文字列を分割すると、行ごとに生成される配列の長さ(行数)が変わる「可変長配列」になります。
これらを1つの表として縦方向(VSTACK)に結合する処理は実務で頻出しますが、対象データが数千行~数万行になると、単純な VSTACK の繰り返しでは処理時間が飛躍的に増大するという問題が発生します。
※本記事の数式および解説文章の多くは、生成AI(ChatGPT、Gemini、Claude)を活用して作成しました。数式動作や解説文の最終的な内容は人間による確認・編集を経て掲載しています。
ページ内目次
1. 順次結合パターン(REDUCE + INDEX)
2. Thunk(サンク)パターン(BYROW + LAMBDA)
3. チャンク分割パターン(ブロック処理 + Thunk)
4. 再帰分割パターン(二分割 + VSTACK)
比較:4方式のパフォーマンス・特性比較
まとめ:実務における使い分けの指針
番外編:Python in Excel
課題の設定:データ展開のイメージ


1. 順次結合パターン(REDUCE + INDEX)
REDUCE を使って入力行を1行ずつループ処理し、分割・整形した配列を VSTACK で順次積み重ねていきます。
=LET(
input, A2:B5000,
DROP(
REDUCE(0, SEQUENCE(ROWS(input)), LAMBDA(stack, i,
LET(
str, INDEX(input, i, 1) & "",
cat, INDEX(input, i, 2) & "",
items, IFERROR(TEXTSPLIT(str, , ",", TRUE), ""),
VSTACK(
stack,
HSTACK(
items,
EXPAND(cat, ROWS(items), 1, cat)
)
)
)
)),
1
)
)数式の詳細解説
- SEQUENCE(ROWS(input))
入力範囲(input)の行数を ROWS で取得し、1, 2, 3... という連番配列を作成します。
この連番を REDUCE の繰り返し処理のカウンターとして利用します。
- str, INDEX(input, i, 1) & "" / cat, INDEX(input, i, 2) &
""
INDEX 関数を使って i 行目のデータ(1列目:文字列、2列目:カテゴリ)を取り出します。
末尾に付ける & ""(空文字の連結) は重要なテクニックです。
参照先のセルが空白(未入力)だった場合、空白セルを参照した結果が数値の 0 として扱われるケースがあります。
あらかじめ文字列化しておくことで、意図しない 0 の発生やエラーを防ぎます。
- items, IFERROR(TEXTSPLIT(str, , ",", TRUE), "")
TEXTSPLIT の第3引数(行区切り)に "," を渡すことで、カンマ区切りテキストを縦方向の配列に分割します。
第4引数を TRUE に設定すると、連続したカンマによる空要素を無視します。
データが空欄の場合などに TEXTSPLIT が #VALUE! エラーを返すのを防ぐため、IFERROR で包んで安全に空文字 "" へ変換します。
- EXPAND(cat, ROWS(items), 1, cat)
分割された要素数(ROWS(items))に合わせて、1行分のカテゴリ文字列(cat)を同じ行数まで縦に複製します。
第4引数(pad_with)に cat 自身を指定することで、パディング部分を同じカテゴリ名で埋めています。
- HSTACK(...) と VSTACK(...)
HSTACK で「分割要素」と「複製したカテゴリ」を横方向に結合し、1行分データから小さな2列の表を作成します。
それを REDUCE の累積変数である stack に対して VSTACK で縦方向に追加していきます。
- DROP(..., 1)
REDUCE の初期値として渡した 0 が最上行に残っているため、最後に DROP 関数で1行目を除去して最終結果を取り出します。
構造上の課題

2. Thunk(サンク)パターン(BYROW + LAMBDA)
そのままでは可変長配列を出力できないため、「配列を返す無名関数(Thunk)」 にラップして一度出力し、後から取り出すテクニックを用います。
=LET(
input, A2:B5000,
make_thunk, LAMBDA(r,
LET(
str, INDEX(r, 1) & "",
cat, INDEX(r, 2) & "",
items, IFERROR(TEXTSPLIT(str, , ",", TRUE), ""),
LAMBDA(
HSTACK(
items,
EXPAND(cat, ROWS(items), 1, cat)
)
)
)
),
thunks, BYROW(input, make_thunk),
DROP(
REDUCE(0, thunks, LAMBDA(stack, th, VSTACK(stack, th()))),
1
)
)数式の詳細解説
- make_thunk, LAMBDA(r, ...)
入力範囲の1行分(r)を受け取り、データ加工を行う無名関数を作成します。
- LAMBDA( HSTACK(...) ) (Thunkの生成)
make_thunk の最終出力部分で、配列(HSTACK の結果)を直接返すのではなく、さらに 引数のない LAMBDA(...) で包んでいます。これにより、「配列そのもの」ではなく「実行すると配列を返す関数(Thunk)」という単一のオブジェクトに変換されます。
- thunks, BYROW(input, make_thunk)
BYROW は各行に対して処理を行い、行数分の結果を返します。本来であれば配列を返すとエラーになりますが、出力が「Thunk(関数オブジェクト)」であるため、Excelは1行あたり1個の値として正常に配列へ格納できます。
- th() (Thunkの実行と展開)
後段の REDUCE ループ内で、取り出した Thunk 変数 th に対して th() とカッコを付けて呼び出します。カプセル化されていた内部の可変長配列が展開され、VSTACK で結合されます。
Thunk によって、各行の処理結果をいったん「関数」として保持できるようになります。しかし、最終的には
VSTACK(stack, th())
によって、それぞれの結果を順番に結合しています。
そのため、最終的な結合処理についてはパターン1と同じく、処理が進むほど大きくなる stack に対して VSTACK を繰り返す構造が残ります。
つまり、Thunk の導入だけでは、順次結合に伴う累積的なコピーコストを根本的に解消できません。
したがって、Thunk は「高速化のための手法」というより、BYROW と可変長配列を組み合わせるための記述上・構造上のテクニックと考えるのが適切です。

3. チャンク分割パターン(ブロック処理 + Thunk)
=LET(
input, A2:B5000,
n, ROWS(input),
b_size, 100,
b_count, ROUNDUP(n / b_size, 0),
process_row, LAMBDA(r,
LET(
str, INDEX(r, 1) & "",
cat, INDEX(r, 2) & "",
items, IFERROR(TEXTSPLIT(str, , ",", TRUE), ""),
HSTACK(
items,
EXPAND(cat, ROWS(items), 1, cat)
)
)
),
process_block, LAMBDA(b_idx,
LET(
start_row, (b_idx - 1) * b_size + 1,
block_rows, MIN(b_size, n - start_row + 1),
sub_input, CHOOSEROWS(input, SEQUENCE(block_rows, 1, start_row)),
DROP(
REDUCE(0, SEQUENCE(block_rows), LAMBDA(stack, i,
VSTACK(stack, process_row(CHOOSEROWS(sub_input, i)))
)),
1
)
)
),
thunks, MAP(SEQUENCE(b_count), LAMBDA(i, LAMBDA(process_block(i)))),
DROP(
REDUCE(0, thunks, LAMBDA(stack, th, VSTACK(stack, th()))),
1
)
)数式の詳細解説
- b_size, 100 / b_count, ROUNDUP(n / b_size, 0)
1ブロックあたりの行数(b_size)を100行と設定し、全行数 n を割ることで必要なブロック総数(b_count)を算出します。
※100 は固定の正解値ではなく、チャンクサイズの一例です。
小さすぎるとブロック数が増え、大きすぎるとブロック内部の累積結合が大きくなるため、データ量に応じた調整項目です。
- process_row
1行分のデータを切り出して TEXTSPLIT と HSTACK を行う共通処理を独立した関数として定義しています。
- process_block
指定されたブロック番号(b_idx)を受け取り、そのブロック範囲内の行だけをまとめて処理する関数です。- start_row:ブロックの開始行番号を計算します(例:2ブロック目なら (2-1)*100 + 1 = 101 行目)。
- block_rows:最終ブロックが100行未満になるケースに対応するため、MIN 関数で実際の切り出し行数を制御します。
- sub_input:CHOOSEROWS と SEQUENCE を組み合わせ、元データから該当ブロックの100行分だけを部分配列として抽出します。
- ブロック内部の100行に対してのみ REDUCE + VSTACK を行い、100行分の結合結果を作成します。
- thunks, MAP(SEQUENCE(b_count), ...)
全ブロックの処理結果を MAP でループさせますが、ここでもブロックの処理結果を関数として保持するため、LAMBDA(process_block(i)) として Thunk 化しています。
- 最終結合
作成されたブロック単位の Thunk 群を最後に REDUCE + VSTACK で縦に結合します。

ただし、最適なチャンクサイズはデータ量や1行あたりの出力サイズによって異なるため、実データで調整する必要があります。
4. 再帰分割パターン(二分割 + VSTACK)
=LET(
input, A2:B5000,
BYROWEX, LAMBDA(f,
LET(
fx, LAMBDA(x, f(LAMBDA(varray, func, x(x)(varray, func)))),
fx(fx)
)
)(
LAMBDA(BYROWEX,
LAMBDA(varray, func,
LET(
n, ROWS(varray),
IF(
n = 1,
func(varray),
LET(
q, QUOTIENT(n, 2),
VSTACK(
BYROWEX(TAKE(varray, q), func),
BYROWEX(DROP(varray, q), func)
)
)
)
)
)
)
),
BYROWEX(
input,
LAMBDA(r,
LET(
str, INDEX(r, 1) & "",
cat, INDEX(r, 2) & "",
items, IFERROR(TEXTSPLIT(str, , ",", TRUE), ""),
HSTACK(
items,
IF(SEQUENCE(ROWS(items)), cat)
)
)
)
)
)数式の詳細解説
- BYROWEX(Zコンビネータによる再帰無名関数の定義)
Excelのセル数式内では通常、名前定義を行わない限り自分自身を呼び出す「再帰関数」が書けません。
ここではラムダ計算の Zコンビネータ(Z Combinator) 構造を取り入れることで、LET 関数内だけで完結する自己再帰処理を実現しています。 - n, ROWS(varray) と IF(n = 1, ...)
渡された配列の行数 n を確認します。
1行まで分解されていれば「再帰の終点(ベースケース)」となり、実際のデータ加工を行う func を実行します。 - q, QUOTIENT(n, 2)
行数 n が2以上の場合、QUOTIENT(整数除算)で範囲を中央で2つに割ります(例:5,000行なら q = 2500)。 - TAKE と DROP による二分割
TAKE(varray, q) で前半 q 行を抽出し、DROP(varray, q) で後半の残りを抽出します。
それぞれを自分自身(BYROWEX)へ渡して再帰呼び出しを行います。 - VSTACK( BYROWEX(前半), BYROWEX(後半) )
左右に分割された木構造の戻り値を、深さ優先で VSTACK 結合していきます。 - カテゴリ列展開の簡略記述IF(SEQUENCE(ROWS(items)), cat)
パターン1~3では EXPAND を使ってカテゴリ列を縦展開していましたが、ここでは IF と SEQUENCE を組み合わせた手法を用いています。
SEQUENCE(ROWS(items)) は 1, 2, 3... という真の値(0以外の数値)を持つ配列を作るため、IF(配列, cat) と書くことで、items と全く同じ行数分だけ cat の文字列が繰り返された配列が自動生成されます。

比較:4方式のパフォーマンス・特性比較
| パターン | 計算量・結合コスト | 処理速度 | 理解難度 | 主な用途・適用場面 |
| 1. 順次結合 | O(M^2) 相当 | データ量増加で急激に低下 | 低 | 数百行程度の小規模データ |
| 2. Thunk | O(M^2) 相当 | 順次結合と同等 | 中 | BYROW の枠組みで可変長配列を扱いたい場合 |
| 3. チャンク分割 | チャンクサイズに応じ大幅抑制 | 高速 | 中~高 | 数千~数万行の実務データ処理 |
| 4. 再帰分割 | O(M log N) 相当 | 最速クラス | 高 | 超大量データ・数式による極限の速度最適化 |
注記(計算量について)
実際の処理時間は1行あたりの出力行数や TEXTSPLIT の処理頻度、Excel内部のキャッシュ状態などにも依存します。
まとめ:実務における使い分けの指針
- データ量が少ない場合(~数百行)
可読性を最優先し、最も構造がシンプルな 「1. 順次結合」 で十分です。 - データ量が多く、処理速度が問題になる場合(数千行~)
実務的な最適解は 「3. チャンク分割」 です。
数式の可読性・保守性をある程度保ちながら、高速化が期待できます。 - 数式のみで極限までパフォーマンスを追及したい場合
保守性の低さを許容できるのであれば、「4. 再帰分割」 が最も効率的な結合構造を提供します。
番外編:Python in Excel
数式で複雑なループ構造や再帰アルゴリズムを組む必要がなくなり、実務における強力な選択肢となります。
セルに =PY と入力してPythonモードにし、以下のコードを記述します。
# --- 設定 ---
show_header = False # 結果に見出しを出力する場合は True、出力しない場合は False
# A2:B5000 の範囲を取得(1列目:データ, 2列目:カテゴリ)
# ※セル範囲 A2:B5000 自体には見出しを含めないため headers=False
df = xl("A2:B5000", headers=False)
df.columns = ["データ", "カテゴリ"]
# カンマで分割して縦方向に展開
df["データ"] = df["データ"].astype(str).str.split(",")
result = df.explode("データ")[["データ", "カテゴリ"]]
# 設定に応じた出力切り替え
result if show_header else result.values- 設定値の明確化(show_header)
冒頭の show_header 変数で、結果に列見出しを出力するかどうかを切り替えられます。True で見出し付き、False で値のみを出力します。 - データの取得(xl 関数)
xl("A2:B5000", headers=False) で指定範囲を pandas の DataFrame として読み込みます。データ領域のみを読み込むため、headers=False としています。 - 列名の定義と文字列分割(str.split)
df.columns で明示的に列名「データ」「カテゴリ」を付与します。その後、str.split(",") によって1列目のカンマ区切り文字列をリストに分割します。 - 可変長配列の縦展開(explode)
pandas の explode メソッドを使用するのが最大の特徴です。リスト化された要素を縦方向に展開し、対応する2列目のカテゴリ値も自動的に引き伸ばして複製してくれます。 - 出力形式の制御
最終行の三項演算子で、show_header が True なら見出し付きの DataFrame、False なら .values によって見出しを除いた値の二次元配列のみをスピル出力します。
- 処理速度
Python in Excel はクラウド環境でコードが実行されるため、データ通信や評価のオーバーヘッドが発生します。
一方、本編で紹介した「3. チャンク分割」や「4. 再帰分割」の数式アプローチはローカルのメモリ上で直接処理されるため、実際の実行速度(応答速度)としては数式のほうが高速です。 - コードの簡潔さ
文字列の分割(split)と縦展開(explode)のわずか数行で直感的に記述できるため、数式パターンのようなチャンクサイズ管理や複雑な再帰アルゴリズムの定義が不要です。
- 実行速度や即応性を最優先する場合
クラウド通信のオーバーヘッドを発生させず、ローカルのセル計算エンジンで一瞬で結果を得たい場合は、本編の 「3. チャンク分割」 または 「4. 再帰分割」 が最も優れています。
従来のExcel環境(Python非対応の環境)にブックを配布・共有する際にも最適です。 - コードの読みやすさやメンテナンス性を重視する場合
Python in Excel が利用可能な環境であれば、複雑な数式ロジックを組む必要がなく、数行の直感的なコードで処理を完結できます。
数式の解読やデバッグにかかる開発コストを大幅に削減できるのが大きなメリットです。
同じテーマ「エクセル関数応用」の記事
配列を自在に回転させる数式
掛け算(*)を使わない掛け算|足し算(+)を使わない足し算
2段階の入力規則リスト作成:最新関数対応
VLOOKUP/XLOOKUPが異常なほど遅くなる危険なアンチパターン
「SUMIFSで動くのにXLOOKUPでエラー?」型不一致の関数別挙動
「Python in Excel」で自作関数を登録|アンピボット関数と計算順序
「Python in Excel」数独(ナンプレ)解法プログラムの移植と最適化
Excel表とMarkdownテーブルを相互変換する数式
可変長配列をVSTACKする4つの方法|REDUCE・Thunk・チャンク・再帰分割
Excelの正規表現関数(REGEXTEST・REGEXREPLACE・REGEXEXTRACT)の使い方
「Python in Excel」入門:コードを読むための基礎知識
新着記事NEW ・・・新着記事一覧を見る
「Python in Excel」入門:コードを読むための基礎知識|エクセル関数応用(2026-09-10)
M言語入門:Power Queryのコードを読むための基礎知識|Power Query(M言語)入門(2026-09-09)
Excelの正規表現関数(REGEXTEST・REGEXREPLACE・REGEXEXTRACT)の使い方|エクセル関数応用(2026-09-08)
スピルとVBA(Formula2とスピル範囲の取得)|VBA入門(2026-09-08)
「Withの功罪」:コードを読みやすくする強力な道具と、その落とし穴|VBA技術解説(2026-09-03)
可変長配列をVSTACKする4つの方法|REDUCE・Thunk・チャンク・再帰分割|エクセル関数応用(2026-09-01)
Excel表とMarkdownテーブルを相互変換する数式|エクセル関数応用(2026-08-24)
「Python in Excel」数独(ナンプレ)解法プログラムの移植と最適化|エクセル関数応用(2026-08-14)
「Python in Excel」で自作関数を登録|アンピボット関数と計算順序|エクセル関数応用(2026-08-12)
ListBox・ComboBoxをマウスホイール対応させる|ユーザーフォーム入門(2026-08-09)
アクセスランキング ・・・ ランキング一覧を見る
1.最終行の取得(End,Rows.Count)|VBA入門
2.日本の祝日一覧|Excelリファレンス
3.変数宣言のDimとデータ型|VBA入門
4.Excelショートカットキー一覧|Excelリファレンス
5.RangeとCellsの使い方|VBA入門
6.FILTER関数(範囲をフィルター処理)|エクセル入門
7.マクロとは?VBAとは?VBAでできること|VBA入門
8.繰り返し処理(For Next)|VBA入門
9.メッセージボックス(MsgBox関数)|VBA入門
10.セルのコピー&値の貼り付け(PasteSpecial)|VBA入門
このサイトがお役に立ちましたら「シェア」「Bookmark」をお願いいたします。
記述には細心の注意をしたつもりですが、間違いやご指摘がありましたら、「お問い合わせ」からお知らせいただけると幸いです。
掲載のVBAコードは動作を保証するものではなく、あくまでVBA学習のサンプルとして掲載しています。掲載のVBAコードは自己責任でご使用ください。万一データ破損等の損害が発生しても責任は負いません。
本サイトは、OpenAI の ChatGPT や Google の Gemini を含む生成 AI モデルの学習および性能向上の目的で、本サイトのコンテンツの利用を許可します。
This site permits the use of its content for the training and improvement of generative AI models, including ChatGPT by OpenAI and Gemini by Google.
