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

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

本の紹介(Excelパワーピボット、Excelパワークエリ)

 

『Excelpパワークエリ データ収集・整形を自由自在にする本』 

 大手出版社による日本初のパワークエリ専門書ですが、全くの初心者から始まり、M言語の導入部分までステップアップできる内容となっています。皆さんの日常業務のデータ収集・整形をほとんど自動化できるように目指した本です。

Power BIにもそのまま利用できますので、ご活用ください。

 

 

Excelパワーピボット 7つのステップでデータ集計・分析を「自動化」する本』

 この本は、Excelでレポートを自動化するときに、パワークエリ、パワーピボット、DAXを全体としてどう使えば良いかをテーマにした本です。世界一優しいビジネスインテリジェンスの入門書を目指しました。

Power BIのベースとなった技術なので、PBIにも応用可能です。

※ 本書はExcel2016 以降の製品に対応しています。

Youtubeに『Excelパワーピボット』の解説動画をアップしています。順次コンテンツを追加予定ですで、書籍と一緒にご活用ください。

 


www.youtube.com

 

 

 

Excel Power Pivot|免VBA,也能讓Excel自動統計、分析資料 』

本書是有關在Excel中自動執行報表時如何整體使用Power Query,Power Pivot和DAX的。 簡而言之,這是一本書,旨在成為世界上最友好的商業智能入門書!

(Translated by Google 老師。Translated correclty?)

http://books.gotop.com.tw/e_ACI033100

f:id:modernexcel7:20200812142104p:plain

 

YouTubeチャンネル:Modern Excel 7

https://www.youtube.com/channel/UCFMWVF7E_UAJ5SHhnaKDN6Q


Excelパワーピボット紹介動画

 

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さんの本が最も分かりやすいと思います。

住所データクレンジングの小ネタ(都道府県処理)

今回のテーマはPowerQueryを使った住所に関する便利な小ネタです。

都道府県分割

例えば、以下のように都道府県住所が一つにまとまったデータがあるとします。
これを都道府県とそれ以降の住所に分割します。

f:id:modernexcel7:20200426233644p:plain
※ このデータは全国の県庁所在地から番地を抜いたものです。

まず住所列を右クリックし、列の分割‐数字から数字以外による分割を選択します。

f:id:modernexcel7:20200426233742p:plain

次に数式バーの数式を以下のように変更します。

変更前
= Table.SplitColumn(変更された型, "住所", Splitter.SplitTextByCharacterTransition({"0".."9"}, (c) => not List.Contains({"0".."9"}, c)), {"住所.1", "住所.2"})

変更後
= Table.SplitColumn(変更された型, "住所", Splitter.SplitTextByCharacterTransition({"都","道","府","県"}, (c) => not List.Contains({"都","道","府","県"}, c)), {"住所.1", "住所.2"})

これで無事、都道府県を区切り記号として残りの住所部分と切り離すことができました。

f:id:modernexcel7:20200426234137p:plain

元々の { "0" .. "9" } は0から9までの数字のリストであり、Splitter.SplitTextByCharacterTransition(c) => not List.Containsのところで、0から9までの数字からそれらを含まない状態に変化した時に分割するという指示を与えていました。

そこの0から9までの数字を都道府県の文字に置き換えたわけです。

同様に市区町村も分割できます。

= Table.SplitColumn(文字の移行による列の分割, "住所.2", Splitter.SplitTextByCharacterTransition({"市","区","町","村"}, (c) => not List.Contains({"市","区","町","村"}, c)), {"住所.2.1", "住所.2.2"})

f:id:modernexcel7:20200426234558p:plain

住所に限らず、何かの文字でテキストを分割したい場合は { } のリスト部分に文字の候補を並べることで分割できます。

都道府県の重複記述クレンジング

以下のように都道府県住所が別の列であるのですが、住所には都道府県が記載されていたり、いなかったりする場合です。

この場合、都道府県が記載されている住所についてはすべて都道府県部分をクリアする手順です。

f:id:modernexcel7:20200426234921p:plain

住所列を右クリックし、値の置換を選択します。

f:id:modernexcel7:20200426235048p:plain

検索する値には適当に「あ」といれ、置換後は何も入れずブランクのままOKを押します。

f:id:modernexcel7:20200426235152p:plain

次に数式バーの数式を以下のように変更します。

変更前
= Table.ReplaceValue(変更された型,"あ","",Replacer.ReplaceText,{"住所"})

変更後
= Table.ReplaceValue(変更された型,each[都道府県],"",Replacer.ReplaceText,{"住所"})

これで無事、住所側の都道府県がクリアされました。

f:id:modernexcel7:20200426235459p:plain

eachの後の [ ] の中は削除したい値の含まれた列を意味しています。

[ ] (角括弧)列の名前を指定するときに使用します。
(それに対して { } (波括弧)リスト型、または0から始まるレコード行番号を指定するときに使います。)


もしこの後で重複のないきれいなフル住所を作りたければ、都道府県住所を選んで変換(または列の追加)-列のマージを選んで結合すればOKです。

f:id:modernexcel7:20200426235653p:plain

以上、手順として簡単ですが住所データのクレンジングに役立ちそうな小技でした。

List型を使った行の動的選択

List型を使った行の動的選択

今回のテーマはList型を使った行の動的選択です。

前回のList型を使ったの動的選択では横軸であるを選択しましたが、今回は縦軸のがテーマとなります。
modernexcel7.hatenablog.com

技術的なポイント

  • 行選択リストの作成
  • List.Contains(行選択リスト, [フィルターされる列]))をTable.SelectRowsに埋め込む

実践

今回は以下のような商品テーブルを取り込む時に、Excelシート上の行選択リストを使って任意の商品カテゴリーを絞り込めるようにします。

f:id:modernexcel7:20200425232028p:plain

行選択リストを用意する

