鷹尾祥(アキラ)のExcel BI ブログ

Excelの革新的機能であるパワーピボット/パワークエリ/DAXについて紹介します。

[Power BI] SVGで独自グラフを作ろう④:今年、前年、目標値を1つのメジャーで表現

SVGグラフの4回目で、棒グラフについては最終回となります。

目標

前回は実績に応じて長さが変わる棒グラフを完成しました。
今回は、それに前年実績の棒グラフと、今年の目標値のラインを追加して対前年対比、対目標対比を1つのメジャーで表現します。

サンプルファイル

GitHub - modernexcel7/shared at PBI_SVG

前年度グラフを追加する

前回までで階層に応じて最大値を調整する棒グラフが完成しました。ただし、このままでは条件付き書式の棒グラフと特に変わりません。そこでここからはSVGならではの自由さを使って機能を追加していきます。

作成した今年の実績の下に前年度の実績の棒グラフを追加してみましょう。

前年と今年を併記するにあたり、以下の点にご注意ください。

  • 共有されるものについてVAR変数の名前を変更しています。
  • 紛らわしくなるので前回最後に紹介した最大値の色指定の処理は廃止しています。
  • 前年の棒グラフは今年の棒グラフの真下にy=25の開始座標で追加します。
  • 長さの最大値については、前年と今年の最大値をMAX関数で比較して大きい方を採用します。2つのスカラー値を比較するときはMAX関数を使用します。(MAXX関数ではありません。)
対前年実績比図 4-1 = 

// 今年の実績
VAR s_CY_Act_Year_Max = MAXX(ALL('G_カレンダー'[会計年度])  , [売上])
VAR s_CY_Act_Qtr_Max  = MAXX(ALL('G_カレンダー'[会計四半期]), [売上])
VAR s_CY_Act_All      = [売上]

// 前年の実績
VAR s_PY_Act_Year_Max = MAXX(ALL('G_カレンダー'[会計年度])  , [売上(前年)])
VAR s_PY_Act_Qtr_Max  = MAXX(ALL('G_カレンダー'[会計四半期]), [売上(前年)])
VAR s_PY_Act_All      = [売上(前年)]

// 最大値の取得
VAR s_Act_Max
= SWITCH(
        TRUE(),
        ISINSCOPE('G_カレンダー'[会計四半期]), MAX(s_CY_Act_Qtr_Max , s_PY_Act_Qtr_Max) ,
        ISINSCOPE('G_カレンダー'[会計年度])  , MAX(s_CY_Act_Year_Max, s_PY_Act_Year_Max),
        MAX(s_CY_Act_All, s_PY_Act_All)
    )

// バーの長さの定義
VAR s_CY_Act = DIVIDE(s_CY_Act_All, s_Act_Max) * 100
VAR s_PY_Act = DIVIDE(s_PY_Act_All, s_Act_Max) * 100

// カラーの設定
VAR s_CY_Bar_Color 
= SWITCH(
        TRUE(),
        ISINSCOPE('G_カレンダー'[会計四半期]), "#B7D0C8",
        ISINSCOPE('G_カレンダー'[会計年度])  , "#6FA191",
        "#447B69"
    )

VAR s_PY_Bar_Color 
= SWITCH(
        TRUE(),
        ISINSCOPE('G_カレンダー'[会計四半期]), "#E3A193",
        ISINSCOPE('G_カレンダー'[会計年度])  , "#C96A55",
        "#AD4226"
    )

// SVGの定義
VAR s_SVG = 
"data:image/svg+xml;utf8,
<svg xmlns='http://www.w3.org/2000/svg'>
<rect x='5' y='5'  width='" & s_CY_Act & "' height='15' style='fill:" & s_CY_Bar_Color & ";'/>
<rect x='5' y='25' width='" & s_PY_Act & "' height='15' style='fill:" & s_PY_Bar_Color & ";'/>
</svg>"

RETURN s_SVG

画像エリアの下部が隠れないようにマトリクス・ビジュアルをクリックして「画像のサイズ」の高さを「105」まで広げておきます。

こんな感じに仕上がりました。ここまでくると標準機能にはないレイアウトなので、達成感が出てきます。

マトリクス・ビジュアルの列に「商品カテゴリー」を置き、値を「対前年実績比図
」メジャーだけにすると、以下のような感じです。最大値の算出はそれぞれの商品カテゴリー内での最大値に調整されています。


目標値をラインで表す

最後に目標値も縦線で追加してみましょう。

目標値のメジャーは以下のようになります。

目標 = SUMX('F_目標値', 'F_目標値'[目標値])

データモデルやテーブルの説明は省略しますが、詳細を知りたい方は私の本の『Excel パワーピボット』か『Excel パワーピボット DAX編』をご覧ください。

今回はSVGのrectではなく、lineを使ってみます。
s_Act_Maxの条件については今度は売上、売上(前年)、目標の三つのうちの最大を求めるので、MAX関数の入れ子になっています。
s_Planでlineの位置を指定しますが、rectの方はx座標は5から始まるので、ここでも5を足して調整しています。

対前年実績比図 4-2 = 

// 今年の実績
VAR s_CY_Act_Year_Max = MAXX(ALL('G_カレンダー'[会計年度])  , [売上])
VAR s_CY_Act_Qtr_Max  = MAXX(ALL('G_カレンダー'[会計四半期]), [売上])
VAR s_CY_Act_All = [売上]

// 前年の実績
VAR s_PY_Act_Year_Max = MAXX(ALL('G_カレンダー'[会計年度])  , [売上(前年)])
VAR s_PY_Act_Qtr_Max  = MAXX(ALL('G_カレンダー'[会計四半期]), [売上(前年)])
VAR s_PY_Act_All = [売上(前年)]

// 目標値
VAR s_Plan_Act_Year_Max = MAXX(ALL('G_カレンダー'[会計年度])  , [目標])
VAR s_Plan_Act_Qtr_Max  = MAXX(ALL('G_カレンダー'[会計四半期]), [目標])
VAR s_Plan_Act_All = [目標]

// 最大値の取得
VAR s_Act_Max
= SWITCH(
        TRUE(),
        ISINSCOPE('G_カレンダー'[会計四半期]), 
            MAX(MAX(s_CY_Act_Qtr_Max, s_PY_Act_Qtr_Max), s_Plan_Act_Qtr_Max),
        ISINSCOPE('G_カレンダー'[会計年度]),
            MAX(MAX(s_CY_Act_Year_Max, s_PY_Act_Year_Max), s_Plan_Act_Year_Max),
        MAX( MAX(s_CY_Act_All, s_PY_Act_All), s_Plan_Act_All)
    )

// バーの長さの定義
VAR s_CY_Act = DIVIDE(s_CY_Act_All  , s_Act_Max) * 100
VAR s_PY_Act = DIVIDE(s_PY_Act_All  , s_Act_Max) * 100
VAR s_Plan   = DIVIDE(s_Plan_Act_All, s_Act_Max) * 100 + 5

// カラーの設定
VAR s_CY_Bar_Color 
= SWITCH(
        TRUE(),
        ISINSCOPE('G_カレンダー'[会計四半期]), "#B7D0C8",
        ISINSCOPE('G_カレンダー'[会計年度])  , "#6FA191",
        "#447B69"
    )

VAR s_PY_Bar_Color 
= SWITCH(
        TRUE(),
        ISINSCOPE('G_カレンダー'[会計四半期]), "#E3A193",
        ISINSCOPE('G_カレンダー'[会計年度])  , "#C96A55",
        "#AD4226"
    )

// SVGの定義
VAR s_SVG = 
"data:image/svg+xml;utf8,
<svg xmlns='http://www.w3.org/2000/svg'>
<rect x='5' y='5'  width='" & s_CY_Act & "' height='15' style='fill:" & s_CY_Bar_Color & ";'/>
<rect x='5' y='25' width='" & s_PY_Act & "' height='15' style='fill:" & s_PY_Bar_Color & ";'/>
<line x1='" & s_Plan &"' y1='5' x2='"& s_Plan &"' y2='40' stroke='#DFBC2D' stroke-width='2' />

</svg>"

RETURN s_SVG

以下のように目標値が黄色の縦線で表現されました。

ほぼ完成に近いですが、よく見ると目標のない2023年にも開始位置に縦線が入っています。また、列の合計でも2023年と2024年の合計に対して、2024年の目標値を置いた縦線が入っています。
これらはミスリーディングなので非表示にします。

SVGコンポーネントの表示、非表示はテキストで扱えばいいので、Planのlineの部分を独立させてVAR変数のテキストとして定義します。こちらで条件を満たさないときに
“”にしてしまえばよく、その条件は①その年に目標値が入っており、かつ②会計年度のフィルターが存在しているということですので、以下のようになります。

対前年実績比図 = 

// 今年の実績
VAR s_CY_Act_Year_Max = MAXX(ALL('G_カレンダー'[会計年度])  , [売上])
VAR s_CY_Act_Qtr_Max  = MAXX(ALL('G_カレンダー'[会計四半期]), [売上])
VAR s_CY_Act_All = [売上]

// 前年の実績
VAR s_PY_Act_Year_Max = MAXX(ALL('G_カレンダー'[会計年度])  , [売上(前年)])
VAR s_PY_Act_Qtr_Max  = MAXX(ALL('G_カレンダー'[会計四半期]), [売上(前年)])
VAR s_PY_Act_All = [売上(前年)]

// 目標値
VAR s_Plan_Act_Year_Max = MAXX(ALL('G_カレンダー'[会計年度]) , [目標])
VAR s_Plan_Act_Qtr_Max  = MAXX(ALL('G_カレンダー'[会計四半期]), [目標])
VAR s_Plan_Act_All = [目標]

// 最大値の取得
VAR s_Act_Max
= 
    SWITCH(
        TRUE(),
        ISINSCOPE('G_カレンダー'[会計四半期]), 
            MAX(MAX(s_CY_Act_Qtr_Max, s_PY_Act_Qtr_Max), s_Plan_Act_Qtr_Max),
        ISINSCOPE('G_カレンダー'[会計年度]),
            MAX(MAX(s_CY_Act_Year_Max, s_PY_Act_Year_Max), s_Plan_Act_Year_Max),
        MAX( MAX(s_CY_Act_All, s_PY_Act_All), s_Plan_Act_All)
    )

// バーの長さの定義
VAR s_CY_Act = DIVIDE(s_CY_Act_All  , s_Act_Max) * 100
VAR s_PY_Act = DIVIDE(s_PY_Act_All  , s_Act_Max) * 100
VAR s_Plan   = DIVIDE(s_Plan_Act_All, s_Act_Max) * 100 + 5

VAR s_SVG_Plan = 
IF(
    s_Plan_Act_All > 0 && ISINSCOPE('G_カレンダー'[会計年度]),
    "<line x1='" & s_Plan &"' y1='5' x2='"& s_Plan &"' y2='40' stroke='#DFBC2D' stroke-width='2' />",
    ""
)
// カラーの設定
VAR s_CY_Bar_Color 
= 
    SWITCH(
        TRUE(),
        ISINSCOPE('G_カレンダー'[会計四半期]), "#B7D0C8",
        ISINSCOPE('G_カレンダー'[会計年度])  , "#6FA191",
        "#447B69"
    )

VAR s_PY_Bar_Color 
= 
    SWITCH(
        TRUE(),
        ISINSCOPE('G_カレンダー'[会計四半期]), "#E3A193",
        ISINSCOPE('G_カレンダー'[会計年度])  , "#C96A55",
        "#AD4226"
    )

