ヒストリカル シミュレーションによるVaR(Value at Risk)をDAXメジャーで計算できないか試していたら、コンテキスト トランジション(Context Transition)を全然理解していなかったことがわかった。ちゃんと勉強しよう。
…そして今日のPower BI Weekly Newsとだだ被りしてしまった。
コンテキスト トランジションとは
ひとことで言えば: コンテキスト トランジションとは、メジャー計算時に行コンテキストをフィルター コンテキストに変換すること。変換されたフィルター コンテキストは、行コンテキストでイテレーションしている行のすべての列とその値の組み合わせとなっている。
行コンテキストが関係するので、計算列(←あまり使わないかも)かイテレーター関数(SUMXとかAVERAGEXとかのなんたらX関数)を使うときの話。イテレーター関数を使って複雑なメジャーを定義するにはちゃんと押さえておかないとね。
と、これだけでは意味不明なので、以下で具体例を使って説明する。
(自分用メモ: VaRを計算するなら)
サンプル データ
サンプルのデータ モデルは下図のとおり。ファイルはここからダウンロードしてね。
売上テーブル
| 日付 | 商品名 | 売上額 | 
|---|---|---|
| 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月の日別最大売上が1,356(=1月の売上合計)となってしまう。本当は1/31の値の693。
正しいメジャーは以下:
日別最大売上1 = MAXX(
    VALUES('カレンダー'[日付]), 
    [売上額メジャー]
)
売上額メジャー = SUM('売上'[売上額])
え?SUM('売上'[売上額])部分をメジャーにしただけじゃね??と思うかもしれないけど、これでOK。
実はメジャーにすることで、自動的にCALCULATE関数でラップされている(フィルター コンテキストを生成するため…らしいよ)。なので、以下でもOK:
日別最大売上2 = MAXX(
    VALUES('カレンダー'[日付]), 
    CALCULATE(SUM('売上'[売上額]))
)
日別最大売上1メジャーと日別最大売上2メジャーとが同じということはわかった。
コンテキスト トランジション
正しいやり方はわかったので、CALCULATE関数が何をしているのかを考えよう。
CALCULATE関数が何をやっているのかを明示的に示したのが以下:
日別最大売上3 = MAXX(
    VALUES('カレンダー'[日付]), 
    VAR _date = 'カレンダー'[日付]
    RETURN CALCULATE(
        SUM('売上'[売上額]),
        'カレンダー'[日付] = _date
    )
)
つまり、イテレーター関数の内側のCALCULATE関数は、行コンテキストをフィルター コンテキストに変換して追加しているということ。そのフィルター コンテキストは、行コンテキストでイテレーションしている行のすべての列とその値の組み合わせとなっている(今回の例では[日付]列のみ)。これがコンテキスト トランジション(Context Transition)。
(なお、トランザムではないため赤く光ったりしない。)
ちなみに、コンテキスト トランジションを使わないなら、次のように書くことも可能。
日別最大売上4 = MAXX(
    VALUES('カレンダー'[日付]), 
    VAR sales = RELATEDTABLE('売上')
    RETURN SUMX(sales, [売上額])
)
まあ、日別最大売上1メジャーか日別最大売上2メジャーの方がわかりやすいので、こうは書かないよね。
なお、マトリックス ビジュアルの列見出しに'商品'[商品名]を追加しても、日別最大売上1メジャーはちゃんと計算してくれている模様:

やっぱDAXメジャーってすごいね!
なぜ間違っていたのか
コンテキスト トランジションの理解のため、間違っていた日別最大売上_wrongメジャーで何が起きていたかを改めて考えよう。
期待したのは、日別にイテレーションし、各イテレーションではSUM関数で日別の売上額の合計を計算し、日別の売上額の最大値を見つけるというもの。なぜそれができなかったのか??
その答えは、SUM関数がCALCULATE関数で囲われていないため、行コンテキストがSUM関数に渡されずに計算されていたから。つまり、売上テーブルを日付でフィルターできていなかったということ(そもそも、行コンテキストはリレーションシップをフォローしないので、必要に応じてRELATED関数やRELATEDTABLE関数を使っていたことを思い出そう)。SUM関数にはその他のフィルター コンテキスト(今回は1月というフィルター)だけが渡された結果、イテレーターの各行の計算結果がすべて同じ値(=1月の売上合計)になってしまい、MAXX関数が1月の売上合計を返したというわけ。
注意
なお、本稿で説明していないが、コンテキスト トランジションはイテレーションする行のすべての列をその行の値によるフィルター コンテキストとして追加する。そのため、ファクト テーブルをイテレートするメジャーでは、データに重複がある場合(重複自体は問題ないが)、コンテキスト トランジションで意図しない結果となることもあるので注意が必要、、らしい。いやいや、そもそもファクト テーブルのイテレーションにコンテキスト トランジションって必要なくね??と思ったりするけど、コンテキスト トランジションを意識していないとやらかしそう。
参考
いつも勉強させてもらっています。ありがとうございます。