まずは行選択リストを用意します。詳細はすでに前回の記事で紹介しているので、同じ手順で作ってください。

商品カテゴリー選択ワークシートテーブル

f:id:modernexcel7:20200425230513p:plain

商品カテゴリー選択リスト

f:id:modernexcel7:20200425230641p:plain


元表に行フィルターをかける

商品クエリを開き、どの値でもいいので商品カテゴリーの中でフィルターをかけます。今回は菓子でフィルターをかけました。

f:id:modernexcel7:20200425231250p:plain

次に数式バーの式を以下のように書き換えます。

変更前:
= Table.SelectRows(変更された型, each ([商品カテゴリー] = "菓子"))

変更後:
= Table.SelectRows(変更された型, each List.Contains(商品カテゴリー選択, [商品カテゴリー]))

ここで登場したList.Contains関数は第1引数としてリストを受け取り、第2引数の値がそのリストの中にあればtrueを、なければfalseを返します。

つまり、商品クエリのテーブルの1行1行に対して、商品カテゴリーの値が商品カテゴリー選択 リストに含まれる場合はtrueを返し、その結果が Table.SelectRows関数に渡され、行の取捨選択が行われています。

List.Contains - PowerQuery M | Microsoft Docs

ここまで来たら、閉じて読み込むを実行して結果を確認します。

無事、先ほど作成した商品カテゴリー選択リストの値で、商品テーブルを絞り込むことができました!

f:id:modernexcel7:20200425231425p:plain

他の選択条件にして商品テーブルを再読み込みし、結果を確認してみてください。

f:id:modernexcel7:20200425232623p:plain

List型を使った列の動的選択

List型を使った列の動的選択

今日のテーマはList型のデータを用いた列の動的列選択です。

PowerQueryでは、列の削除を行うことで、データソースから読み込んだデータのうち必要な列だけを残すことができます。

f:id:modernexcel7:20200425140040p:plain


今回のゴールは、元表として以下のような5つの列があった場合、

f:id:modernexcel7:20200425140820p:plain

以下のような列選択テーブルでYesと設定された列を元にして

f:id:modernexcel7:20200425135915p:plain