// SVGの定義
VAR s_SVG = 
"data:image/svg+xml;utf8,
<svg xmlns='http://www.w3.org/2000/svg'>
<rect x='5' y='5'  width='" & s_CY_Act & "' height='15' style='fill:" & s_CY_Bar_Color & ";'/>
<rect x='5' y='25' width='" & s_PY_Act & "' height='15' style='fill:" & s_PY_Bar_Color & ";'/>
" & s_SVG_Plan & "
</svg>"

RETURN s_SVG

これで完成です。


まとめ

以上、Power BIのマトリクス・ビジュアルにSVGを組み込んで独自グラフを作っていました。

SVGを工夫すれば、円グラフやゲージを使ったり、アニメーションを組み込んだり、テキストを表示させることもできます。やってみたい応用例があれば、MicrosoftのCopilot先生に聞いてSVGのサンプルを書いてもらい、それをDAXに組み込むのもよいでしょう。いろいろ試してみて、分かりやすいレポートを作ってください!

[Power BI] SVGで独自グラフを作ろう③:最大値を自動調整して棒グラフを完成

SVGグラフの3回目です。

目標

前回はメジャーの値を参照して、その値に応じて長さが変化する棒グラフをSVGで作成しました。
今回は最大値をハードコーディングからその時の最大値に応じて調整します。

サンプルファイル

GitHub - modernexcel7/shared at PBI_SVG

最大値のハードコーディングがいけない理由

最初になぜ最大値のハードコーディングがいけないかを確認しましょう。
スライサーを何も設定していない状態では、うまくいっている感じがします。

ところが、何かスライサーをかけるとすべての棒グラフが短くなってしまい、各四半期の比較が難しくなります。これはグラフの長さがハードコーディングされた最大値を基準とした割合で導出されるためです。

これを回避するためには、その時々の絞り込み条件がかけられた状態での「最大値」を動的に取得して適用する必要があります。
最大値を取得するには、「○○」ごとのという集計単位を意識します。また、このマトリクス・ビジュアルでは、第1階層に年度、第2階層に四半期がセットされているので、それぞれの階層で異なる最大値を取得する必要があります。

最大値を動的に取得する

一歩ずつ進めていきましょう。

まずは年度レベルの最大値を取得します。「①各年度ごとの集計値を計算し、②その中から最大値を取得する」にはMAXX関数を使います。その時、マトリクス・ビジュアルの年度のフィルターを解除する必要があるので、年度ごとの繰り返しを行うときにはVALUES関数の代わりにALL関数を併用します。

年度ごとMAX値 = MAXX(ALL('G_カレンダー'[会計年度]), [売上])

同様に四半期ごとのMAX値も計算します。

四半期ごとMAX値 = MAXX(ALL('G_カレンダー'[会計四半期]), [売上])

それぞれの階層の最大値を取得できました。列の総計については年度ごとではなく、総合計を表示するのが正しいのですがそちらは後回しにします。

ここまで来たら元のメジャーに戻って変更します。
階層を意識する必要がありますが、まずは年度からやっつけていきましょう。以下s_CY_Act_Year_Maxという変数を用意し、それをMAX値に当てています。

対前年実績比図 3-1 = 

// メジャー値の取得
VAR s_CY_Act_Year_Max = MAXX(ALL('G_カレンダー'[会計年度]), [売上])
VAR s_CY_Act_Max = s_CY_Act_Year_Max
VAR s_CY_Act = DIVIDE([売上], s_CY_Act_Max) * 100

// SVGの定義
VAR s_SVG = 
"data:image/svg+xml;utf8,
<svg xmlns='http://www.w3.org/2000/svg'>
<rect x='5' y='5' width='" & s_CY_Act & "' height='15' style='fill:#4B8975;'/>
</svg>"

RETURN s_SVG

会計年度の階層ではスライサーの値に応じてグラフの長さが変化するようになりました。

列の総計行は各年度の最大値より常に大きいのでグラフが最大値を振り切っていますが、こちらも後で対応します。

続いて四半期の階層でのMAX値を追加します。
今回はs_CY_Act_Qtr_Maxを追加しますが、MAX値は階層による条件判定は入れずにいったん四半期の最大値に設定して動作を確認します。

対前年実績比図 3-2 = 

// メジャー値の取得
VAR s_CY_Act_Year_Max = MAXX(ALL('G_カレンダー'[会計年度]) , [売上])
VAR s_CY_Act_Qtr_Max  = MAXX(ALL('G_カレンダー'[会計四半期]), [売上])
VAR s_CY_Act_Max = s_CY_Act_Qtr_Max
VAR s_CY_Act = DIVIDE([売上], s_CY_Act_Max) * 100

// SVGの定義
VAR s_SVG = 
"data:image/svg+xml;utf8,
<svg xmlns='http://www.w3.org/2000/svg'>
<rect x='5' y='5' width='" & s_CY_Act & "' height='15' style='fill:#4B8975;'/>
</svg>"

RETURN s_SVG

会計四半期単位で条件に応じた最大値を反映するようになりました。いい感じです。
ただし、階層による条件判定を入れていないので会計年度のレイヤーではグラフは振り切っています。


マトリクスの階層ごとに最大値を調整する

最後に階層判定を追加します。階層判定を行うには、SWITCH関数ISINSCOPE関数を組み合わせます。

まず、独立したメジャーで動作を確認しましょう。マトリクス・ビジュアルでの階層判定を行うには、総計を除き下の階層から判定を行っていくのが鉄則なので、深い階層から判定していきます。

階層判定付き最大値 = 
SWITCH(
    TRUE(),
    ISINSCOPE('G_カレンダー'[会計四半期]), [四半期ごとMAX値],
    ISINSCOPE('G_カレンダー'[会計年度])  , [年度ごとMAX値],
    [売上]
)

こちらもきれいに階層ごとの最大値を取得できました。

ここまで出来たので、本体のグラフのメジャーに組み込んでみましょう。列の総計も条件に組み込んでいます。

対前年実績比図 3-3 = 

// メジャー値の取得
VAR s_CY_Act_Year_Max = MAXX(ALL('G_カレンダー'[会計年度]) , [売上])
VAR s_CY_Act_Qtr_Max  = MAXX(ALL('G_カレンダー'[会計四半期]), [売上])
VAR s_CY_Act_All = [売上]
VAR s_CY_Act_Max_Select
= SWITCH(
        TRUE(),
        ISINSCOPE('G_カレンダー'[会計四半期]), s_CY_Act_Qtr_Max,
        ISINSCOPE('G_カレンダー'[会計年度])  , s_CY_Act_Year_Max,
        s_CY_Act_All
    )
VAR s_CY_Act_Max = s_CY_Act_Max_Select
VAR s_CY_Act = DIVIDE(s_CY_Act_All, s_CY_Act_Max) * 100

// SVGの定義
VAR s_SVG = 
"data:image/svg+xml;utf8,
<svg xmlns='http://www.w3.org/2000/svg'>
<rect x='5' y='5' width='" & s_CY_Act & "' height='15' style='fill:#93B8AC;'/>
</svg>"

RETURN s_SVG

それぞれの階層で階層内の最大値に基づいてグラフの長さを調節できました。


階層ごとにグラフの色を変える

ところで、グラフの長さを調節できたのはよいものの、会計年度と四半期で同じ色のグラフであるため、見ていてどちらの階層か混乱します。こちらも同じように階層に応じて色を変えてみましょう。要領は先ほどの最大値と同じですが、今回はカラーコードを変更してSVGに埋め込みます。
今回はs_CY_Bar_Colorを追加し、これをSVGfillに設定しました。こうすることでメジャーの中で動的に色をコントロールできます。

対前年実績比図 3-4 = 

// メジャー値の取得
VAR s_CY_Act_Year_Max = MAXX(ALL('G_カレンダー'[会計年度])  , [売上])
VAR s_CY_Act_Qtr_Max  = MAXX(ALL('G_カレンダー'[会計四半期]), [売上])
VAR s_CY_Act_All = [売上]
VAR s_CY_Act_Max_Select
= SWITCH(
        TRUE(),
        ISINSCOPE('G_カレンダー'[会計四半期]), s_CY_Act_Qtr_Max ,
        ISINSCOPE('G_カレンダー'[会計年度])  , s_CY_Act_Year_Max,
        s_CY_Act_All
    )
VAR s_CY_Act_Max = s_CY_Act_Max_Select
VAR s_CY_Act = DIVIDE(s_CY_Act_All, s_CY_Act_Max) * 100

VAR s_CY_Bar_Color 
= SWITCH(
        TRUE(),
        ISINSCOPE('G_カレンダー'[会計四半期]), "#B7D0C8",
        ISINSCOPE('G_カレンダー'[会計年度])  , "#6FA191",
        "#447B69"
    )

// SVGの定義
VAR s_SVG = 
"data:image/svg+xml;utf8,
<svg xmlns='http://www.w3.org/2000/svg'>
<rect x='5' y='5' width='" & s_CY_Act & "' height='15' style='fill:" & s_CY_Bar_Color & ";'/>
</svg>"

RETURN s_SVG

このように階層ごとに色味を変えることができました。


最大値のグラフの色を変えてハイライトする

さらに応用で、各四半期内で最大値のグラフの色だけ変えるには以下のような式になります。今回はs_CY_Bar_Color_Maxを追加して、四半期の階層かつ最大値の場合に黄色に設定しています。

対前年実績比図 3-5 = 

// メジャー値の取得
VAR s_CY_Act_Year_Max = MAXX(ALL('G_カレンダー'[会計年度])  , [売上])
VAR s_CY_Act_Qtr_Max  = MAXX(ALL('G_カレンダー'[会計四半期]), [売上])
VAR s_CY_Act_All = [売上]
VAR s_CY_Act_Max_Select
= SWITCH(
        TRUE(),
        ISINSCOPE('G_カレンダー'[会計四半期]), s_CY_Act_Qtr_Max ,
        ISINSCOPE('G_カレンダー'[会計年度])  , s_CY_Act_Year_Max,
        s_CY_Act_All
    )
VAR s_CY_Act_Max = s_CY_Act_Max_Select
VAR s_CY_Act = DIVIDE(s_CY_Act_All, s_CY_Act_Max) * 100

VAR s_CY_Bar_Color 
= SWITCH(
        TRUE(),
        ISINSCOPE('G_カレンダー'[会計四半期]), "#B7D0C8",
        ISINSCOPE('G_カレンダー'[会計年度])  , "#6FA191",
        "#447B69"
    )
VAR s_CY_Bar_Color_Max 
= IF(s_CY_Act_All = s_CY_Act_Max && ISINSCOPE('G_カレンダー'[会計四半期]), 
     "#DFBC2D", s_CY_Bar_Color)

// SVGの定義
VAR s_SVG = 
"data:image/svg+xml;utf8,
<svg xmlns='http://www.w3.org/2000/svg'>
<rect x='5' y='5' width='" & s_CY_Act & "' height='15' style='fill:" & s_CY_Bar_Color_Max & ";'/>
</svg>"

RETURN s_SVG

最大の四半期の色が黄色になりました。

このようにSVGの仕組みとDAXを理解すれば、DAXで用意した値をSVGに送るだけで自由にグラフを描画することができます。

次回は前年度実績も追加して同じグラフ内で比較できるようにしましょう。

[Power BI] SVGで独自グラフを作ろう②:メジャーの値で変化する棒グラフ

SVGグラフの2回目です。

目標

