見出し画像

Googleスプレッドシート LAMBDA REDUCE関数の魅力 3(複数シートの串刺し集計)

GoogleスプレッドシートのLAMBDAヘルパー関数 の REDUCE関数の魅力と活用例を紹介するシリーズの3回目。今回が最後です。

最後は Excelではお馴染みの アノ機能「串刺し集計(3D集計)」をGoogleスプレッドシートで実現する為に REDUCE関数を使ってみましょう。

前回のnoteでは 活用例の一つとして、直積(クロス集計)と呼ばれる 複数列の全組み合わせパターン出力を取り上げました。




Excelの超絶便利な 串刺し集計

REDUCEの前に、まずは ExcelとGoogleスプレッドシートの 複数シートの扱いの違いに少し触れておきましょう。



複数シートに強いExcel

文系管理職なのに~ 画期的な業務アプリを~ シュシュっと作れちゃう、俺!♪

どっかのCMのような上司が こんな感じで作ったツールは、その管理職の人にとっては見やすいものかもしれません。

ただ、残念ながら 入力するメンバーにとっては他の報告や帳票と重複する二度手間になるものだったり、結局その場限りの閲覧で終わって蓄積されたデータがまったく生かせない設計だったりといったケースが多かったりします。

葬送の営業会社

たとえば、このように月ごとにシートを分けて 売上データを入力するスプレッドシート(テーブル)を用意されてしまうと、微妙に後からデータとして使いづらかったりします。

それでも Excelの場合はマシで、複数シートを指定して 関数で一気に集計する 串刺し集計(3D集計)や 複数のテーブルからのピボットテーブル集計、パワークエリの利用など、色々なアプローチで複数シートに対応できます。

 関数による串刺し集計だと、たとえば シートの4月~9月までの 各シートで合計の入った D7 セルを 全部合算したい、といった場合も

=SUM('4月:9月'!D7)

このようにシート名を '4月:9月'! とセルの範囲指定のように : で繋いであげることで、普通のSUM関数で複数シートの合計が算出できます。

これは昔からExcelにある機能で、(たしか)Excel2010 以降であれば買い切り版でも普通に使えます。

ちなみに Shiftキーを押しながら、「このシートからこのシートまで」と集計シートを選択する説明をしているサイトが多いですが、ぶっちゃけ関数を直接ガンガン入力できる人なら手入力の方が簡単です。

4月:9月!D7

このように入力して確定すれば、勝手に前後の '(シングルクォート)を付けてくれます。

SUM以外のCOUNT、AVERAGE、MAX、MIN などの集計関数も、串刺し集計は可能。ただし、COUNTIFやSUMIFSなどの〇〇IF系、〇〇IFS系では 串刺しが機能しません

一方、意外にもテキスト連結系の関数は串刺しが可能で TEXTJOINやCONCAT関数で 串刺し指定することで 複数シートの文字列を一気に連結することができます。

=TEXTJOIN(",",TRUE,'4月:9月'!A1)

こんな感じで、各シートのA1セルの文字列を カンマ区切りで連結して出力なんてことが出来るわけです。



Excelの 配列操作系新関数でも 串刺しが使える

Microsoft365もしくは Web版のExcelで最新関数が使える環境であれば、配列操作系の新関数を使った串刺しも可能です。

たとえば セル範囲や配列を1列のデータにする TOCOL関数、1行のデータにする TOROW関数も 串刺しが可能。

=TOCOL('4月:9月'!D7)

そして以前にも紹介しましたが範囲や配列を連結させることが出来る VSTACK関数、HSTACK関数 も 串刺しが使えます。これが超絶便利。

VSTACKの串刺しにTOCOLの串刺しをHSTACKで連結

=VSTACK('4月:9月'!B7:D7)

ちなみに 7月以降は 営業担当が2名増えて 合計セルの位置が変わってしまった場合

通常の SUMによる 串刺しではセル位置が違うので集計できなくなりますが、串刺しVSTACKで連結したデータから A列が 集計の時のD列の値を合計といった式を組むことで、串刺しSUMIFみたいなことも出来ます。

もうちょっといいやり方があるかも