選択された列だけ拾ってくることです。(動的選択

f:id:modernexcel7:20200425140516p:plain

技術的なポイント

  • Table.SelectColumnsで残す列を選ぶ
  • 選択テーブルをList型に変換する
  • { } で囲まれたList部分を差し替える

実践

いったん通常の手順で列を選択した後、List部分のみを差し替えます。

通常の手順で列を選ぶ

まずは動的選択によらず、通常の手順で列を選択します。

元となるテーブル上にカーソルを置きます。

f:id:modernexcel7:20200425140820p:plain

データーデータの取得と変換-テーブルまはた範囲からをクリック。

f:id:modernexcel7:20200425141122p:plain

PowerQueryエディターが開くので、Ctrlキーを押しながら残したい列を選びます。

f:id:modernexcel7:20200425141358p:plain

列の削除ー他の列の削除を選択します。

f:id:modernexcel7:20200425140040p:plain

すると、以下のように選択された列だけが残ります。

f:id:modernexcel7:20200425141727p:plain

Table.SelectColumns関数について

この時、数式バーは以下の通りです。

= Table.SelectColumns(変更された型,{"列A", "列B", "列C"})

Table.SelectColumns - PowerQuery M | Microsoft Docs

Table.SelectColumnsは文字通り「列を選択する」、つまり残す列を選ぶときに使われます。今回は、列A、列B、列Cを残したかったので、数式の後半の変数部分にこれらの列が指定されています。

なお、列の削除を行ったときはTable.RemoveColumns関数が呼ばれます。

Table.RemoveColumns - PowerQuery M | Microsoft Docs

これは逆に選択した列を削除するときに使われます。

List型データについて

次に関数の第二引数部分を確認します。

= Table.SelectColumns(変更された型,{"列A", "列B", "列C"})

前半の変更された型は、直前のステップで作成されたテーブルのことで、後半の{"列A", "列B", "列C"}は残す列名を並べている部分です。この { } (中括弧)で囲まれた部分は、PowerQueryでいうところのList型と呼ばれるデータ型の部分です。

List型は複数のデータを並べるときに使用する型で、何があるか?どういう順番か?という2点が重要なポイントとなります。

ワークシートテーブルに読み込む

ここまでできたら、いったん閉じて読み込むを実行します。

f:id:modernexcel7:20200425143330p:plain

これで三つの列が選択されたテーブルが読み込まれました。

f:id:modernexcel7:20200425143416p:plain

別なワークシートテーブルを使って列名を選択する

次に別な管理用ワークシートテーブルを使って、残す列を指定していきます。

選択列名Listを作る

まずは以下のように残す列名を並べたワークシートテーブルを作り、名前を選択列名Listとします。

f:id:modernexcel7:20200425143618p:plain

このテーブルの上にカーソルを置いて、テーブルまたは範囲からをクリックし、PowerQueryエディターを開きます。

次に列名右クリックし、ドリルダウンを選びます。

f:id:modernexcel7:20200425144055p:plain

すると、テーブルがListに変換されます。

f:id:modernexcel7:20200425144259p:plain

数式バーを見ると、以下のように記載されています。

= 変更された型[列名]

これは直前のステップで作成された「変更された型」テーブルのうち、列名だけにナビゲーション(移動)した記述になります。(ちなみに行を切り出すとRecord型になります。)

これで閉じて読み込む-接続の作成のみを実行します。

クエリと接続ペインで作成された選択列名Listクエリを確認すると元表クエリとは異なるアイコンが表示されていますが、これがList型のアイコンとなります。

f:id:modernexcel7:20200425144600p:plain

Table.SelectColumnsのList部分を差し替える

今度は元表クエリに戻り、Table.SelectColumnsの列名部分を 選択列名Listに差し替えます。

元表クエリダブルクリックし、PowerQueryエディターを開いたら、削除された他の列のステップの数式の{ } で囲まれた部分をそっくり入れ替えます。

変更前:
= Table.SelectColumns(変更された型,{"列A", "列B", "列C"})

変更後:
= Table.SelectColumns(変更された型,選択列名List)

結果、同じ列が残っていればOKです。

f:id:modernexcel7:20200425171416p:plain

これで選択列をExcelのワークシートで管理することができるようになりました。

試しにワークシートの選択列名Listの最終列を列Eに変更して、元表クエリを再読み込みしてみます。

f:id:modernexcel7:20200425171708p:plain

すると、選択列名Listの内容を反映して、列Cの代わりに列Eが読み込まれました。

f:id:modernexcel7:20200425171843p:plain

選択列名ListからYes/Noで列を選ぶ

今度は、先ほどの手順をもう少し進化させて選択列名ListYes/Noの選択列を追加して、列の表示/非表示を切り替えます。

まずワークシートテーブルの選択列名Listにすべての列名を追加し、それぞれの選択列にYes/Noの選択値を入力します。

f:id:modernexcel7:20200425172612p:plain

選択列のYes/Noは2択なので、データの入力規則で制限をかけておくとよいでしょう。

f:id:modernexcel7:20200425172656p:plain

f:id:modernexcel7:20200425172752p:plain

選択列の追加とYes, Noの入力が終わったら、選択列名Listクエリをダブルクリックして、PowerQueryエディターを開きます。

f:id:modernexcel7:20200425172950p:plain

List型に変換する前の変更された型ステップに移動し、選択列を右クリックYesのみにフィルターします。

f:id:modernexcel7:20200425173841p:plain

ここまで来たら設定完了です。元表クエリに戻り、閉じて読み込むを実行します。結果がYesと選択された列のみになったらOKです。

f:id:modernexcel7:20200425174106p:plain

試しに列選択を変更して、元表クエリを再実行してみましょう。

f:id:modernexcel7:20200425174456p:plain

無事、選択された列のみが読み込まれています。

列名の並び替えもListで管理

おまけですが、列の順番も同じようにワークシートテーブルで管理できます。

以下のように選択列名Listテーブルの行を入れ替えて、列B、列Aの順番にしてから元表クエリを再読み込みします。

f:id:modernexcel7:20200425190010p:plain

すると、元表クエリの結果にも順番が反映されています。

列の順番を変更するには通常、Table.ReorderColumns関数(列の並び替え)を使うのですが、Table.SelectedColumns関数ならばList型データの順番も反映してくれるようです。

Table.ReorderColumns - PowerQuery M | Microsoft Docs

パワーピボットのドリルスルー

今回のテーマはパワーピボットを使用した場合のドリルスルーです。

具体的にはピボットテーブルを作成するときにこのブックのデータモデルを使用するを選んだケースのことです。

ピボットテーブルの選択
f:id:modernexcel7:20200418201503p:plain

分析するデータを選択してください。
f:id:modernexcel7:20200418201443p:plain


ドリルスルーについて

一般的に集計の範囲を小さくすることをドリルダウンと言いますが、集計対象の明細レコードをすべて表示することドリルスルーと言います。

Excelのピボットテーブルでドリルスルーを行うには以下二つの方法があります。

  • 集計値セルにカーソルを置き、ダブルクリック
  • 集計値セルにカーソルを置き、右クリックで詳細の表示

f:id:modernexcel7:20200418203223p:plain

すると新しいシートが作られ、集計対象の明細レコードが表示されます。

f:id:modernexcel7:20200418202014p:plain


ドリルスルー上限値の変更

データモデルから作ったピボットテーブルでドリルスルーを行った場合、デフォルトの明細数の上限値は1,000行です。

f:id:modernexcel7:20200418202014p:plain

この上限値はThisWorkbookDataModelプロパティで変更します。

以下の手順で明細行数の上限値を変更します。

データメニューのクエリと接続をクリック

f:id:modernexcel7:20200418203646p:plain

クエリと接続ペインが表示されたら接続を選択

f:id:modernexcel7:20200418203745p:plain

ThisWorkbookDataModelを右クリックし、プロパティを選択

f:id:modernexcel7:20200418204523p:plain

接続のプロパティ画面が表示されたら、OLAPドリル スルー取得するレコードの最大数を変更してOKをクリック。

f:id:modernexcel7:20200418203828p:plain

これでドリルスルーの最大行数が変更されました。

f:id:modernexcel7:20200418203847p:plain

Excelのワークシートで表示できる行数は最大100万行超なので、多くとも100万行にしておくのが無難です。

ドリルスルー対象のテーブル

通常のピボットテーブルでは明細は一つのテーブルしかないので選択肢はありませんが、データモデルを使用した場合は複数のテーブルが登場します。

ドリルスルー対象となる明細はメジャーテーブルで選びます。

メジャーの編集画面でテーブル名を確認すると、現在はF_売上明細となっています。

f:id:modernexcel7:20200418205616p:plain

これをカレンダーに変更します。

f:id:modernexcel7:20200418205559p:plain

これで再びドリルスルーを実行すると、今度は集計範囲のカレンダーテーブルの明細が表示されます。

f:id:modernexcel7:20200418205534p:plain


結構、重要な設定だと思うのですがまとまって紹介されていないので、今回記事にしました。

列のピボットによる2点データ比較

目標

今回のテーマは同じフォーマットで異なる2点間のデータがある場合、それらをユニークキーでつないで項目ごとの比較を自動化することです。
用途として以下のようなケースが考えられます。

  • ERPや営業支援システム導入プロジェクトの移行データの検証
  • 2点間のマスタデータを比較して項目ごとの変更点を調べる

例えば以下のようなデータがあったとします。

前回のデータ
f:id:modernexcel7:20200307153038p:plain

今回のデータ(変更点を太字にしています)
f:id:modernexcel7:20200307113156p:plain


この2点間のデータをユニークキー(ここではID)を元に比較し、項目ごとの差異を自動検出して表にします。

f:id:modernexcel7:20200307114637p:plain

技術的なポイント

ユニークキーと項目の組み合わせを作り、前回と今回の値をペアにして横に並べて比較します。

そのためには、ピボット解除列のピボットを駆使して表を組み替える必要があります。

その時、パワークエリでは同じ列名が登録されると末尾に「.1]が付くのでそれを利用して並び替えることがポイントです。

  • キー項目でマージ
  • マージしたテーブルを展開するときにユニークキーを除き、またプレフィックスを使わない。
  • ユニークキー以外をピボット解除
  • ユニークキーと属性(項目名)でソート
  • インデックス列を追加して列のピボットの準備
  • 列のピボットで対比表を作る
  • 条件列で差異のあるレコードのみに絞る

