Power BI - SUM, CALCULATE(SUM), SUMX がどのように行コンテキストに沿って評価しているか


一度丁寧に具体例を追ってみると、SUM関数の仕様や、
CALCULATE関数と組み合わせる意味、
そして、SUMX関数との違いがよくわかるのではないか、
と思ってまとめてみました。


SUM関数が出す結果

画像1

この'Transaction'テーブルの[Amount]列の合計を、
SUM関数を用いて、新しい列に計算してみます。

SUM_Amount =
  SUM(
    'Transaction'[Amount]
  )

結果は次のように、全て同じ値([Amount]列の合計)が表示されます。

画像2


もしかして、Orangeの行には、OrangeAmountの合計(1300) が表示されると思いましたか?

SUM関数は、
Row Context に沿った(つまり、行ごとに)計算を行う設計にはなっておらず(その行が何を記録している行なのかは無視して)、列全体を対象として合計を算出する関数なので、そのような結果にはならないのです。


CALCULATE ( SUM() )が出す結果

もう一度同じ 'Transaction' テーブルを使いましょう。

画像3

では次は、
SUM関数をCALCULATE関数で挟んであげたらどうなるでしょうか。
DAX式はこれです。

CALCLATE_SUM_Amount =
  CALCULATE(
    SUM(
      'Transaction'[Amount]
      )
    )

結果は、次のように、[Amount]列と同じ値となりました。

画像4


なぜでしょうか?

SUM_Amount 列は、
SUM ('Transaction' [Amount] ) で計算したのに対し、
CALCULATE_SUM_Amount 列は、
CALCULATE( SUM( 'Transaction'[Amount] ) ) と、CALCULATE関数でSUM関数をはさんだだけです。

それは、
CALCULATE関数は、Row Context に沿って(つまり、1行づつその行には、どのような属性の情報が記録されているのか判定しながら)計算を実行するDAX関数だからです。

ではどのようにCALCULATE関数はRow Contextを判定しているのでしょうか?
具体的にはこんなイメージです。

画像5

つまり、CALCULATE関数は、SUM関数の計算結果を算出するまえに、
各行が全部独立した行であると判定しているので、
それぞれ独立した行ごとにその合計を算出(SUM関数で評価)しているのです。


次のように、'Transaction' テーブルを修正しましたので、
具体的に確認してみましょう。

画像6

Orangeの行は、Quantity も、Price も、Amount も全て同じ値にしました。

よって、CALCULATE関数は、2行目と3行目の Orange のレコードは、
同一の行とみなすので、
結果はこのようになります。

画像7

Orange の行にある [CALCULATE_SUM_Amount] は、
いずれも各OrangeのAmountの合計である2000となっています。


これが、中々理解しがたい「コンテキスト」のうち、「Row Context(行コンテキスト)」の意味しているところです。


一応MSの説明用のリンクを張っておきます。
(でもプログラミングをしてこなかった人が見ても、理解しがたいと思います。。)


SUMX(SUM)が出す結果

次に、この際なので、SUMX関数についても書いておきます。

最初と同じ 'Transaction' テーブルを使用します。

画像8

この'Transaction'テーブルの[Amount]列の合計を、
SUMX関数を用いて、新しい列に計算してみます。

SUMX_SUM_Amount =
  SUMX(
    'Transaction',
    SUM('Transaction'[Amount])
  )

SUMX関数はイテレータ関数なので、
どのテーブルの配列を処理しに行くのかを特定するために、
第一パラメータ(第一引数)にテーブルを指定します。

※イテレータについては、こちらも参照してみてください。


結果は、このようになりました。

画像10


何じゃこりゃ! 

と一瞬思いますよね。

でもこれには理由がきっちりあります。

上述でCALCULATE関数が、Row Contextに沿ってSUM関数を評価したように、SUMX関数もRow Contextに沿って、SUM関数を評価しています。

しかし!

CALCULATE関数とSUM関数の組み合わせとの違いは、
SUMX関数はイテレータ(配列に対して反復計算を行う関数)であるということです。

SUMX関数の動きは、可視化してみると以下のようなステップを踏んでいます。

SUMX説明