前回の記事でPower BIのマトリクス・ビジュアルに静的なSVGチャートを描きました。
今回は実際に変化するメジャーの値に応じて長さが変化する棒グラフを作ります。

サンプルファイル

GitHub - modernexcel7/shared at PBI_SVG

技術的なポイント

バーチャートを作るにあたっては、以下の2点がポイントです。

  1. 動的な値をSVGのパラメーターとして渡す
  2. 値の最大値を取得してそれを横軸の基準にした相対的な長さに調整する

ベース・メジャーを作る

まずは値を取得するための簡単なメジャーを作ります。この値がそれぞれチャートの値となります。

売上 = SUMX('F_売上明細','F_売上明細'[販売価格] * 'F_売上明細'[販売数量] ) 
売上(前年) = CALCULATE([売上], SAMEPERIODLASTYEAR('G_カレンダー'[日付])) 

マトリクス・ビジュアルのレイアウトとスライサーはとりあえず以下のようにします。

  • 会計年度
  • 会計四半期

  • 売上
  • 売上(前年)

SVGで棒グラフを作る

それではSVGによる棒グラフの作成に移ります。

ひな形を作る

とりあえずひな形を作ります。以下のメジャーを新規に追加してください。前回作成したSVGをVARで定義し直して、RETURNで返す形にしただけです。

対前年実績比図 2-1 = 
// SVGの定義
VAR s_SVG = 
"data:image/svg+xml;utf8,
<svg xmlns='http://www.w3.org/2000/svg'>
<rect x='5' y='5' width='50' height='15' style='fill:#4B8975;'/>
</svg>"

RETURN s_SVG

追加したら、こちらも前回と同じく「対前年実績比図」メジャーを選択した状態でメジャーツール > データカテゴリ > 画像のURLにします。

これでSVGが画像として表示されました。


SVGの長方形のwidthをパラメーターに差し替える

ステップバイステップで1つずつ理解しながら進みましょう。先ほどのメジャーを以下のように修正します。修正箇所は「width」に渡す値をVARで外で定義して、SVGの該当箇所に差し替えただけです。

対前年実績比図 2-2 = 

// メジャー値の取得
VAR s_CY_Act = 50

// SVGの定義
VAR s_SVG = 
"data:image/svg+xml;utf8,
<svg xmlns='http://www.w3.org/2000/svg'>
<rect x='5' y='5' width='" & s_CY_Act & "' height='15' style='fill:#4B8975;'/>
</svg>"

RETURN s_SVG

無事、同じチャートが描画できることを確認してください。
これで、SVGにパラメーターを動的に渡す方法がわかりました。数字を別途取得して、それを文字列としてのSVGに差し替えればよいだけです。これは色についても同じで、「#4B8975」についてもVARを使用して同様の差し替えが可能です。

widthに売上の値を代入する

今度は「売上」メジャーの値に応じて変化するグラフにします。S_CY_Actを[売上]メジャーにしただけです。

対前年実績比図 2-3 = 

// メジャー値の取得
VAR s_CY_Act = [売上]

// SVGの定義
VAR s_SVG = 
"data:image/svg+xml;utf8,
<svg xmlns='http://www.w3.org/2000/svg'>
<rect x='5' y='5' width='" & s_CY_Act & "' height='15' style='fill:#4B8975;'/>
</svg>"

RETURN s_SVG

結果が変わりませんね。

実はこれは既定の画像イメージのサイズを超過しているためです。画像のサイズを変更するには、マトリクス・ビジュアルをクリックし、視覚化 > ビジュアル > 画像のサイズを変更します。こちらで「幅(px)」の値を「110」に変更します。

「rect」の「x」の値が5です。これは左側からの開始位置の座標で、棒グラフの最大長を100pxとすると左から合計105pxになります。これに余裕を持たせて110pxとしています。

すると、以下のようにグラフが長くなりました。

ただし、すべての行で同じ長さであるため、売上の値を反映していないように見えます。これは例えば、2024年でいえば売上が「74,013,780」であるためwidthの値が「画像のサイズ」の最大長110を振り切っているためです。実際にはxは5から始まっているので、105を振り切ると見切れてしまいます。

このため、最大値を基準とした相対的なwidthの調整が必要となります。今回はいったん「74,013,780」を100 px となるように調整してみます。メジャーを以下のように書き換えます。

対前年実績比図 2-4 = 

// メジャー値の取得
VAR s_CY_Act_Max = 74013780
VAR s_CY_Act = DIVIDE([売上], s_CY_Act_Max) * 100

// SVGの定義
VAR s_SVG = 
"data:image/svg+xml;utf8,
<svg xmlns='http://www.w3.org/2000/svg'>
<rect x='5' y='5' width='" & s_CY_Act & "' height='15' style='fill:#4B8975;'/>
</svg>"

RETURN s_SVG

これで「売上」の値に応じて長さが変わる棒グラフが準備できました。

ただし、今回は最大値をハードコーディングしています。
ここは本来はMAXX関数で行って、集計単位ごとに動的に都度最大値を取得するのが正解です。動的な最大値の取得と対前年比較は次回の記事で紹介します。

[Power BI] SVGで独自グラフを作ろう①:SVGを描いてみる

SVGとは?

Power BIには様々なビジュアルが用意されており、またカスタムビジュアルとして世の中に公開されているチャートを使うことができます。しかし、それでも痒いところに手が届かず自分でフレキシブルにグラフを作れたらと思うこともあります。

そんなときにはSVGを使ってカスタム・グラフを作ることができます。SVGとはScalable Vector Graphicsの略で、画像を描画するためのXML形式の定義です。このSVGにより様々な図形を作成できます。

このSVGは、マトリクス・ビジュアルとテーブル・ビジュアルの中の1つのメジャーとして使うことができます。こちらを使ってより直感的にわかりやすいレポートを作ってみるのはどうでしょう?

今回は第1回としてSVGの作成とPower BIでの表示を紹介します。第2回以降では実際の数値メジャーと組み合わせて動的にグラフを描画します。

参考

Power BIでSVGを埋め込むにあたり、以下2つのサイトを参考にさせていただきました。

皆さん御存知のSQLBIさんのサイトです。こちらのKurtさんの記事でSVGでチャートを作るテクニックを知りました。
www.sqlbi.com

Zero Sysさんの記事です。2021年ということでかなり早い時期からSVGが取り上げられていました。InkscapeやVidual Studio Codeの使い方も紹介してあるので参考にされるとよいでしょう。
qiita.com


SVGについて包括的に扱った書籍です。SVGを本格的に使用したい方は是非参考にするとよいでしょう。
www.oreilly.co.jp

目標

今回のは①とりあえずSVGを書いてみる。②Power BIで表示するのを目指します。

サンプルファイル

GitHub - modernexcel7/shared at PBI_SVG

とりあえずSVGを書いてみる

まずはSVGを書いてみましょう。
メモ帳を開き、以下のコードをコピペして拡張子を「html」にしてブラウザで開きます。

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>SVG表示</title>
</head>
<body>
    <svg xmlns="http://www.w3.org/2000/svg">
        <rect x="5" y="5" width="50" height="15" style="fill: #4B8975;"/>
    </svg>
</body>
</html>

そうすると以下のように横長の長方形が表示されます。

svgのタグで囲まれた部分で図形を定義しています。
今回は1つの長方形だけですが、複数の図形を描画することも可能です。それぞれの設定値は以下のようになっています。

‐ rect: 長方形を定義します。同様に円や楕円、もろもろ定義が可能です。

  • x: 横座標の開始位置
  • y: 縦座標の開始位置
  • width: 幅
  • height: 高さ
  • style: 色などの属性です。今回は色だけ定義しています。

Power BIのメジャーでSVGチャートを書いてみる

とりあえずSVGで長方形をを表示することはできました。今度はPower BIで同じものを表示します。
基本的にSVGタグの中身を文字列としてPower BIのメジャーとして定義すればよいのですが、いくつかポイントがあります。

メジャーの書き方

新しくメジャーを作成し、SVGのタグを部分そのままコピペして、先頭に「data:image/svg+xml;utf8,」をつけて前後を「"」で囲みます。すると以下のようにエラーになってしまいます。

これはタグ中に「"」が使われているため、文字列が中途半端になっていることが原因です。なので、タグ中の「"」は以下のように「""」として連続させることでエスケープが可能です。


Rectangle Escape = 
"data:image/svg+xml;utf8,
<svg xmlns=""http://www.w3.org/2000/svg"">
<rect x=""5"" y=""5"" width=""50"" height=""15"" style=""fill: #4B8975;""/>
</svg>"

こちらでひとまずうまくいったのですが、「""」を書いていくのは面倒くさいし、メジャーの式が複雑になります。そういうときはタグ中の「""」を「'」で置き換えることが可能です。これで「"」は全体の最初と最後だけになり、かなりすっきりしました。


Rectangle = 
"data:image/svg+xml;utf8,
<svg xmlns='http://www.w3.org/2000/svg'>
<rect x='5' y='5' width='50' height='15' style='fill:#4B8975;'/>
</svg>"

データカテゴリを「画像のURL」に

メジャーができたので、マトリクスビジュアル、またはテーブルビジュアルの「値」に設定してみましょう。
すると、以下のようにタグの中身のテキストがそのまま表示されます。

これを画像として定義するため、メジャーをクリックし、

メジャーツールの中でデータカテゴリを「画像のURL」にします。

これでビジュアルのセルの中にSVGで定義した画像を埋め込むことができました。

次回は値に応じて変化するグラフを作ります。

今回はスタティック=静的な画像を表示するところまでです。
次回はこれをベースに、売上、前年売上といった値を受け取ってグラフをダイナミックに描写します。

[Power BI] JSONテーマに画像を埋め込む

今回はPower BIについての記事です。

Power BI ではビジュアルの色使いやスライサーの振る舞いなどの初期設定JSONのテーマとして外部ファイルとして保存することができます。また、それらを取り込むことでその他のPBIXにも同一のテーマを適用することが可能です。テーマをきちんと管理・運用することでチーム作業を大きく効率化できます。

このJSONテーマに関して、実は「キャンバスの背景」となる画像ファイルもテーマの中に組み込むことが可能です。会社のロゴやテーマなどをイメージとして作成し、テーマに組み込んでおくテーマとしての統一性が増し、効率的にテーマを管理することができます。

目標

テーマファイルに「キャンバスの背景」の画像データを埋め込み、PBIXにテーマとして取り込む。

技術的なポイント

  • JSONテーマファイルの構成の理解
  • 画像データのコード化

サンプルファイル

以下、GitHubリポジトリJSONテーマと取り込んだPBIXファイルを公開しています。

github.com

こちらをダウンロードして解凍して使用します。
サンプル画像は拙著の広告です。Power BIにも使える内容なのでよろしくお願いします!


手順

JSONテーマと取り込み

上記サンプルファイルの「Canvas_background.json」の中身を確認します。

Visual Styles > page > background > image > url 以下がイメージの実体です。
ここではpng, base64形式で指定したあと、エンコードされた画像のバイナリが続きます。

このファイルの取り込むには「表示」タブの以下の部分をクリックし・・・

「テーマを参照」を選び・・・

テーマファイルを指定します。

テーマが正常に取り込まれると、以下のように「キャンバスの背景」が更新されます。

テーマファイルの用意と画像の埋め込み

テーマファイルは手作りでもよいですが、「現在のテーマのカスタマイズ」で少し編集した後、「現在のテーマを保存」でひな形を作るのもよいでしょう。