実践

比較する二つのテーブルのマージ

まず、前回と今回のデータをそのまま読み込むクエリを作り、結合(クエリのマージ)の準備をします。

f:id:modernexcel7:20200307114753p:plain

今回は接続専用としていますが、データをExcelワークシート上で確認したい場合は、テーブルに読み込んでおくとよいでしょう。

次に、クエリのマージを行います。

データの取得 → クエリの結合 → マージ
f:id:modernexcel7:20200307115049p:plain

前回今回のクエリを選択し、ID列を照合列としてマージします。
結合の種類については今回はユニークキーのあるもののみを対象にしていますので、内部(一致する行のみ)を選択します。

f:id:modernexcel7:20200307115302p:plain

二つのテーブルがIDでマージされた結果が表示されますので、今回列の右の矢印をクリックして展開します。

f:id:modernexcel7:20200307115659p:plain

展開の際はIDのチェックを外し、また元の列名をプレフィックスとして使用します。のチェックを外してください。

f:id:modernexcel7:20200307115803p:plain


こうすることで展開された項目名の末尾にすべて「.1」が追加されました。あとで並び替えをするときにこれを利用します。

f:id:modernexcel7:20200307115945p:plain

ピボット解除&列のピボットで表の組み替え

次にID列を選択し、右クリックを押してその他の列のピボット解除を実行します。

f:id:modernexcel7:20200307120239p:plain

こうするとID列ごとに、名前と名前.1、住所と住所.1・・・のペアが並びます。

続いてID右上の▼をクリックして昇順で並び替え、さらに属性の▼をクリックして昇順で並び替えで二つの項目で並び替えを行います。
二つの項目を使って並び替えを行う場合、それぞれの項目に優先順位として1 , 2 が表示されます。

f:id:modernexcel7:20200307121108p:plain

これで前回と今回の同じ項目のペアが上下に並びました。これからそのペアを横に並べます

まず列のピボットの相棒、インデックス列を追加します。開始は0でも1でも構いません。今回は0で進めます。

列の追加 → インデックス列

右端に0から始まる連番が追加されました。

f:id:modernexcel7:20200307142504p:plain

続いてインデックス列を選択した状態で以下の手順で0,1のリズムを作ります。
項目が縦にペアで並んでいるので、それぞれに番号を割り当てます。

列の追加 → 数値から → 標準 → 剰余 

f:id:modernexcel7:20200307142858p:plain

2を入力します。

f:id:modernexcel7:20200307184935p:plain

すると、先の名前と名前.1、住所と住所.1・・・のペアの対応する形でそれぞれ、01が割り当てられます。

f:id:modernexcel7:20200307143128p:plain

ここまで来たら、属性「.1」はもう使用しないので値の置換で消します。
属性列を選び、右クリック → 値の置換検索する値「.1」置換後何も入力しないOKを押します。

f:id:modernexcel7:20200307143612p:plain

これで名前が統一されました。

つづいてインデックス列も用済みなので列の削除で削除します。インデックス列を選択して列の削除を行います。

f:id:modernexcel7:20200307143838p:plain

これで列のピボットの準備ができました。

列のピボットでは最初に、ヘッダー(列名)になる列を選択します。
今回は先ほどインデックスを元に作成した剰余を選択します。この0と1が列名として横に並ぶことになります。

f:id:modernexcel7:20200307144236p:plain

、変換 → 列のピボットを開き、詳細オプションを開いて、値の集計関数集計しないにしてOKを押します。

f:id:modernexcel7:20200307144028p:plain

これで比較項目が左右に並びました!

f:id:modernexcel7:20200307144417p:plain

仮にあてた列名(01)をそれぞれ前回今回に変更します。

f:id:modernexcel7:20200307144519p:plain

変更の有り無しチェック

最後に仕上げの比較です。

列の追加 → 条件列で以下のように入力して前回今回の変更をチェックします。

f:id:modernexcel7:20200307144613p:plain

これでID×項目単位での前回と今回の比較ができました。

f:id:modernexcel7:20200307144708p:plain

最後に変更チェックフィルターを掛け、変更のあるもの(ちがう)だけを表示します。

f:id:modernexcel7:20200307144830p:plain

f:id:modernexcel7:20200307144801p:plain

変更チェック列はもう使用しないので列の削除で削除します。

f:id:modernexcel7:20200307144909p:plain

これで閉じて読み込むを実行して出来上がりです。

f:id:modernexcel7:20200307144948p:plain

関数化してエクスポート&共有

今回のクエリを再利用するため、関数化エクスポートによるチーム内での共有を行います。

クエリ関数化

まずは以下の手順で関数化します。

先ほどのクエリを開き、詳細エディターを開いてletの前に以下の文を追加してください。
関数の引数として宣言されているtable型の前回今回はそれぞれ、先ほど作ったクエリの最初のマージのテーブル名と一致していることに注意ください。

(前回 as table, 今回 as table) as table =>

これで、前回と今回のテーブルを選択するとその差分をピックアップする関数になりました。