=LET(x,VSTACK('4月:9月'!A1:D100),
 SUM(FILTER(INDEX(x,,4),INDEX(x,,1)="集計",)))

SUMIFは配列に対して使えないので他の関数で代替する必要がある


また、残念ながら mirは試せる環境にありませんが、もしかすると 365に追加予定となっているExcelの超強力な最新集計関数 GROUPBY関数、PIVOTBY関数 でも 串刺しが使えるのかもしれません。(未確認)

これで出来ちゃうと GoogleスプレッドシートのQUERY関数超えちゃうかもw

複数シートにデータがまたがっていた場合だと、串刺し集計(3D集計)が使えるExcelは Googleスプレッドシートに比べて圧倒的に便利だなと痛感します。。


Googleスプレッドシート は串刺せない

Excelで使える便利な串刺し集計ですが、残念ながら Googleスプレッドシートに は串刺しに該当する機能がありません

Excelをドライブに入れて互換表示しても串刺しで計算しているセルは #REF!エラーとなります。 (「4月:9月」というシート名を探しにいっちゃう)



複数シートに弱い Googleスプレッドシート

そもそも、串刺しどころか Googleスプレッドシートで 複数シートを選択した時に出来るアクションは非常に限られています。

まとめて削除、コピー、移動、非表示、あとはシートの色を変更くらいです。

Excelのような 複数シートに対して同じ操作を一括で行う 作業グループ 機能や、串刺し集計といった機能がありません。ピボットテーブルも 元データは1つしか選択できませんし、パワークエリに該当するものもありません。

ちなみに 複数シートを選択しての削除や非表示といった機能すら、比較的最近(2021年の7月頃)できるようになりましたw

それ以前は複数シートをまとめて削除や非表示すらできなかったわけです。



従来のGoogleスプレッドシートでの 複数シート集計方法

では、Googleスプレッドシートでは複数シートの串刺し集計が必要な時は、どのように対処していたか?

=SUM('4月'!D7,'5月'!D7,'6月'!D7,'7月'!D7,'8月'!D7,'9月'!D7)

基本的には、このように愚直にシート毎に選択したものを入れる方法しかありませんでした。

もちろんこの中身の部分を1つ1つ選択、もしくは手入力といった手間は軽減したいので、

こんな感じで生成したものをSUMの中に値コピペしていました。

シート名が シート1、シート2とか 月なら オートフィルで生成できるんで楽ですね。

規則性のない 〇〇営業部のようなシートが複数あるときは・・・、どこかからデータをコピーするか手入力しかないです。

もちろんGASで自作関数(カスタム関数)を作って対処すれば対処できますが、できればシート関数で処理したいですよね。

REDUCE関数他、LAMBDAヘルパー関数の登場で、現在は シート名に規則性があれば 作業セル無しで Googleスプレッドシートでも串刺し集計 のようなことが出来るようになりました。

今回は Googleスプレッドシートで複数セルの串刺し集計する方法を、お題形式で問題を解きながら理解していきましょう。



Googleスプレッドシートで 串刺し集計(合計)をやってみよう

というわけで、シートに規則性があることを前提として、まずは今回のテーマである REDUCE関数を使ってGoogleスプレッドシートで 串刺し集計(合計)をする式を作ってみましょう!



Q1. REDUCE関数を使って 複数シートの合計を算出したい

4月実績表			
			
担当	武器売上実績	魔導書売上実績	合計
ヒンメル	100	80	180
ハイター	10	120	130
アイゼン	130	40	170
集計	240	240	480

こんな感じで、某葬送の営業会社が 販売実績を月ごとにシートにまとめていたとします。

同じ構成で シート単位の合計がD7セルに入った シート名が4月から9月までの6枚のシートがあります。

この複数シート(6枚のシート)の D7セルを REDUCE関数を使って1つの式で合計を算出したい。というお題です。

どうでしょう、REDUCE関数を使って作れそうでしょうか? シリーズの1回目、2回目を参考に まずは考えてみましょう!








↓↓↓
ここから回答




A1. REDUCE関数を使って 複数シートの合計を算出する

回答です。たぶんREDUCEを使った一番短い記述がこれかなと。