なお、デフォルトの潮汐テーマでも画像が最初から埋め込まれているのでこちらを参考にするのもよいでしょう。ただし、「キャンバスの背景」ではなく、その後ろの「壁紙」の設定なので注意してください。こちらでは「キャンバスの背景」の透過性を100%にすることで、壁紙がすべて見えるようにしています。

エクスポート直後は中身がすべて1ラインでとても使いにくいので、WebのJSONフォーマッターのサイトやVisual Studio Codeなどで階層を見やすくコンバートしておくとよいでしょう。

次に背景のサイズに合わせた元画像をpng形式などで用意します。
PBIのデフォルトの「キャンバスの設定」は1280 x 720 ピクセルなので、このサイズで用意しておくとよいでしょう。

画像を用意したらこちらもWebのコンバーターで変換します。私は以下のサイトを使わせていただきました。

base64.guru

エンコードされたら内容のを前述したJSONの階層追加して完成です。コピペするときは、階層の位置とエンコードの最初と最後の「"」に注意しましょう。

新刊のご案内:Excelパワーピボット 第2版 7つのステップでデータ集計・分析を「自動化」する本

新刊のご案内となります。

大好評でした『Excelパワーピボット 7つのステップでデータ集計・分析を「自動化」する本』の第2版が発売されます。

第2版では、既存のシナリオをブラッシュアップしただけでなく、第3部応用編を追加してPowerShellExcel BIの連携を紹介します。これでExcel BIによる自動化が完成します。

【第2版の変更点】

Excel BI:基本の7つのステップの紹介
  • 既存のシナリオをより分かりやすくブラッシュアップ
  • ドリルスルー:レポートから明細を取得する
  • カラーページの追加
第3部:応用編の追加
パワーシェルで自動更新
  • パワーシェルによるExcelファイルの自動更新とバックアップ
  • 更新リストを使った連続ファイル更新
  • スライサーを切り替えてPDFファイルを連続発行
  • タスクスケジューラーに登録して決まった時間に更新

www.youtube.comyoutu.be

PowerQuery 目次

Power Queryについて  

modernexcel7.hatenablog.com

データソース(Extract)

データソースからの取り込み

 

modernexcel7.hatenablog.com

 

 Webから

modernexcel7.hatenablog.com

  

フォルダーから

 

modernexcel7.hatenablog.com

 

 

modernexcel7.hatenablog.com

 

クエリのマージ

 modernexcel7.hatenablog.com

 

 

modernexcel7.hatenablog.com

 

 

modernexcel7.hatenablog.com

 

 

加工(Transform)

テキスト変換

 T.B.D.

 

 

日付関数

 

 

modernexcel7.hatenablog.com

 

 

modernexcel7.hatenablog.com

 

 

modernexcel7.hatenablog.com

 

modernexcel7.hatenablog.com

 

フィル

 

modernexcel7.hatenablog.com

 

ピボット解除

 

modernexcel7.hatenablog.com

 

 

modernexcel7.hatenablog.com

 

Power Query以外のテクニック

 

modernexcel7.hatenablog.com

 

  

インタラクティブ相関テーブル

インタラクティブ相関テーブル

f:id:modernexcel7:20210529131046p:plain

目標

今回のテーマはスライサーを使って条件をダイナミックに変えられる相関係数テーブルです。

ある二つの変数のペアに関連がある場合、分析ツールExcel関数相関係数を求め、その両者の関連の度合いを数値化することができます。

通常、分析ツールExcel関数では新しい組み合わせで相関係数を求めるとき、一回一回計算をやり直さないといけませんが、今回はピボットテーブルで自由に分析できるようにします。

技術的なポイント

  • 非アクティブなリレーションシップを使う
  • 縦軸と横軸の数値を取ってくるメジャーを作る
  • レイアウトはピボットや条件付き書式で工夫

実践

元のデータ

元のデータは以下のようなアンケートの回答を想定しています。

Name列は回答者を示すユニークなID、その他の緑色の部分は回答者の属性です。回答者の属性はのちほどスライサーをかけるときに使います。

水色の部分はそれぞれの質問に対する回答になります。
今回は「食べ物の好みに関連性が見られるか」を組み合わせごとに調べていきます。

f:id:modernexcel7:20210529130839p:plain

3つのテーブルを作る

元のデータから、パワークエリで以下三つのテーブルを作ります。
テーブルを作るときは、ワークシートテーブルに読み込む必要はないので読み込み先接続の作成のみこのデータをデータモデルに追加するにチェックを入れて作成するのが良いでしょう。

回答者一覧テーブル(T_Participant)

緑色の列のみを残したテーブルです。

このテーブルは二つの回答を接続し、スライサーで回答をまとめてフィルタリングするのに使用します。

f:id:modernexcel7:20210529131224p:plain

回答テーブル(T_Survey_X、T_Survey_Y)

回答者のIDと質問と回答をセットにしたテーブルです。

GendarAge列を削除し、Name列を選択して、その他の列のピボット解除を実行します。

属性列は質問に、値列は回答に列名を変更しましょう。
このテーブルを二つ作り、それぞれT_Survey_XT_Survey_Yという名前で取り込みます。
f:id:modernexcel7:20210529131156p:plain

リレーションシップを作る

T_ParticipantのName列とT_Survey_X、T_Survey_YのName列をリレーションシップでつなぎます。ただし、リレーションシップでつないだ後、それぞれ非アクティブ化をクリックしてリレーションシップを非アクティブにします。

f:id:modernexcel7:20210529131442p:plain

ダイアグラムビューを見ると以下のように非アクティブなリレーションシップが点線で表示されます。

f:id:modernexcel7:20210529131313p:plain

X軸とY軸の回答を取得する

ワークシート上でピボットテーブルをこのブックのデータモデルから作成するで作成し、列にT_Survey_Xの質問を、行に列にT_Survey_Yの質問を配置します。

f:id:modernexcel7:20210529132728p:plain

続いて以下のメジャーを作り、T_Survey_Xの回答を得られるようにします。

