銀の弾丸

プログラミングに関して、いろいろ書き残していければと思っております。

スプレッドシートで行列演算:数式コピペを削減できる

f:id:takamints:20190829170340j:plain
photo credit: j_lai Photo-0141.jpeg via photopin (license)

Googleスプレッドシートの行列演算を上手く使うと、計算式を入力するべきセルの数を非常に少なくできます。 隣り合わせのセルに、ほぼ同じ計算式をズラリとコピーしているようなケースで特に。

セルで行列演算ができることは知っていましたが、特に使う必要がないと思っていました。 しかし、職場の移転をきっかけに、とある情報管理の必然性が出てきまして、使ってみると「これめっちゃ便利じゃないですかー!」とプチ感動。 頭で考える以上に使えるシーンがありそうですので、ここにお知らせしておきます。

おそらくExcelでも同じことができるはずなのですが、今のところはGoogleスプレッドシートでやっています。そのうち調べてみるつもり。

Excel関数リファレンスシリーズ「検索/行列関数」
NOOK出版 (2019-01-26)
売り上げランキング: 32,694
目次

長い前置き(ここは無理に読まなくてもよろしいですよ)

弁当男子の憂鬱話

話は一旦戻りまして、お盆を境に就業場所が変わりました。転職したわけではありません。作業場所が変わっただけです。

が、それによってお昼ごはんの事情が急転、お盆明けから5時半起床で弁当男子をやっております。

以前は職場でまとめて発注する仕出しのお弁当を頂いていたのですが、場所が変わって、そのシステムが無くなって、弁当つくらにゃお昼の費用が跳ね上がるのよ。

月額5500円が心理的な上限だだだ

従来の仕出しのお弁当1食あたりのスペックは、1食350円(ライス無しなら250円)、おかずは360~500kcal。 自分はカラダが小さめ省エネタイプ。さらに通勤が短距離&短時間だったので、ライス無しで問題なし。 月22日勤務で昼食代はなんと破格の5500円でした。

ところが、新しい仕事場では、お弁当システムがなくなりました。 近隣に飲食店やコンビニはたくさんありますが、それでは上記価格水準を保てそうにありません。

レンチンご飯と冷凍食品

昼食代が、ひと月のお小遣いに直接影響する制度な家庭。 もう弁当男子以外に取る手がありませんねということで、こういうときは初動が大事。

分量的に不安定な炊飯ジャーの残りご飯と朝イチ夢の炊きたてご飯のせめぎあい。 朝はパン食、深夜の米とぎ、タイマーセットで日の出の時間にシュシュポポポ。 腕に覚えのだし巻き卵と焦がしタコさんウィンナーと・・・

しかし残念!根本的に平日早朝キッチンを専有するのはどうあっても認められず。 そこでレンチンご飯と冷凍食品で手早く5分で済ませるからって提案(懇願?)により、めでたく許可を得ました有難うヨメ。

実現可能性調査が必要

ということで、さっそく近所の格安スーパーで食材購入。 多少の料理は趣味でかじっておりますが、冷食の価格帯やらお弁当に適正な分量などは全く未知数。 そもそも1食あたり250円程度に抑えて必要なエネルギーを得られるものなのか全く知識がありません。

なるべく低価格で高エネルギーなお弁当を目指したいので、緻密で厳格な実現可能性調査が必要です。 ちなみにお弁当箱は百均で600ccのを買ってきました。テキトーです。

ワークシートで行列演算

さて実現可能性調査のため、やおらGoogleスプレッドシートを取り出して、作ってみたのが以下のシート(見た目は普通の表ですが)。 購入食材とその内容(価格、内容量、熱量)、そして月~金の5日分のお弁当で「どの食材を何個使うか?」を入力し、それぞれ1食あたりの価格とエネルギーを算出しています(文字色が青い部分が一次データの入力セル)。

それほど複雑な演算はしていませんが、「計算式が入力されているのは背景が黄色のセルだけですよ」と言えば少し意外に思われないでしょうか? 全部で6つのセルがありますが、右端の平均値以外ならば3箇所です。

従来から表計算では慣習的に縦一列や横一列に単純で似たような計算式をコピペしていましたが、複数の情報に個数を掛けて積算するところでは「これは行列演算すべきところだ・・・」と気が付きました。

で、この3箇所のセルには行列演算の計算式を入力しており、その式の演算結果は(数学的な)行列です。 Googleスプレッドシートでは、演算結果が行列だと隣接する空白のセルに結果の行列の各要素を展開してくれるんですね。 上のシートでは、背景黄色のセルに隣接する、背景が白以外のセルに、行列の要素が展開されています。

以下でこれら3つの計算式を説明します(上の埋め込みでは式が見えないみたいなので)。

税抜単価(F列)の計算式

セルF5では、各商品の税抜価格(セル範囲D5:D12)を内容数(セル範囲E5:E12)で割っています。

セルF5に入力された計算式:

=ArrayFormula( D5:D12 / E5:E12 )

セルの範囲は行列として扱える