f:id:modernexcel7:20200307151231p:plain

試しに前回、今回にテーブルを選択し、呼び出しをクリックしてみてください。

f:id:modernexcel7:20200307151518p:plain

新しいクエリが呼び出された関数として作成され、結果が表示されました。

f:id:modernexcel7:20200307151608p:plain

クエリのエクスポート

続いて作成した関数をエクスポートしてチーム内で共有します。

PowerQuery エディターを閉じて、クエリと接続ペインで先ほど作った関数を右クリックし、接続ファイルのエクスポートを実行し、共有フォルダに保存します。

f:id:modernexcel7:20200307151801p:plain

f:id:modernexcel7:20200307152027p:plain

クエリのインポート

共有フォルダ内のクエリのエクスポートファイルを別な人がインポートするときは既存の接続を選びます。
f:id:modernexcel7:20200307152131p:plain

参照を押して保存されている場所を開き、ファイルを選択します。

f:id:modernexcel7:20200307152354p:plain

インポート先ですが、今回は関数なので接続の作成のみを選択します。
f:id:modernexcel7:20200307152746p:plain

これでクエリがインポートされました。

f:id:modernexcel7:20200307152834p:plain

エクスポートファイルはチーム内で共有するなどしてデータとプロセスの統一を図るとよいでしょう。

ソースコード

今回のソースコード

以下が今回のクエリのコードとなります。詳細エディターにコピペすればそのまま使えます。

(前回 as table, 今回 as table) =>
let
// 前回と今回テーブルのマージ
ソース = Table.NestedJoin(前回, {"ID"}, 今回, {"ID"}, "今回", JoinKind.Inner),
#"展開された 今回" = Table.ExpandTableColumn(ソース, "今回", {"名前", "住所", "売上", "利益"}, {"名前.1", "住所.1", "売上.1", "利益.1"}),