IF ( HASONEVALUE ( 'T_Survey_X'[回答] ), VALUES ( 'T_Survey_X'[回答] )」は集計せずにテーブルの値を拾ってくるときの書き方です。

また、非アクティブなリレーションシップを有効にするため、USERRELATIONSHIP関数を使用します。

f:id:modernexcel7:20210529132819p:plain

Value_X
=CALCULATE (
    IF ( HASONEVALUE ( 'T_Survey_X'[回答] ), VALUES ( 'T_Survey_X'[回答] ) ),
    USERELATIONSHIP ( T_Participant[Name], T_Survey_X[Name] )
)

メジャーが完成したらピボットテーブルの値にセットして列のT_Survey_Xの回答が表示されることを確認します。

そのまま実行すると値が何も表示されませんが、それは「 IF ( HASONEVALUE ( 'T_Survey_X'[回答] ), VALUES ( 'T_Survey_X'[回答] ) )」が値が複数ある場合にブランクにしているためです。

以下のようにT_Participantの「Name」列をスライサーにし、どれか一人の回答者を選択すると値が表示されます。

このとき、行のリレーションシップは非アクティブ状態なので、T_Survey_Xの同じ値が繰り返されます。
f:id:modernexcel7:20210529133210p:plain

同様にT_Survey_Yの回答を取得するメジャーを作成します。

Value_Y
=CALCULATE (
    IF ( HASONEVALUE ( 'T_Survey_Y'[回答] ), VALUES ( 'T_Survey_Y'[回答] ) ),
    USERELATIONSHIP ( T_Participant[Name], T_Survey_Y[Name] )
)

同様にピボットテーブルの値に配置すると、T_Survey_Yの回答が各行で等しく表示されます。

これより、XとYのそれぞれの回答の組み合わせを取得できました。ここまで来れば、あとは計算するだけなのでほぼできたも同然です。

f:id:modernexcel7:20210529133253p:plain

相関係数を求めるメジャーを作る

あとは計算で相関係数を求めるだけです。

たくさんありますが以下のメジャーを作成してください。
SUMXのイタレイターでT_Participant「Name」毎にそれぞの計算を行った後に合計しています。

Sum_X
=SUMX(VALUES(T_Participant[Name]), [Value_X])
Sum_Y
=SUMX(VALUES(T_Participant[Name]), [Value_Y])
Sum_X2
=SUMX(VALUES(T_Participant[Name]), POWER([Value_X], 2))
Sum_Y2
=SUMX(VALUES(T_Participant[Name]), POWER([Value_Y], 2))
Sum_XY
=SUMX (
    VALUES ( T_Participant[Name] ),
    [Value_X] * [Value_Y]
)
Count_Items
=DISTINCTCOUNT('T_Participant'[Name])
Pearson_Denominator_X
=([Count_Items] * [Sum_X2]) - POWER([Sum_X], 2)
Pearson_Denominator_Y
=([Count_Items] * [Sum_Y2]) - POWER([Sum_Y], 2)
Pearson_Numerator
=([Count_Items] * [Sum_XY]) - ([Sum_X] * [Sum_Y])
Pearson_Denominator
=SQRT([Pearson_Denominator_X] * [Pearson_Denominator_Y])
Pearson
=DIVIDE([Pearson_Numerator], [Pearson_Denominator])

最後に作成したPearsonのメジャーの身を値に入れて相関係数が計算されるか確認してください。
今回は、Nameのスライサーは解除し、代わりにGendarとAgeのスライサーを追加し、動作を確認します。

f:id:modernexcel7:20210529133729p:plain

応用

相関の強いもののみを表示する

ちなみに、相関強さによって値の表示・非表示をコントロールすることもできます。

以下のようなテーブルを作り、T_Strengthとしてデータモデルに読み込みます。

f:id:modernexcel7:20210529174839p:plain

パラメーターテーブルとして使うため、読み込んだ後はリレーションシップはつながずに孤立したテーブルにしておきます。

f:id:modernexcel7:20210529175112p:plain

後は以下の二つのメジャーを追加します。

Grade
=IF(HASONEVALUE('T_Strength'[Grade]), VALUES('T_Strength'[Grade]))
Pearson Selected
=
VAR v_Pearson =DIVIDE([Pearson_Numerator], [Pearson_Denominator])
VAR v_OverGrade = ABS(v_Pearson) >= [Grade]

RETURN 
IF( v_Pearson <> 1 && v_OverGrade , v_Pearson)

あとはスライサーを追加すれば、Strengthのスライサーを追加して出来上がりです。
f:id:modernexcel7:20210529175443p:plain

条件付き書式でハイライト

条件付き書式をメジャーにセットすることで相関の強さ・正負で表示を変えることも可能です。

f:id:modernexcel7:20210529175530p:plain

ピボットはレイアウトを変えられるので、行にXとYを並べたり、列に回答者の属性を並べたりすることで相関のある組み合わせを見つけやすくなります。

f:id:modernexcel7:20210529175701p:plain

参考

今回のテクニックは以下の二つの記事を自分なりに応用し、組み合わせたものとなります。

www.daxpatterns.com

xxlbi.com

PowerShellでExcelパワークエリを更新

f:id:modernexcel7:20210417191401p:plain

今回はWindows PowerShellを使って、Excelをバックグラウンドで開きパワークエリの更新を行います。バックグラウンド処理なので画面には表示されませんが、背後のプロセスでデータを更新します。

こちらのPowerShellスクリプトを元にタスクスケジューラPower Automate Desktopで定期的な自動更新を目指します。

PowerShellの起動と初期設定

PowerShellスクリプトを実行できるようにするための初期設定を行います。具体的には実行ポリシーを変更します。

実行ポリシーの変更

まずWindowsボタン隣の検索でPowerShellと入力します。

f:id:modernexcel7:20210417164051p:plain


Windows PowerShellが表示されたら、右クリックを押して管理者として実行を選択します。

f:id:modernexcel7:20210417165124p:plain


以下のPowerShellコンソールが表示されます。

f:id:modernexcel7:20210417165200p:plain


現在の実行ポリシーの設定を確認するため、以下のコマンドレットを入力します。

Get-ExecutionPolicy


変更する前は下記のように「Restricted」となっており、スクリプトが実行できない設定になっています。

f:id:modernexcel7:20210417165535p:plain

ちなみにこの状態で通常のユーザーがスクリプトを実行すると、以下のようなエラーメッセージが表示されます。

f:id:modernexcel7:20210417165346p:plain


スクリプトの実行を可能にするため、以下のコマンドレットを入力します。質問には「Y」で回答します。

Set-ExecutionPolicy RemoteSigned

f:id:modernexcel7:20210417165831p:plain

設定ができたら、「exit」と入力してPowerShellコンソールを閉じます。

f:id:modernexcel7:20210417192829p:plain

PowerShell ISEによるスクリプトの実行テスト

再び左下の検索でPowerShellと入力しますが、今度はスクリプトを作成するためWindows PowerShell ISEを起動します。

f:id:modernexcel7:20210417170046p:plain


画面上部のスクリプトウィンドウに以下のテキストを入力し、「▶」をクリックしてスクリプトを実行します。

Write-Host "Hello World"

f:id:modernexcel7:20210417170423p:plain


すると、画面下部のコンソールスクリプトの内容と実行結果が表示されます。

f:id:modernexcel7:20210417170334p:plain


今度は保存ボタンを押してスクリプトを保存します。

f:id:modernexcel7:20210417170446p:plain


ファイル名は「HelloWorld.ps1」とします。PowerShellスクリプトファイルの拡張子は「ps1」にします。

f:id:modernexcel7:20210417170535p:plain


保存されるとスクリプトウィンドウのタブにファイル名が表示されます。
次にスクリプトとして実行するため「×」をクリックしてこのスクリプトを閉じます。

f:id:modernexcel7:20210417170635p:plain


Shiftキーを押しながら先ほど作成したスクリプト右クリックし、パスのコピーを選びます。

f:id:modernexcel7:20210417170714p:plain


再びPowerShell ISEに戻り以下のボタンをクリックして新しいスクリプトウィンドウを開きます。

f:id:modernexcel7:20210417193905p:plain


スクリプトウィンドウが開いたら、先ほどのパスをコピーし先頭と末尾のダブルクォートを削除して実行します。

f:id:modernexcel7:20210417170806p:plain


すると下のコンソールに正常に実行結果が表示されます。

f:id:modernexcel7:20210417170844p:plain

実行結果が確認できたので、スクリプトウィンドウを閉じます。

PowerShellExcelを開く

スクリプトの動作確認ができたので、いよいよパワークエリの更新に移ります。

クエリの「バックグラウンドで更新する」をオフにする

PowerShellでクエリを更新するときバックグラウンドで更新するの設定をオフにしておかないとクエリの更新が完了する前に次の処理に進み、エラーが発生するのでオフにします。

最初にパワークエリでクエリを作成したxcelを開きクエリと接続ペインを表示します。

f:id:modernexcel7:20210418224338p:plain

対象のクエリを右クリックし、プロパティを選択します。

f:id:modernexcel7:20210418224126p:plain

クエリ プロパティが表示されたらバックグラウンドで更新するのチェックを外します。
すべて更新でこの接続を更新するにはチェックを付けたままにしておきます。

f:id:modernexcel7:20210418224222p:plain

Excelブックを保存して閉じます

なおこの設定を行わない場合、更新の途中で以下のエラーメッセージが表示されてしまいます。

f:id:modernexcel7:20210418223728p:plain

Excelを表示しながら実行

PowerShell ISEに戻り、新しくスクリプトウィンドウを開き、以下のスクリプトを貼り付けます。

この時、以下2点のみご自身の環境に応じて変更してください。


# Excelファイルのオープン(要変更)
$book = $excel.Workbooks.Open("C:\PowerS_TEST\キャンペーン.xlsx")

⇒ パワークエリのあるファイルの絶対パスを入力します。


# シート移動(要変更)
$sheet = $excel.Worksheets.Item("Home")

⇒ 更新日時を記入するためのシートを入力します。

# Excelオブジェクト作成
$excel = New-Object -ComObject Excel.Application

# Excelの表示(ここで表示かバックグラウンドか)
$excel.Visible = $true

# Excelファイルのオープン(要変更)
$book = $excel.Workbooks.Open("C:\PowerS_TEST\キャンペーン.xlsx")

# シート移動(要変更)
$sheet = $excel.Worksheets.Item("Home")

# 更新日記入
$log = Get-Date -Format "yyyy/MM/dd HH:mm"
$range =$sheet.Range("A1")
$range.Value ="更新日時: $log"

# クエリ更新
$book.refreshall()

# 上書き保存
$book.Save()

# 閉じる
$excel.Quit()

#プロセスの削除
[System.Runtime.Interopservices.Marshal]::ReleaseComObject($excel)
[System.Runtime.Interopservices.Marshal]::ReleaseComObject($sheet)

これで実行するとExcelが自動的に立ち上がり、以下の作業を行った後、ファイルを保存して閉じます。

1.HomeシートのA1セルに更新日時を記入( $range.Value ="更新日時: $log"
2.クエリの更新( $book.refreshall()

PowerShell ISEの後ろに隠れていますが、Excelが一連のタスクを行っています。

f:id:modernexcel7:20210417195305p:plain

Excelを表示せずにバックグラウンドで実行

今度は同じ処理をExcelを表示させずにバックグラウンドで実行します。

前のスクリプトと異なるのは以下の部分のみです。

$visible = false

# Excelオブジェクト作成
$excel = New-Object -ComObject Excel.Application

# Excelの表示(ここで表示かバックグラウンドか)
$excel.Visible = $false

# Excelファイルのオープン(要変更)
$book = $excel.Workbooks.Open("C:\PowerS_TEST\キャンペーン.xlsx")

# シート移動(要変更)
$sheet = $excel.Worksheets.Item("Home")

# 更新日記入
$log = Get-Date -Format "yyyy/MM/dd HH:mm"
$range =$sheet.Range("A1")
$range.Value ="更新日時: $log"

# クエリ更新
$book.refreshall()

# 上書き保存
$book.Save()

# 閉じる
$excel.Quit()

#プロセスの削除
[System.Runtime.Interopservices.Marshal]::ReleaseComObject($excel)
[System.Runtime.Interopservices.Marshal]::ReleaseComObject($sheet)


今回は画面上には現れませんが、タスクマネージャーには確かにExcelのプロセスが現れ、処理が終わった後消えていきます。

f:id:modernexcel7:20210417173418p:plain

なお、何らかの理由で途中でプロセスが止まってしまった場合はタスクマネージャーExcel右クリックしてタスクの終了を実行して強制的に落とします。

f:id:modernexcel7:20210418223838p:plain


Excelファイルを開き、クエリの最終更新時刻を見るとこちらも更新されています。

f:id:modernexcel7:20210417174011p:plain


ここまで来たらスクリプトウィンドウ保存ボタンを押して、保存します。

f:id:modernexcel7:20210417173822p:plain

PowerShellスクリプトの実行

コマンドプロンプトから実行する

コマンドプロンプトから実行する場合には、以下のコマンドになります。

PowerShell C:\PowerS_TEST\PowerQueryRefresh.ps1

このコマンドをバッチファイルとして保存すれば、ダブルクリックで実行することもできます。

Power Automate Desktopから実行する

Power Automate Desktop実行する場合は、アクションシステムPowerShellスクリプトの実行を選び、実行する PoweShell コードに上のスクリプトの中身をそのまま貼り付ければ実行できます。

f:id:modernexcel7:20210417202433p:plain

参考資料

今回は以下の記事を参考にさせていただきました。ありがとうございました。

PowerShellExcelを操作するのはVBAによく似ているそうなので、VBAが得意な方は利用してみるのも良いかと思います。

PowershellでExcel操作 - Qiita

【PowerShell】Excelを読み書きする - Qiita

EXCELでDAXクエリを使うには

EXCELDAXクエリを使うには

f:id:modernexcel7:20210403202827p:plain

DAXSQLなどと同じようにクエリ言語として使用することができます。

メジャーの結果はいわゆるスカラーといった1つの値ですが、そこにたどり着くまでの途中経過のテーブル関数の動作などメジャーでは見えにくい部分を検証する上で役に立ちます。

DAXクエリを呼び出すための準備

EXCELではちょっと変わった呼び出し方をします。

データモデルが組んであるEXCELブックを開く

まずパワーピボットでデータモデルが組んであるEXCELブックを開きます。
今回は『EXCELパワーピボット』のサンプルファイルを開きます。

f:id:modernexcel7:20210403200929p:plain

データモデルに読み込むダミーのクエリを作る

EXCELシートの何もないセルを選択し、データからテーブルまたは範囲からをクリックします。

f:id:modernexcel7:20210403200213p:plain

テーブルの作成が現れるので、そのままOKを押して次に進みます。

f:id:modernexcel7:20210403200312p:plain

PowerQueryエディターが開いたら、そのまま閉じて読み込む→閉じて次に読み込む…に進みます。

f:id:modernexcel7:20210403200400p:plain

このデータをデータモデルに追加するにチェックを入れて、OKを押します。

f:id:modernexcel7:20210403200533p:plain

何もない空のテーブルが読み込まれます。

f:id:modernexcel7:20210403200651p:plain

ダミーのテーブルからDAXの編集を開く

ダミーで作成したクエリを右クリックし、テーブル→DAXの編集を選択します。

f:id:modernexcel7:20210403200740p:plain

以下のような画面が表示されます。

f:id:modernexcel7:20210403201434p:plain

コマンドの種類テーブルからDAXに変え、以下の式を入力します。

EVALUATE
'F_売上明細'

f:id:modernexcel7:20210403201849p:plain

データモデルの中からF_売上明細テーブルがそのまま読み出されました。

f:id:modernexcel7:20210403202125p:plain

構文

式はEVALUATEで始めます。

例1: FILTER関数

FILTER関数を使って、売上が100万円以上のデータを取得します。

EVALUATE
FILTER (
    'F_売上明細',
    'F_売上明細'[売上] > 1000000
)

売上が100万円以上のF_売上明細テーブルです。

f:id:modernexcel7:20210403203846p:plain

例2: FILTER関数 と RELATEDでリレーションショップによるフィルター

RELATED関数を使ってリレーションショップを介したフィルタリングを行います。

EVALUATE
FILTER (
    'F_売上明細',
    RELATED ( 'カレンダー'[会計年度] ) = 2017
)

会計年度が2017のF_売上明細テーブルです。

f:id:modernexcel7:20210403203550p:plain

例3: ADDCOLUMNS関数とメジャーで集計

以下の構文では年度ごとの「売上合計」メジャーを参照して集計しています。

EVALUATE
ADDCOLUMNS (
    VALUES ( 'カレンダー'[会計年度] ),
    "Sales", [売上合計]
)

会計年度ごとの売上合計を集計しました。

f:id:modernexcel7:20210403202518p:plain

まとめ

EXCELの標準機能でDAXを呼び出すのは今回の手順になりますが、インテリセンスが無いので構文やテーブル、列名などが誤っていても気が付かないことがあります。その場合は、DAX Studioをインストールして使うのが良いでしょう。

daxstudio.org

非アクティブなリレーションシップとUSERRELATIONSHIP関数

アクティブなリレーションシップとUSERRELATIONSHIP関数

f:id:modernexcel7:20210403113217p:plain

目標

通常、テーブルとテーブルは1つのリレーションシップでつなげることができます。このとき、追加のリレーションシップをつなげると非アクティブなリレーションシップになります。

非アクティブなリレーションシップはふだんは休眠状態ですが、USERRELATIONSHIP関数を使うことで有効にすることができます。同時にアクティブなリレーションシップは休眠状態になります。

技術的なポイント

  • 既にリレーションシップのあるテーブルに、追加で非アクティブなリレーションシップを作ります。
  • メジャーを作るときに、USERRELATIONSHIP関数をCALCULATEに渡してリレーションショップを切り替えます。

USERELATIONSHIP 関数 (DAX) - DAX | Microsoft Docs

実践

売上明細に日付請求日の2つの日付があったとき、カレンダーテーブルを増やすことなく、1つのカレンダーテーブルを共有してそれぞれの売上を計算します。

データとレポートについて

以下のようにF_売上明細テーブルとカレンダーテーブルは日付を介してリレーションショップが定義されています。
f:id:modernexcel7:20210403175421p:plain

売上合計を集計するためのメジャーは以下のように定義されています。

f:id:modernexcel7:20210403175553p:plain

この時、ピボットで集計すると通常の集計を行います。
f:id:modernexcel7:20210403175715p:plain

「つなげる」ステップ

F_売上明細テーブルには日付の他に請求日があります。
請求日は取引日が10日より以前の時は、翌月25日、それ以降は翌々月の25日がセットされています。

f:id:modernexcel7:20210403175814p:plain

この請求日カレンダーテーブルの日付をリレーションショップでつなぎます。

すると、これらのテーブルには既存のリレーションシップがあるので、黄色い部分のメッセージが表示されます。

f:id:modernexcel7:20210403113029p:plain

追加されたリレーションシップは以下のように非アクティブの状態になります。

f:id:modernexcel7:20210403113455p:plain

Power Pivotアドインのある環境でダイアグラムビューを確認すると、非アクティブなリレーションシップは以下のように点線で表示されます。

f:id:modernexcel7:20210403113217p:plain

「かぞえる」ステップ

この非アクティブなリレーションシップを使用した集計を行うため、以下のメジャーを追加します。

売上合計(請求基準) :=
CALCULATE (
    [売上合計],
    USERELATIONSHIP ( 'F_売上明細'[請求日], 'カレンダー'[日付] )
)

f:id:modernexcel7:20210403114300p:plain

ここで登場したUSERRELATIONHSIP関数は、CALCULATE関数のようにフィルターを引数として受け取る関数の中でのみ使用できます。

また、1 : Manyの関係性で言うと、それぞれの引数は以下のようになります。

第1引数:Many(数字テーブル = Fact Table)側、F_売上明細の「請求日」
第2引数:1(まとめテーブル= Dimension Table)側、カレンダーの「日付」

作成したメジャーを追加します。それぞれ同じカレンダーテーブルを参照していますが、結果が異なります。

f:id:modernexcel7:20210403114645p:plain


値を確認するために、2016年4月10日以前の売上合計を確認すると、2,057,900です。

f:id:modernexcel7:20210403114555p:plain

f:id:modernexcel7:20210403181640p:plain

翌月の売上合計(請求基準)を見ると、2,057,900となっており一致しています。

f:id:modernexcel7:20210403183617p:plain

まとめ

通常、テーブルどうしは1つのアクティブなリレーションシップでのみ連結されています。それ以外のリレーションシップは非アクティブとして休眠状態になりますが、USERRELATIONSHIP関数で他のリレーションシップに代わり有効化できます。

利用方法としては、日付のほか、①取引先と顧客を同一テーブルで定義している場合、②1つの売上に営業主担当と営業副担当が付いている場合の例が挙げられます。

SharePoint List登録からPower Automateで承認プロセスを開始する

今回の記事は、SharePoint List とPower Automateを組み合わせて、承認ワークフローを作ります。

f:id:modernexcel7:20210328170835p:plain

【承認ワークフローの動作】

承認ワークフローの動作は以下のようになります。
1.依頼者がSharePoint Listのフォームに情報を入力する
2.自動で承認プロセスが開始され、決まった承認者に承認依頼メールが飛ぶ
3.承認者がメール上で回答する
4.承認プロセスが完了し、SharePoint Listの情報がアップデートされる

【承認ワークフローを作るメリット】

なお、SP & PAで承認フローを作るメリットは以下のものが挙げられます。

  • 適合性:自分の環境にあった情報を管理できる
  • 手間:都度メールによるやり取りの手間が省ける
  • 証跡の検索:承認の履歴がデータベース化されているので追跡しやすい
【作るにあたって】

手順は、可能な限りラクに作れることを目標にしました。つまり、技術に強い人だけしか作れないのではなく、普通の人でもこれを見ながらなら作れるよう、3分クッキングを目指しています。

社内や組織の一握り、一つまみのスーパーマンしか作れないものではなく、業務の引継ぎ・継続性を考え、これを見ながらなら普通の人でも作れることを目標にしています。

【技術的なポイント】

SharePoint ListとPower Automateにはそれぞれ独自の癖があるので、そのあたりを上手くわきまえて利用するのがよさそうです。つまり、ある種のベストプラクティスの使い方をする必要があります。

今回の技術的なポイントは以下の通りです。

  • SharePoint Listではフォームの入力の禁止とグリッドビューの禁止で読み取り専用項目を作る
  • Power Automate ではテンプレートを拝借し、一部改造する

SharePoint Listを作成する

まず、データの入力と保存を行うSharePoint Listを作ります。
Listを作るにあたっては、人が入力するデータとPower Automateのみが更新できるデータを別に作る点がポイントです。

リストを作成する

SharePointサイトに移動し、以下のステップで承認依頼リストを作ります。

  • 右上の歯車をクリック ⇒ サイトコンテンツ
  • +新規 ⇒ 空白のリスト ⇒ 名前:承認依頼 ⇒ 「作成」ボタン

f:id:modernexcel7:20210328155147p:plain

リストの項目を追加する

リストに項目を追加していきます。「+列の追加」ではなく「リストの設定」から追加します。

  • 右上の歯車をクリック ⇒ リストの設定 ⇒ 列の作成

f:id:modernexcel7:20210328155404p:plain

ここから「列の作成」をクリックして以下の設定を入力⇒OKを繰り返し、一つずつ列を追加していきます。

Amount

依頼者が承認を依頼する金額です。

  • 列名:Amount
  • この列の情報の種類:数値
  • この列への情報の入力を必須にする:はい
Amount_Approved

リクエストが承認されたときにPower Automateが更新する金額です。

  • 列名:Amount_Approved
  • この列の情報の種類: 数値
Approval

承認のステータスです。
これもリクエストが承認されたときにPower Automateが更新します。

  • 列名:Approval
  • この列の情報の種類:1行テキスト
  • 規定値:テキスト、申請中
Comment

承認のステータスです。
これもリクエストが承認されたときにPower Automateが更新します。

  • 列名:Comment
  • この列の情報の種類:1行テキスト

この段階で列は以下のようになります。

f:id:modernexcel7:20210328162428p:plain

「すべてのアイテム」ビューの設定

続いて、非表示になっている作成者、作成日を表示します。

  • ビュー ⇒ すべてのアイテム

f:id:modernexcel7:20210328160726p:plain

  • Created、Created byの「表示チェック ⇒ OK

入力禁止設定を行う

ここからが少しトリッキーな設定になります。
再び、歯車から「リストの設定」に移動し、以下の設定を行います。

  • 詳細設定

f:id:modernexcel7:20210328161129p:plain

  • コンテンツ タイプの管理を許可する ⇒ はい

f:id:modernexcel7:20210328161239p:plain
この設定により「リストの設定」画面に「コンテンツタイプ」が表示されます。
f:id:modernexcel7:20210328162057p:plain

  • この リスト のアイテムを [クイック編集] と [詳細] ウィンドウを使用して編集することを許可しますか? ⇒ いいえ

f:id:modernexcel7:20210328161353p:plain

この設定によりリストの「グリッドビューでの編集」が非表示になり、フォームでしか項目を編集できないようになります。つまり、あとはフォームで非表示にすればその項目は読み取り専用になります。
f:id:modernexcel7:20210328161642p:plain

ここまで入力したら、「OK」を押して一度「詳細設定」を抜けます。

フォームの非表示設定を行う

続いて読み取り専用にする項目をフォームから非表示にします。

  • コンテンツタイプ ⇒ Item

f:id:modernexcel7:20210328162010p:plain

「リストコンテンツタイプ」画面に移動したら、以下の列をそれぞれクリックし、この列 ⇒ 非表示(フォームに表示しない)の設定を行います。
f:id:modernexcel7:20210328162157p:plain

  • Amount_Approved
  • Approval
  • Comment

設定された項目は以下のように「隠しファイル」となります。
f:id:modernexcel7:20210328162548p:plain

ここまで来たら再び、「リストの設定」画面でコンテンツタイプの管理を元に戻します。

  • 詳細設定 ⇒ コンテンツ タイプの管理を許可する ⇒ いいえ ⇒ OK

この設定を戻しておかないとListに以下の項目が表示されてしまいます。
f:id:modernexcel7:20210328162730p:plain

これでリストの作成は完了です。
この時点で一覧画面は以下のようになります。

f:id:modernexcel7:20210328163023p:plain

「+新規」を押したときのフォームは以下のようになります。

f:id:modernexcel7:20210328163043p:plain

Power Automateを作成する

ここからPower Automateで承認フローを作ります。

テンプレートを呼び出す

  • Power Automateのホーム画面に移動
  • 「新しいアイテムが追加されたときに承認を開始する」で検索 & 選択
  • 続行

f:id:modernexcel7:20210328163306p:plain

フローを編集する

テンプレートのフローを一部変更して完成させます。

サイトとリストの選択
  • When a new item is created:⇒ サイトのアドレスと先ほど作ったリスト名を選択

f:id:modernexcel7:20210328164516p:plain

承認者の設定
  • Start an Approval: ⇒ 「割り当て先」に承認者のメールアドレスを記入(今回は固定)

f:id:modernexcel7:20210328163658p:plain

※ タイトルのテキスト部分は適宜変更してください。

承認時のアクションを追加
  • 「はいの場合」の末尾の「アクションの追加」をクリック

f:id:modernexcel7:20210328163921p:plain

  • 「操作を選択してください」で「項目の更新 - SharePoint」を選択

f:id:modernexcel7:20210328164044p:plain

  • サイトのアドレス ⇒ 選択
  • リスト名: ⇒ 選択
  • Title => Title (動的なコンテンツのSharePointから選択)
  • Amount => Amount (動的なコンテンツのSharePointから選択)
  • Amount_Approved => Amount (動的なコンテンツのSharePointから選択)
  • Approval => 承認 (固定値で手入力)
  • Comment => コメント(承認者によって提供されたコメント)

f:id:modernexcel7:20210328164811p:plain

このとき、「動的なコンテンツ」は右側のボックスから検索&選択します。
f:id:modernexcel7:20210328164703p:plain

却下時のアクションを追加

  • 「いいえの場合」の末尾の「アクションの追加」をクリック
  • 入力は以下を除いて「はいの場合」と同じ

Approval_Status_Value => 却下 (固定値で手入力)

f:id:modernexcel7:20210328170602p:plain

フローの保存

ここまで来たら画面下の「保存」を押してフローを保存します。

f:id:modernexcel7:20210328165115p:plain

承認ワークフローの実行

完成したフローをテストします。「承認依頼」リストに戻り新しいリスト項目を追加します。

  • +新規

f:id:modernexcel7:20210328165245p:plain

項目を入力し、「保存」を押します。
f:id:modernexcel7:20210328165332p:plain

以下のメールが承認者あてに送られてきます。

f:id:modernexcel7:20210328165502p:plain

「承認」をクリックし、コメントを入力して「送信」を押します。
f:id:modernexcel7:20210328165549p:plain

以下のように結果を通知するメールが依頼者に送られます。

f:id:modernexcel7:20210328165718p:plain

同時にListのApproveそのほかのステータスが更新されます。
f:id:modernexcel7:20210328165802p:plain

※ Approval_Amountが$になっていましたね。後で直します・・・
※ 承認処理はスマホアプリのPower Automateでもできるのでとても便利です。

まとめ

SharePoint ListとPower Automate の情報はとても少ないです。

ネット上では散発的に見かけるのですが、さらにその両者を連携させるテクニックは一癖あってなかなか一筋縄ではいきません。

そういう状況ですが、最近電子書籍で出版された以下の本はそのあたりのベストプラクティスを重点的にケアしてくれているのでとても勉強になりました。特にSharePointの管理者向けではなく、普通のユーザー向けなので広く役立つのではないでしょうか。

SharePoint List 設定について

EXCELではないですが、SharePointの設定について備忘として記録します。

リスト項目をフォームから隠す

1)  コンテンツ タイプの管理を許可する

  • 歯車をクリック ⇒ リストの設定 ⇒ 詳細設定
  • 「コンテンツ タイプの管理を許可する」を「はい」⇒ OK

2)  フォームの非表示設定を行う

  • コンテンツタイプ ⇒ Item
  • 対象の項目を選ぶ
  • この列 ⇒ 「非表示 (フォームに表示しない)」を選択 ⇒ OK

3)  コンテンツ タイプの管理を許可を解除

  • 歯車をクリック ⇒ リストの設定 ⇒ 詳細設定
  • 「コンテンツ タイプの管理を許可する」を「いいえ」⇒ OK