ArrayFormulaの引数にはセル範囲が記述されています。

計算式内のセル範囲は、その範囲に入力されているデータを要素として持つ行列として扱われます。

このため、D5:D12 は、このセル範囲に入力されている8行1列の行列として計算に使えるのです({E5:E12} も同じ)。

ArrayFormula:配列数式を計算して展開する

ArrayFormula は、引数の配列数式の計算結果を返す関数です。

「配列数式の計算」とは、行列の要素単位の計算を行う数式だと理解しています(ちゃんとした定義は不明)。MATLABOctaveのドット演算とほぼ同じ。

ここでは行列で行列を割っています({D5:D12}/{E5:E12})が、配列数式の計算として、個々の要素間で割り算が行われるため、結果として同じ大きさ(8行1列)の行列が返されます。

計算結果の行列は式を起点としたセル範囲に表示される

計算結果が行列である場合、式の入力されたセルを起点とした範囲に各要素が展開されるため、セル範囲 F5:F12 に単価が表示されています。

税込単価(G列)の計算式

ここでも ArrayFomula が使われており、行列の定数倍が行われています。

セルG5に入力された計算式:

=ArrayFormula( F5:F12 * (1+$M$1) )

F列に表示された単価に対して、消費税を適用して税込みの単価を算出し、結果をセル範囲G5:G12へ表示しています。 見た目、なんとなくArrayFormulaは不要なのでは?と思いましたが、結果をセル範囲に展開するためには必要なようです。 ArrayFormulaを外すとセルG5だけに結果が表示されてしまいます。

1食あたりの価格と熱量を一括計算する(セルI15)の計算式

行列演算の真骨頂がこちらです。セルI15のひとつの式(以下)で、3行5列のセル範囲I15:M17の範囲に結果を表示しています。

セルI15に入力された計算式:

= MMULT( TRANSPOSE( F5:H12 ), I5:M12 )

ここでは、MMULTとTRANSPOSEという2つの関数が使われています。 どちらも行列の演算に関しては基本的な関数ですね。

TRANSPOSE:行列の転置

TRANSPOSEは行列の転置を行います。転置とは行列の行と列を入れ替えることです。

TRANSPOSE(F5:H12)は、セル範囲F5:H12が表す8行3列の行列を、3行8列の行列に変換しています。

なぜここで転置を行っているのかは、次のMMULTで説明します。

MMULT:行列の積

MMULTは2つの行列を引数に取り行列の積を行います。 行列演算の基本として、2つの行列AとBがある場合、Aの列数とBの行数が同じでないとA×Bの演算はできません。 このため、第一引数をTRANSPOSEで転置しているのです。

ちなみに、この場合の結果の行列は、「行数がAの列数」で「列数がBの列数」となります。

その他の行列演算にまつわる話題

ArrayFormulaの自動挿入

数式を入力して、[Ctrl]+[Shift]+[Enter]を入力すると、数式全体をArrayFomulaで括ってくれますメチャ便利。

直接要素を指定して行列を初期化する

セル範囲を参照せず直接行列の要素を指定して行列を生成・初期化するには、以下に示す例のように、全要素を区切り文字をつかって行と列を区切って羅列し、全体を波括弧({})で囲みます。列の区切りはカンマ,、行の区切りはセミコロン;です。ちなみに全要素を特定の値で初期化するような関数は無いようです。表計算でそういう需要はあまりないかも知れませんが、全要素を記述するしか無いようです(巨大な行列ではやりたくないが)。

例1) {1,2,3} → \(\begin{bmatrix} 1 & 2 & 3 \end{bmatrix}\)

例2) {1; 2 } → \(\begin{bmatrix} 1 \\ 2 \\ \end{bmatrix}\)

例3) { 1, 2, 3; 4, 5, 6 } → \(\begin{bmatrix} 1 & 2 & 3 \\ 4 & 5 & 6 \\ \end{bmatrix}\)

単位行列を作る

単位行列は(まあ一般の計算式では使わないかもですが) ワークシート関数 MUNIT を使います。引数には行と列の数です。

例: MUNIT(3) → \(\begin{bmatrix} 1 & 0 & 0 \\ 0 & 1 & 0 \\ 0 & 0 & 1 \\ \end{bmatrix}\)

行や列の挿入がとても楽だが空白セルは認められない。

表の途中に行や列を挿入する時、行や列ごとに計算式を入れていると計算式をコピペしないといけませんが、行列演算でやってる場合は特に必要ありません。

しかし、通常の計算式では何も入力されていないセルを数値としての 0 として扱ってくれますが、行列として扱っている場合は、計算結果がエラーになります。

あとがき

行や列を操作する関数がもっと増えてくれたら嬉しいな。 行や列の範囲を指定して取り出すとか、行または列の合計とか平均とかね。

ま、使えるところにガンガン使えばきっと便利になるでしょう。エクセル上でディープラーニングとかできると面白いかもね(さすがに無理か)。

とりあえず数学的な行列演算の感覚は思い出しておかないと。