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

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

…そして今日の[Power BI Weekly News](https://youtu.be/TPfbQ8OFiLc)とだだ被りしてしまった。

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

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

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

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

# サンプル データ
サンプルのデータ モデルは下図のとおり。ファイルは[ここ](https://github.com/knmaki/dax-context-transition-example/blob/main/context%20transition.pbix)からダウンロードしてね。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/239172/a628308b-1606-0704-3211-63a5789ea7cb.png)

![モデル ビュー_trimed.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/239172/1964f224-a576-63c6-49b2-baf109d9b86d.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


``日付``テーブル（本当は[ちゃんと作るべき](https://qiita.com/PowerBIxyz/items/924893764e6fdce30f92)だけど、今回は手抜き）

|日付|月|
|:-:|-:|
|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|


``商品``テーブル

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


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

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

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

```DAX
日別最大売上wrong = MAXX(
    VALUES('カレンダー'[日付]), 
    SUM('売上'[売上額])
)
```
良さそうなんだけど、これだと失敗。次のようになる:

![レポート ビュー_マトリックス1_wrong.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/239172/c74c91b4-a40b-85f6-2b01-cb272c24e1d8.png)


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

正しいメジャーは以下：

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

```DAX
売上額メジャー = SUM('売上'[売上額])
```

![レポート ビュー_マトリックス1_正解1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/239172/e38c37e9-7aba-5737-e614-dfbddee9ff1f.png)


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


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

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

![レポート ビュー_マトリックス1_正解2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/239172/17a19ce6-82a8-f315-0b29-8b216ecd76ca.png)


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

## コンテキスト トランジション
正しいやり方はわかったので、``CALCULATE``関数が何をしているのかを考えよう。

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

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

![レポート ビュー_マトリックス1_正解3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/239172/a3692315-60cd-39ac-157e-ee5549712695.png)


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

~~（なお、[トランザム](https://dic.pixiv.net/a/TRANS-AM)ではないため赤く光ったりしない。）~~

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

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

![レポート ビュー_マトリックス1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/239172/f21babf3-40d8-3723-d6d9-fb69f3ac4e2d.png)

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

なお、マトリックス ビジュアルの列見出しに``'商品'[商品名]``を追加しても、``日別最大売上1``メジャーはちゃんと計算してくれている模様:
![レポート ビュー_マトリックス2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/239172/9e63d99f-a33e-3f26-eeb6-c0e6faae8551.png)

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

# なぜ間違っていたのか

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

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

その答えは、``SUM``関数が``CALCULATE``関数で囲われていないため、行コンテキストが``SUM関数``に渡されずに計算されていたから。つまり、``売上``テーブルを日付でフィルターできていなかったということ（そもそも、行コンテキストはリレーションシップをフォローしないので、必要に応じて``RELATED``関数や``RELATEDTABLE``関数を使っていたことを[思い出そう](https://qiita.com/zaboom/items/55cf376f4f5fae3ac444#%E8%A1%8C%E3%82%B3%E3%83%B3%E3%83%86%E3%82%AD%E3%82%B9%E3%83%88%E3%81%AE%E3%83%AA%E3%83%AC%E3%83%BC%E3%82%B7%E3%83%A7%E3%83%B3%E9%96%93%E4%BC%9D%E6%92%AD)）。``SUM``関数にはその他のフィルター コンテキスト（今回は1月というフィルター）だけが渡された結果、イテレーターの各行の計算結果がすべて同じ値（＝1月の売上合計）になってしまい、``MAXX``関数が1月の売上合計を返したというわけ。

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

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

https://youtu.be/lMuDz6ViU1w

https://www.sqlbi.com/articles/understanding-context-transition-in-dax/

https://www.amazon.co.jp/dp/B09S2XYDXH/

https://www.udemy.com/course/daxadvanced/

https://qiita.com/PowerBIxyz/items/75c477dc27d0a3c3412e

https://qiita.com/sakaikosuke/items/e041f0e22e091e87f5e7#%E3%82%B3%E3%83%B3%E3%83%86%E3%82%AD%E3%82%B9%E3%83%88%E5%A4%89%E6%8F%9B-context-transition

https://youtu.be/TPfbQ8OFiLc