お客様から割賦ビジネスに関する質問がありましたので、今回はそれについて詳しく解説したいと思います。類似業態ですが、1年前にはSaaS分析に関してKPIの作成やDAXのディープダイブについて、10回にわたるシリーズで解説を行いました。
SaaSビジネスに近い特性を持つ割賦ビジネスには、保険契約やリース・レンタルなどが挙げられます。これらはいわゆるサブスクリプション(定額制)ビジネスの一種ですが、今回は保険契約を例に取り上げ、ユーザー契約からファクトデータを生成し、Power BIを使用してレポートを作成するコツについて解説します。
なお、pbixのみ必要な方は一番下のダウンロード🔗よりダウンロードしてお使いください。
サンプルデータ解説
シンプルなデータモデルですが、モデリングのイメージは以下の通りです。
- Dim契約
このテーブルは契約に関するマスタ(他のケースであれば、DimCustomer)のようなイメージであり、契約番号別に契約開始・終了・課金開始・課金終了等の情報を含みます
- Dim日付
時系列分析を行うためのMonthlyベースの日付テーブル。フィルター用に必要最小限のデータのみを格納
- Fact販売
販売実績と販売予定金額の両方を含むFactテーブル。Dim契約テーブルからPower Queryで作られたものになります
- その他
これらの情報を元に、下記のようなレポートを作っていきます(※ロゴがMicrosoft Fabricの各種アイコンになっているのに深い意味はありません)。
Mコード
今回使用するサンプルデータは非常にシンプルで、一から作る場合は以下の通りに行います。なお、各クエリを作る順番は紹介したソースコード(M言語)順となります。
まずは以下のソースコードをコピーします。
//Dim契約
let
Source = Table.FromRows(
Json.Document(
Binary.Decompress(
Binary.FromText(
"i45WclTSUXI2MDAwBNJGBkZG+ob6hqZANowLFIAwjUEyQOazOR0vF/YCGaYGBiAZpVidaCUnqClGMG0mqKaY6iPMRzPGDGyMsVJsLAA=",
BinaryEncoding.Base64
),
Compression.Deflate
)
),
let
_t = ((type nullable text) meta [Serialized.Text = true])
in
type table [
ケース = _t, 契約番号 = _t, 契約開始日 = _t, 契約終了日 = _t, 課金サイクル基準日 = _t, 課金サイクル終了日 = _t, 課金サイクル = _t, 単価 = _t, 数量 = _t
]
),
データ型の変更 = Table.TransformColumnTypes(
Source,
{
{"ケース", type text},
{"契約番号", type text},
{"契約開始日", type date},
{"契約終了日", type date},
{"課金サイクル基準日", type date},
{"課金サイクル終了日", type date},
{"課金サイクル", type text},
{"単価", Int64.Type},
{"数量", Int64.Type}
}
)
in
データ型の変更
空のPower BIデスクトップを立ち上げます。以下、順番にクエリを作っていきます。
- Power Queryエディタを立ち上げます
- クエリペインで右クリック > 新しいクエリ > 空のクエリ
- ホーム > 詳細エディター
- (コピーした)ソースコードを張り付けて「完了」
- クエリ名を変更
これを下記全てのクエリについて、ソースコードが紹介された順番通りにやっていきます。
//fnDatedif
let
fnDuration = (StartDate as any, EndDate as any, MonthCap as number, optional Interval as text) =>
let
//Assign and check the data type for each start and end value
ValueType = [
date_type_start = Type.Is(Value.Type(StartDate), Date.Type),
date_type_end = Type.Is(Value.Type(EndDate), Date.Type),
datetime_type_start = Type.Is(Value.Type(StartDate), DateTime.Type),
datetime_type_end = Type.Is(Value.Type(EndDate), DateTime.Type),
text_type_start = Type.Is(Value.Type(StartDate), Text.Type),
text_type_end = Type.Is(Value.Type(EndDate), Text.Type),
number_type_start = Type.Is(Value.Type(StartDate), Number.Type),
number_type_end = Type.Is(Value.Type(EndDate), Number.Type)
],
//全ての型に対応できるようにStarDateとEndDateを調整(例:2020/11/1, 202011, 2020110111等)
Step1 = [
text_date_start_check = if ValueType[datetime_type_start] then
StartDate
else if Text.Contains(Text.From(StartDate), "/") then
Date.From(Text.From(StartDate))
else
StartDate,
text_date_end_check = if ValueType[datetime_type_end] then
EndDate
else if Text.Contains(Text.From(EndDate), "/") then
Date.From(Text.From(EndDate))
else
EndDate,
text_date_start = Text.Start(Text.Select(Text.From(text_date_start_check), {"0".."9"}), 8),
text_date_end = Text.Start(Text.Select(Text.From(text_date_end_check), {"0".."9"}), 8),
date_start = if Text.Length(text_date_start) = 6 then
Date.From(text_date_start & "01")
else
Date.From(text_date_start),
date_end = if Text.Length(text_date_end) = 6 then
Date.From(text_date_end & "01")
else
Date.From(text_date_end)
],
//結果を計算(y = 年、m = 月、d = 日)
Step2 = [
y = Number.Round(Number.From(Step1[date_end] - Step1[date_start]) / 365, 2),
//m_original = ( Date.Year(Step1[date_end]) * 12 + Date.Month(Step1[date_end]) ) - ( Date.Year(Step1[date_start]) * 12 + Date.Month(Step1[date_start]) ) + 1,
m = if MonthCap = 0 then
(Date.Year(Step1[date_end]) * 12 + Date.Month(Step1[date_end])) - (
Date.Year(Step1[date_start]) * 12 + Date.Month(Step1[date_start])
) + 1
else
List.Min(
{
12,
(Date.Year(Step1[date_end]) * 12 + Date.Month(Step1[date_end])) - (
Date.Year(Step1[date_start]) * 12 + Date.Month(Step1[date_start])
) + 1
}
),
d = Number.From(Step1[date_end] - Step1[date_start])
],
//結果を返す
Result =
if List.Contains({"y", "m", "d", null}, Interval) then
if Interval = null then
Step2
else
Record.Field(Step2, Interval)
else
null
in
Result,
IntervalType = type text meta [Documentation.AllowedValues = {"y", "m", "d"}],
//関数自体の説明書き,設例
NewFunctionType = type function (
StartDate as any, EndDate as any, MonthCap as number, optional Interval as IntervalType
) as any meta [
Documentation.Name = "fnDatedif",
Documentation.LongDescription = "<code>StartDate</code>から<code>EndDate</code>の期間に係るデータを取り出します。MonthCap = 0はそのままの経過月数、1は上限が12ヶ月となる。"
& "<code>Interval</code>を省略した場合は、年月日の3種類すべての結果をレコードで返します。なお、StartDateとEndDateは省略可能とありますが、省略はできません。",
Documentation.Examples = {
[
Description = "2019/12/29から2020/2/29の月数を求めます(日付型で計算)",
Code = "fnDatedif(#date(2019,12,29), #date(2020,2,29), 0, ""m"")",
Result = "3"
],
[
Description = "2020/2/29から2020/3/15の月数を求めます(日付型で計算)。通常は1ヶ月未満であるが、在庫評価に使う場合、2ヶ月としてカウントされるため、用途の応じて結果の調整を行って下さい。",
Code = "fnDatedif(#date(2020,2,29), #date(2020,3,15), 0, ""m"")",
Result = "2"
],
[
Description = "2018/12/29から2020/2/29の年数を求めます(日付型で計算)",
Code = "fnDatedif(#date(2018,12,29), #date(2020,2,29), 0, ""y"")",
Result = "1.17"
],
[
Description = "2019/12/29から2020/2/29の日数を求めます(日付型で計算)",
Code = "fnDatedif(#date(2019,12,29), #date(2020,2,29), 0, ""d"")",
Result = "62"
],
[
Description = "2019/12/29から2020/2/29の月数を求めます(テキスト型で計算)",
Code = "fnDatedif(""20191229"", ""202002"", 0, ""m"")",
Result = "3"
],
[
Description = "2019年10月から2020年11月までの月数を求めます(数字型とテキスト型で計算)。MonthCapに0を指定した場合、そのままの経過月数が算出されます。",
Code = "fnDatedif(201910, ""202011"", 0, ""m"")",
Result = "14"
],
[
Description = "2019年10月から2020年11月までの月数を求めます(数字型とテキスト型で計算)。MonthCapに1を指定した場合、最大12と算出されます。",
Code = "fnDatedif(201910, ""202011"", 1, ""m"")",
Result = "12"
],
[
Description = "テキスト型でyy/m(例:20/7)yy/mm(例:20/07)のようなケースはエラーとなります。",
Code = "fnDatedif(""20/7"", ""202011"", 0, ""m"")",
Result = "エラー"
],
[
Description = "2018/12/29から2020/2/29の期間情報を求めます。<code>Interval</code>を省略します。",
Code = "fnDatedif(#date(2018,12,29), #date(2020,2,29), 0 )",
Result = "[y = 0.17, m = 15, d = 427 ]"
]
}
],
ReplaceType = Value.ReplaceType(fnDuration, NewFunctionType)
in
ReplaceType
//Fact販売_追加用
let
Source = Table.FromRows(
Json.Document(
Binary.Decompress(
Binary.FromText(
"i45WclTSUXI2MDAwBtJGBkZG+qb6hqZAti6Mb6FvCGEa65uAmc/mdLxc2AtkWBgYAElDA6XYWAA=",
BinaryEncoding.Base64
),
Compression.Deflate
)
),
let
_t = ((type nullable text) meta [Serialized.Text = true])
in
type table [
ケース = _t, 契約番号 = _t, 契約開始日 = _t, 契約終了日 = _t, 課金サイクル基準日 = _t, 課金サイクル終了日 = _t, 課金サイクル = _t, 単価 = _t, 数量 = _t
]
),
AddMonths = Table.AddColumn(Source, "月数", each fnDatedif([課金サイクル基準日], [課金サイクル終了日], 0, "m")),
MonthSequence = Table.AddColumn(AddMonths, "年月Number", each {0.. [月数] - 1}),
ExpandSequence = Table.ExpandListColumn(MonthSequence, "年月Number"),
AddYM = Table.AddColumn(ExpandSequence, "年月", each Date.AddMonths(Date.From([課金サイクル基準日]), [年月Number]), type date)
in
AddYM
//AppendFlag
"No" meta [
IsParameterQuery = true,
List = {"Yes", "No"},
DefaultValue = "No",
Type = "Any",
IsParameterQueryRequired = true
]
//Fact販売
let
Source = Dim契約,
AddMonths = Table.AddColumn(Source, "月数", each fnDatedif([課金サイクル基準日], [課金サイクル終了日], 0, "m")),
MonthSequence = Table.AddColumn(AddMonths, "年月Number", each {0.. [月数] - 1}),
ExpandSequence = Table.ExpandListColumn(MonthSequence, "年月Number"),
AddDate = Table.AddColumn(
ExpandSequence, "年月", each Date.AddMonths(Date.From([課金サイクル基準日]), [年月Number]), type date
),
Combined = if AppendFlag = "Yes" then Table.Combine({AddDate, Fact販売_追加用}) else AddDate,
データ型の変換 = Table.TransformColumnTypes(
Combined,
{
{"ケース", type text},
{"契約番号", type text},
{"契約開始日", type date},
{"契約終了日", type text},
{"課金サイクル基準日", type date},
{"課金サイクル終了日", type date},
{"課金サイクル", type text},
{"単価", Int64.Type},
{"数量", Int64.Type},
{"月数", Int64.Type},
{"年月Number", Int64.Type}
}
)
in
データ型の変換
//Dim日付
let
Source = Table.Distinct(Fact販売[[年月]]),
YMD = Table.AddColumn(Source, "カスタム", each Date.ToRecord([年月])),
Expanded = Table.ExpandRecordColumn(YMD, "カスタム", {"Year", "Month"}, {"Year", "Month"}),
MonthName = Table.AddColumn(Expanded, "MonthName", each Date.ToText([年月], "MMMM"), type text),
YMNumber = Table.AddColumn(MonthName, "YearMonthNumber", each Date.Year([年月]) * 12 + Date.Month([年月])),
YMName = Table.AddColumn(YMNumber, "YearMonthName", each Date.ToText([年月], "yyyy-MM")),
FiscalQtrNumber = Table.AddColumn(
YMName, "QtrNumber", each if Date.QuarterOfYear([年月]) = 1 then 4 else Date.QuarterOfYear([年月]) - 1
),
FiscalQtrName = Table.AddColumn(FiscalQtrNumber, "QtrName", each Text.From([QtrNumber]) & "Q"),
FiscalYear = Table.AddColumn(
FiscalQtrName, "FiscalYear", each if [Month] <= 3 then [Year] - 1 else [Year], type number
),
ChangeDT = Table.TransformColumnTypes(
FiscalYear,
{
{"年月", type date},
{"Year", Int64.Type},
{"Month", Int64.Type},
{"YearMonthNumber", Int64.Type},
{"YearMonthName", type text},
{"QtrNumber", Int64.Type},
{"QtrName", type text}
}
)
in
ChangeDT
全てのクエリを作り終えると、以下のような順番で表示されます。
読み込まないクエリ(a, b)は両方、右クリック > 「読み込みを有効にする」のチェックをOFFにしておきます。
ここからフォルダ別に各クエリをまとめていきます。Ctrlを押しながらクエリを選択し、右クリック > グループへの移動 > グループの作成。
上記①と②が完了すれば、クエリは以下のようにまとまって表示されます。
これで最初の関門、データを作ることが完了しました。具体的にデータを見る前に、fnDatedif関数について少し解説します。
Power QueryにおけるDatedif関数
ExcelはDATEDIF関数が存在します。
DATEDIF( start_date, end_date, 単位 ) は「スタート日付」、「終了日付」、「単位(年、月、日等)」を指定すれば、StartとEndの離れている期間を計算することができます。
未だに謎ですが、DATEDIFは隠された関数であり、Excelのオートコンプリート機能では表示されません。一つ上の図には表示されていますが、=datedif(と入力するまでは、DATEDIF関数の存在に気づかないことも珍しくありません。
Excelでさえ隠されているので、Power Queryではどうかと言いますと、実はPower Queryにこの関数はありません。ということで、こちらは自作することになりますが、検索エンジンで調べれば沢山出てきます。
今回はPower Query名人のたぬきさんのソースコードを参考にして、上記Power QueryにおけるDATEDIF関数(fnDatedif)を作ってみました(たぬきさん、ありがとう!)。
fnDatedifの特徴は以下の通りです。
- 説明あり
- パラメータStartDateとEndDateは日付タイプのみならず、他のタイプでも計算可能
通常のPower Query関数であれば、同じデータでないとエラーが起こりますが、上図のように、異なるタイプのデータ型でも可能な限り、結果を出力してくれます。
- Cap(上限)設定可能
関数の最初の部分に記載されていますが、「MonthCap = 0は経過月数に制限なし、1は上限が12ヶ月」となりますので、3番目のパラメーターを1に設定すれば、乖離月数は最大で12と算出されるようになります。ユースケースとしては、在庫回転期間の計算において、年間売上高を算出する際に一般的に使用される式を作成する場合、各品目別の最大値を変数として抽出するために利用することができます。
fnDatedifの詳細解説は長くなるので割愛しますが、ロジックとしては
SatrtDateの 「年×12 + 月」 - EndDateの「年×12 + 月」
という計算で乖離月数を算出しています。
Dim契約からFact販売へ
手作業管理になってしまいますが、システムからある程度のデータを抽出した状態(Dim契約)であれば、そのデータをベースにして、販売実績と販売計画の数値をFactテーブルとして再構築することができます。
イメージとしては、下図のようになります。
以下、Fact販売クエリのポイントだけ、解説をします。
ステップ「AddMonths」では、fnDatedifを使用して、「課金サイクル基準日」から「課金サイクル終了日」までに何ヶ月必要かを算出しています。2つのレコードのうち、最初のレコードはちょうど1年間(12ヵ月)、2番目のレコードは7ヶ月であることが分かります。
ここから重要ですが、課金基準日が最初に収益が発生する年月日とした場合、この1行の2022/02/01をベースに、「年月」列を作り、その中に格納するレコードとして、2022/02/01, 2022/03/01...2023/01/01になるよう、1行→計12行に展開できれば、月次ベースでの課金データが取得できるようになります。
従って、次のステップ「MonthSequence」と「ExpandSequence」はこれを実現するものであり、前者は月数をベースにシーケンス(年月Number)を作り、後者はセル内で生成されたシーケンスのリストを新しい行に展開したものとなります(下図)。
ステップ「MonthSequence」
ステップ「ExpandSequence」
これにより、元々2行だったDim契約が計19行に拡張され、「年月」という列を作成するために、「課金サイクル基準日」に対して「年月Number」をDate.AddMonths関数を使用して変換し、「年月」列を作成していきます。
毎月の課金情報が同じであるとした場合、この作り方で契約別にいくらでも行数をダイナミックに増やすことができ、Fact販売[年月]とDim日付[年月]との間にリレーションシップを構築できるようになります。
DAXメジャー
モデリングが完成しましたので、メジャーを記述していきます。基本的に冒頭で紹介したSaaS分析にて使用される各種KPIを転用すれば良いと思いますが、いくつかベーシックなものを作っていきます。
//契約数
契約数 = SUM( 'Fact販売'[数量] )
//# ユニーク契約月数
# ユニーク契約月数 = DISTINCTCOUNT( 'Fact販売'[年月] )
//# 総契約月数
# 総契約月数 = COUNTROWS( 'Fact販売' )
//契約金額All
契約金額All = SUMX( 'Fact販売', 'Fact販売'[数量] * 'Fact販売'[単価] )
//HasValue = フィルターされている年月でデータが存在するかどうかのチェック。True/Falseを返す
HasValue =
VAR _last_selling_date =
CALCULATE ( MAX ( 'Fact販売'[年月] ), ALL ( 'Dim日付' ) )
VAR _current_date =
MAX ( 'Dim日付'[年月] )
VAR _result = _current_date <= _last_selling_date
RETURN
_result
//累積契約金額
累積契約金額 =
var _filter =
FILTER(
ALL( 'Dim日付' ),
'Dim日付'[年月] <= MAX( 'Dim日付'[年月] )
)
var _result = CALCULATE( [契約金額All], _filter )
return _result
//累積契約金額_調整
累積契約金額_調整 =
var _filter =
FILTER(
ALL( 'Dim日付' ),
'Dim日付'[年月] <= MAX( 'Dim日付'[年月] )
)
var _result =
IF(
[HasValue],
CALCULATE( [契約金額All], _filter )
)
return _result
ActualFlag = INT( DATE(2022, 10, 1 ) >= 'Dim日付'[年月] )
/*ActualFlag.デモファイルでは2022年10月までが実績と想定。実際にはDATEの中身を
ActualFlag = DATE(YEAR(TODAY()), MONTH(TODAY()), 1)
に変更でダイナミックに計算*/
//契約金額Act.既に実現された実績売上
契約金額Act = CALCULATE( [契約金額All], 'Dim日付'[ActualFlag] = 1 )
//契約金額Future.まだ収益が実現していない未来の想定売上
契約金額Future = CALCULATE( [契約金額All], 'Dim日付'[ActualFlag] = 0 )
//累積契約金額Act
累積契約金額Act =
var _filter =
FILTER(
ALL( 'Dim日付' ),
'Dim日付'[年月] <= MAX( 'Dim日付'[年月] )
)
var _result = CALCULATE( [契約金額Act], _filter )
return _result
//累積契約金額Act_調整
累積契約金額Act_調整 = IF( [HasValue], [累積契約金額Act] )
//累積契約金額Future
累積契約金額Future =
var _filter =
FILTER(
ALL( 'Dim日付' ),
'Dim日付'[年月] <= MAX( 'Dim日付'[年月] )
)
var _result = CALCULATE( [契約金額Future], _filter )
return _result
//累積契約金額Future_調整
累積契約金額Future_調整 = IF( [HasValue], [累積契約金額Future] )
DateFilterFlag = INT( NOT ISEMPTY( 'Fact販売' ) )
上記メジャーを全て作り終えたら、可視化を行います。
可視化
可視化は以下のように行いました。
別URL: https://shorturl.at/ilxU1
可視化は簡単ですが、以下いくつかポイントをご紹介します。
新しいカードビジュアルの使用
Power BIのコアビジュアルの新しいジャーニーの第一弾として、新しいカードビジュアルが生まれました。2023年6月のPower BI Desktopをインストールし、「ファイル」>「オプションと設定」>「オプション」>「プレビュー機能」より、”新しいカードビジュアル”を有効にしてください。
これにより、下記ビジュアルペインより、カードビジュアルを使用することができます。
使用法は非常にシンプルで、下記のように必要なメジャーをまとめてデータ欄にDrag & Dropしていきます。
新しいカードビジュアルの優れた点は、複数のメジャーを1つのカードビジュアルに組み込むことができるだけでなく、さらに画像を挿入することもできるという点です。
さらに、1つのカードビジュアルに複数のメジャーを配置できるようになったことで、従来の複数のカードビジュアルによるパフォーマンスの低下の懸念も解消されました。
上図は、新しいカードビジュアルが登場する前の状況で、指標を表示するために8つのカードビジュアルが必要でした。しかし、新しいカードビジュアルではこれらを1つのビジュアルに統合することができます。その結果、1つのクエリとして処理されるため、Power BIのレポートのパフォーマンス改善に繋がりやすくなります。
DAXメジャーの留意点①: 正しい可視化の選択
Power BIを使用する上で、最も避けるべきことは間違った数値やミスリーディングな印象を与えてしまうことです。Power BIのエンドユーザーは、表示された結果(ビジュアルや具体的な数字など)が正しいと前提にして意思決定を行いますので、結果が間違っていた場合は深刻な問題となります。
特に、Power BIで定義したDAXメジャーは、間違った計算式でも結果が間違っていることを自動的に示してくれません。この分野に精通している人でもミスをしてしまうことがありますので、それは避けられないと言えますが、常に数字のチェックを怠らないことが重要です。
今回の例では、意図的に分かるよう、下記3つのビジュアルで解説をします。
一番上の時系列ビジュアルは契約番号別の月次契約金額=売上高になります。期間が1年の場合もあれば、それ未満もありますが、これを累積で見た場合、その下にある2つのビジュアルで表現されます。果たして、どちらのビジュアルが正しいものでしょうか?
この質問に答えるためには
- ユースケースとして、どの時点でデータを見ているか
- ビジュアルで表現したいものは何か
という2つの質問に回答する必要があります。1について、例えば現在の観測時点(メジャー「ActualFlag」より、2022年10月時点)までの「契約金額」を見たい場合、2022年11月以降は関係ないデータとなりますので、どちらのチャートも不要な情報(2022年11月以降のデータ)が載っていることになります。下図の例で言えば、縦線から右の部分は不要な情報であり、本来はメジャー「HasValue」で非表示されるべき部分となります。
ところが、「HasValue」は将来の金額も含めて各契約番号別に判定を行っているため、C0002は2022年11月まで金額計上があり、C0001は2023年1月まで金額計上がある状態で可視化されています。
割賦ビジネスでは将来の予想販売金額を可視化するかどうか?
という2番目の問いに対する回答が必要になってくるわけです。
DAXメジャーの留意点②: 将来の予想契約金額
違う角度から見てみましょう。下図は契約番号に関係なく、「累積契約金額」と「累積契約金額_調整」別の数値となります。結果的にどちらも同じ数値となり、時系列チャートは2022年11月以降に実現される未来の金額(累積契約金額Future)も含まれることになります。未来の予想契約金額が入ることで、ビジュアルとしては2022年10月で切るわけにもいかない、ということになります。
ここで契約番号をC00002にしてみます。すると右側の2つのチャートの範囲が変わりました。調整が入っていない側は2023年1月まで累積額が延びた一方、「HasValue」による調整が入っているチャートは2022年11月時点で終了したことになります(下記テーブル内の数字にも注目)。累積メジャーの特性ですが、日付テーブルが実際のデータが存在する期間よりも長い場合、その期間の最後まで同じ値が続くことがあります。これを回避したメジャーが「累積契約金額_調整」ですが、上述の通り、契約番号がフィルターされていない状態では「累積契約金額」と同じになってしまいます。どの切り口が使用されたかを認識することがポイントとなります。
話が長くなってきましたのでまとめます。
- 未実現売上を可視化する場合、取得期間の確認と混乱要素を排除すること
- 割賦ビジネスの累積チャートを作った場合、通常の月次販売額と勘違いを起こす可能性があることに留意
特に2番目のポイントは、ビジネスが好調な場合において金額が累積的に見えてしまうことがあるかもしれません。そのため、累積ベースなのか月次ベースなのかを明確に認識し、適切な可視化を行うことが重要です。例えば、以下のビジュアルは累積ではなく月次ベースの数値ですが、一見すると累積のように見えてしまいます。
https://bit.ly/3q3Y1gV
まとめ
割賦ビジネスの可視化は、Power BIを使えば非常に簡単に行えます。重要なのは、適切な期間(この場合は月次)と各期間のデータをしっかりと準備することです。Power Queryを使用すれば、データを柔軟に拡張することも可能ですので、今回の方法が役立つことを願っています。
>>ダウンロード
前回記事