=REDUCE(,SEQUENCE(6,1,4),LAMBDA(pv,cv,pv+INDIRECT(cv&"月!D7")))

解説していきましょう。

規則性さえあれば シート名は数式で生成ができます。ここで使えるのが、mirの推し関 SEQUENCE関数です。

まず SEQUENCE(6,1,4)で 4~9の数値配列を生成します。

この段階で "月"を付けても良いんですが、その場合 Googleスプレッドシートだと Arrayformulaが必要になってきます。

式が長くなるのを避ける為に、"月"は REDUCE内のコールバック関数で付けるとして、この数値配列のまま REUDUCEの 第2引数 配列として使います。

そして第1引数 初期値は 0でもいいんですが、合計(和)を算出するなら 空白でも問題ないので空白としておきましょう。

そして、LAMBDA内で 一つ前の結果を pv、一つずつ配列から取り出した現在の値を cvと置きます。

=REDUCE(,SEQUENCE(6,1,4),LAMBDA(pv,cv,

参照するシート名とセルを文字列で生成すると

cv&"月!D7"

こうなります。この文字列を参照として機能させる為には INDIRECT
関数
と組み合わせればよいですね。

INDIRECT(cv&"月!D7")

INDIRECT関数 は文字列を使ってセル参照を可能とする 特殊な関数で、これ実は LAMBDAと組み合わせてめっちゃ使えるんです。

これを1つ前までの結果に 足す という処理を

pv+INDIRECT(cv&"月!D7")

このように式にすることで、複数シートの指定セルを合計することが出来ます。

SUMを使わなくていいのが面白いですね。

ちなみに 合計を算出する処理は GAS(JavaScript)の配列メソッドの reduceでは定番で、よく事例に載ってます。

でも、シート関数として利用する場合は通常ならSUMがあるんで、REDUCEをわざわざ使う意味がなくピンとこないんですよね。

今回のような 複数シートを串刺しでSUMできないGoogleスプレッドシートのケースでは、REDUCE関数を使った合計算出の基本が役に立ったわけです。

  • 左手は添えるだけ

  • 左を制すものは世界を制す

  • 一日一万回感謝の正拳突き

やはり、なにごとも基本が大事ってことですねw



【別解】REDUE以外のLAMBDAヘルパー関数でも 複数シートの合計は可能

ちなみに 今回は REDUCE関数を使ってという縛りで お題を出しましたが、この複数シートの串刺し合計だったら、REDUCE以外の LAMBDAヘルパー関数を使っても対応できます。

=SUM(BYROW(SEQUENCE(6,1,4),LAMBDA(r,INDIRECT(r&"月!D7"))))

=SUM(MAP(SEQUENCE(6,1,4),LAMBDA(r,INDIRECT(r&"月!D7"))))

今回の場合、各シートの1セル(D7)を取得すればよいので、BYROWやMAPを使って

このように 各シートの合計値を1列の配列として簡単に取得できます。これを SUMで合計すれば 串刺しで合計した結果になりますね。

REDUCEに苦手意識のある方は、MAPやBYROWを使って対応するのもアリでしょう。



Googleスプレッドシートで 串刺し集計(TOCOL,TOROW)をやってみよう

さきほどの MAP、またはBYROWを使った 串刺し合計の途中過程

=MAP(SEQUENCE(6,1,4),LAMBDA(r,INDIRECT(r&"月!D7")))

これって、やってることは Excelの

=TOCOL('4月:9月'!D7)

と一緒なんですよね。つまり、串刺しTOCOLの処理です。

これに関しては正直 REDUCEを使うよりも MAPやBYROWを使った方が簡単です。



Q2. MAPを使って複数シートのA1セルを横1列に出力したい

それでは、1つ簡単なお題をいってみましょう。上のように4月~9月までの6シートの各 A1セルの値横1行に出力したい場合、MAPを使ってどのような式を組めばよいでしょうか?

要は Excelの 

=TOROW('4月:9月'!A1)

と同じことがやりたいってことです。考えてみましょう!








↓↓↓
ここから回答




A2. MAPを使って複数シートのA1セルを横1列に出力する

回答です。

=MAP(SEQUENCE(1,6,4),LAMBDA(v,INDIRECT(v&"月!A1")))

このように MAPに渡すSEQUENCEで生成する配列を

縦方向ではなく 横に展開させることで、出力される結果も 横に1行のデータとなります。

今回はこれに MAPを組み合わせてますが、BYCOLを使っても同じことが出来ます。

ただ、縦方向・横方向でBYROW,BYCOLを使い分けるよりは、どちらも使えるMAPを使うのが良いでしょう。(今回のような 1行または1列の配列を元に処理する場合)



【参考】複数行・複数列を串刺しでTOCOL(TOROW)する場合

各シート1セルの時はすんなり出来ましたが、例えば各シートの複数行複数列の セル範囲をTOCOLしたい、といった場合はどうすればよいか?

この場合は、LAMBDA内 に処理を記述することで対応できます。

=TOCOL(MAP(SEQUENCE(6,1,4),LAMBDA(v,TOROW(INDIRECT(v&"月!A4:B6")))))

TOCOLしたいのに、LAMBDA内ではTOROWしているのは なーぜなぜ?

となるかもしれませんが、これはデータを横に展開させることで配列内の配列(配列のネスト)が重なり合ってエラーとなるのを避ける為です。

縦方向の配列の各結果は横方向に展開させる
縦方向の配列の各結果を縦に出力すると エラーに

要は重ならないように出力すればよいってことです。


細かい説明は割愛しますが、複数シートの複数セルをTEXTJOINで連結する場合も、

LAMBDA内でのTEXTJOIN + MAP完了後の配列全体をTEXTJOIN

とすることで実現できます。

=TEXTJOIN(",",TRUE,MAP(SEQUENCE(6,1,4),LAMBDA(v,TEXTJOIN(",",TRUE,INDIRECT(v&"月!A4:B6")))))



Googleスプレッドシートで 串刺し集計(VSTACK)をやってみよう

串刺し集計の最後は、Googleスプレッドシートで複数シートのVSTACKによる範囲連結にチャレンジしてみましょう。

=VSTACK('4月:9月'!B7:D7)

Excelの串刺しVSTACK



Q3.1行データの串刺しVSTACKをMAPでやりたい

ただ、各シートから「1行データ」串刺しで縦に連結するだけなら、まだREDUCEパイセンが登場するまでもありません。

先ほどの 方法と同じでMAPを使えば対応できます。これをお題にしてみましょう。

少しアレンジして シート名を一番左に差し込んで 4月~9月の6枚の各シートのA7:D7を 上のように連結(VSTACK)したい場合、どのような式を組めばよいでしょうか?

考えてみましょう!








↓↓↓
ここから回答




A3. 1行データの串刺しVSTACKをMAPで実現する

回答です。

=MAP(SEQUENCE(6,1,4),LAMBDA(v,{v&"月",INDIRECT(v&"月!A7:D7")}))

VSTACKを使う必要はなく、先ほどと同じように SEQUENCEで生成した縦並びの数値配列を使ってINDIRECTで取得した横1行の範囲を出力していけばよいですね。

シート名の連結は、こちらもHSTACKを使うまでもなく 中カッコでの配列結合でシンプルに記述できます。

中カッコ連結は Excelには出来ない処理です

ちなみに普通に MAPの結果としてネストされた配列を出力していますが、こちらも Googleスプレッドシートでは出来ますが、Excelでは出来ない処理の一つです。

Excelでは「範囲を含む配列はサポートされていません」 とエラーになる

ちなみに Googleスプレッドシートでも LAMBDAヘルパー関数登場時 2022年9月の段階では Excelと同じく LAMBDAヘルパー関数内で配列を出力することは出来ませんでした。

しかし、大々的な発表もなくしれっと2022年12月にはできるようになってたんです。もっとアピールすりゃいいのに。

個人的意見ですが、Googleスプレッドシート が Excel に対して優位性を出してく為には、この「配列の扱いの柔軟性」をもっと突き詰めていくべきと考えます。



Q4.REDUCE関数で複数列、複数行のデータを串刺しVSTACKしたい

では、ようやく本題です。先ほどのように1行や1列といった一次元配列のデータであれば MAPを使って 複数シートにまたがって 縦または横に連結することが出来ます。

しかし、これが A4:D6などの複数行複数列のセル範囲だった場合はどうでしょう?

この場合は MAPやBYROWでは対処できず、ようやく REDUCE関数の出番となります。

Excelだったら

=VSTACK('4月:9月'!A4:D6)

で串刺し出来る処理ですが、Googleスプレッドシートでは REDUCE関数に頼るしかありません。ちょっとハードルがあがりますね。

とはいえ、これまでのステップを踏まえれば REDUCE関数の式として簡単なものです。チャレンジしてみましょう!








↓↓↓
ここから回答




A3. REDUCE関数で複数列、複数行のデータを串刺しVSTACKする

回答です。

=REDUCE('4月'!A3:D3,SEQUENCE(6,1,4),
 LAMBDA(pv,cv,VSTACK(pv,INDIRECT(cv&"月!A4:D6"))))

REDUCEの初期値は '4月'!A3:D3 といった形で、タイトル行を入れておくのがよいでしょう。ここに SEQUENCEで生成した数値配列の順に INDIRECTでそれぞれの月のシートから A4:D6の範囲を取得し、VSTACKで pv(1つ前の処理の結果)の下に連結することで、複数シートの串刺し範囲結合を実現しています。

LAMBDAヘルパー関数と INDIRECTを組み合わせた 複数シートを順に取得する処理では、状況に応じて MAP、REDUCEを使い分けるのがポイントです!



【余談】12月の次が1月なんだけど・・・て時は?

今回は4月から9月までの6シートでしたが、決算が3月の会社で 年度で管理する為に12月の次が1月、2月、3月というシートが続く場合はどうすればよいでしょうか?

もちろん単純に SEQUENCEでは生成できないので一工夫いりますが、そんなに難しいことではありません。

=ARRAYFORMULA(MOD(SEQUENCE(12,1,3),12)+1)

=ARRAYFORMULA(TEXT(EDATE("2023/03/01",SEQUENCE(12)),"M月"))

たとえば、MOD関数を使って 12の次は1,2,3と循環させる、もしくは EDATE関数で1月ずつずらした日付を TEXT関数で 〇月 という形式で取得する。

この辺りの方法が有効でしょう。

もちろん コールバック関数側(LAMBDA内)で対処することで ARRAYFORMULAを省くことも出来ますが、先に加工した配列をREDUCEに渡しちゃった方がわかりやすいかもしれません。



Googleスプレッドシート 串刺し集計応用

ここまでだと一部の人には REDUCE活用としてはお題が簡単すぎるんで、最後に少し応用問題をやっておきましょう。



Q4. 各シートの可変する担当者の実績データを一覧表にまとめたい

やりたいことは先ほどと同じ 複数シートの複数セルの串刺しVSTACKですが、幾つか追加要件が出てきました。

・一番左の列にどのシートのデータかわかるように シート名の列を追加

・最初から12ヶ月分のシートを取得する式を入れておきたい
 (ただし現時点では 4月から9月のシートまでしかない。要は存在しないシートをINDIRECTで取得しようとした時にエラーにならないようにしたい)

・各月の担当者の実績のみを集計したいが月によって担当者の人数(取得したいセル範囲)は変わる。開始は4行目は固定。

ひどいもんですw

この表構成でこんな要件って、まさに 集計担当者を殺す魔法(ゾルトラーク)と言えるかもしれませんw 

元データを月ごとにシートを分けるのではなく、1枚のシートにマスタデータを作成し、月ごとのシート側からQUERY関数やFILTER関数で呼び出す という構成にした方がよっぽど簡単ですね。

でも、現実(リアルケース)では、この元データが月ごと(複数シート)に分散されてるってのは、本当に多いんですよね。

これに対応することが正解か?は置いといて、興味がある人は式の作成にチャンレジしてみましょう!








↓↓↓
ここから回答




A4. 各シートの可変する担当者の実績データを一覧表にまとめる

回答です。

=REDUCE({"月",'4月'!A3:D3},ARRAYFORMULA(MOD(SEQUENCE(12,1,3),12)+1),
  LAMBDA(pv,cv,
    LET(
      m,cv&"月",
      range,INDIRECT(m&"!A4:D"),
      data,FILTER(range,index(range,,1)<>"",index(range,,1)<>"集計"),
      IF(
        ISERROR(INDEX(range,1,1)),
        pv,
        VSTACK(pv,IFERROR(HSTACK(m,data),m))
      )
    )
  )
)

LAMBDA内の記述が複雑になってきたので、中の処理で LETを使って整理しています。

m, cv&"月",

まず シート名である 〇月 という文字列 を m と置いて

range, INDIRECT(m&"!A4:D"),

ここから 各シートの A4:D の範囲をINDIRECTで取得したものを range とおいて

data, FILTER(range,index(range,,1)<>"",index(range,,1)<>"集計"),

さらにこの range を 1列目(index(range,,1) が空白 かつ "集計" ではないものにFILTER関数で絞り込んだものを data と置いてます。

そのうえで、まだシート名が作成されていない(10月~3月)に関しては

ISERROR(INDEX(range,1,1))

range の1丁目1番地 を INDEX関数で取得し ISERRORでエラー判定をかけてIFで分岐させます。

ISERROR(INDEX(range,1,1)) → TRUEである(エラーである)場合

は、シートが取得できない、つまりシートが無いので処理をスキップすべきってことで、そのまま pvを返す。

もし

ISERROR(INDEX(range,1,1)) → FALSEである(エラーではない)場合

なら、シートが取得できたってことなんで、

HSTACK(m,data)

で、担当者の実績データ data  の左に m (シート名)を連結させて

この 結合時のサイズ不一致で#N/Aエラーとなった箇所をIFERRORを使って mで埋めることで 一番左の列を シート名とします。

IFERROR(HSTACK(m,data),m)

最後に これを pv(1つ前の処理の結果)の下に VSTACKで縦連結!

VSTACK(pv,IFERROR(HSTACK(m,data),m))

処理の流れとしては、こんな感じです。新関数フル活用ですね。



GASでシート名が取得できれば 応用できる

実はこの複数シートの串刺し処理ですが、以前 GASでシート名やシートIDを取得するカスタム関数を作成する回でも触れています。

この時は、まだ LET や VSTACK、HSTACKが無かったんで、今見るとLAMBDAでよく頑張って書いたなという印象w

=LAMBDA(sheets,title,range,
  Arrayformula(
    REDUCE({"シート名",INDIRECT(INDEX(sheets,1,1)&"!"&title)},sheets,
      LAMBDA(pv,cv,
        LAMBDA(data,
          LAMBDA(x,
            IFERROR({pv;x},pv)
          )(Query({IF(SEQUENCE(ROWS(data))>0,cv,),data},"where Col2 is not null"))
        )(INDIRECT(cv&"!"&range))
      )
    )
  )
)(sheetNames3(1,true),"A3:D3","A4:D")

やってることは一緒なんですけどね。

今回は GASなしでやりましたが、GASを使うことで規則性のない複数シートでも対応できます。

興味がある方はシート情報取得の自作関数シリーズ noteも参照ください。



REDUCEが使えれば不可能が可能になる

3回にわたって REDUCE関数の活用を書いてきました。

  1. REDUCEを使った 変換リストによる置換

  2. REDUCEを使った 直積(組み合わせ全パターン出力)

  3. REDUCEを使った 複数シートの串刺しVSTACK

いずれも、REDUCE関数なしだとシート関数では対処が難しいものばかりです。

少しハードルは高いですが、REDUCE関数 をマスターして、一つ上のGoogleスプレッドシート使いを目指しましょう!

ちなみに X(旧Twitter)でも、たまにnoteに関連した情報を書いたりしてますので、そちらも是非チェックください。

↑ これは 複数列のデータを 指定した行数で折り返す式ですね。いわゆる WRAPCOLSの拡張版。これも REDUCE 他新関数がフル活用されてます。


重めの関数ネタが続いたので、次回はちょっと方向性を変えた軽めのネタを書こうかと思います。



この記事が気に入ったらサポートをしてみませんか?