以上のように、結果が4350になったのは、
SUMX関数がバグってるのではなく、
SUMX関数の反復処理の性格を考慮せずに上記のようなDAX式を書いた結果(もっと具体的に言うと、反復処理の対象として'Transaction'テーブル全体を第1引数で指定し、それに対してSUMを指定したテーブルの行数分だけ反復処理させた結果
なので、
今回の例では、ちょっと使えない感じの結果になってしまっていたということでした。


SUMX関数にSUM関数を引数として使用しない場合に出る結果

ここまで来たら、もうちょっと突っ込んで確認してみましょう。

上記のSUMX関数とSUM関数の組み合わせで作成した列は、

SUMX関数の反復計算を行う性格と、
SUM関数の列全体を評価(合計)するという性格

の2つが合わさって、

第1引数で指定したテーブルの行数分だけ、何度もSUM計算を繰り返し、
最後にその複数回行われたSUMの結果を合計する

という結果になりました。

では、SUMX関数の第2引数にSUM関数を指定しなければ、
どうなるのでしょうか?

SUMX_ROW_Amount =
  SUMX(
    'Transaction',
    'Transaction'[Amount]
  )

SUM ('Transaction' [Amount] ) ではなく、単に、'Transaction' [Amount] を
第2引数にもってきました。

結果はこのようになりました。

画像11

SUM_Amount列と同じ結果になりましたね。

この理由は、
SUMX関数の第1引数では'Transaction'テーブル全体を指定しましたが、
第2引数では、(列全体の合計を出す集約関数であるSUMを使用せず)
計算の機能を持たない、ただの列を指定しています。

従って、
SUMX_ROW_Amount の中では、
計算に必要な指示は、SUMX関数の「合計せよ」だけになるので、
SUM関数で単純集計したSUM_Amountと同じ結果になるのです。
(SUMX関数の第2引数にSUM関数を持ってきたケースでは、
SUMX関数の「合計せよ」とSUM関数の「合計せよ」という2重の合計させる指示が存在していたので、重複計算の結果になっていたんですね)

CALCULATE関数>SUMX関数>SUM関数の順にネストさせるた場合に出る結果

もうそろそろ訳が分からなくなってきたかもしれませんが笑、
仕組みをちゃんと理解するために別の例を出します。

最初の例と同じ、'Transaction'テーブルを使用します。

画像12

このテーブルに、以下の式で列を追加します。

CALCULATE_SUMX_SUM_Amount =
  CALCULATE(
    SUMX(
      'Transaction',
      SUM('Transaction'[Amount])
    )
  )

先述のSUMX関数の中に「SUM関数を入れて」列を作成した際は、
全て4350(=Amount列の合計 x 行数)が計算結果になりましたよね。

今回は、その「SUMX関数の中にSUM関数を入れた式」を、
さらにCALCULATE関数で包みました。

結果はこうなります。

画像13

一瞬「あれ?」と思いますよね。

これは、結論だけ先に書くと、
式の外側にあるCALCULATE関数が、SUMX関数より優先して
行コンテキストに沿ってSUMX関数が参照する'Transaction'テーブルを絞り込んでしまうからです。

再掲しますが、
CALCULATE関数は、このように1行ずつ式の評価の際にテーブルを処理します。

画像5

従って、SUMX関数は、本来であれば(CALCULATE関数がなければ)、
'Transaction'テーブル全体に対して繰り返し計算を行うのですが、
CALCULATE関数が、
SUMX関数が「'Transaction'テーブル全体」と認識しているテーブル範囲を先に(←外側にあるからです)行コンテキストに沿って絞り込んでしまうので
テーブル内の固有の3行について、
1行ずつしか「合計」計算を発動できなくなってしまっているのです。


CALCULATE関数>SUMX関数>特定の列の順にネストさせるた場合に出る結果

では、上記と同じテーブルを用いて、
以下の式で列を作成したらどうなるでしょうか?

CALCULATE_SUMX_ROW_Amount =
  CALCULATE(
    SUMX(
      'Transaction',
      'Transaction'[Amount]
    )
  )

そろそろ、イメージできますよね。
結果はこのようになります。

画像15

これは、SUMX関数が合計しようとするテーブルが、
CALCULATE関数で絞り込まれているので、Amountと同じ結果になるという、前述の例と同じです。
(前述の例では、SUMもSUMXも特定の1行しかないテーブルを合計対象にしていたので、今回の結果と同じになっています)


SUM関数がメジャーとしてCALCULATE関数内で処理される場合の結果

最後です。

最初の例と同じテーブルを使います。

画像1

このテーブルに対して、次の式で列を追加します。

CALCULATE_CALCULATE_Measure_SUM_Amount =
  VAR  SUM_Amount = SUM('Transaction'[Amount])
  RETURN
    CALCULATE(SUM_Amount)

※ VARとかRETURNってなに?となった人は、こちらに説明を書いているので見てみて下さい。(ざっくり説明すると、ここでは VAR の後ろでメジャーを定義して、RETURN の後ろで定義したてのメジャーを使っています)

結果はこうです。

画像18


ちゃんと「ん??」
となりましたか?


ちょっと先ほどの例に振り返ってみましょう。
だいぶ上の方に書いた例でですが、このような式で列を追加しました。

CALCLATE_SUM_Amount =
  CALCULATE(
    SUM(
      'Transaction'[Amount]
      )

    )

その結果はこうでした。

画像4


2つの違いは、
・中身をメジャーで指定するか(CALCULATE ( メジャー ) で書くか)、
・なまの式(CALCULATE ( SUM ) で書いた式)で書くか

の違いです。

この答えは、
「X」系の関数(イテレータ関数。例:SUMX、MAXX、MINX、AVERAGEX)をちゃんと理解した方がよいと思うので、
時間があれば次は「X」系の関数について書きたいと思います。


以上になります。



列を作ったり、メジャーを作る際に使用するDAX関数は、
コンテキストを理解すると
より複雑な計算も、よりシンプルに行えるようになります。

でも、
そう簡単にコンテキストを深く理解することは困難なので、
式を書いてみて、
「なんでこんな結果になったんだろう?」と
面倒ですがDAX式を丁寧に振り返りながら進めていくのが、
コンテキストを理解する近道だと思います。


でわ

いいなと思ったら応援しよう!