不可思議なスプレッドシートの世界「ArrayFormula関数内でSUM関数を使いたい！」の謎を解明する

## はじめに
[**Googleスプレッドシート**](https://www.google.com/intl/ja_jp/sheets/about) はオンラインで利用できる表計算ソフトとして非常に有用なのですが、[**ARRAYFORMULA**](https://support.google.com/docs/answer/3093275?hl=ja) や [**QUERY**](https://support.google.com/docs/answer/3093343?hl=ja) といった複雑な関数が使われると、直感的に分からない挙動を示すことがあります。

私は最近「**[ArrayFormula関数内でSUM関数を使いたい！](https://aimstogeek.hatenablog.com/entry/2019/03/01/161004)**」という記事に出会いました。
記事の中でも「**3. 行列積を使って各行の和を求める**」の方法は非常に鮮やかで感動したのですが、どうも「**2. SUMIF関数を使う**」は何故うまく動作しているのか分からず、記事中でもそのメカニズムは解明されていませんでした。
本記事ではそのメカニズムを細かく解明していきたいと思います。

## 課題: 今回解明していく「謎の数式」
ARRAYFORMULAを使って、各行の合計値(SUM)を1セルだけ数式入力して求めたいという状況です。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/497579/dac56f2a-617b-8d62-2509-f5417a401a6c.png" width="360"> ⇒ <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/497579/1e03ba52-da0b-734e-2e81-d1367c6c2bf6.png" width="360">

上の例で`F2`セルに入力した数式と一般化した数式は以下の通りです。

入力した数式
`=ARRAYFORMULA(SUMIF(IF(COLUMN(B2:E2), ROW(B2:B8)), ROW(B2:B8), B2:E8))`
一般化した数式
`=ARRAYFORMULA(SUMIF(IF(COLUMN(1番上の行), ROW(1番左の列)), ROW(1番左の列), 全範囲))`

一般化した数式をある程度インデントを揃えて書いてみると以下のようになります。

```
=
ARRAYFORMULA(
  SUMIF(
    IF(COLUMN(1番上の行), ROW(1番左の列)),
    ROW(1番左の列),
    全範囲
  )
)
```
関数がネストしていて分かりにくいのは仕方ないとしても、謎ポイントがたくさんありますね。
さっそく謎を解明していきましょう！

## 謎1. IF関数の引数は3つのはずなのに2つしか書いてないんだけど...?
**要旨:** 第3引数を省略すると、第3引数に**FALSE**を指定した時と同じ挙動を示します。

### 1.1 IF関数の定義を調べる
Google公式ドキュメントで [**IF関数**](https://support.google.com/docs/answer/3093364?hl=ja) のページを見てみると、
> `IF(論理式, TRUE値, FALSE値)`
> 論理式 - 論理値（TRUE か FALSE）を表す式またはそうした式を含むセルへの参照です。
> TRUE値 - 論理式が TRUE の場合に返される値です。
> FALSE値 - [省略可 - デフォルトは空白] - 論理式が FALSE の場合に返される値です。

とありますね。
なるほど、FALSE値は省略すると空白扱いになるらしいので問題無いと。
では挙動を確かめてみましょう。

### 1.2 引数3つのIF関数の挙動
まず、次のような引数3つのIF関数を書いてみます。
`=IF(FALSE, "真", "偽")`
![false.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/497579/c2e490e8-8c6b-30cb-a34d-9ac63fcfcb44.png)
第1引数がFALSEなので第3引数の**"偽"**が出力されてますね。

### 1.3 引数2つのIF関数の挙動
では問題の引数2つのIF関数を書いてみます。省略すると空白扱いになるんでしたね。
`=IF(FALSE, "真")`
![blank.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/497579/0660f8cb-871a-f3e0-f073-2bf52c24e937.png)
うん？省略したら空白ではなくFALSEが出力されちゃってますね...
実はこの現象、ドキュメント側が間違っているようで、**IF関数の引数3つ目を省略すると、デフォルトでFALSE扱い**になります。
(英語のドキュメントでも blank by default って書いてあるので日本語の誤訳でも無いようで...)

第3引数を空白にしたい場合には次のようにすると上手く動きます。
`=IF(FALSE, "真",)`
![true_blank.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/497579/e8919b5d-73c8-8c45-b5c4-ee7bddc6878c.png)

後ろに`,`が付いただけですが、これは「**第3引数は存在するが何も書いていない**」という書き方で、第3引数は空白であることを明示的に指定できる方法です。

### 1.4 引数が2つのIF関数まとめ
というわけで**IF関数の引数を2つにすると、第3引数はFALSE扱いになる**というお話でした。

これで、問題の式は、
`IF(COLUMN(1番上の行), ROW(1番左の列))`
　　　　　　　　　　　⇓
`IF(COLUMN(1番上の行), ROW(1番左の列), FALSE)`
と同じ意味になります。

これでもまだ「謎の数式」の全容はあまり見えてきませんが...
気を取り直して次に行きましょう。


## 謎2. IF関数の第1引数が真偽値じゃない...?
**要旨:** IF関数の第1引数は、**0**や**空白**、**空文字列**といった値は**FALSE**扱いになるという特性があります。


### 2.1 「そうした式」
[**IF関数**](https://support.google.com/docs/answer/3093364?hl=ja) の公式ドキュメントをもう一度見てみましょう。
> `IF(論理式, TRUE値, FALSE値)`
> 論理式 - 論理値（TRUE か FALSE）を表す式またはそうした式を含むセルへの参照です。

よく見ると、ものすごい曖昧な文言がありますね。 論理値を表す式「**またはそうした式**」？
詳しいことは分かりませんが、ここから読み取れるのは**どうやら論理値以外を入れるケースもある**と。


### 2.2 IF関数の第1引数に整数を入れてみる
今回の問題としている式では、IF関数の第1引数に、整数を返すCOLUMN関数が入っています。
というわけで、IF関数の第1引数に整数を入れた場合の挙動を見てみましょう。

以下の3通りを試してみます。
`=IF( 3, "真", "偽")`
`=IF( 0, "真", "偽")`
`=IF(-1, "真", "偽")`

結果は以下の通り。
![boolean_3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/497579/e4985368-d1b0-2e00-20ed-c96bf6d2d382.png)
![boolean_0.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/497579/11ae149d-7fab-e61a-43db-49b0adfebb1a.png)
![boolean_-1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/497579/05e5c62f-c833-eb08-7114-7d7b927d45e7.png)
プログラミングをある程度やったことある方々にはピンと来そうな結果が出ましたね。
C言語やJavaScriptと同様に、論理式に整数を入れると**0の場合FALSE、0以外の場合TRUE**という挙動を示すようです。
また、実際に試した画像は省略しますが、IF関数の第1引数を**空文字列や空白にした場合もFALSE扱い**になることも確認しています。

### 2.3 IF関数の第1引数における論理値判定まとめ
IF関数の第1引数に整数を入れる際には、

・**0以外**を入れる　→　**TRUE**
・**0**を入れる　　　→　**FALSE**

ということが分かりました。つまり、
`IF(COLUMN(1番上の行), ROW(1番左の列))`
の場合には、**COLUMN関数は必ず1以上の数を返す**ので、結果としては**必ずTRUE**となり、IF関数全体では常に`ROW(1番左の列)`が返るわけですね。
**謎1を考察した意味がねえ...！**

## 謎3. 結局、IF(COLUMN(1番上の行), ROW(1番左の列)) って何が起きてるの？
**要旨:** IF関数の第1引数が常に**TRUE**なので、ROW関数により指定範囲のセルがそれぞれ何行目かの値が返ります。なお、**ROW関数は行方向、COLUMN関数は列方向にしか展開されない**のですが、**ROW関数を2次元展開したいがために回りくどい書き方をしています**。

### 3.1 取り敢えず結果を眺めてみる
「謎の数式」からSUMIFを取り除き、ARRAYFORMULAやIFはそのまま残した数式を入力してみましょう。
対象とする範囲は`B2:E8`とするので、「一番上の行」は`B2:E2`、「一番左の列」は`B2:B8`になりますね。

`=ARRAYFORMULA(IF(COLUMN(B2:E2), ROW(B2:B8))))`
![arrayformula1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/497579/6abf3711-a0c4-1351-dd0d-6569362593e4.png)
何やら綺麗な結果が返ってきましたね。`B2:E8`の各セルの行数が出力されているようです。
2行目のセルであれば2と出力され、3行目では3と出力、8行目では8と出力されています。

先ほど**2.3**で触れた通り、`COLUMN(B2:E2)`は常に1以上の整数なのでIF関数は真、つまり`ROW(B2:B8)`の結果を返します。
よって`B2:E8`の行数が出力されるのは当然と言えば当然です。が、ここで1つの疑問が生まれてきます。

### 3.2 ARRAYFORMULA(ROW(全範囲))って書いちゃダメなの？
ここまで紐解いてきてお分かりかと思いますが、この数式は非常に回りくどい書き方をしています。
`=ARRAYFORMULA(IF(COLUMN(B2:E2), ROW(B2:B8))))`

「指定範囲のセルがそれぞれ何行目かを示している」と言えば一発ですが、IF関数やCOLUMN関数が書いてあることによって、分かりにくい数式となってしまっています。
直感的に分かりやすい数式にするなら次のような形が良さそうです。
`=ARRAYFORMULA(ROW(B2:E8))`

では実際に入力してみましょう。
![arrayformula2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/497579/1b0a9735-6af8-cdc8-8c85-2d2ab5fe046b.png)
んんん？ARRAYFORMULAによって縦には展開されているけど横には展開されない...？

ちょっとROWをCOLUMNに変えてもう一回試してみましょう。
`=ARRAYFORMULA(COLUMN(B2:E8))`
![arrayformula3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/497579/5d6e119a-0f95-89bb-75a8-1a846254dcdc.png)

これではっきりしましたね。
ARRAYFORMULAを利用した際には**ROW関数は行方向、COLUMN関数は列方向にしか展開されない**んです。
まあROWやCOLUMNの用途を考えれば2次元展開する必要はあまり無さそうではあるのですが...
だからROW関数を列方向にも伸ばしてやりたい場合には、COLUMN関数を併用して、IF関数で無理矢理繋げる必要があったんですね。
ARRAYFORMULAによる展開は、中身の関数によって2次元的に展開できるものだったり、縦か横にしか展開できないものだったり、縦にも横にも展開できないものだったりするので、ここはかなり理解が難しい仕様です。

もちろんIF関数などではARRAYFORMULAによる2次元展開が可能なですね。
`=ARRAYFORMULA(IF(B2:E8 = 100, "満点！",))`
![arrayformula4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/497579/b073825f-b1f0-b5f3-0835-c525f7d358aa.png)

### 3.3 IF(COLUMN(1番上の行), ROW(1番左の列)) のまとめ
**ARRAYFORMULAで展開できる方向は、中身の関数によって異なるために注意が必要です。**
そして `IF(COLUMN(1番上の行), ROW(1番左の列))` はARRAYFORMULAで**ROW関数を2次元的に展開するためにわざわざ回りくどい書き方をしていた**というお話でした。

## 謎4. SUMIF関数の挙動が理解できない...

**要旨:** SUMIF関数の**第1引数に数式を入れた**場合、**範囲は単に第1引数に入力した範囲**となりますが、**第2引数の条件として判定される値は、第1引数の数式の結果**となります。また、**第3引数を指定しない場合、第1引数の数式の結果の総和がSUMIF関数の返り値**となります。

### 4.1 SUMIF関数の定義を調べる
Google公式ドキュメントで [**SUMIF関数**](https://support.google.com/docs/answer/3093583?hl=ja) のページも調べてみましょう。
> `SUMIF(範囲, 条件, [合計範囲])`
> 範囲 - 条件と比較して検証する範囲です。
> 条件 - 範囲に適用するパターンまたはテストです。
> 合計範囲 - 合計する範囲です。範囲と異なる場合に指定します。

一部説明を省略していますが上記の通りです。
普通にSUMIF関数を使った場合の例を挙げます。
(画像では間違ってARRAYFORMULAが入ってますが、無くても同じ挙動です)

`D3` → `=SUMIF(B3:B21, ">0")`
`E3` → `=SUMIF(B3:B21, "<0")`
![得失点.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/497579/be00a838-e53c-4533-58d5-fb4706993da2.png)
このように**第1引数に範囲**、**第2引数に加算する値の条件**を書けば**条件に合ったものだけを全て加算する**のがSUMIF関数です。

また、第3引数に範囲を指定した場合には、**加算する対象だけ別範囲にすることが出来ます**。
(こちらも画像では間違ってARRAYFORMULAが入ってますが、無くても同じ挙動です)

`E3` → `=SUMIF(B3:B12, ">0", C3:C12)`
`F3` → `=SUMIF(B3:B12, "<0", C3:C12)`
![得失点2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/497579/a3a8fe85-dd16-9f00-269d-b5c474707a4c.png)
**第1引数に条件用の範囲**、**第2引数に条件**、**第3引数は条件を満たしている場合に加算する範囲**という形ですね。
ここまでが普通の使い方ですが、これだけでは今回の謎はまだ解けません。

### 4.2 SUMIF関数の第1引数に数式を入れてみる
今回の問題は、SUMIF関数の第1引数に、単なる範囲ではなく数式が入ってしまっていることです。
では、数式が入っているとどのような挙動になるのか見てみましょう。

`=ARRAYFORMULA(SUMIF(A2:A11 + 3, ">10"))`
![変則SUMIF.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/497579/3cf55afd-213c-fc5d-c7f4-fd1140e2ad2e.png)
ARRAYFORMULAが適用されているため、1, 2, ..., 10 はそれぞれ+3されて 3, 4, ..., 13 となります。
計算前に10より大きい数はありませんが、計算後の値で 11, 12, 13 の3つは10より大きいため、これらの総和の 11 + 12 + 13 = 36 が値として返ります。
**第1引数の範囲は変わりませんが、第2引数の条件は「第1引数の数式の計算後の値」で判定される**のようですね。

ではさらに次の例です。
`=ARRAYFORMULA(SUMIF(A2:A11 + 3, ">10", A2:A11))`
![変則SUMIF2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/497579/a7c8d5e3-1203-68e5-97af-10f04e8162c9.png)
先ほどの例の第3引数に `A2:A11` を入れた形です。最終的な値が36から27に変わっていますね。
こちらは先ほど同様、3を足すと 11, 12, 13 が10より大きくなるため加算対象となりますが、**第3引数があるため、実際に足すのは第3引数の値(つまり元の値)となります**。
よって、3を足す前の 8 + 9 + 10 = 27 が返ります。
つまり、**「3を足したら10を超えるものに対して、元の数の総和」を求めている式**になるんですね。
うーんややこしい。

しかし、これでようやく元の式を紐解く準備が整いました。

### 4.3 SUMIF関数の第1引数に数式を入れた場合の挙動まとめ
#### 通常のSUMIF関数
* 第2引数は条件
* 条件で判定される範囲は第1引数
* 総和の対象となる範囲は、第3引数が存在すれば第3引数、第3引数が存在しなければ第1引数

#### 第1引数に数式が使われるSUMIF関数
* 第2引数は条件
* 条件で判定される範囲は第1引数で指定した範囲だが、**条件判定の値は数式の計算結果**
* 総和の対象となる範囲は、第3引数が存在すれば第3引数、第3引数が存在しなければ第1引数だが、**総和の対象となる値は数式の計算結果**

では最終決戦と行きましょう！

# まとめ: 「謎の数式」を解明する
「謎の数式」を再掲します。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/497579/dac56f2a-617b-8d62-2509-f5417a401a6c.png" width="360"> ⇒ <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/497579/1e03ba52-da0b-734e-2e81-d1367c6c2bf6.png" width="360">

```
=
ARRAYFORMULA(
  SUMIF(
    IF(COLUMN(B2:E2), ROW(B2:B8)),
    ROW(B2:B8),
    B2:E8
  )
)
```

まず、ARRAYFORMULA化されている `IF(COLUMN(B2:E2), ROW(B2:B8))` は、COLUMNが常に1以上でIF関数がTRUEになり、ROWの値が2次元的に返るんでしたね。


`=ARRAYFORMULA(IF(COLUMN(B2:E2), ROW(B2:B8))))` (再掲)
![arrayformula1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/497579/6abf3711-a0c4-1351-dd0d-6569362593e4.png)

次にSUMIF関数の視点から見ると、まず第1引数の範囲は`B2:E8`です。
そして第2引数の条件は`ROW(B2:B8)`、これはイコールが省略された形で、`"=" & ROW(B2:B8)`と第2引数に書くことと同義です。
ただし第2引数の条件は、**通常の場合は範囲ではなく単数で書くはずなので、ARRAYFORMULA関数の効果により複数行に展開されます**。
つまり、2行目の`F2`では`"=" & ROW(B2)`、3行目の`F3`では`"=" & ROW(B3)`、8行目の`F8`では`"=" & ROW(B8)`が第2引数である際の結果が返ります。
そして第3引数は元の範囲`B2:E8`を指定しているため、第1引数のROW関数の値ではなく元のテストの点数の総和を返します。

よって、`F2`に返る値は、「**2行目から8行目のROW関数の結果のうち、2行目のROW関数の値と一致する場所の元の値の総和**」、`F3`に返る値は、「**2行目から8行目のROW関数の結果のうち、3行目のROW関数の値と一致する場所の元の値の総和**」、...というように、`F2:F8`から見て「**自身の列と同じ列だけがSUMIFの条件に当てはまり、元の値の総和が返ってくる**」のです。

(再掲)
![テスト結果2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/497579/9791884c-d478-d025-d919-c7f1ed3fa76a.png)


# おわりに
本記事ではGoogleスプレッドシートに関して、リファレンスに載ってない複雑な事例を実際に試し、1つ1つ挙動を確かめてみました。
予想以上に長くなってしまいましたが、スプレッドシートの挙動を深く理解したい方々の一助になれば幸いです。

ここまでお読みくださり、本当にありがとうございます！