// 表の組み換え
ピボット解除された他の列 = Table.UnpivotOtherColumns(#"展開された 今回", {"ID"}, "属性", "値"),
並べ替えられた行 = Table.Sort(ピボット解除された他の列,{{"ID", Order.Ascending}, {"属性", Order.Ascending}}),
追加されたインデックス = Table.AddIndexColumn(並べ替えられた行, "インデックス", 0, 1),
挿入された剰余 = Table.AddColumn(追加されたインデックス, "剰余", each Number.Mod([インデックス], 2), type number),
置き換えられた値 = Table.ReplaceValue(挿入された剰余,".1","",Replacer.ReplaceText,{"属性"}),
削除された列 = Table.RemoveColumns(置き換えられた値,{"インデックス"}),
ピボットされた列 = Table.Pivot(Table.TransformColumnTypes(削除された列, {{"剰余", type text}}, "ja-JP"), List.Distinct(Table.TransformColumnTypes(削除された列, {{"剰余", type text}}, "ja-JP")[剰余]), "剰余", "値"),
#"名前が変更された列 " = Table.RenameColumns(ピボットされた列,{{"0", "前回"}, {"1", "今回"}}),

// 変更有り無しチェック
追加された条件列 = Table.AddColumn(#"名前が変更された列 ", "変更チェック", each if [前回] = [今回] then "同じ" else "ちがう"),
フィルターされた行 = Table.SelectRows(追加された条件列, each ([変更チェック] = "ちがう")),
削除された列1 = Table.RemoveColumns(フィルターされた行,{"変更チェック"})
in
削除された列1

汎用化したソースコード

テーブルの列数の変化に耐えられるように汎用化したソースコードです。
パラメーターとしてユニークキーの指定が必要になります。

(前回 as table, 今回 as table, ユニークキー as text) =>
let

// パラメータのセット
// 前回 = 前回,
// 今回 = 今回,
// ユニークキー = "ID",

// 前回と今回テーブルのマージ
ソース = Table.NestedJoin(前回, {ユニークキー}, 今回, {ユニークキー}, "今回", JoinKind.Inner),

// ユニークキーを除いた列名リストを作成
列名一覧 = List.RemoveMatchingItems(Table.ColumnNames(前回), {ユニークキー}),

// マージ後列名リストを作成 (List.Generate版)
// マージ後列名一覧 = List.Generate(
// () => [x=0, y = 列名一覧{0} & ".1"],
// each [x] < List.Count(列名一覧) ,
// each [x = [x] + 1, y= 列名一覧{x} & ".1"],
// each [y]
// ),
// マージ後列名リストを作成 (List.Transform版)
マージ後列名一覧 = List.Transform(列名一覧, each _ &".1"),

// テーブルをマージ
#"展開された 今回" = Table.ExpandTableColumn(ソース, "今回", 列名一覧, マージ後列名一覧),

// 表の組み換え(ユニークキーでピボット解除)
ピボット解除された他の列 = Table.UnpivotOtherColumns(#"展開された 今回", {ユニークキー}, "属性", "値"),

// 表の組み換え(列のピボット準備)
並べ替えられた行 = Table.Sort(ピボット解除された他の列,{{ユニークキー, Order.Ascending}, {"属性", Order.Ascending}}),
追加されたインデックス = Table.AddIndexColumn(並べ替えられた行, "インデックス", 0, 1),
挿入された剰余 = Table.AddColumn(追加されたインデックス, "剰余", each Number.Mod([インデックス], 2), type number),
置き換えられた値 = Table.ReplaceValue(挿入された剰余,".1","",Replacer.ReplaceText,{"属性"}),
削除された列 = Table.RemoveColumns(置き換えられた値,{"インデックス"}),

// 表の組み換え(列のピボット)
ピボットされた列 = Table.Pivot(Table.TransformColumnTypes(削除された列, {{"剰余", type text}}, "ja-JP"), List.Distinct(Table.TransformColumnTypes(削除された列, {{"剰余", type text}}, "ja-JP")[剰余]), "剰余", "値"),
#"名前が変更された列 " = Table.RenameColumns(ピボットされた列,{{"0", "前回"}, {"1", "今回"}}),

// 変更有り無しチェック
追加された条件列 = Table.AddColumn(#"名前が変更された列 ", "変更チェック", each if [前回] = [今回] then "同じ" else "ちがう"),
フィルターされた行 = Table.SelectRows(追加された条件列, each ([変更チェック] = "ちがう")),
削除された列1 = Table.RemoveColumns(フィルターされた行,{"変更チェック"}),

// 項目を列に再び移動してフォーマット調整
結合された列 = Table.CombineColumns(Table.TransformColumnTypes(削除された列1, {{"前回", type text}, {"今回", type text}}, "ja-JP"),{"前回", "今回"},Combiner.CombineTextByDelimiter(" -> ", QuoteStyle.None),"値"),
ピボットされた列1 = Table.Pivot(結合された列, List.Distinct(結合された列[属性]), "属性", "値"),

// 仕上げの列の並び替え(差異のあった列を元の順番に戻す)
最終列名一覧 = List.Intersect({列名一覧, Table.ColumnNames(ピボットされた列1)}),
比較結果 = Table.ReorderColumns(ピボットされた列1, {ユニークキー} & 最終列名一覧)
in
比較結果

Excel方眼紙のデータ取得とカスタム関数

目標

今回のテーマはネ申Excelとも呼ばれるExcel方眼紙のデータ化(テーブル化)です。

Excel方眼紙とは、各セルを正方形にし、セルの結合を駆使して項目を並べた入力フォームのことです。

f:id:modernexcel7:20191218174737p:plain

そのデータ化の煩わしさから悪名高いExcel方眼紙ですが、「どの座標にどのデータがある」というフォーマットさえ決まっていれば、パワークエリでデータ・テーブル化することは難しくありません。

技術的なポイント

基本的にどの座標に何のデータがあるかを調べ、それを拾ってくるという流れです。

  • Excel方眼紙の各項目の座標を知る
  • 行・列の座標で指定したセルの値を取得する
  • テーブル、行・列座標からセルの値を取得するカスタム関数を作る
  • 複数Excelまとめて取り込み、カスタム関数でデータを取得する

実践

今回は、単に一つのExcel方眼紙のデータを取得するだけでなく、以下のように一つのフォルダ内の複数のファイルをまとめてデータ化するシナリオです。

f:id:modernexcel7:20191218194852p:plain

Excel方眼紙の項目の座標を知る

まず対象のExcel方眼紙のデータ項目を調べます。サンプルとしてファイルを一つ開いてください。
※ 今回は分かりやすくするため、入力セルの色を変えています。

f:id:modernexcel7:20191218195130p:plain


入力項目を見ると、右上に入力日、中央に氏名生年月日住所の4つがあります。
ありがちですが、入力日は和暦、月、日がそれぞれ別々の入力セルになっています。

次にそれぞれの座標を数字で取得します。行はもともと数字ですが列がアルファベットなので一工夫します。

ファイル → オプション → 数式

f:id:modernexcel7:20191218201424p:plain

数式の処理 → R1C1参照形式を使用するにチェック

f:id:modernexcel7:20191218201238p:plain

これで列も数字表記になりました。

f:id:modernexcel7:20191218201542p:plain

ここから各項目の座標を調べます。

セルの結合が行われた場合、結合されたセルの左上のセルにデータが入るので、左上の座標だけ抑えればOKです。

なお、入力項目にカーソルを当てると行・列の色がハイライトされるので便利です。
以下の例では、行・列の一番小さい数字を取ってくればよいので、行:7、列:10の座標となります。

f:id:modernexcel7:20191218202400p:plain


それぞれの項目の座標を調べると以下のようになります。

Excel座標

項目
入力日(和暦) 2 24 1
入力日(月) 2 27 12
入力日(日) 2 30 18
氏名 7 10 Excel 太郎
生年月日 7 25 1970年1月1日
住所 9 10 東京都 XX区 12345

行・列の座標で指定したセルの値を取得する

次に行列の座標を指定すると、そこの値を取ってくるクエリを作ります。
実際のデータを使う前に、簡単なサンプルテーブルを自作して実験を行います。

データ取り込み用の新しいExcelファイルを作り、以下の手順で空のクエリを開きます。

データ → データの取得と変換 → その他のデータソースから → 空のクエリ

f:id:modernexcel7:20191218204346p:plain

Power Queryエディターが開いたら数式バーに以下のように入力して、実験用テーブルをマニュアル作成します。

= #table({"列1", "列2", "列3"}, {{11, 12, 13}, {21, 22, 23}, {31, 32, 33}})

実験用テーブルが作成されました。

f:id:modernexcel7:20191218205409p:plain

今度は、試しに列23行目をクリックします。
すると、以下のように画面下にセルの値が32と表示されます。

f:id:modernexcel7:20191218224259p:plain

次に同じセルを右クリックし、ドリルダウンを選択します。

f:id:modernexcel7:20191218224526p:plain

するとテーブル表示が消え、ドリルダウンされた一つの値だけが表示されます。

f:id:modernexcel7:20191218224559p:plain

ついでに数式バーを見ると以下の式が表示されています。

= ソース{2}[列2]

ここで数式を見ると、三つの部分があることが分かります。

1)ソース 【テーブル】
  これは直前のステップで作成されたテーブルのことです。

2){2} 【行】
  パワークエリではテーブルの行を中カッコ { } で指定します。
  また、パワークエリでは数字のカウントは常に0から始まります。
  したがって数字は2ですが3行目のことです。

3)[列2] 【列】
  パワークエリでは各カッコ [ ] でテーブルの列を指定します。
  したがって、列2のことです。

つまり、これら3つの情報を渡すことができれば、Excel方眼紙の特定の項目を取得できそうです。

カスタム関数を作る

さて、ここまで分かったのでこのクエリを徐々に関数化してゆきます。

関数化するということは、クエリの外から入ってきたデータを元に、別な何かをアウトプットするということです。ここで言う別な何かとは、Excel方眼紙の特定の項目に他なりません。

クエリの名前の変更

まず関数化するにあたって、クエリの名前を変更します。

右側のクエリの設定プロパティ名前fnGetValueに変更します。ちなみにfnはfunctionから来ています。

f:id:modernexcel7:20191218231123p:plain

行のパラメーター化

先に述べた3つの要素をクエリの外から持ってこれるように(パラメーター化)します。

こういう場合はつねに一番簡単なところから始めます。
を見ると{2}とあるので、この数字をクエリの外から入ってきた数字と置き換えられるか実験します。

ホーム→詳細エディター詳細エディターを開き、クエリの内容を以下赤字のように変更します。

(myRow as number) =>
let
    ソース = #table({"列1", "列2", "列3"}, {{11, 12, 13}, {21, 22, 23}, {31, 32, 33}}),
    セル値 = ソース{myRow}[列2]
in
    セル値

つまり、
1) (myRow as number) =>でクエリをmyRowという数字パラメーターを受け取る関数にし、
2) そこで受け取った数字をmyRowとして行の指定に使用しています。