※ この設定を行わないとフォームに不要な項目が表示される。

リスト項目を読み取り専用にする方法①

Power Automateと組み合わせて「承認ステータス」の値をフローから書き換えるときに、その項目がユーザーから編集可能だと承認の意味がなくなるので読み取り専用にします。

1) 読み取り専用にしたい列で「リスト項目をフォームから隠す」を実行する

  • これによりフォームでの入力は不可能になる。
  • ただしグリッドビューでは編集できてしまうので、次の手順で 禁止する

2) グリッドビューでの編集を禁止する

  • 歯車をクリック ⇒  リストの設定 ⇒ 詳細設定
  • この リスト のアイテムを [クイック編集] と [詳細] ウィンドウを使用して編集することを許可しますか? ⇒ いいえ ⇒ OK

リスト項目を読み取り専用にする方法②

1) データの実体を持つ「1行テキスト」の列を作る
2) 参照用の列を追加する

  • 歯車をクリック ⇒ リストの設定 ⇒ 列の追加
  • この列の情報の種類を「集計値」に
  • 数式にデータの実体を持つ列を「列の挿入」で選択  ⇒ OK

3) 実体の方はビューから非表示にする。

  • 歯車をクリック ⇒ リストの設定 ⇒ ビューの「すべてのアイテム」をクリック
  • データの実体を持つ列の「表示」のチェックを外す ⇒ OK

