LoginSignup
11
9

Power BI: コンテキスト トランジションを理解する

Last updated at Posted at 2023-04-09

ヒストリカル シミュレーションによるVaR(Value at Risk)をDAXメジャーで計算できないか試していたら、コンテキスト トランジション(Context Transition)を全然理解していなかったことがわかった。ちゃんと勉強しよう。

…そして今日のPower BI Weekly Newsとだだ被りしてしまった。

コンテキスト トランジションとは

ひとことで言えば: コンテキスト トランジションとは、メジャー計算時に行コンテキストをフィルター コンテキストに変換すること。変換されたフィルター コンテキストは、行コンテキストでイテレーションしている行のすべての列とその値の組み合わせとなっている。

行コンテキストが関係するので、計算列(←あまり使わないかも)かイテレーター関数(SUMXとかAVERAGEXとかのなんたらX関数)を使うときの話。イテレーター関数を使って複雑なメジャーを定義するにはちゃんと押さえておかないとね。

と、これだけでは意味不明なので、以下で具体例を使って説明する。

(自分用メモ: VaRを計算するなら)
シナリオ別現在価値を格納したファクト テーブルとシナリオを格納したディメンション テーブルをつなぎ、PERCENTILEX.INC関数で、シナリオのテーブルをイテレートし、(フィルター コンテキストを効かせた上での)シナリオごとの現在価値合計から、下位1%点を計算するという感じで(VaRの計算方法の詳細は別途ポストするかも)。

サンプル データ

サンプルのデータ モデルは下図のとおり。ファイルはここからダウンロードしてね。
モデル ビュー_trimed.png

売上テーブル

日付 商品名 売上額
2023年1月20日 ノート 120
2023年1月21日 鉛筆 221
2023年1月22日 消しゴム 322
2023年1月31日 消しゴム 331
2023年1月31日 鉛筆 231
2023年1月31日 ノート 131
2023年2月1日 ノート 101
2023年2月11日 鉛筆 211
2023年2月21日 消しゴム 321
2023年2月28日 消しゴム 328
2023年2月28日 鉛筆 228
2023年2月28日 ノート 128
2023年3月3日 ノート 103
2023年3月13日 鉛筆 213
2023年3月23日 消しゴム 323
2023年3月31日 消しゴム 331
2023年3月31日 鉛筆 231
2023年3月31日 ノート 131

日付テーブル(本当はちゃんと作るべきだけど、今回は手抜き)

日付
2023年1月20日 1
2023年1月21日 1
2023年1月22日 1
2023年1月31日 1
2023年2月1日 2
2023年2月11日 2
2023年2月21日 2
2023年2月28日 2
2023年3月3日 3
2023年3月13日 3
2023年3月23日 3
2023年3月31日 3

商品テーブル

商品名
ノート
鉛筆
消しゴム

いくつかのメジャーで試してみる

最大売上日の売上額を返す「日別最大売上」メジャーの作成を通して考えてみる。マトリックス ビジュアルで、行見出しを'カレンダー'[月]'カレンダー'[日付]、値をそういうメジャーにしてみよう。

間違いと正解

素朴に考えたメジャーが以下。日付テーブルでイテレーションして、最大値を返したい。

日別最大売上wrong = MAXX(
    VALUES('カレンダー'[日付]), 
    SUM('売上'[売上額])
)

良さそうなんだけど、これだと失敗。次のようになる:

レポート ビュー_マトリックス1_wrong.png

うーん('ω')…1月の日別最大売上が1,356(=1月の売上合計)となってしまう。本当は1/31の値の693。

正しいメジャーは以下:

日別最大売上1 = MAXX(
    VALUES('カレンダー'[日付]), 
    [売上額メジャー]
)
売上額メジャー = SUM('売上'[売上額])

レポート ビュー_マトリックス1_正解1.png

え?SUM('売上'[売上額])部分をメジャーにしただけじゃね??と思うかもしれないけど、これでOK。

実はメジャーにすることで、自動的にCALCULATE関数でラップされている(フィルター コンテキストを生成するため…らしいよ)。なので、以下でもOK:

日別最大売上2 = MAXX(
    VALUES('カレンダー'[日付]), 
    CALCULATE(SUM('売上'[売上額]))
)

レポート ビュー_マトリックス1_正解2.png

日別最大売上1メジャーと日別最大売上2メジャーとが同じということはわかった。

コンテキスト トランジション

正しいやり方はわかったので、CALCULATE関数が何をしているのかを考えよう。

CALCULATE関数が何をやっているのかを明示的に示したのが以下:

日別最大売上3 = MAXX(
    VALUES('カレンダー'[日付]), 
    VAR _date = 'カレンダー'[日付]
    RETURN CALCULATE(
        SUM('売上'[売上額]),
        'カレンダー'[日付] = _date
    )
)

レポート ビュー_マトリックス1_正解3.png

つまり、イテレーター関数の内側のCALCULATE関数は、行コンテキストをフィルター コンテキストに変換して追加しているということ。そのフィルター コンテキストは、行コンテキストでイテレーションしている行のすべての列とその値の組み合わせとなっている(今回の例では[日付]列のみ)。これがコンテキスト トランジション(Context Transition)

(なお、トランザムではないため赤く光ったりしない。)

ちなみに、コンテキスト トランジションを使わないなら、次のように書くことも可能。

日別最大売上4 = MAXX(
    VALUES('カレンダー'[日付]), 
    VAR sales = RELATEDTABLE('売上')
    RETURN SUMX(sales, [売上額])
)

レポート ビュー_マトリックス1.png

まあ、日別最大売上1メジャーか日別最大売上2メジャーの方がわかりやすいので、こうは書かないよね。

なお、マトリックス ビジュアルの列見出しに'商品'[商品名]を追加しても、日別最大売上1メジャーはちゃんと計算してくれている模様:
レポート ビュー_マトリックス2.png

やっぱDAXメジャーってすごいね!

なぜ間違っていたのか

コンテキスト トランジションの理解のため、間違っていた日別最大売上_wrongメジャーで何が起きていたかを改めて考えよう。

期待したのは、日別にイテレーションし、各イテレーションではSUM関数で日別の売上額の合計を計算し、日別の売上額の最大値を見つけるというもの。なぜそれができなかったのか??

その答えは、SUM関数がCALCULATE関数で囲われていないため、行コンテキストがSUM関数に渡されずに計算されていたから。つまり、売上テーブルを日付でフィルターできていなかったということ(そもそも、行コンテキストはリレーションシップをフォローしないので、必要に応じてRELATED関数やRELATEDTABLE関数を使っていたことを思い出そう)。SUM関数にはその他のフィルター コンテキスト(今回は1月というフィルター)だけが渡された結果、イテレーターの各行の計算結果がすべて同じ値(=1月の売上合計)になってしまい、MAXX関数が1月の売上合計を返したというわけ。

注意

なお、本稿で説明していないが、コンテキスト トランジションはイテレーションする行のすべての列をその行の値によるフィルター コンテキストとして追加する。そのため、ファクト テーブルをイテレートするメジャーでは、データに重複がある場合(重複自体は問題ないが)、コンテキスト トランジションで意図しない結果となることもあるので注意が必要、、らしい。いやいや、そもそもファクト テーブルのイテレーションにコンテキスト トランジションって必要なくね??と思ったりするけど、コンテキスト トランジションを意識していないとやらかしそう。

参考

いつも勉強させてもらっています。ありがとうございます。

11
9
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
11
9