ここまで変更して完了を押すと、表示が以下のように変わります。

f:id:modernexcel7:20191218231934p:plain

myRowとして入力ボックスが表示され、その下に呼び出しボタンが表示されます。

試しにmyRow1と入力して呼び出しを押してください。

f:id:modernexcel7:20191221063741p:plain

すると、新しく自動作成されたクエリから、関数fnGetValueが指定されたパラメーターで呼び出され、2行目の値を取得することに成功しました!

f:id:modernexcel7:20191218232112p:plain

列のパラメーター化

次に列をパラメーター化します。

前回は行だったので数字の指定だけで十分でしたが、今回は列という文字の指定なので一工夫が必要です。つまり、座標としての数字を指定し、そこから何番目の列かという形で列名を指定します。

左側のクエリペインfnGetValueを選択し、詳細エディターを開いてください。

f:id:modernexcel7:20191221065132p:plain


詳細エディターが開いたら、以下のように変更します。

(myRow as number, myCol as number) =>
let
    // テーブルの作成
    ソース = #table({"列1", "列2", "列3"}, {{11, 12, 13}, {21, 22, 23}, {31, 32, 33}}),

    // 列の指定
    列名リスト = Table.ColumnNames(ソース),
    列名 = 列名リスト{myCol},
    列リスト = Table.Column(ソース, 列名),

    // 行の指定

    セル値 = 列リスト{myRow}
in
    セル値


今回はクエリが長くなってきたので//でコメント行を入れました。//の後は処理されないので、わかりやすいコメントを入れましょう。

列の指定は以下の手順で行っています。

1) 列名リスト = Table.ColumnNames(ソース): テーブルの列名一覧を取得
   {"列1", "列2", "列3"}

2) 列名 = 列名リスト{myCol}:        列名リストのX番目の文字を取得
   {"列1", "列2", "列3"} (myCol = 1 の場合)

3) 列リスト = Table.Column(ソース, 列名):   列名の値を縦にリスト化
   {12, 22, 32}


行の指定は上の列リストを使って以下のようになります。

1) セル値 = 列リスト{myRow}
   {12, 22, 32} (myRow = 2 の場合)


それでは以下のように入力して呼び出しを実行してみてください。

myRow = 2 (3行目)
myCol = 1 (2列目)

結果が32になれば成功です。

f:id:modernexcel7:20191221073846p:plain

無事、32となり、3行2列目の座標の値をゲットできました!

f:id:modernexcel7:20191221074003p:plain

テーブルのパラメーター化

最後にテーブルをパラメーター化して完成です。

まず、現在カスタム関数内にある実験用テーブルを別なクエリとして作成します。

画面右上から以下の手順で空のクエリを開きます。

新しいソース → その他のソース → 空のクエリ

f:id:modernexcel7:20191221094036p:plain


数式バーに以下のように入力して、実験用テーブルを作ります。

= #table({"列1", "列2", "列3"}, {{11, 12, 13}, {21, 22, 23}, {31, 32, 33}})

f:id:modernexcel7:20191221094315p:plain

クエリの名前を実験用テーブルにします。

f:id:modernexcel7:20191221094602p:plain

再び左側のクエリペインよりfnGetValueを選択します。
f:id:modernexcel7:20191221094749p:plain

同じく詳細エディターを開き、以下のように変更します。

(myTable as table, myRow as number, myCol as number) =>
let
    // テーブルの作成
    ソース = myTable,

    // 列の指定
    列名リスト = Table.ColumnNames(ソース),
    列名 = 列名リスト{myCol},
    列リスト = Table.Column(ソース, 列名),

    // 行の指定
    セル値 = 列リスト{myRow}
in
    セル値

テーブルの部分をmyTableとしてパラメーター化し、letの中のソースの値と差し変えました。差し替えただけなので、後続の処理はそのままで結構です。

今度はmyTableもパラメーターに追加されているので、以下のように選択し呼び出しを押します。

myTable = 実験用テーブル
myRow = 2 (3行目)
myCol = 1 (2列目)

f:id:modernexcel7:20191221095427p:plain

無事32の結果が表示されました!
f:id:modernexcel7:20191221095535p:plain

※ 2019/12/27 この座標を-1するアイデア『いちばんやさしいExcelピボットテーブルの教本 人気講師が教えるデータ集計が一瞬で終わる方法』著者 羽毛田 睦土先生のアドバイスを受けて追加した箇所です。

これでも関数の機能としては十分ですが、もう一歩手を加えてさらに使い易くします。

パワークエリでは数字はすべて0開始なので、1開始のExcel座標はすべて-1して渡さないといけません。それだったら関数の中で-1してしまえばよいというわけで、以下のように修正します。

