MENU

【VBA→GAS検証】Excelのスピル関数をGASカスタム関数で再現する

GASカスタム関数でExcelのスピルを再現する検証
目次

はじめに

GASでローカルファイルを取り込む|Drive経由方式では、Excelのスピル関数(FILTERUNIQUESORTSEQUENCEなど)を含むファイルを取り込むと、スピル先の値が空白になって失われる問題が見つかりました。クライアント側パース方式ではこの問題自体は起きませんが、取り込まれるのはあくまで表示されている時点の静的な値で、元の数式が持っていた「参照元が変わると自動で展開結果が更新される」という動的な性質は失われます(比較記事参照)。

そもそもGoogleスプレッドシートには、Excelのスピル関数に一対一で対応する標準関数がありません。ARRAYFORMULAはありますが、FILTERUNIQUE自体は使えるものの、Excel側で独自に組み立てたスピル数式をそのまま移植する手段は用意されていません。

そこで今回は、GASのカスタム関数(@customfunction)に2次元配列を返させることで、スピルと同じ「自動展開」をGAS側で再現できるかを検証します。これができれば、取り込み後に静的値になってしまったデータに対しても、動的な展開処理を後付けできることになります。

全体像:カスタム関数が2次元配列を返すとスピルする

GASのカスタム関数は、returnする値が2次元配列(配列の配列)であれば、数式を入力したセルを起点に周囲のセルへ自動的に結果を展開します。これはExcelのスピルと見た目上ほぼ同じ挙動です。

①GASのカスタム関数を作成し、2次元配列をreturnする
②スプレッドシートのセルに=関数名(…)の形で入力する
③関数を入れたセルを起点に、返した配列の行数×列数ぶん自動展開される
④展開先に既存データがあると#REF!エラーになる(Excelの#スピル!に相当)

以降、実際にGASエディタとスプレッドシートで動作を確認しながら解説します。

Step1:2次元配列を返す基本パターン

まずは最小構成で、スピルするカスタム関数を作ります。

/**
 * 指定した行数×列数の連番を展開するカスタム関数
 * @param {number} rows 行数
 * @param {number} cols 列数
 * @customfunction
 */
function MY_SEQUENCE(rows, cols) {
  const result = [];
  for (let r = 0; r < rows; r++) {
    const rowData = [];
    for (let c = 0; c < cols; c++) {
      rowData.push('R' + (r + 1) + 'C' + (c + 1));
    }
    result.push(rowData);
  }
  return result;
}

スプレッドシートのセルに=MY_SEQUENCE(3,2)と入力すると、そのセルを起点に3行×2列の範囲へ自動的に結果が展開されました(実機確認済み)。