添付ファイルの追加を無効にする

  • 歯車をクリック ⇒ リストの設定 ⇒ 詳細設定
  • リスト アイテムへのファイルの添付: ⇒ 無効 ⇒ OK

参考文献

こちらの本で勉強しています。SIerなどではなく実際のユーザー向けの本なのでとても参考になります。

DAX式サンプル

よく使われるDAX式のサンプルです。時々更新します。

なお、ページ数は私の著作『Excelパワーピボット』のでの参照ページです。



[P194] 売上合計

 =
    SUM('F_売上明細'[売上])



[P197] 平均単価(割り算を行うときはDivide By ZeroエラーはNULLに)

=
    DIVIDE([売上合計],[販売数量合計])



[P218] 商品売上総計(テーブル単位でのフィルター解除)

=
    CALCULATE(
        [売上合計], 
        ALLSELECTED('商品')
    )



[P228] 商品カテゴリー売上小計(テーブルの列単位でのフィルター解除)

=
    CALCULATE(
        [売上合計],
        ALLSELECTED('商品'[商品名])
    )



[P233] 商品カテゴリー選択(現在当たっているフィルターを検知)

=
    ISFILTERED('商品'[商品カテゴリー])



[P235] 商品割合(ドリルダウンのレベルごとにメジャーの表示を切り替える)

=
    IF(
        ISFILTERED('商品'[商品名])=TRUE, [商品売上小計割合], 
        [商品カテゴリー割合]
    )


累計売上(FILTER関数)

=
    CALCULATE (
        [売上合計],
        FILTER (
            ALL('カレンダー'),
            'カレンダー'[日付] <= MAX('カレンダー'[日付])
        )
    )



[P254] 当期累計売上①詳細パターン

=
    CALCULATE (
        [売上合計],
        FILTER (
            ALL('カレンダー'),
            'カレンダー'[日付] <= MAX('カレンダー'[日付])
            && 'カレンダー'[会計年度] = MAX('カレンダー'[会計年度])
        )
    )



当期累計売上(FILTER関数&VAR)

=
VAR DateFilter
=
        FILTER (
            ALL('カレンダー'),
            'カレンダー'[日付] <= MAX('カレンダー'[日付])
            && 'カレンダー'[会計年度] = MAX('カレンダー'[会計年度])
        )

RETURN
    CALCULATE (
        [売上合計],
        DateFilter
    )  



[P256] 当期売上累計②タイムインテリジェンス関数

=
    CALCULATE(
        [売上合計],
        DATESYTD('カレンダー'[日付], "3/31")
)

=
    CALCULATE(
        [売上合計],
        DATESQTD('カレンダー'[日付])
    )

=
    CALCULATE(
        [売上合計],
        DATESMTD('カレンダー'[日付])
    )



[P263] 前年度売上合計(SAMEPERIODLASTYEAR)

=
    CALCULATE(
        [売上合計],
        SAMEPERIODLASTYEAR('カレンダー'[日付])
    )



前年度売上(FILTER関数&VAR&ISFILTERED&IF)

[売上合計_前年_年]
=VAR DateFilter
=
        FILTER (
            ALL('カレンダー'),
            'カレンダー'[会計年度] = MAX('カレンダー'[会計年度]) -1
        )
RETURN
    CALCULATE (
        [売上合計],
        DateFilter
    )

[売上合計_前年_Qtr]
=VAR DateFilter
=
        FILTER (
            ALL('カレンダー'),
            'カレンダー'[会計年度] = MAX('カレンダー'[会計年度]) -1 &&
            'カレンダー'[Qtr]       = MAX('カレンダー'[Qtr])
        )
RETURN
    CALCULATE (
        [売上合計],
        DateFilter
    )

[売上合計_前年_月]
=VAR DateFilter
=
        FILTER (
            ALL('カレンダー'),
            'カレンダー'[会計年度] = MAX('カレンダー'[会計年度]) -1 &&
            'カレンダー'[月]       = MAX('カレンダー'[月])
        )
RETURN
    CALCULATE (
        [売上合計],
        DateFilter
    )

[売上合計_前年_日付]
=VAR DateFilter
=
        FILTER (
            ALL('カレンダー'),
            'カレンダー'[会計年度] = MAX('カレンダー'[会計年度]) -1 &&
            'カレンダー'[YTD]      = MAX('カレンダー'[YTD])
        )
RETURN
    CALCULATE (
        [売上合計],
        DateFilter
    )

[売上合計_前年]
=
IF (
    ISFILTERED ( 'カレンダー'[日付] ) = TRUE,
    [売上合計_前年_日付],
    IF (
        ISFILTERED ( 'カレンダー'[月] ) = TRUE,
        [売上合計_前年_月],
        IF (
            ISFILTERED ( 'カレンダー'[会計四半期] ) = TRUE,
            [売上合計_前年_Qtr],
            IF (
                ISFILTERED ( 'カレンダー'[会計年度] ) = TRUE,
                [売上合計_前年_年],
                BLANK ()
            )
        )
    )
)



[P263] 前年度売上累計(DATESYTDをSAMEPERIODLASTYEARで囲む)

=
    CALCULATE(
        [売上合計],
        SAMEPERIODLASTYEAR(
            DATESYTD('カレンダー'[日付],"3/31")
        )
    )



3カ月移動平均 DATESINPERIODを使用

=
    CALCULATE (
        [売上合計],
        DATESINPERIOD ( 'カレンダー'[日付], MAX ( 'カレンダー'[日付] ), -3, MONTH )
    )



ランキング(ALLと組み合わせて使用する)

=
    RANKX(
        ALLSELECTED('商品'), [売上合計]
    )

=
    IF(
        [売上合計],
        RANKX(
            ALLSELECTED('支店'), [売上合計]
        )
    )



値表示( VALUES は列の値を列挙するが、1行のみのときスカラーになる)

COUNTROWSによるパターン
=
    IF (
        COUNTROWS ( VALUES ( 'P_集計タイプ'[集計タイプ] ) ) = 1,
        VALUES ( 'P_集計タイプ'[集計タイプ] )
    )

HASONEVALUEによるパターン
=
    IF (
        HASONEVALUE ( 'P_集計タイプ'[集計タイプ] ),
        VALUES ( 'P_集計タイプ'[集計タイプ] )
    )

Power BIのみ
=  SELECTEDVALUE('P_集計タイプ'[集計タイプ] )



スライサーとパラメーターテーブルによる選択
リレーションシップを持たない分類だけのテーブルを作り、スライサー経由でフィルターコンテキストを渡して、表示するメジャーを切り替える

COUNTROWSによるパターン
=
    IF (
        COUNTROWS ( VALUES ( 'P_集計タイプ'[集計タイプ] ) ) = 1,
        SWITCH (
            VALUES ( 'P_集計タイプ'[集計タイプ] ),
            "売上", [売上合計],
            "原価", [売上合計] - [利益合計],
            "利益", [利益合計]
        )
    )

HASONEVALUEによるパターン
=
    IF (
        HASONEVALUE ( 'P_集計タイプ'[集計タイプ] ),
        SWITCH (
            VALUES ( 'P_集計タイプ'[集計タイプ] ),
            "売上", [売上合計],
            "原価", [売上合計] - [利益合計],
            "利益", [利益合計]
        )
    )

Power BIのみ
=
    SWITCH (
        SELECTEDVALUE('P_集計タイプ'[集計タイプ] ),
        "売上", [売上合計],
        "原価", [売上合計] - [利益合計],
        "利益", [利益合計]
    )

セグメンテーション(値を元にAランク、Bランクというように分類)
区分化テーブルにリレーションシップは作らず、不等号によるレンジでセグメントを返す。

メジャー1レベル
Grade =
CALCULATE(
    VALUES( '区分化テーブル'[Rank] ),
    FILTER( '区分化テーブル', '区分化テーブル'[Start] < [売上合計] && [売上合計] < '区分化テーブル'[End] )
)

