PowerBI/PowerPivot(以下、総称してDAXと呼びます)で非常に重要な意味合いを持つCaluculate関数について、書いてみたいと思います。この関数については、単に処理内容を理解するだけでは不十分です。裏側でDAXが処理しているコンテキストの変換についての理解がなければ、最悪の場合、間違った集計値を出してしまうかもしれません。逆に言えば、Calculateを理解できれば、DAXの理解は大きく前進します。そのくらい重要な関数です。
なぜこの関数がそれほど重要かというと、やはりコンテキストの概念が大きく関係しているからです。コンテキストの解釈によってPowerBIやPowerPivotはGUI上における直感的な分かりやすさを得ている反面、難しさや取っ付きにくさの原因にもなっています。つまり、あらゆる意味で「肝」になっている部分なのです。
今回は、Calculate関数の基礎部分を見ていきます。Calculateを理解するにあたって、その代表的な使い方のひとつである「売上割合の算出」を使います。
基本的な計算は
「売上高」/「総売上高」
です。
非常に単純な計算ですが、DAXではフィルターが関係します。分母は総売上高なので、フィルターの影響を除外する必要があります。つまり、フィルターをクリアする必要があります。
ALL関数について
ALL関数を使えば、フィルターをクリアできます。式としては、以下のようになります。
売上割合 =
DIVIDE(
SUM(売上明細 [売上])、
SUMX(ALL(売上明細)、売上明細[売上])
)
この式は正常に動作します。ALLでフィルターをクリアした売上明細テーブルをSUMXでイテレーションして総合計を算出しています。
ただし、ここにフィルターを掛けると、想定通りには動きません。
この状態では、「トップス」「小物」カテゴリーに対して、商品のフィルターがかかっています。(PCバッグとTシャツだけを抽出しています) つまり、「トップス」「小物」の数値は、PCバッグとTシャツの売上高のみ抽出されて合計されています。ですから、この場合の「総計」はPCバッグとTシャツの売上高のみ抽出された結果としての総計でなければ不自然です。
にもかかわらず、ここでの売上割合は「総合計」に対する売上割合になっています。スライサーでかけたフィルターが反映されず、分母は全商品の総売上高になっています。そのため、「総計」の売上割合も100%ではなく、これら商品の売上割合を表す数値になっています。(総売上高に対する売上割合、という意味で考えれば間違った数値ではありませんが、この表における文脈で考えれば不自然です)
これを解決するには、分母を「スライサーで選択したフィルターを適用したうえでの総合計」にする必要があります。
Calculateを使う
特定のフィルターをクリアするには、Calculateを使います。
Calculateの最初の引数は、評価実行する式を取ります。2つ目以降の引数では、既存のフィルターを置き換える新たな条件を指定します。
例を見てみましょう。
=DIVIDE(
SUM('売上明細'[売上]),
CALCULATE(
SUM('売上明細'[売上]),
ALL('商品'[商品カテゴリー])
)
)
この例は、先ほどALL関数のところで見た式をCalculateを使って書き直したものになります。
Calculateは、以下のような処理を行います。
・2番目の引数であるALL('商品'[商品カテゴリー])によって、商品カテゴリー列へのフィルターをクリア
・その状態で'売上明細'[売上]を評価し、SUM('売上明細'[売上])を実行
・集計した値を返します
その結果、下記のようになります。(行に指定しているのは'商品'[商品カテゴリー]です。スライサーで'商品'[商品名]を選択しています)
これで、スライサーで指定した'商品'[商品名]のフィルターを維持したまま、各カテゴリーの売上割合を出すことができました。CalculateでALL('商品'[商品カテゴリー])を第二引数に指定することで、クリアするフィルターを明示的に指定しています。その結果、スライサーで指定している商品名のフィルターは維持されたまま集計することができています。
なお、ここでは商品テーブルでしかフィルターのクリアを行っていません。にもかかわらず、'売上明細'[売上]を合計しています。'売上明細'[売上]のフィルターはクリアしていません。しかし、最終結果としては、'商品'[商品カテゴリー]列へのフィルターがクリアされた総売上高が返されています。
これは、フィルターの伝播によるものです。リレーションシップで定義された関係性の方向により、フィルターのクリアも伝播されます。'商品'[商品カテゴリー]でクリアしたフィルターは売上明細テーブルの該当列におけるフィルターもクリアします。これにより、'商品'[商品カテゴリー]へのフィルターの影響を除外した売上合計を算出することが可能になっているわけです。
ここではCalculateの引数にとっているのは非常に単純な数式のみでしたが、FILTER関数と組み合わせて、より高度なフィルター操作も可能です。それについては項を改めて書くことにします。