A1:B3の範囲(=MY_SEQUENCE(3,2)が3行×2列に展開された画像

展開方向は配列の構造で決まります。 横方向に展開したい場合は1次元配列を1つの配列で包み([["データ1","データ2"]])、縦方向にしたい場合は各要素をそれぞれ配列で包みます([["データ1"],["データ2"]])。今回のように縦横(表形式)にしたい場合は、外側の配列を行、内側の配列を列として組み立てます。

Step2:展開先にデータがあるとどうなるか

Excelのスピルでは、展開先のセルに既存データがあると#スピル!エラーになります。GASのカスタム関数でも同様の制約があるか実機で確認しました。

展開先のセル(今回はF1)にあらかじめ文字列を入れておき、その左隣(E1)に=MY_SEQUENCE(1,2)を入力したところ、E1に#REF!エラーが表示されました。

エラーの詳細には次のように表示されます。

展開先のセル文字列があったため #REF! エラーが表示された画像

エラー:F1のデータを上書きするため、配列結果は展開されませんでした。

Excelの#スピル!とメッセージの文言は異なりますが、「展開先に既存データがあると失敗する」という制約自体は同じです。カスタム関数を組み込む際は、展開先を必ず空欄にしておく必要があります。

Step3:FILTER関数の結果をカスタム関数で加工して再展開する

ここまでは固定の配列を返すだけでしたが、実務で使うには他のスピル関数の結果を引数として受け取り、加工して再展開するパターンが重要です。GASのカスタム関数は引数としてセル範囲(2次元配列)を受け取れるため、FILTERなどの結果をそのまま渡せます。

/**
 * 渡された範囲(FILTER等の結果)の文字列だけを大文字にして再展開する
 * @param {Array} inputRange 元になるセル範囲や配列数式の結果
 * @customfunction
 */
function MY_UPPERCASE_SPILL(inputRange) {
  if (!Array.isArray(inputRange)) {
    return [[inputRange.toString().toUpperCase()]];
  }
  return inputRange.map(row =>
    row.map(cell => typeof cell === 'string' ? cell.toUpperCase() : cell)
  );
}

J1:L4に商品データ(itemcategoryprice)を用意し、次の数式を入力しました。

=MY_UPPERCASE_SPILL(FILTER(J2:L4,K2:K4="fruit"))

FILTERが返した「categoryがfruitの行」(apple・carrot・orangeの3行×3列)がMY_UPPERCASE_SPILLに渡り、文字列だけ大文字化されたうえで、元と同じ3行×3列の形でそのまま再展開されました(実機確認済み)。

FILTER(J2:L4,...)の結果がMY_UPPERCASE_SPILLで大文字化され再展開された様子

FILTERUNIQUESORTXLOOKUPなど、どのスピル関数の結果であっても、GAS側から見れば「単一の値」か「2次元配列」のどちらかに集約されるため、同じ考え方で加工関数を作れます。

Step4:元データを変更したときに自動で再計算されるか

GASのカスタム関数には「引数が変わらない限り前回の計算結果を使い回す」というキャッシュの仕様があり、外部APIの戻り値のようにGASが依存関係を検知できない値を扱う場合は再計算されないことがあります。今回のようにセル参照を経由する使い方でこの問題が起きるか、実機で確認しました。

Step3の状態から、carrotのcategoryfruitからvegetableに書き換えたところ、FILTERの結果に連動してMY_UPPERCASE_SPILL側の展開結果もAPPLE・ORANGEの2行へ自動的に縮小されました。しかも、それまでCARROTの行が展開されていた3行目は空欄に自動クリアされており、消し忘れのような跡は残りませんでした。

通常のセル参照を経由してFILTER等の引数として渡す使い方であれば、キャッシュ問題は発生せず、展開範囲の拡大・縮小も含めて正しく再計算されました。 キャッシュが問題になるのは、NOW()や外部サービスの状態のように、スプレッドシートの依存関係グラフでは追跡できない値に依存する場合に限られると考えられます。

できること・できないことの整理

項目結果
カスタム関数が2次元配列を返すとスピルするか✅ 実機確認済み(Step1)
展開方向(縦・横・表)の制御✅ 配列の入れ子構造で制御可能
展開先に既存データがある場合の挙動⚠️ #REF!エラーになる。展開先は必ず空欄にする(Step2)
FILTER等の結果を引数として受け取り加工・再展開✅ 実機確認済み(Step3)
元データ変更時の自動再計算(拡大・縮小とも)✅ セル参照経由なら実機で確認。縮小時に余分な行も自動クリアされた(Step4)
外部要因(時刻・外部API等)依存時のキャッシュ問題⚠️ 今回は未検証。発生する可能性あり

よくある質問

Q. ExcelのA1#のようなスピル範囲演算子はGASでも使える?

→ Googleスプレッドシートに#によるスピル範囲参照はありません。カスタム関数に渡す場合は、通常のセル範囲(A1:B10)か、対象の数式そのもの(FILTER(...)など)を直接引数に指定します。

Q. XLOOKUPUNIQUEでも同じ書き方で対応できる?

→ できます。GAS側から見るとどの関数の結果も「単一の値」か「2次元配列」に集約されるため、Step3のMY_UPPERCASE_SPILLのような加工関数はそのまま流用できます。

Q. 展開先が足りているのに#REF!になる場合は?

→ 展開範囲のどこか1セルでも値が入っていると発生します。罫線や書式だけが残っている場合は問題ありませんが、数式の削除跡に空文字("")が残っているケースもあるため、Deleteキーで明示的にクリアしてから確認してください。

まとめ

  • GASのカスタム関数は2次元配列をreturnすることで、Excelのスピルと同じ「自動展開」を再現できる(実機確認済み)
  • 展開先に既存データがあると#REF!エラーになる。Excelの#スピル!と文言は異なるが制約は同じ
  • FILTERなどスピル関数の結果を引数として受け取り、加工して再展開するパターンも実機で動作を確認した
  • 通常のセル参照を経由する使い方であれば、元データの変更に対して展開範囲の拡大・縮小を含めて自動再計算されることを確認した。キャッシュ問題は外部要因に依存する場合の話と考えられる

次回予告

今回は特定の処理を1つずつ書くカスタム関数を検証しました。次回は、どんな関数の結果にも対応できる「万能カスタム関数」のテンプレートと、Excelファイルをインポートした際に数式を自動でスキャンしてカスタム関数に組み込む仕組みを検証します。

サンプルファイルについて

この記事で検証したMY_SEQUENCEMY_UPPERCASE_SPILLのコードはStep1・Step3に掲載のとおりです。スプレッドシートの拡張機能からApps Scriptエディタを開き、貼り付けてご利用ください。

関連記事

当サイトの記事で使用したVBAなどのサンプルをDLできます

この記事のサンプルファイルはありません!

ダウンロードページへは下のカードをクリックすればジャンプできます。
よろしければご利用ください!

よかったらシェアしてね!
  • URLをコピーしました!
目次