メジャー2レベル
Grade_2 = 
VAR PRODUCT_CATEGORY = SELECTEDVALUE('商品'[商品カテゴリー])
RETURN
CALCULATE (
    VALUES ( '区分化テーブル'[Rank] ),
    FILTER ( '区分化テーブル', '区分化テーブル'[Start] < [売上合計] && [売上合計] < '区分化テーブル'[End] ),
    FILTER ( '区分化テーブル', '区分化テーブル'[Category]  = PRODUCT_CATEGORY )	
)




デバッグ

タイムインテリジェンス関数(シリアル値による最大・最小)
=
    // カレンダー
    VAR DateFilter = SAMEPERIODLASTYEAR('カレンダー'[日付])

    // Date Range
    VAR MaxDate = FORMAT(CALCULATE(MAX('カレンダー'[日付]), DateFilter), "YYYY/MM/DD")
    VAR MinDate = FORMAT(CALCULATE(MIN('カレンダー'[日付]), DateFilter), "YYYY/MM/DD")
    VAR CountDates = COUNTROWS(DateFilter)

    RETURN
        MinDate & " - " & MaxDate & " (" & CountDates & ")"

FILTER関数(シリアル値による最大・最小)
=
    // カレンダー
    VAR DateFilter
        = FILTER(
                ALL('カレンダー'),
                'カレンダー'[日付] <= MAX('カレンダー'[日付]) &&
                'カレンダー'[会計年度] = MAX('カレンダー'[会計年度])
            )
    // Date Range
    VAR MaxDate = FORMAT(CALCULATE(MAX('カレンダー'[日付]), DateFilter), "YYYY/MM/DD")
    VAR MinDate = FORMAT(CALCULATE(MIN('カレンダー'[日付]), DateFilter), "YYYY/MM/DD")
    VAR CountDates = COUNTROWS(DateFilter)

    RETURN
        MinDate & " - " & MaxDate & " (" & CountDates & ")"

テキスト値(最大・最小を持たないので列挙する)
=
    CONCATENATEX('商品', '商品'[商品名] , " / ")


メジャーによるカテゴリー別累積計算テーブル(以下リンクを参考にしたものです)
ABC dynamic classification – DAX Patterns

テーブル(Power BIのみ)
=
    ADDCOLUMNS(
        -- レベル1:商品ID、売上合計
        ADDCOLUMNS( VALUES( '商品'[商品ID] ), "商品ごと売上", [売上合計] ),
        -- レベル1:追加項目
        "順位", COUNTROWS(
            -- サブテーブルの作成
            FILTER(
                -- レベル2:商品ID、売上合計
                ADDCOLUMNS( VALUES( '商品'[商品ID] ), "サブ商品ごと売上", [売上合計] ),
                -- 外側の1行1行に対して内側の方が大きいものに絞る
                [サブ商品ごと売上] >= [商品ごと売上]
            )
        ),
        -- レベル1:追加項目 
        "累積", -- 上位からの合計
        SUMX(
            -- サブテーブルの作成
            FILTER(
                -- レベル2:商品ID、売上合計
                ADDCOLUMNS( VALUES( '商品'[商品ID] ), "サブ商品ごと売上", [売上合計] ),
                -- 外側の1行1行に対して内側の方が大きいものに絞る
                [サブ商品ごと売上] >= [商品ごと売上]
            ),
            -- サブテーブルの売上を合計
            [サブ商品ごと売上]
        ),
        -- レベル1:追加項目
        "商品売上 Total", CALCULATE( [売上合計], VALUES( '商品'[商品ID] ) ),
        -- レベル1:追加項目
        "累積割合", -- 上位からの合計
        DIVIDE(
            SUMX(
                -- サブテーブルの作成
                FILTER(
                    -- レベル2:商品ID、売上合計
                    ADDCOLUMNS( VALUES( '商品'[商品ID] ), "サブ商品ごと売上", [売上合計] ),
                    -- 外側の1行1行に対して内側の方が大きいものに絞る
                    [サブ商品ごと売上] >= [商品ごと売上]
                ),
                -- サブテーブルの売上を合計
                [サブ商品ごと売上]
            ),
            CALCULATE( [売上合計], VALUES( '商品'[商品ID] ) )
        )
    )

メジャーによるABC分析(以下リンクを参考にしたものです)
ABC dynamic classification – DAX Patterns

売上合計 ABC = 
CALCULATE(
    [売上合計],

    VALUES( '商品'[商品ID] ),

    -- 商品IDごとのA,B,Cに限定
    FILTER(
        CALCULATETABLE(
            ADDCOLUMNS(
                -- レベル1:商品ID、売上合計
                ADDCOLUMNS(
                    VALUES( '商品'[商品ID] ),
                    "商品ごと売上", [売上合計]
                ),
                -- レベル1:追加項目
                "累積割合", -- 上位からの合計
                DIVIDE(
                    SUMX(
                        -- サブテーブルの作成
                        FILTER(
                            -- レベル2:商品ID、売上合計
                            ADDCOLUMNS(
                                VALUES( '商品'[商品ID] ),
                                "サブ商品ごと売上", [売上合計]
                            ),
                            -- 外側の1行1行に対して内側の方が大きいものに絞る
                            [サブ商品ごと売上] >= [商品ごと売上]
                        ),
                        -- サブテーブルの売上を合計
                        [サブ商品ごと売上]
                    ),
                    CALCULATE(
                        [売上合計],
                        VALUES( '商品'[商品ID] )
                    )
                )
            ),
            -- 商品テーブルのフィルターを解除
            ALL( '商品' )
        ),
        -- FILTERでレンジを限定
        [累積割合] > [区分下限値]
            && [累積割合] <= [区分上限値]
    )
)

ある月に売上のない顧客ID(計算テーブル)


顧客ID売上無し =
FILTER (
    ADDCOLUMNS (
        VALUES ( 'F_売上明細'[顧客ID] ),
        "前回の売上",
            CALCULATE (
                COUNTROWS ( 'F_売上明細' ),
                 (
                    FILTER (
                        ALL ( 'カレンダー' ),
                        'カレンダー'[月] = 9
                    )
                )
            )
    ),
    [前回の売上] = 0
)

売上無し顧客名(リレーションシップ経由)


顧客名売上無し = 
FILTER (
    ADDCOLUMNS (
//        VALUES ( 'F_売上明細'[顧客ID] ),
        VALUES ( 'D_顧客'[顧客名] ),
        "前回の売上",
            CALCULATE (
                COUNTROWS ( 'F_売上明細' ),
                 (
                    FILTER (
                        ALL ( 'カレンダー' ),
                        'カレンダー'[月] = 9
                    )
                )
            )
    ),
    [前回の売上] = 0
)

新規顧客数(メジャー)


新規顧客数 = 
COUNTROWS (
    FILTER (
        ADDCOLUMNS (
            VALUES ( 'F_売上明細'[顧客ID] ),
            "前回の売上",
                CALCULATE (
                    COUNTROWS ( 'F_売上明細' ),
                     (
                        FILTER (
                            ALL ( 'カレンダー' ),
                            'カレンダー'[日付] < MIN( 'カレンダー'[日付])
                        )
                    )
                )
        ),
        [前回の売上] = 0
    )
)

新規顧客数(前年売上無し)


新規顧客数(前年売上無し) = 
COUNTROWS (
    FILTER (
        ADDCOLUMNS (
            VALUES ( 'F_売上明細'[顧客ID] ),
            "前回の売上",
                CALCULATE (
                    COUNTROWS ( 'F_売上明細' ),
                     (
                        FILTER (
                            ALL ( 'カレンダー' ),
                            'カレンダー'[会計年度] = SELECTEDVALUE('カレンダー'[会計年度]) -1 
                        )
                    )
                )
        ),
        [前回の売上] = 0
    )
)

Many to Many の集計


TREATAS_売上 = CALCULATE(
     [売上合計],
     TREATAS( VALUES('P_顧客と支店の組み合わせ'[支店]), 'D_支店'[支店名]),
     TREATAS( VALUES('P_顧客と支店の組み合わせ'[顧客]), 'D_顧客'[顧客ID])
     
     )

ピボットテーブルのフィルターコンテキストの中身を確認する

今回のテーマはメジャーがピボットテーブル内の各セルで受け取っているフィルターコンテキストの中身を知る方法です。

メジャーの計算では、そのメジャーが受け取って計算のネタにするためのフィルターコンテキストが重要になるのですが、それを可視化します。

なお、ピボットテーブルの中にを表示することはできないので、それぞれ文字列に変換することがポイントです。

元データの特性によって以下のアプローチを使い分けるとよいでしょう。

  • 数値や日付 ⇒ MINとMAX、COUNTROWSの組み合わせ
  • テキスト ⇒ CONCATENATEX関数で列挙

カレンダーテーブルの場合

カレンダーの日付の実体は連続した数値なので、最小値最大値を求めることができます。したがって、MIN関数MAX関数いつからいつまでの情報を取得することができます。これに加えて何日間という情報を追加します。

カレンダー範囲 =
// 日付フィルター(ここにテストしたいフィルターを定義)
VAR DateFilter =
    SAMEPERIODLASTYEAR ( 'カレンダー'[日付] ) 

// 最後の日付
VAR MaxDate =
    FORMAT ( CALCULATE ( MAX ( 'カレンダー'[日付] ), DateFilter ), "YYYY/MM/DD" ) 
// 最初の日付
VAR MinDate =
    FORMAT ( CALCULATE ( MIN ( 'カレンダー'[日付] ), DateFilter ), "YYYY/MM/DD" ) 
// 日数
VAR CountDates =
    COUNTROWS ( DateFilter ) 

// 結果の表示
RETURN
    MinDate & " - " & MaxDate & " (" & CountDates & ")"

こちらが実行結果です。

f:id:modernexcel7:20200704143822p:plain

上の例ではSAMEPERIODLASTYEAR('カレンダー'[日付])が動作を確認するためのフィルターになりますので、適宜この部分を書き換えて動作を検証することになります。

なお、DateFilterの部分に来る値はテーブル型であることに注意してください。こちらの例のSAMEPERIODLASTYEARの戻り値もテーブルとなります。
SAMEPERIODLASTYEAR 関数 (DAX) - DAX | Microsoft Docs


実際に使う際にはここで検証されたフィルターを以下のようにCALCULATE関数に読み込ませて以下のように使います。

前年売上合計 =
VAR DateFilter =
    SAMEPERIODLASTYEAR ( 'カレンダー'[日付] )
RETURN
    CALCULATE ( [売上合計], DateFilter )

テキストデータの場合

テキスト型データの場合、数値と異なり定性的なデータであるため、最大値、最小値といったものは存在しません。したがって、それぞれの値を列挙することになります。

商品名一覧 =
CONCATENATEX ( '商品', '商品'[商品名], " / " )

f:id:modernexcel7:20200704145301p:plain

このCONCATENATEXを使用した例はフィルターコンテキストの確認の他、メンバー表作成などに重宝します。

CONCATENATEX関数は、テーブルを受け取り、それを元にテキスト文字列を返す関数です。
CONCATENATEX 関数 (DAX) - DAX | Microsoft Docs

参考図書

こちらの例は以下の本のChapter 9を参考にさせていただきました。
洋書も含めDAXに関する書籍はまだ少ないですが、こちらの著者であるPhilip SeamarkさんとThomas Martensさんの本が最も分かりやすいと思います。