(myTable as table, myRow as number, myCol as number) =>
let
    // テーブルの作成
    ソース = myTable,

    // 列の指定
    列名リスト = Table.ColumnNames(ソース),
    列名 = 列名リスト{myCol - 1},
    列リスト = Table.Column(ソース, 列名),

    // 行の指定
    セル値 = 列リスト{myRow - 1}
in
    セル値

今度は以下のパラメータで関数を実行します。

myTable = 実験用テーブル
myRow = 3 (3行目)
myCol = 2 (2列目)

f:id:modernexcel7:20191227234228p:plain

今回はExcel座標を渡しても32の結果が得られました!

f:id:modernexcel7:20191227234317p:plain


これで関数は完成です。左側のクエリペインの呼び出された関数はもういらないので削除します。
Ctrlキーを押しながらそれぞれクリックし、右クリック削除を選びます。

f:id:modernexcel7:20191221095723p:plain

これですっきりしました。実験用テーブルは消しても構いませんが、変更や調査の為に残しておくと便利です。

f:id:modernexcel7:20191221095753p:plain

複数Excelファイルの一括取り込みとデータ化

いよいよ仕上げです。次はExcel方眼紙をまとめて読み込みます。
以下の記事を参考に複数のExcelファイルを読み込みます。

modernexcel7.hatenablog.com

まず、データソースとしてフォルダーを指定します。

新しいソース → ファイル → フォルダー

f:id:modernexcel7:20191221100306p:plain

Excel方眼紙のファイルが保存されているフォルダー パスを指定します。
f:id:modernexcel7:20191221100847p:plain


そのままデータの変換を押します。
f:id:modernexcel7:20191221101049p:plain


データが表示されたら、まずは不要な列を削除します。

Ctrlキーを押しながらContentとNameを選択→ホーム→列の削除→他の列の削除

f:id:modernexcel7:20191221101339p:plain


次に以下の式で列の追加 → カスタム列で、データテーブルを追加します。

=Excel.Workbook([Content]) (大文字・小文字を正確に入力してください。)
f:id:modernexcel7:20191221101855p:plain

カスタム列が追加されたら右上の展開ボタンを押し、列はDataのみを選択し、プレフィックスのチェックを外してOKを押します。
※ シートを指定する場合などはいったんその他の列も含めて展開し、フィルターで絞り込んでください。

f:id:modernexcel7:20191221101825p:plain

いよいよ先ほど作った関数を使って座標を指定します。ここで最初のステップで用意したExcel座標が登場します。

Excel座標

項目
入力日(和暦) 2 24 1
入力日(月) 2 27 12
入力日(日) 2 30 18
氏名 7 10 Excel 太郎
生年月日 7 25 1970年1月1日
住所 9 10 東京都 XX区 12345

これでExcel方眼紙のデータを読み込む準備ができました。さっそく氏名データを読み込んでみましょう。

列の追加 → カスタム関数の呼び出し

f:id:modernexcel7:20191221105231p:plain

カスタム関数の呼び出し画面では以下のように設定します。

新しい列名: 氏名
関数クエリ: fnGetValue
myTable:   Data (左側のアイコンを列名にしてください。)
myRow:   7
myCol:    10

f:id:modernexcel7:20191227234711p:plain

これで氏名としてExcel方眼紙のデータ取り出しに成功しました!

f:id:modernexcel7:20191221104341p:plain

同じ要領で他の項目を追加していきます。

f:id:modernexcel7:20191221105503p:plain

必要なデータはすべて準備できましたので、仕上げにかかります。

まず、分割入力された入力和暦入力月入力日を結合して日付型データにします。
列の追加 → カスタム列入力年月日として以下の計算式を入力します。

"令和" & Text.From([入力和暦]) & "年" & Text.From([入力月]) & "月" & Text.From([入力日]) & "日"

f:id:modernexcel7:20191221110257p:plain

入力年月日が追加されたら型変換で日付を選びます。

f:id:modernexcel7:20191221205307p:plain

日付型データに変換されました。

f:id:modernexcel7:20191221110559p:plain

ここまでできたら以下の列をそれぞれ型変換し、それ以外の列は削除します。

Name:    テキスト
氏名:    テキスト
住所:    テキスト
入力年月日: 日付
生年月日:  日付

f:id:modernexcel7:20191221110914p:plain

※ Nameはファイル名になりますので、意味を持たせる名前にしてデータとして利用するなど状況に応じてご使用ください。

ここまで来たら完成です。ホーム → 閉じて読み込むExcelのワークシートテーブルに読み込んでください。

f:id:modernexcel7:20191221111017p:plain

これでExcel方眼紙を一括データ化できました!皆さんを悩ませ、時に日本人の文化論にまで発展するネ申Excelですが、PowerQueryさえあれば比較的簡単にデータ化が可能です。

制限事項

上記手順でデータ化したExcel方眼紙ですが、以下のようなケースはデータ化は対応が難しいのでご注意ください。

フォーマットが固定されていないファイル

今回のアプローチは、あくまでどの座標にどのデータがあるかが固定されているケースにのみ通用しますので、ファイルごとに座標がズレていると上手くいきません。

Excelファイルを緩く作っておくと、入力者が自由にレイアウトを変更してしまう可能性があるので、セルのロックワークシートの保護を使ってフォーマットを固定してから配りましょう。

以下、参考記事です。 
modernexcel7.hatenablog.com

値を〇などの図形で囲ませる

f:id:modernexcel7:20191218175628p:plain

画像はデータ化不能です。これは手に負えません。
このようなフォーマットはやめてせめてドロップダウンにしておきましょう。

そのほか

なお、Excel方眼紙と同じくしばしば話題になるセルの結合についてですが、こちらもPowerQueryでデータ化が可能です。以下記事を参考にしてください。
modernexcel7.hatenablog.com