« 2025年1月 | トップページ | 2025年3月 »

2025年2月

Excel Power Queryで1レコードが複数行、複数列に分かれている表の整形方法

Yahoo!知恵袋に1レコードが複数行、複数列に分かれている表をデータベース形式に変換する質問がありました。
https://detail.chiebukuro.yahoo.co.jp/qa/question_detail/q12311497522

関数での回答が付いており、このケースでは関数の方が簡単です。
ファイルやシートが増えるケースの場合にも[すべて更新]だけで集計できるように、Power Queryでの整形方法をメモしました。

1. 元の表と整形後の表

元の表(Sheet1)
1 a:あ b:い c:う d:え h:く
e:お f:か g:き
2 a:け b:こ c:さ d:し h:た
e:す f:せ g:そ

特徴
1) 1レコード2段で構成されている。
2) 項目名と内容が":"で区切られて各フィールドに入っている。(見出し行に項目名がある場合は、最後の参考の#2を見てください。)

これを1レコード1行に整形します。

整形後の表(Line2title)
No. a b c d e f g h
1 あ い う え お か き く
2 け こ さ し す せ そ た

 

2. Power Queryの詳細エディタのコードと作成の方法

Power Query Mのコードの行頭//で始まるコメントの()内が操作方法です。Power Query Mのコードは自動的に生成されるので入力不要です。殆どクリック操作で作成できます。

1) Sheet1クエリ
データを取り込み、2段のインデックスを付ける。

let
// 元データの取込み(Excelの[データ]-[データの取得]-[ファイルから]-[Excelブックから]でファイルを選択し、[インポート]-[Sheet1]-[データの変換])
    ソース = Excel.Workbook(File.Contents("C:\Users\Public\Documents\250227xl2row.xlsx"), null, true),
    Sheet1_Sheet = ソース{[Item="Sheet1",Kind="Sheet"]}[Data],
// 左端のNo.列の2段目のnullを埋める(Power Queryエディタの[変換]-[フィルv]-[下へ])
    下方向へコピー済み = Table.FillDown(Sheet1_Sheet,{"Column1"})
// 段の連番を付ける為"Column1"のグループ化を行いその他のすべての情報をTEMP列に格納("Column1"を選択し[ホーム]-[グループ化]で新しい列名に"TEMP"を入力、操作に"全ての行"を選択し、[OK])
    グループ化された行 = Table.Group(下方向へコピー済み, {"Column1"}, {{"TEMP", each _, type table [Column1=number, Column2=text, Column3=nullable text, Column4=nullable text, Column5=nullable text, Column6=text, Column7=nullable text]}}),
// TEMPに格納した各段に連番を追加([列の追加]-[カスタム列]で式に下記の赤字の式を入力し[OK])
    追加されたカスタム = Table.AddColumn(グループ化された行, "カスタム", each Table.AddIndexColumn([TEMP], "インデックス", 1)),
// 連番を追加したカスタム列の情報を展開する
("カスタム"列の右の矢印アイコンをクリック、さらに読み込むをクリック、"Column1"と元の列名をプレフィックスとして使用しますのチェックを外し[OK])
    #"展開された カスタム" = Table.ExpandTableColumn(追加されたカスタム, "カスタム", {"Column2", "Column3", "Column4", "Column5", "Column6", "Column7", "インデックス"}, {"Column2", "Column3", "Column4", "Column5", "Column6", "Column7", "インデックス"}),
// 作業列を削除
("TEMP"を右クリックし[列の削除])
    削除された列 = Table.RemoveColumns(#"展開された カスタム",{"TEMP"})
in
    削除された列

[*1]グループ化された行~追加されたカスタムの2ステップは、下記の1ステップに置き換えることもできます。
    グループ化された行 = Table.Group(
        下方向へコピー済み ,
        "Column1",
        {"カスタム", each Table.AddIndexColumn(_, "インデックス", 1)}    // _が上記クリック操作のコードのTEMPに相当
    ),


2) Line2dataクエリ
2段目のデータを抽出する。

let
//("Sheet1"クエリを右クリックし[参照]、プロパティの名前を"Line2data"に変更)
    ソース = Sheet1,

// 2段目のみに絞り込み("インデックス"列の[▼]-[数値フィルタ]-[指定の値に等しい]で値の入力または選択欄に"2"を入力し[OK])
    フィルターされた行 = Table.SelectRows(ソース, each [インデックス] = 2),
// 2段目に値が無い列を削除("Column3","Column5","Column7","インデックス"を選択し、右クリックで[列の削除])
    削除された列 = Table.RemoveColumns(フィルターされた行,{"Column3", "Column5", "Column7", "インデックス"}),
// 2段目の内容を抽出
("Column2"~"Cplumn6"を選択し、[変換]-[抽出]-[区切り記号後のテキスト]で区切り記号に":"を入力し[OK])
    区切り記号の後の抽出されたテキスト = Table.TransformColumns(削除された列, {{"Column2", each Text.AfterDelimiter(_, ":"), type text}, {"Column4", each Text.AfterDelimiter(_, ":"), type text}, {"Column6", each Text.AfterDelimiter(_, ":"), type text}})
in
    区切り記号の後の抽出されたテキスト

3) Line1dataクエリ
1段目のデータを抽出する。

let
//("Sheet1"クエリを右クリックし[参照]、プロパティの名前を"Line1data"に変更)
    ソース = Sheet1,

// 1段目に絞り込み("インデックス"列の[▼]-[数値フィルタ]-[指定の値に等しい]で値の入力または選択欄に"1"を入力し[OK])
    フィルターされた行 = Table.SelectRows(ソース, each [インデックス] = 1),

// 1段目の値が無い列を削除("Column4","インデックス"を選択し、右クリックで[列の削除])
    削除された列 = Table.RemoveColumns(フィルターされた行,{"Column4", "インデックス"}),

// 1段目の内容を抽出("Column2"~"Cplumn7"を選択し、[変換]-[抽出]-[区切り記号後のテキスト]で区切り記号に":"を入力し[OK])
    区切り記号の後の抽出されたテキスト = Table.TransformColumns(削除された列, {{"Column2", each Text.AfterDelimiter(_, ":"), type text}, {"Column3", each Text.AfterDelimiter(_, ":"), type text}, {"Column5", each Text.AfterDelimiter(_, ":"), type text}, {"Column6", each Text.AfterDelimiter(_, ":"), type text}, {"Column7", each Text.AfterDelimiter(_, ":"), type text}})

in
    区切り記号の後の抽出されたテキスト

4) Line2titleクエリ
2段目の項目名を抽出し、2段目のデータを追加する。

let
//("Sheet1"クエリを右クリックし[参照]、プロパティの名前を"Line2title"に変更)
    ソース = Sheet1,

// 1レコード目を抽出("Column1"列の[▼]-[数値フィルタ]-[指定の値に等しい]で値の入力または選択欄に"1"を入力し[OK])
    フィルターされた行 = Table.SelectRows(ソース, each [Column1] = 1),

// 2段目を抽出("インデックス"列の[▼]-[数値フィルタ]-[指定の値に等しい]で値の入力または選択欄に"2"を入力し[OK])
    フィルターされた行1 = Table.SelectRows(フィルターされた行, each [インデックス] = 2),

// 2段目の値が無い列を削除("Column3","Column5","Column7","インデックス"を選択し、右クリックで[列の削除])
    削除された列 = Table.RemoveColumns(フィルターされた行1,{"Column3", "Column5", "Column7", "インデックス"}),

// 2段目の項目名を抽出("Column2"~"Cplumn6"を選択し、[変換]-[抽出]-[区切り記号前のテキスト]で区切り記号に":"を入力し[OK])
    区切り記号の前の抽出されたテキスト = Table.TransformColumns(削除された列, {{"Column2", each Text.BeforeDelimiter(_, ":"), type text}, {"Column4", each Text.BeforeDelimiter(_, ":"), type text}, {"Column6", each Text.BeforeDelimiter(_, ":"), type text}}),
// 2段目のデータを追加
([ホーム]-[クエリの追加]でLine2Dataを選択し[OK])
    追加されたクエリ = Table.Combine({区切り記号の前の抽出されたテキスト, Line2data}),
// 2段目の項目名をヘッダに昇格
([変換]-[1行目をヘッダーとして使用])
    昇格されたヘッダー数 = Table.PromoteHeaders(追加されたクエリ, [PromoteAllScalars=true]),
// 型の変更
(自動生成されるので必要に応じて修正)
    変更された型 = Table.TransformColumnTypes(昇格されたヘッダー数,{{"1", Int64.Type}, {"e", type text}, {"f", type text}, {"g", type text}}),
// 左端の列名を設定
(左端の項目名をWクリックして"No."に変更)
    #"名前が変更された列 " = Table.RenameColumns(変更された型,{{"1", "No."}})
in
    #"名前が変更された列 "

5) Line1titleクエリ
1段目の項目名を抽出し、1段目のデータを追加し、2段目のデータをマージする。これが整形後の表になる。

let
//("Sheet1"クエリを右クリックし[参照]、プロパティの名前を"Line1title"に変更)
    ソース = Sheet1,

// 1レコード目を抽出("Column1"列の[▼]-[数値フィルタ]-[指定の値に等しい]で値の入力または選択欄に"1"を入力し[OK])
    フィルターされた行 = Table.SelectRows(ソース, each [Column1] = 1),

// 1段目を抽出("インデックス"列の[▼]-[数値フィルタ]-[指定の値に等しい]で値の入力または選択欄に"1"を入力し[OK])
    フィルターされた行1 = Table.SelectRows(フィルターされた行, each [インデックス] = 1),

// 1段目の値が無い列を削除("Column4","インデックス"を選択し、右クリックで[列の削除])
    削除された列 = Table.RemoveColumns(フィルターされた行1,{"Column4", "インデックス"}),

// 1段目の項目名を抽出("Column2"~"Cplumn7"を選択し、[変換]-[抽出]-[区切り記号前のテキスト]で区切り記号に":"を入力し[OK])
    区切り記号の前の抽出されたテキスト = Table.TransformColumns(削除された列, {{"Column2", each Text.BeforeDelimiter(_, ":"), type text}, {"Column3", each Text.BeforeDelimiter(_, ":"), type text}, {"Column5", each Text.BeforeDelimiter(_, ":"), type text}, {"Column6", each Text.BeforeDelimiter(_, ":"), type text}, {"Column7", each Text.BeforeDelimiter(_, ":"), type text}}),

// 1段目のデータを追加([ホーム]-[クエリの追加]でLine1Dataを選択し[OK])
    追加されたクエリ = Table.Combine({区切り記号の前の抽出されたテキスト, Line1data}),

// 項目名をヘッダに昇格([変換]-[1行目をヘッダーとして使用])
    昇格されたヘッダー数 = Table.PromoteHeaders(追加されたクエリ, [PromoteAllScalars=true]),

// 型の変更(自動生成されるので必要に応じて修正)
    変更された型 = Table.TransformColumnTypes(昇格されたヘッダー数,{{"1", Int64.Type}, {"a", type text}, {"b", type text}, {"c", type text}, {"d", type text}, {"h", type text}}),

// 左端の列名を設定(左端の項目名をWクリックして"No."に変更)
    #"名前が変更された列 " = Table.RenameColumns(変更された型,{{"1", "No."}}),

// 2段目のデータをマージ([ホーム]-[クエリのマージv]-[クエリのマージ]でLine2titleを選択し、両方のNo.を選択して[OK])
    マージされたクエリ数 = Table.NestedJoin(#"名前が変更された列 ", {"No."}, Line2title, {"No."}, "Line2title", JoinKind.LeftOuter),

    #"展開された Line2title" = Table.ExpandTableColumn(マージされたクエリ数, "Line2title", {"e", "f", "g"}, {"e", "f", "g"})
//("h"列を右クリック、[移動]-[末尾に移動])
    並べ替えられた列 = Table.ReorderColumns(#"展開された Line2title",{"No.", "a", "b", "c", "d", "e", "f", "g", "h"})
in
    並べ替えられた列

3. Excelへの読み込み

Power Queryエディタの[ホーム]-[閉じて読み込むv]-[閉じて次に読み込む]で"接続のみ"を選択し、[OK]
Excelの[データ]-[クエリと接続]で右のLine1titleを右クリック、[読み込み先]でテーブルと新規ワークシートを選択し、[OK]

4. サンプルファイル
C:\Users\Public\Documentsに格納して開いてください。

ダウンロード - 250227xl2row.xlsx


■ 参考(外部リンク)

【Power Query】グループごとに連番をつける方法
https://blog.truestar.co.jp/microsoft/power-query/20210707/45919/ 

#2 表の変換にマクロなんかいらない【エクセル パワークエリ PowerQuery】1レコード複数行・複数列の表をリスト形式に変換する方法
https://www.youtube.com/watch?v=VleTkjBmaCA

【Excel パワークエリ】これを見ても、まだコピペでいいなんて言うヤツ……いねぇよなぁ
https://www.youtube.com/watch?v=UapvFSxv2mE

Power Queryで表をリスト形式に整形する方法(まとめ)
https://bigarrow.tea-nifty.com/blog/2025/11/post-4925ca.html

1レコードが1シート複数行複数列から成る複数シートを1つのデータベース形式にする
https://bigarrow.tea-nifty.com/blog/2025/09/post-4beb66.html

| | コメント (0)

Excel Power Query Power Pivotで項目数が変化する集計

Yahoo!知恵袋にスタンプラリーのログの集計の質問がありました。
https://detail.chiebukuro.yahoo.co.jp/qa/question_detail/q10308245620

この集計では取得者により通過点の数が異なります。なので、通過点のパターン別の取得者数の表として次のような表が考えられます。

集計表の例
1番目 2番目 3番目 地点数 取得者数
A C B 3 2
C A B 3 2
B     1 1

3行目は1地点しか通過していないので、2番目と3番目がありません。
横軸のx番目の項目数は変化します(例えば4番目があるかもしれない)。
このようにデータにより項目数や値の有無が可変します。

これをExcelのPower QueryとPower Pivotで集計することを考えたのでメモにしました。集計結果は一番下にあります。

1. 元のログデータと集計表

LOG
時刻 地点 取得者
11:00 C a
8:00 A a
9:00 A b
10:00 C c
12:00 C b
13:00 A c
14:00 B a
15:00 B b
17:00 B d
16:00 B c

Excelのテーブルです。テーブル名はLOGです。

集計表(ピボットテーブル)
ルート 地点数 取得者数 平均時刻 取得者
A-C-B 3 2 14:30 a,b
C-A-B 3 1 16:00 c
B 1 1 17:00 d

本メモの集計では、可変する項目である通過地点を1つのルートにまとめることで項目数を固定しました。取得者もメジャーで1つの項目にまとめました。ルートについてはPower Queryで列の分解をすることで、可変の項目にすることもできます。

2. Power Query

以下にPower Query Mのコードを示します。
コード中の行頭//から始まる行は、操作方法や動作の解説です。
大半はクリック操作でコードが自動生成されますが、一部詳細エディタでコードを記述するところがあります。
コードは、Power Queryエディタの[ホーム]-[詳細エディタ]で表示、編集します。

0) 概要

Power Queryで1つの最終集計表に整形するのは複雑です。そこで、通過ルートと取得者に表を分けに作成して、マージやリレーションで連結することにします。
(1) 通過ルート別と取得者別の表を分けて作ります。
(2) それらをデータモデルに追加します。
(3) ピボットテーブルで各データモデルのデータを集計します。

1) LOG取込0

LOGを取り込みます。接続のみで、データモデルに追加しません。

let
//
(LOGテーブル中の1つのセルを選択し、Excelの[Data]-[テーブルかたは範囲から]、プロパティの名前を"LOG取込0"に変更)
    ソース = Excel.CurrentWorkbook(){[Name="LOG"]}[Content],
//
(ヘッダの項目名の"時刻"を右クリックし、[型の変更]-[時刻])
    変更された型 = Table.TransformColumnTypes(ソース,{{"時刻", type time}, {"地点", type text}, {"取得者", type text}})
in
    変更された型

2) 取得者
LOG取込0から取得者毎の表を作ります。ピボットテーブルに表示し、データモデルに追加しません。

let
//(右のクエリ"LOG取込0"を右クリックし、[参照]、プロパティの名前を"取得者時刻"に変更)
    ソース = LOG取込0,
//
("取得者"を選択し[ホーム]-[グループ化]で詳細設定を選択し、新しい列名/操作/列を以下のように設定して[OK]
//
時刻/最大/時刻、地点数/行数のカウント)
    グループ化された行 = Table.Group(ソース, {"取得者"}, {{"時刻", each List.Max([時刻]), type nullable time}, {"地点数", each Table.RowCount(_), Int64.Type}})
in
    グループ化された行

3) ルート0

LOG取込0から所得者毎の地点取得ルート、取得地点数他の表を作ります。接続のみで、データモデルに追加します。

let
//(右のクエリ"LOG取込0"を右クリックし、[参照]、プロパティの名前を"地点数0"に変更)
    ソース = LOG取込0,
// 後で時刻順にIndexを付けるためソートする("取得者"の[▼]-[昇順で並び替え]、"時刻"の[▼]-[昇順で並び替え]。数式バーで下記の赤字部分を追加。)
    並べ替えられた行 = Table.Buffer(Table.Sort(ソース,{{"取得者", Order.Ascending}, {"時刻", Order.Ascending}})),
// 取得者をグループ化し取得者毎にIndexを追加したテーブルにまとめる([ホーム]-[詳細エディタ]で下記の行を追加。後のステップで参照しているステップ名を作成したステップ名に変更。)[*1]
   グループ化された行1 = Table.Group(並べ替えられた行, {"取得者"}, {{"カウント", each Table.AddIndexColumn(_, "Index", 1, 1), type table}}),
// Index追加したまとめテーブルを展開する("カウント"の右の矢印アイコンをクリックし、"取得者"と"元の列名をプレフィックスとして使用します"のチェックを外し、[OK])
    #"展開された カウント" = Table.ExpandTableColumn(グループ化された行1, "カウント", {"時刻", "地点", "Index"}, {"時刻", "地点", "Index"}),
// ("時刻"を右クリックし[削除])
    削除された列 = Table.RemoveColumns(#"展開された カウント",{"時刻"}),
//
("Index"を選択し[変換]-[列のピボット]で値列に[地点]、詳細設定オプションの値の集計関数を[集計しない]、[OK])
    ピボットされた列 = Table.Pivot(Table.TransformColumnTypes(削除された列, {{"Index", type text}}, "ja-JP"), List.Distinct(Table.TransformColumnTypes(削除された列, {{"Index", type text}}, "ja-JP")[Index]), "Index", "地点"),
// ExcludeColumn~CombindTableで"取得者"以外の列を結合
([ホーム]-[詳細エディタ]で下記の行を追加。後のステップで参照しているステップ名を作成したステップ名に変更。 )[*2]
    // 除外する列名
    ExcludeColumn = "取得者",
    // 除外列を除いた列名(=結合する列名)のリストを取得
    ColumnsToMerge = List.RemoveItems(Table.ColumnNames(ピボットされた列), {ExcludeColumn}),
    // 指定した列以外の列を結合
    CombinedTable = Table.CombineColumns(    // 列結合の関数
        ピボットされた列,                                    // 全体の表
        ColumnsToMerge,                                  // 除外する列名を除いた列名=結合する列名
        Combiner.CombineTextByDelimiter("#(tab)", QuoteStyle.None),     // 区切り記号
        "ルート"                                                // 結合後の列名
    ),
// 後ろの余計な区切り記号(タブ)を削除("ルート"を選択し[変換]-[書式v]-[トリミング])
    #"トリム テキスト" = Table.TransformColumns(CombinedTable,{{"ルート", Text.Trim, type text}}),
// タブをカンマに置換("ルート"を選択し[変換]-[値の置換]で検索する値に「#(tab」、置換後に「-」を入力、[OK])
    置き換えられた値 = Table.ReplaceValue(追加されたカスタム,"#(tab)","-",Replacer.ReplaceText,{"ルート"}),
//
([ホーム]-[クエリのマージ]で"取得者"クエリを選択し両"取得者"列を選択して、[OK])
    マージされたクエリ数 = Table.NestedJoin(置き換えられた値, {"取得者"}, 取得者, {"取得者"}, "取得者.1", JoinKind.LeftOuter),
//
("取得者.1"の右の矢印アイコンをクリックし、"取得者"と"元の列名をプレフィックスとして使用します"のチェックを外し[OK])
    #"展開された 取得者.1" = Table.ExpandTableColumn(マージされたクエリ数, "取得者.1", {"時刻", "地点数"}, {"時刻", "地点数"})
in
    #"展開された 取得者.1"

[*1] 次の方法を使うとこの部分をクリック操作で作成できます。(上記コードはこれを1行減らしたものです。)
【Power Query】グループごとに連番をつける方法
https://blog.truestar.co.jp/microsoft/power-query/20210707/45919/ 

[*2] Power Queryには、「指定の列を除く」列結合などの関数がありません。そこで、1)列名の指定、2)指定した列名を除いた列名を作成、3)その列名を結合する処理にしています。こうすることで、本例の場合通過点が増えてもクエリの修正が不要になります。このコードはChatGPTによるものです。

4) ルート

ルート0からルート毎の表を作成します。ピボットテーブルに表示し、データモデルに追加します。

let
//(右のクエリ"ルート0"を右クリックし、[参照]、プロパティの名前を"ルート"に変更)
    ソース = ルート0,
//
("ルート"を選択し[ホーム]-[グループ化]で詳細設定を選択し、新しい列名/操作/列を次のように設定し、[OK]
//
地点数/最大/地点数、取得者数/行数のカウント、平均時刻/平均/時刻、最小時刻/最小/時刻、最大時刻/最大/時刻)
    グループ化された行 = Table.Group(ソース, {"ルート"}, {{"地点数", each List.Max([地点数]), type nullable number}, {"取得者数", each Table.RowCount(_), Int64.Type}, {"平均時刻", each List.Average([時刻]), type nullable time}, {"最小時刻", each List.Min([時刻]), type nullable time}, {"最大時刻", each List.Max([時刻]), type nullable time}})
in
    グループ化された行

5) Power Queryエディタの終了処理

[ホーム]-[閉じて読み込むv]-[閉じて次に読み込む]で接続のみ、データモデルに追加するのチェックを外し、[OK]

6) ピボットテーブルの読込とデータモデルの追加

[データ]-[クエリと接続]で右のクエリを右クリック、[読み込み先]で次のように設定する。
- 取得者 : ピボットテーブルを選択、データを返す場所を選択、このデータモデルに追加するのチェックをしない、[OK]
- ルート0 : 接続の作成のみを選択、このデータモデルに追加するのチェックをする、[OK]
- ルート : ピボットテーブルを選択、データを返す場所を選択、このデータモデルに追加するのチェックをする、[OK]


3. リレーション

[Power Pivot]-[管理]-[ホーム]-[ダイアグラムビュー]で'ルート0'[ルート]から'ルート'[ルート]にドラッグする。([Poer Pivot]が表示されていない時は[データ]-[データモデル(緑のアイコン)]でPower Pivot for Excelを開くか、[データ]-[データモデル(緑のアイコン)]-[リレーション]でリレーションを設定してください。)

'ルート0'[ルート] *:1 'ルート'[ルート]

250227stampr1 250227stampr2


4. メジャー

次の章のルートのピボットテーブルのピボットテーブルのフィールドでルートを右クリックし、[メジャーの追加]

1) M取得者:
ルート毎の取得者の表示をします。CANCATENATEXで取得者を,区切りで連結するメジャーです。

=IF(ISFILTERED('ルート'[ルート]), CONCATENATEX('ルート0', 'ルート0'[取得者], ","))

5. ピボットテーブル

ピボットテーブルのフィールドに項目をドラッグします。時刻は[値フィールドの設定]-[表示形式]で集計と表示を変えてください。

1) ルート
列 : Σ値
行 : ルート
Σ値 : 合計/地点数、合計/取得者数、最大値/平均時刻、M取得者

2) 取得者
列 : Σ値
行 : 取得者
Σ値 : 合計/地点数、最大/時刻

6. 結果

250227stamp

ダウンロード - 250227stamp.xlsx

冒頭の集計表の例に示すように通過地点の列を分離するなら、ルート列を区切り記号"-"で分解してください。


7. 参考

【Excel】自分で数式を作れば、ピボットテーブルで文字列をまとめることもできる。
https://www.youtube.com/watch?v=jC7POhejvPk

| | コメント (0)

Excel メジャーのRANKXで順位を求める方法(式の中に適用されるコンテキスト)

Excel メジャーのRANKXで順位を求めるDAX式の動作を確認したメモです。
イテレータ関数の中でコンテキストが何を示しているのかをメモしました。例えば、イテレータ関数の中で行コンテキストがCALCULATE関数でフィルターコンテキストに移行する時にそれらが具体的に何処を示しているのかをメモしました。
Power BIも同じだと思います。

素人の解釈なので、間違っているかもしれません。間違いを発見したらコメントをください。

RANKXの使い方の参考情報

Power BI Desktopでたびたび使い方に迷う「RANKX」メモ
https://app.cocolog-nifty.com/cms/blogs/632678/entries/95296087

[Power BI] RANKX関数を紐解く。JAPAN RUGBY LEAGUE ONE 選手たちの身長・体重で。
https://qiita.com/akihiro_suto/items/a6f77d453107bd05acf4

結果の表はピボットテーブル(マトリックス)です。

1. 集計対象の表

売上
顧客 売上
A 10
A 20
A 30
B 40
B 50
C 20

この表から顧客別の売上の順位を求めます。

2-1. 順位を求めるメジャー(1)
1つのメジャーで売上の合計を行い順位を求める方法です。

1) メジャーの式

M順位:
=RANKX(
    ALLSELECTED('売上'[顧客]),
    CALCULATE(
        SUM('売上'[売上])
    )
)


2) 結果

顧客 合計/売上 M順位
A 60 2(例)
B 90 1
C 20 3

3) DAX式の動作の解説

例:ピボットテーブルのフィルタコンテキストが顧客=Aの場合に、順位2を算出する例を説明します。
ALLSELECTED('売上'[顧客])で以下のリストが得られる。
(参考:ALLSELECTED('table'[column])では、指定されたcolumnの組合せの行の重複が排除されます。一方、ALLSELECTED('table')は、行単位の重複の削除をしません。)

現在の行:顧客
1行目   :A
2行目   :B
3行目   :C

イテレータ関数により上記の現在の行毎に、
イテレータ関数が処理する行コンテキスト=現在の行がCALCULATE関数のフィルタコンテキストになり、
現在の行毎にSUMが計算さる。

例えば、現在の行の2行目Bを処理しているときは、ピボットテーブルのフィルタコンテキストAに関係なく、
行コンテキストBがCALUCULATE関数のフィルタコンテキスト(顧客=B)に変換され、SUMは(顧客=B)の[売上]を合計する(40+50=90)。

現在の行:CALUCLATE(SUM('売上'[売上]))
A         :10+20+30=60           :2位←ピボットテーブルのフィルタコンテキストA
B         :40+50=90                 :1位
C         :20=20                       :3位

RANKXは、ピボットテーブルのフィルタコンテキストの顧客=AのCALUCLATE(SUM('売上'[売上]))が
各行のCALCULATE(SUM('売上'[売上]))の2番目なので、2を返す。

2-2. 順位を求めるメジャー(2)
順位メジャーから売上計メジャーを呼び出す方法です。

1) メジャー式

M売上計:
=SUM('売上'[売上])

M売上順2:
=RANKX(
    ALLSELECTED('売上'[顧客]),
    [M売上計]
)

2) 結果

顧客 合計/売上 M売上計 M売上順2
A 60 60(例) 2(例)
B 90 90 1
C 20 20 3

3) DAX式の動作の解説

例:ピボットテーブルのフィルタコンテキストが顧客=Aの場合に、M売上計が60を算出する例を説明します。
ピボットテーブルのフィルタコンテキストAにより、SUMは(顧客=A)の[売上]を合計する。

A       :10+20+30=60

例:ピボットテーブルのフィルタコンテキストが顧客=Aの場合に、M売上順2が2位を算出する例を説明します。
ALLSELECTED('売上'[顧客])で以下のリストが得られる。

現在の行:顧客
1行目   :A
2行目   :B
3行目   :C

イテレータ関数により上記の現在の行毎にメジャーM売上計が計算されるが、
この時「メジャーを呼び出したときの DAX の評価ルール」 によりDAX の "メジャーの暗黙的な CALCULATE の適用" が行われ[*1]、
行コンテキストがメジャーのフィルタコンテキストになり、メジャーM売上計の中のSUMが[売上]を合計する。

例えば、現在の行の2行目Bを処理しているときは、ピボットテーブルのフィルタコンテキストAに関係なく、
行コンテキストBが暗黙的なCALUCULATEの適用によりM売上計のフィルタコンテキストが(顧客=B)になり、SUMはBの[売上]を合計する。

現在の行:CALUCLATE(SUM('売上'[売上]))
A       :10+20+30=60           :2位←ピボットテーブルのフィルタコンテキスト
B       :40+50=90                 :1位
C       :20=20                       :3位

RANKXは、ピボットテーブルのフィルタコンテキストの顧客=AのCALUCLATE(SUM('売上'[売上]))が
各行のCALUCLATE(SUM('売上'[売上]))の2番目なので、2を返す。

[*1]メジャーを呼び出したときの暗黙的なCALCULATEの適用

本メモはChatGPTの回答が根拠なのですが、このことが書いてあるサイトを発見できませんでした。>サイトを知っていればコメントください。

下記の備考に「行コンテキストでモデル メジャーを使用する場合、コンテキスト遷移は自動的に行われます。」と書かれています。
CALCULATE
https://learn.microsoft.com/ja-jp/dax/calculate-function-dax

ChatGPTによると下記の本に詳しく書いてあるらしいですが、確認していません。
「The Definitive Guide to DAX: Business intelligence with Microsoft Excel, SQL Server Analysis Services, and Power BI」
https://www.amazon.co.jp/dp/9353945488/

下記にメジャーでメジャーを引数にするときの動作が書かれていますが、そのような動作になる理由は書かれていません。
Q3. メジャーでDAX関数を直接記述する場合と、メジャーを引数とする場合で、処理結果が変わる例(SUMX)
https://note.com/tentomusea/n/n3f2df502780d


3. 順位を求めるメジャー(誤)

CALCULATEが無いとどうなるのか?を試してみました。結果はNGです。前記正解との動作の差異を理解すると、前記正解の理解が深まります。
1) メジャー式

M順位_誤:
=RANKX(
    ALLSELECTED('売上'[顧客]),
    SUM('売上'[売上])
)

2) 結果

顧客 合計/売上 M順位_誤
A 60 1(例)
B 90 1
C 20 1

3) DAX式の解説

例:ピボットテーブルのフィルタコンテキストが顧客=Aの場合、M順位_誤が1を算出する例を説明します
ALLSELECTED('売上'[顧客])で以下のリストが得られる。

現在の行:顧客
1行目   :A
2行目   :B
3行目   :C

イテレータ関数により上記の現在の行毎に、
ピボットテーブルのフィルタコンテキストによりSUMが計算さる。

例えば、現在の行の2行目Bを処理しているときは、イテレータ関数の行コンテキストに関係なく、
ピボットテーブルのフィルタコンテキストAにより、SUMはAの[売上]を合計する。

現在の行:SUM('売上'[売上])
A       :10+20+30=60                 :1位←ピボットテーブルのフィルタコンテキストA
B       :10+20+30=60                 :1位
C       :10+20+30=60                 :1位

RANKXは、ピボットテーブルのフィルタコンテキストの顧客=AのCALUCLATE(SUM('売上'[売上]))が
各行のSUM('売上'[売上])の1番目なので、1を返す。

4. 順位を出す項目が別の表の場合

営業所別の売上件数の順位と売上の順位を出す方法です。売上には営業所の情報が無いので、顧客マスタと営業所マスタをPower Pivotでリレーションして作成します。

1) 集計対象の表

売上
顧客 売上
A 10
A 20
A 30
A 40
B 50
B 20
B 1
C 2
C 3
D 4
E 5
F 6
顧客マスタ
顧客 営業所
A S1
B S2
C S2
D S3
E S3
F S3
営業所マスタ
営業所
S1
S2
S3


2) リレーション

'売上'[顧客] *:1 '顧客マスタ'[顧客]
'顧客マスタ'[営業所] * :1 '営業所マスタ'[営業所]

3) メジャー

売上表の1行を1件とする件数の順位を求めるメジャーです。
M順位件数:

=VAR x=RANKX(
    ALLSELECTED('営業所マスタ'[営業所]),
    CALCULATE(
        COUNT('売上'[顧客])
    )
)
RETURN IF(COUNT('営業所マスタ'[営業所])=1, x)

売上表の合計売上の順位を求めるメジャーです。
M順位売上

=VAR x=RANKX(
    ALLSELECTED('営業所マスタ'[営業所]),
    CALCULATE(
        SUM('売上'[売上])
    )
)
RETURN IF(COUNT('営業所マスタ'[営業所])=1, x)

式の動作は、2.順位を求めるメジャー(1)と同じです。

合計行の順位は意味が無いので、IFで表示しないようにしています。次の式でもOKです。
RETURN IF(HASONEFILTER('営業所マスタ'[営業所]), x)

3) 結果

営業所 カウント/顧客 M順位件数 合計/売上 M順位売上
S1 4 2 100 1
S2 5 1 76 2
S3 3 3 15 3

RANKXの参考情報

 

以降は、上記の説明ロジックで他のパターンが説明できるのかを検証した実験です。
イテレータ関数MAXXの中のコンテキストが何を指しているのかを確認したものです。求めている値に意味はありません。

5. MAXXでの実験(1)
上記のイテレータ関数RANKXを動作が単純なMAXXに置き換え、同じ論理で説明できるかを確認しました。最初は、2.のM順位のMAXX置き換え版です。
1) メジャー

MMAXcs:
=MAXX(
    ALLSELECTED('売上'[顧客]),
    CALCULATE(
        SUM('売上'[売上])
    )
)

2) 結果

顧客 合計/売上 MMAXcs
A 60 90(例)
B 90 90
C 20 90

3) DAX式の動作

例:ピボットテーブルのフィルタコンテキストが顧客=Aの場合、90になる例を説明します。
ALLSELECTED('売上'[顧客])で以下のリストが得られる。

現在の行:顧客
1行目   :A
2行目   :B
3行目   :C

イテレータ関数MAXXにより上記の行毎に、
イテレータ関数が処理する行コンテキスト=現在の行がCALUCULATE関数のフィルタコンテキストになり、
現在の行毎にSUMが計算される。

例えば、イテレータ関数MAXが処理している現在の行が2行目の場合、
イテレータ関数が処理している現在の行 = 行コンテキスト = Bが
CALCULATE関数のフィルタコンテキスト(顧客=B)になり、
顧客=Bの売上がSUM関数の対象となり、SUM(40+50)=90が求まる。

各行の結果は次のようになる。

現在の行:CALUCLATE(SUM('売上'[売上]))
A       :10+20+30=60
B       :40+50=90
C       :20=20

イテレータ関数MAXXにより各行で求めた値のMAX値=MAX(60,90,20)=90が算出される。

6. MAXXでの実験(2)
M順位_誤のRANKXをMAXXに置き換えても、同じ論理で説明できるか?
1) メジャー

MMAXs:
=MAXX(
    ALLSELECTED('売上'[顧客]),
    SUM('売上'[売上])
)

2) 結果

顧客 合計/売上 MMAXs
A 60 60(例)
B 90 90
C 20 20

3) DAX式の動作

例:ピボットテーブルのフィルタコンテキストが顧客=Aの場合、60になる例で説明する。
ALLSELECTED('売上'[顧客])で以下のリストが得られる。

現在の行:顧客
1行目   :A
2行目   :B
3行目   :C

イテレータ関数MAXXにより上記の行毎に、
ピボットテーブルのフィルタコンテキストAでフィルタされた'売上'[売上]のSUMが計算される。

例えば、イテレータ関数MAXXが処理している現在の行が2行目の場合、
イテレータ関数が処理している現在の行 = 行コンテキスト = Bではあるが、
SUM関数のフィルタコンテキスト = ピボットテーブルのフィルタコンテキストA = {{A,10},{A,20},{A,30}}になり、
SUM(10,20,30)=60が求まる。

各行の結果は次のようになる。

現在の行:SUM('売上'[売上])
A       :10+20+30=60
B       :10+20+30=60
C       :10+20+30=60

イテレータ関数MAXXにより各行で求めた値のMAX値=MAX(60,60,60)=60が算出される。

7. MAXXでの実験(3)
MAXXでの実験(1)でALLSELECTEDを削除しても、同じ論理で説明できるか?
1)メジャー

MMAXcsn:
=MAXX(
    '売上',
    CALCULATE(
        SUM('売上'[売上])
    )
)

2) 結果

顧客 合計/売上 MMAXcsn
A 60 30(例)
B 90 50
C 20 20

3) DAX式の説明

例:ピボットテーブルのフィルタコンテキストが顧客=Aの場合、30となる例で説明します。
'売上'はピボットテーブルのフィルタコンテキストAでフィルタされた以下のテーブルになる。

現在の行:顧客:売上
1行目   :A   :10
2行目   :A   :20
3行目   :A   :30

イテレータ関数MAXXにより上記の行毎に、
イテレータ関数が処理する行コンテキスト=現在の行がCALUCULATE関数のフィルタコンテキストになり、
現在の行毎にSUMが計算される。

例えば、イテレータ関数MAXが処理している現在の行が2行目の場合、
イテレータ関数が処理している現在の行 = 行コンテキスト = {A,20}が
CALCULATE関数のフィルタコンテキスト(顧客=A, 売上=20)になり、
SUM(20)=20が求まる。

各行の結果は次のようになる。

現在の行:CALUCLATE(SUM('売上'[売上]))
A,10    :10
A,20    :20
A,30    :30

イテレータ関数MAXXにより各行で求めた値のMAX値=MAX(10,20,30)=30が算出される。

8. MAXXでの実験(4)
MAXXでの実験(2)でALLSELECTEDを削除しても、同じ論理で説明できるか?

1)メジャー

MMAXsn:
=MAXX(
    '売上',
    SUM('売上'[売上])
)

2) 結果

顧客 合計/売上 MMAXsn
A 60 60(例)
B 90 90
C 20 20

3) DAX式の説明

例:ピボットテーブルのフィルタコンテキストが顧客=Aの場合、60になる例を説明します。
'売上'はピボットテーブルのフィルタコンテキストAでフィルタされた以下のテーブルになる。

現在の行:顧客:売上
1行目   :A   :10
2行目   :A   :20
3行目   :A   :30

イテレータ関数MAXXにより上記の行毎に、
ピボットテーブルのフィルタコンテキストAでフィルタされた'売上'[売上]のSUMが計算される。

例えば、イテレータ関数MAXXが処理している現在の行が2行目の場合、
イテレータ関数が処理している現在の行 = 行コンテキスト = A,20ではあるが、
SUM関数のフィルタコンテキスト = ピボットテーブルのフィルタコンテキスト(顧客=A) = {{A,10},{A,20},{A,30}}になり、
SUM(10,20,30)=60が求まる。

各行の結果は次のようになる。

現在の行:SUM('売上'[売上])
A,10    :10+20+30=60
A,20    :10+20+30=60
A,30    :10+20+30=60

イテレータ関数MAXXにより各行で求めた値のMAX値=MAX(60,60,60)=60が算出される。

9. MAXXでの実験(5)
MAXXでの実験(1)でCALCLATE関数の第2引数の中の関数の引数のコンテキストが何に従っているのかをVALUES()関数の例で調べました。
1) メジャー

MMAXcs:
=MAXX(
    ALLSELECTED('売上'[顧客]),
    CALCULATE(
        SUM('売上'[売上]),
        VALUES('売上'[顧客])

    )
)

2) 結果

顧客 合計/売上 MMAXcs
A 60 60(例)
B 90 90
C 20 20

3) DAX式の動作

例:ピボットテーブルのフィルタコンテキストが顧客=Aの場合、60になる例を説明します。
ALLSELECTED('売上'[顧客])で以下のリストが得られる。

現在の行:顧客
1行目   :A
2行目   :B
3行目   :C

イテレータ関数MAXXにより上記の行毎に、
イテレータ関数が処理する行コンテキスト=現在の行がCALUCULATE関数の中のフィルタコンテキストになる。
CALCULATE関数の第2引数のVALUES関数の引数はメジャーのピボットテーブルのフィルタコンテキストに従う。
CALCULATE関数の第2引数のVALUES関数の結果で現在の行と同じ内容を持つ行が抽出される。
CALCULATE関数の第1引数のSUMが現在の行毎に計算される。

例えば、イテレータ関数MAXが処理している現在の行が2行目の場合、
イテレータ関数が処理している現在の行 = 行コンテキスト = Bが
CALCULATE関数のフィルタコンテキスト(顧客=B)になるが、
VALUES('売上'[顧客])の中はメジャーのフィルタコンテキスト(顧客=A)で、このCALCULATE関数のフィルタコンテキストにAが無いからSUMの引数の'売上'[売上]=nullになる

各行の結果は次のようになる。

現在の行:CALUCLATE(SUM('売上'[売上]), VALUE('売上'[顧客]))
A       :10+20+30=60
B       :null
C       :null

イテレータ関数MAXXにより各行で求めた値のMAX値=MAX(60,null,null)=60が算出される。

以上の実験では、同じロジックで説明が付いたと思っています。

10. サンプルファイル

上記動作を試したファイルです。

ダウンロード - 250224xlrank.xlsx

ダウンロード - 250223xlrank.xlsx

250224xlrank.xlsx : RANKX()の動作確認
250223xlrank.xlsx : 順位を出す項目が別の表の場合

11. 式の中に適用されるコンテキスト(まとめ?)

DAX式に適用されるコンテキストは、メジャーと計算列、関数の外と中、関数の中の引数の機能(位置)、等によって異なるようです。
そこで、式の各部位に適用されるコンテキストの整理を試みました(見直し中)。素人の解釈なので、間違っていればコメントをください。

- 行コンテキスト : 1行を示すもの。計算列やイテレータ関数などで作られる。
- フィルタコンテキスト : フィルタで絞り込まれるもの。複数行になることがある。ピボットテーブルやCALCULATE関数で作られる。

1) DAX式で直接参照される項目と関数の中の項目のコンテキストの基本

(1) メジャーの場合 :

- メジャーの値が表示される場所に掛かるフィルタがコンテキスト。フィルタコンテキストと呼ばれる。
- メジャーは集計値なので項目を直接参照することはできない。(1つの値を得る式が必要)
- 関数の中はメジャーのフィルタコンテキストに従う。

例 : 問題番号別の回数順位(11.関連記事の式)
=RANKX(    // ランクを求める
    FILTER(    // ランクの対象範囲を'回数'の[問題番号]の行に絞り込む
        ALL('回数'),    // FILTER関数のフィルタ対象。ALLで'回数'に掛かるメジャーのフィルタコンテキストを解除する。
        '回数'[問題番号] = MAX('回数'[問題番号])    // MAX()内の'回数'[問題番号]はメジャーのフィルタコンテキストに従う
                                                                   // 左辺の直接参照されている'回数'[問題番号]はFILTER関数の行コンテキスト

    ),
    CALCULATE(MAX('回数'[回数]))    // ランクを求める対象列。
    //
イテレータ関数の第2引数のコンテキストは、イテレータ関数の第1引数の行コンテキストに従う。
    // CALCULATE関数の中は、
CALCULATE関数によりイテレータ関数の第1引数の行コンテキストがフィルタコンテキストに変わる。
    // CALCULATE関数の第2引数が無いので、MAX()内の'回数'[問題番号]はフィルタされず、CALCULATE関数の中のフィルタコンテキストに従う。

)

(2) 計算列の場合 :

- 式がある行がコンテキスト。行コンテキストと呼ばれる。
- 式の中で直接参照される項目は、行コンテキストに従う(例 : C売上 := '売上'[単価] * '売上'[数量])
- 関数の中では行コンテキストが消える。全ての行が含まれる。

例 : 問題番号別の回数順位(11.関連記事の式)
=RANKX(    // ランクを求める
    FILTER(    // ランクの対象範囲を'回数'の[問題番号]の行に絞り込む
        '回数',    // フィルタ対象。行コンテキストが消える。'回数'全体が対象となる。
        '回数'[問題番号] = EARLIER('回数'[問題番号])    // フィルタ条件。()内の'回数'[問題番号]はコンテキストが無い。
        // なので、EARLIERで計算列の行コンテキストの'回数'[問題番号]を取得する。
        // 左辺の直接参照されている'回数'[問題番号]はFILTERの行コンテキスト
    ),
    '回数'[回数],    // ランクを求める対象列。イテレータ関数の第2引数の直接参照項目には、イテレータ関数の第1引数の行コンテキストが適用される。
// ┗
仮に第2引数がMAX('回数'[回数])だと、()内は行コンテキストが消え'回数'全体のMAXになる。全てが最大値になるので全て1位になる。
)

2) CALCULATE関数の中(CALCULATE関数の第1引数は少し特殊?)

(1) 第1引数(計算式)の中の関数の引数 : 「CALCULATE関数に掛かるコンテキスト」を「第2引数以降の条件」でフィルタしたコンテキストに従う。
(「CALCULATE関数に掛かるコンテキスト」とは、CALCULATE関数が呼ばれた場所のコンテキストである。例えば、メジャーで直接呼ばれたらメジャーのフィルタコンテキストである。
計算列やイテレータ関数の中で呼ばれた場合は、それらの行コンテキストがフィルターコンテキストに変換されたものになる。これをコンテキストトランザクションと呼ぶ。この場合、1行の行コンテキストの値に合致する行が複数あれば、複数行になる。)
(2) 第2引数以降(フィルタ条件)の直接参照項目 : CALCULATE関数に掛かるコンテキストに従う。
(3) 第2引数以降(フィルタ条件)の中の関数の引数 : 1)基本に従う。
(参考 : CALCULATE(第1引数, 第2引数, ALL(引数))の場合、第2引数は第3引数のALL()の影響を受けない。第2引数以降のフィルタのANDが第1引数に掛かる。)

3) FILTER()関数の中

(1) 第1引数(対象表)の直接参照テーブルと関数の引数 : 1)基本の関数の中に従う。
(2) 第2引数(フィルタ条件)で直接参照される項目 : 第1引数の行コンテキストに従う。
(3) 第2引数(フィルタ条件)の中の関数の引数 : 1)基本に従う。

4) イテレータ関数の中

(1) 第1引数(対象表)の直接参照テーブルと関数の引数 : 1)基本の関数の中に従う。
(2) 第2引数(計算式)で直接参照される項目 : 第1引数の行コンテキストに従う。(このケースは計算列のみ存在し、メジャーには無い。)
(3.1) 第2引数(計算式)の中の関数の引数 : 1)基本に従う。但し、CALCULATEやメジャーの場合は、下記に従う。
(3.2) CALCULATE関数が使われた場合、CALCULATE関数の中は、CALCULATE関数が呼ばれる場所のコンテキストであるイテレータ関数の第1引数の行コンテキストをフィルタコンテキストに変えたものになる。(1行の行コンテキストの値に合致する行が複数あれば、複数行になる。)
CALCULATE関数の第2引数以降の中の関数の引数は、1)基本に従う。
(3.3) イテレータ関数の第2引数がメジャーの場合、CALCULATE関数が無くても、そのメジャーのコンテキストは第1引数の行コンテキストがフィルターコンテキストに変わったものになる。暗黙的にCALCULATEが適用されるような動作となる。

5) ALL(Column)が示すもの

ALL(<column>[, <column>[,…]] )は、指定されたColunmのフィルタを解除し、そのColumnのみを含むテーブルを返す。
ALL(<column>)は、1列のテーブルを返す。これは列ではない。
(テーブル : テーブル名、列目、値。列 : 列名、値? テーブルと列の違い : テーブル名の有無?)

理由

下記Microsoft Learnには「ALL (Column[, Column[, ...]])」は「テーブル内の指定した列からすべてのフィルターを削除します。テーブル内の他の列に対する他のすべてのフィルターは引き続き適用されます。」と書かれています。なので、一見すると他の列が残るように感じる。
    https://learn.microsoft.com/ja-jp/dax/all-function-dax
しかし、FILTER(ALL('Table'[Column1']), 'Table'[Column2] =... や SUMX(ALL('Table'[Column1]), 'Table'[Column2])などは、[Column2]が無いという理由でエラーになる。
これは、ALL(Column)が指定された他の列を残すという解釈が間違っていることを示す。
なので、Microsoft Learnの説明は、ALL()がテーブルの説明ではなく、CALCULATE関数の<filter>の引数でALLが使用された時のベーステーブルの説明と考えるのが良いと思われます。

余談 :(CALCULATE関数の第1引数は少し特殊?)に感じる理由??

CALCULATE関数の第1引数は集計式(集計関数)であり、集計の範囲は第1引数の中の関数の中の引数で示されている。
その範囲は、第2引数以降のフィルタ条件でフィルタされる。
なので、第1引数の中の関数の引数のコンテキストがメジャーのコンテキストから変わる必要がある。(この仕様にしないと、CALCULATE関数の意味がない。)

FILTER関数の第1引数は、範囲を示す。(イテレータ関数も同様)
FILTER関数の第1引数は、第2引数のフィルタ条件で縮小方向にフィルタされる。
FILTER関数の第1引数の範囲を拡大するためには、第1引数の中でALL等の関数を使用する。
ALL等の関数の引数は範囲を拡大する前の表や列を示すので、この引数自体は拡大前=メジャーのコンテキストとなる。
即ち、第1引数の中の関数の中の引数のコンテキストはメジャーのコンテキストのままで変化しない。

下線部の差がCALCLATE関数の第1引数が特殊に感じる理由なのかもしれない???
各引数の機能の違いを考えれば当然なのだけど、引数の機能を考えずに関数の引数のコンテキストは何かと考えると間違える。

参考

Power BI: コンテキスト トランジションを理解する
https://qiita.com/k_maki/items/c24c5258fccb45cd4582

Power BI Desktop における DAX の基本を学習する
https://learn.microsoft.com/ja-jp/power-bi/transform-model/desktop-quickstart-learn-dax-basics


11. 関連記事

(Excel/Power BI)メジャーが予期しない値を返す原因とDAXコンテキストの理解(DAX式の中のコンテキストの適用ルール)
https://bigarrow.tea-nifty.com/blog/2025/03/post-8025c7.html

Excelのピボットテーブルでグループ内の順位を項目にしたクロス表を作る の6.2) 計算列とサンプルファイルの'メジャーと計算列'シート
https://bigarrow.tea-nifty.com/blog/2025/03/post-e0f809.html

累計メジャーDAXの勘違い(歯抜け)
https://bigarrow.tea-nifty.com/blog/2025/01/post-08315e.html

| | コメント (0)

RealFlight 無人航空機操縦技能証明 実技試験コース

■ 前置き

Yahoo!知恵袋でRealFlightに無人航空機操縦技能証明の実技試験コースを取り込む方法の質問がありました。
https://detail.chiebukuro.yahoo.co.jp/qa/question_detail/q11309613636

試験を受ける気は全くないのですが、どんなものか検索したところ、下記の情報を見つけました。
【前編】RealFlight9.5(フライト シミュレータ)でドローン国家資格実技試験会場を作ってみる
https://www.nttpc.co.jp/technology/realflight_1.html

#05 RealFlightで遊んでみる~無人航空機操縦技能証明 実技試験コース~
https://www.youtube.com/watch?v=-sVcZ-tfrl0

飛行場データのリンク先はRealFlightのSwap Pageになっています。
https://forums.realflight.com/index.php?resources/categories/airports.184/
ところがリンク先に見当たりおません。登録が増えてページが変わったようです。以下にダウンロードリンクを示します。


■ ダイレクト ダウンロード リンク

03_exam_line_with_guides_AP
https://forums.realflight.com/index.php?resources/03_exam_line_with_guides_ap.31805/

03_exam_line_no_guides_AP
https://forums.realflight.com/index.php?resources/03_exam_line_no_guides_ap.31804/

02_exam_eight_with_guides_AP
https://forums.realflight.com/index.php?resources/02_exam_eight_with_guides_ap.31803/

02_exam_eight_no_guides_AP
https://forums.realflight.com/index.php?resources/02_exam_eight_no_guides_ap.31802/

01_exam_square_with_guides_AP
https://forums.realflight.com/index.php?resources/01_exam_square_with_guides_ap.31801/

01_exam_square_no_guides_AP
https://forums.realflight.com/index.php?resources/01_exam_square_no_guides_ap.31800/

uav_license_test_square_2nd_AP
https://forums.realflight.com/index.php?resources/uav_license_test_square_2nd_ap.29950/

uav_license_test_eight_2nd_AP
https://forums.realflight.com/index.php?resources/uav_license_test_eight_2nd_ap.29951/

uav_license_test_line_2nd_AP
https://forums.realflight.com/index.php?resources/uav_license_test_line_2nd_ap.29952/

二等無?航空機操縦士実地試験実施細則 : https://www.mlit.go.jp/common/001516517.pdf
⇒ リンク切れしてます。改定されたのかも知れません?ということは、上記飛行場データも古いかもしれません?[2026/03/05]

ダウンロードするためには、RealFlight Forumsへの登録が必要です。登録は無料です。


■ ダウンロードした.RFXの取り込み方法

[Esc]キーを押して[Main Menu]を表示し、[My RealFlight]-[Import]-[3D Model]or[RealFlight Archive]で、.FBXや.RFXファイルなどを取り込みます。その後、[Main Menu]から[Free Play]-[Airport]で取り込んだ飛行場を選択します。

旧メニューバーからは、[Simulatin]-[Import]-[3D Model(FBX,KEX)]or[RealFlight Archive(RFX,G3X)]です。

■ 視点

Realflightを起動した状態では、視点が機体になっています。この状態だと画面の中心が機体を追うので、機体が移動したことが分かりにくいです。なので、w,s,a,dキーで視点を決めてマウスを左クリックして視点を固定したほうが良いです。

■ VR

PCのモニタだと遠近感が無いので操縦が難しいです。Meta Quest 3を使ってVRにすると、多少遠近感が出るのでPCモニタよりは操縦しやすくなります。(ヘリの静演技は非常に難しいです。[Training]-[Heli Orientation Trainer...]で練習すると良いかも?)

RealFlightは比較的軽いソフトですが、VRはマシンパワーが必要です。i9-12900+RTX3070のPCで、72fpsです。最高画質にはできませんが練習には十分です。RXT5080は最高画質にできますが、72fpsが無難です。1/72=14msecです。

Quest 3は、SteamLinkかVirtual DesktopでLAN接続しSteamVRを利用します[*1]。Wi-Fi 6/6E、IEEE802.11axで1Gbps以上でのリンクがあると良いです。Wi-Fi 7、IEEE802.11be、MLOは、無効にした方が良いです。

[*1] : Microsoft Flight Simulator 2024も利用する場合は、Virtual Desktopで、VDXRを使用し、SteamVRを使用しない方が良いかも知れません?


■ 参考(外部リンク)

Menu bar not visible
https://forums.realflight.com/index.php?threads/menu-bar-not-visible.59202/

HOW TO: Install Custom Airfields & Airplanes in RealFlight
https://www.youtube.com/watch?v=LoArX_trUPg

RCGroups FPV and Air Field - OFFICIAL!_AP
https://forums.realflight.com/index.php?resources/rcgroups-fpv-and-air-field-official-_ap.21952/

Fly Online with RCGroups.com!
https://www.youtube.com/watch?v=7DAv5mSfGJo
https://www.rcgroups.com/forums/showthread.php?2494138-Fly-Online-with-RCGroups-com

【リアルフライト9.5s】シミュレーターでドローン国家資格実技試験場を作成!使い方も解説【ドローン資格】
https://www.youtube.com/watch?v=b_KyaNDBcCw
3D飛行場の作成方法が開設されています。

【二等 25kg大型機】円周飛行 模範飛行|ドローン国家資格 実技試験対策【RealFlight Evolution】 : ドロスタ DroSta : 2026/05/03
https://www.youtube.com/watch?v=aPyskQW6D2A


■ 関連記事

Meta Quest 3 の動画キャプチャーの画質
https://bigarrow.tea-nifty.com/blog/2025/02/post-a679e6.html

RealFlight Evolution + Quest 3
https://bigarrow.tea-nifty.com/blog/2025/02/post-89d797.html

RealFlight Evolution
https://bigarrow.tea-nifty.com/blog/2022/11/post-4622cb.html

RealFlight8 and Blender with "RealFlight FBX Exporter"
https://bigarrow.tea-nifty.com/blog/2018/09/realflight8-and.html

RealFlight G3.5、FMS alpha 8.5用のフォトフィールドの作り方
https://bigarrow.tea-nifty.com/blog/2009/03/realflight-g35f.html

| | コメント (0)

Power QueryでExcelの複数のシートを縦結合する方法

[2025/12/20] 追記

詳細エディタを使用せず、ヘルパークエリを使用する方法を記事にしました。

Excel Power Query Table.Combine を用いた複数ブック、複数シートの縦結合
https://bigarrow.tea-nifty.com/blog/2025/12/post-60e0ad.html

複数ブック、複数シートをPower Queryで1つの表にする方法(1ヘルパークエリ)
https://bigarrow.tea-nifty.com/blog/2025/11/post-d0f322.html

[2025/02/21] 本記事:詳細エディタを使用する方法

Yahoo!知恵袋にシート結合の質問があり検索したところ、2つの方法がありそうなのでメモしました。
https://detail.chiebukuro.yahoo.co.jp/qa/question_detail/q12311158122

2つとも1つのシートをデータベース形式に整形する関数クエリを作成し、連結クエリで複数のファイルやシートを取り込み、関数クエリを呼び出して各シートをデータベース形式にして連結する仕組みです。
主な相違点は、各シートを連結するときに使用する関数の違いと、コードの作成方法です。

1. 元の表と集計表

元の表はファイルに同じ形式の複数のシートがあります。ファイルのすべてのシートを集計します。

Sheet1~SheetN
         
2月1日 土      
         
  予約 来店 退店 帰客
15:00 0 1 3 1
16:00 1 2 2 0
17:00 0 3 1 1
18:00 1 1 3 0
19:00 0 2 2 1
20:00 1 3 1 0
21:00 0 1 3 1
22:00 1 2 2 0
23:00 0 3 1 1

上記複数のシートを取り込んで連結し、ピボットテーブルで時刻別、曜日別の平均値を集計したものが下記の集計表です。

集計シート(Pivot Table)
  日曜日 土曜日 平均/予約 平均/来店 平均/退店 平均/帰客
時刻 平均/予約 平均/来店 平均/退店 平均/帰客 平均/予約 平均/来店 平均/退店 平均/帰客
15:00 0 1 3 1 0 1 3 1 0 1 3 1
16:00 1 2 2 0 1 2 2 0 1 2 2 0
途中略                        
23:00 0 3 1 1 0 3 1 1 0 3 1 1
総計 0.44 2 2 0.56 0.44 2 2 0.56 0.44 2 2 0.56


2. 各クエリの関係

次の表で必要な章番号の部分のみを見てください。関数クエリと連結クエリを両方使います。

2つの方法の特徴(個人の感想です)
  - Table.ExpandTableColumn() : 手で書くコードは少ないが、全体のコードは多い。--> コードを全く読めない人向け。
  - Table.Combine() : 手で書くコードは多いが、全体のコードは少ない。--> コードを少し読める人向け。

本記事で説明するクエリの組合せ
取込元 Table.ExpandTableColumn()を使用する方法 Table.Combine()を使用する方法
関数クエリ 連結クエリ 関数クエリ 連結クエリ
ファイルから 3.1 整形関数/サンプル 4.1.1 展開 3.1 整形関数/サンプル 4.2.1 結合
フォルダから

3.2 ファイルの変換/サンプルファイルの変換:シートの展開処理

3.2 展開整形関数/展開サンプル:シートの整形処理

4.1.2 展開フォルダ 3.1 整形関数/サンプル 4.2.2 結合フォルダ


3. 関数クエリ

後述の4.連結クエリから呼ばれる関数クエリの作成方法のメモです。1つのシートをデータベース形式に整形するクエリです。

3.1 "整形関数"関数クエリ

後述の4.1.1, 4.2.1, 4.2.2で使用する関数クエリです。

Pros:1シートのみ整形する関数なので、若干コードが少ない。
Cons : サンプルクエリと連動しないので、変更する時は手動で関数クエリも変更する必要がある。([*1]本記事では記載していませんが、ヘルパークエリを使用する方法もあります。ヘルパークエリを使用すると、サンプルクエリと連動します。)

(1) [データ]-[データの取得v]-[ファイルから]-[Excelブックから]で1つのファイルを[インポート]し、シートを1つ選択して[データの変換]して、プロパティの名前を"サンプル"にします。

(2) "サンプル"クエリにクリック操作などで整形ステップを追加し、データベース形式(項目名がヘッダに並び、値が下に続く形式)にします。

(3) "サンプル"クエリを右クリックして[複製]し、新しいクエリの名前を"整形関数"に変更します。

(4) [ホーム]-[詳細エディタ]で整形関数クエリを以下のように修正します。

※ この後、整形ステップを修正する時は、"サンプル"クエリを修正し、同じ修正を"整形関数"関数クエリにも適用します。

修正前(Sheet1を選択した場合)
let
    ソース = Excel.Workbook(File.Contents("ファイルパス"), null, true),
    Sheet1_Sheet = ソース{[Item="Sheet1",Kind="Sheet"]}[Data]
    // 以下、整形するコード(上記(2)のサンプルで作成したコード)

修正後
(_table as table) =>
let
    // ソース = Excel.Workbook(File.Contents("ファイルパス"), null, true),
    // Sheet1_Sheet = ソース{[Item="Sheet1",Kind="Sheet"]}[Data],
    Sheet1_Sheet = _table,
    // 以下、整形するコード(上記(2)のサンプルで作成したコード )

★ヘルパークエリを手動で作る方法【お勧め】ファイルから限定

上記の方法は関数クエリとその元クエリが連動していないので、修正する場合は両方を修正するので不便です。しかし、シートのヘルパークエリは自動作成されません。そこで、手動でヘルパークエリを作成する方法がありましたので、紹介します。上記よりこの記事の方法の方がおすすめです。但し、この方法で作成した関数クエリは、フォルダからの取込みには使用できません。

Power Query:複数のシートのデータを縦結合する(ヘルパークエリ編)
https://nandemo-xl.hatenablog.com/entry/2023/11/14/161634

 

3.2 "ファイルの変換"関数クエリ、"展開整形関数"関数クエリ、"展開フォルダ"連結クエリ

フォルダからTable.ExpandTableColumn()を用いて連結する、関数クエリと連結クエリです。
"展開整形関数"関数クエリで1シートを整形し、"ファイルの変換"関数クエリで1ファイルのシートを展開(連結)し、"展開フォルダ"クエリでファイルを展開(連結)します。
ヘルパークエリによりファイルを整形(シートを結合)する関数クエリと連結クエリが同時に作成されます。自動生成された関数クエリから更に1シートを整形するクエリを手動で作成します。

Pros : 手で書くコードが少ない。1つのシートを整形するので、各シートの列構成が異なっていても対応できる。
Cons : クリック数が多い。関数クエリが2つになるので、構成が複雑である。(シートの整形とシートの連結の2つの関数クエリになる)

(1) [データ]-[データの取得v]-[ファイルから]-[フォルダから]-フォルダを選択、[開く]-[データの変換]、プロパティの名前を"展開フォルダ"に変更します。

(2) "Content"の右のファイルの結合アイコンをクリック、パラメータ1を選択し、[OK]。(ヘルパークエリが作られる)

(3) "展開フォルダ"クエリの最後のステップ"変更された型"を削除します。

(4) "サンプルファイルの変換"クエリの"Data"列のTableを右クリックし[*2]、[新しいクエリとして追加]し、プロパティの名前を"展開サンプル"に変更します。[*2]この時選ぶTableは、一番列数の多いものを選んでください。

(5) "展開サンプル"クエリに1シート分の整形ステップを追加します。

(6) "展開サンプル"クエリを右クリックし、[複製]し、プロパティの名前を"展開整形関数"に変更します。

(7) "展開整形関数"クエリを右クリックし、[詳細エディタ]で下記のようにコードを修正します。

修正前
let
    ソース = Excel.Workbook(パラメーター1, null, true),
    Sheet1_Sheet1 = ソース{[Item="Sheet1",Kind="Sheet"]}[Data],

修正後
(_table as table) =>
let
    // ソース = Excel.Workbook(パラメーター1, null, true),
    // Sheet1_Sheet1 = ソース{[Item="Sheet1",Kind="Sheet"]}[Data],
    Sheet1_Sheet1 = _table,

(8) "サンプルファイルの変換"クエリの"Kind"列の[▼]-[テキストフィルタ]-[指定の値に等しい]で値の入力または選択欄で[Sheet]を選択、[OK]

(9) "サンプルファイルの変換"の"Data"列を選択し、[列の追加]-[カスタム関数の呼び出し]で"関数クエリ"に[展開整形関数]を選択し、_tableを列名にして[Data]を選択し、[OK]

(10) 必要な列以外の列を削除します。"展開整形関数"は残してください。

(11) "展開整形関数"の右の展開アイコンをクリックし、さらに読み込むをクリックし、元の列名をプレフィックスとして使用しますのチェックを外し、[OK]

(12) "展開フォルダ"クエリを選択し、結合後の不要な列の削除(Source.Name)、型の変更などの整形ステップを追加します。

作成されるクエリ
フォルダ:展開フォルダからファイルを変換する[4]
    フォルダ:ヘルパークエリ[3]
        パラメータ1(サンプルファイル) -- "サンプルファイルの変換"のソース
        サンプルファイル ------- パラメータ1のファイル
        fxファイルの変換 ------- fx"展開整形関数"を呼び出してシートの展開(連結)をする
    サンプル ファイルの変換 -- これを変更するとfx"ファイルの変換"が連動して変更される
    展開サンプル -------------- fx"展開整形関数"の元。連動しないので、変更する場合はfx"展開整形関数"も変更すること
    fx展開整形関数 ------------ 1つのシートを整形する
フォルダ:その他のクエリ
    展開フォルダ -------------- fx"ファイルの変換"を呼び出してファイルの展開(連結)をする

 

【参考】別の方法:"ファイルの変換"、"展開フォルダ"クエリ:添付のサンプルファイルには入っていません。

"ファイルの変換"関数クエリで1ファイルの全シートを展開(連結)してから成形し、"展開フォルダ"クエリでファイルを展開(連結)します。
ヘルパークエリによりファイルを整形(シートを結合)する関数クエリと連結クエリが同時に作成されます。自動生成されたクエリを編集します。

Pros : クリック操作でコードの生成ができる。"サンプルファイルの変換"を変更すると関数クエリが連動して変更されるので、後からの変更が容易である。
Cons : 1つのファイルの複数シートが連結した表を整形するので、各シートの列項目名の構成が同じである必要がある。

(1) Excelの[データ]-[データの取得v]-[ファイルから]-[フォルダから]でフォルダを選択し、[開く]-[データの変換]。プロパティの名前を"展開フォルダ"に変更します。

(2) Contentの列の結合アイコンをクリック、パラメータ1を選択し、[OK]。⇒ 関数クエリ"ファイルの変換"を含むヘルパークエリが生成されます。"展開フォルダ"クエリの最後のステップの変更された型を削除します。

(3) "サンプルファイルの変換"クエリの"kind"の列の[▼]-[テキストフィルタ]-[指定の値に等しい]で値の入力または選択でSheetを選択し、[OK]。不要な列を削除します。少なくともData列は残します。Data列の右の展開矢印アイコンをクリックして、さらに読み込むをクリックし、元の列名をプレフィックスとして使用しますのチェックを外し、[OK]。

(4) "サンプルファイルの変換"クエリを編集して整形ステップを追加します。⇒ 関数クエリ"ファイルの変換"が連動して修正されます。
※ 前記2.1)と異なり複数シートの同じ列が縦に連結された表を整形します。なので、各ファイルの列名は全て同じである必要があります。

(5)  "展開フォルダ"クエリで不要な列(Source.Name列など)の削除と型変更をします。
注意 : 関数クエリ"ファイルの変換"を詳細エディタで直接編集すると、"サンプルファイルの変換"クエリと連動しなくなります。

"サンプルファイルの変換"クエリ
let
    // 上記(2)のContentの列の結合アイコンをクリックすると自動生成されるコード
    ソース = Excel.Workbook(パラメーター1, null, true),

    // 以下、1ファイルの全シートを展開(連結)した表の整形ステップ

"ファイル変換"関数クエリ
let
    // 上記(2)のContentの列の結合アイコンをクリックすると自動生成されるコード
    ソース = (パラメーター1 as binary) => let
    ソース = Excel.Workbook(パラメーター1, null, true),
    フィルターされた行 = Table.SelectRows(ソース, each ([Kind] = "Sheet")),
    削除された他の列 = Table.SelectColumns(フィルターされた行,{"Data"}),
    #"展開された Data" = Table.ExpandTableColumn(削除された他の列, "Data", {"Column1", "Column2", "Column3", "Column4", "Column5"}, {"Column1", "Column2", "Column3", "Column4", "Column5"}),
    // 以下、 "サンプルファイルの変換"クエリを編集すると自動的に追加される整形ステップ

"展開フォルダ"クエリ
let
    // 上記(1)の[データの変換]で自動生成されるコード
    ソース = Folder.Files("C:\Users\Public\Documents\Sample"),
    // 上記(2)のContentの列の結合アイコンをクリックすると自動生成されるコード
    #"フィルター選択された非表示の File1" = Table.SelectRows(ソース, each [Attributes]?[Hidden]? <> true),
    カスタム関数の呼び出し1 = Table.AddColumn(#"フィルター選択された非表示の File1", "ファイルの変換", each ファイルの変換([Content])),
    #"名前が変更された列 1" = Table.RenameColumns(カスタム関数の呼び出し1, {"Name", "Source.Name"}),
    削除された他の列1 = Table.SelectColumns(#"名前が変更された列 1", {"Source.Name", "ファイルの変換"}),
    展開されたテーブル列1 = Table.ExpandTableColumn(削除された他の列1, "ファイルの変換", Table.ColumnNames(ファイルの変換(#"サンプル ファイル"))),
    // 以下、上記(5)の整形処理
    削除された列 = Table.RemoveColumns(展開されたテーブル列1,{"Source.Name"}),

4. 連結クエリ

3.の関数クエリを呼び出して各シートを整形し、成形したシートを連結するクエリです。

4.1 Table.ExpandTableColumn()を使用する方法

https://learn.microsoft.com/ja-jp/powerquery-m/table-expandtablecolumn
参考 : Power Query Editorのクリック操作の[変換]-構造化列の[展開]で生成される関数です。

Pros : クリック操作でコードの自動生成ができる。フォルダから取り込む場合、ヘルパークエリが作られるので、修正が容易である。
Cons : 引数に"columnNames as list"が必要なので、全てのシートの列の構成は同じである必要があり、後で変更することはできない。[*3]サンプルファイルに一番列数が多いシートを選べば、それ以下の列構成のシートを連結することが可能です。

4.1.1 Excelファイルから:"展開"連結クエリ

(1) ファイルの取得 : Excelの[データ]-[データの取得v]-[ファイルから]-[Excelファイルから]でファイルを選択し[インポート]、ファイルを選択し[データの変換]、プロパティの名前を"展開"に変更。)

(2) 整形関数クエリの呼び出し各シートを整形 : [列の追加]-[カスタム関数の呼び出し]で、_tableの下のアイコンを列名に変更し、右のドロップダウンリストから[Data]を選択し[OK])

(3) 元の表を削除 : 作成された[整形関数]列を選択し、右クリックで[他の列の削除]。※ 元のシート名を使う場合はNameも残す。

(4) 整形した表を展開 : [整形関数]の展開アイコンをクリックし、整形したテーブルを格納した列を展開。(生成されたコードを赤字のように書き換えると列名が異なっていても展開できるかも知れない?)

// ファイルの取得
    ソース = Excel.Workbook(File.Contents("ファイルのパス(例:C:\Sample.xlsx)"), null, true),
// 整形関数クエリの呼び出し各シートを整形
    呼び出されたカスタム関数 = Table.AddColumn(ソース, "整形関数", each 整形関数([Data])),
// 元の表を削除
    削除された他の列 = Table.SelectColumns(呼び出されたカスタム関数,{"整形関数"}),
// 整形した表を展開
    #"展開された 変換" = Table.ExpandTableColumn(削除された他の列, "整形関数", {"予約", "来店", "退店", "帰客", "日付", "時刻", "曜日名"}, {"予約", "来店", "退店", "帰客", "日付", "時刻", "曜日名"}),
//    #"展開された 変換" = Table.ExpandTableColumn(削除された他の列, "整形関数", List.Union(List.Transform(削除された他の列[Data], each Table.ColumnNames(_)))),
    // 以下、結合後の型変更等をクリック操作などで追加

参考 : Excel PowerQuery 列の展開を全てまとめて実施する方法
https://excel-excellent-technics.com/excel-powerquery-table-expandtablecolumn-11572

4.1.2 Excelフォルダから:"展開フォルダ"連結クエリ

複数のシートを連結するクエリは、上記3.2 の"ファイルの変換"と同時に自動生成されます。
作成手順やコードは、上記3.2 を参照してください。

4.2 Table.Combine()を使用する方法

https://learn.microsoft.com/ja-jp/powerquery-m/table-combine
参考 : Power Query Editorのクリック操作の[ホーム]-[クエリの追加]で生成される関数です。

Pros : 若干コードが少ない。引数に"columnNames as list"が無いので、シートにより列名が変化するテーブルでも使える。
Cons : クリック操作では関数クエリを組み込んだ結合クエリのコードを生成できず、手でコードを記述する必要がある。

4.2.1 Excelファイルから:"結合"連結クエリ

(1) ファイルの取得 : [データ]-[データの取得v]-[ファイルから]-[Excelブックから]で1つのファイルを[インポート]し、ファイル名を選択して[データの変換]、プロパティの名前を"結合"に変更。

(2) 整形関数を呼び出して整形 : 以下、[ホーム]-[詳細エディタ]で下記コードを記述する。そして、ソースの行の最後に","を付け、inの下のステップ名をCombinedDataに変更する。

// ファイルの取得(これは(1)の作業で自動生成されている)
    ソース = Excel.Workbook(File.Contents("ファイルのパス(例:C:\Sample.xlsx)"), null, true),
    // Sheetに絞り込み(ここから手入力する)
    Sheets = Table.SelectRows(ソース, each ([Kind] = "Sheet")),
    // 各シートを成形関数で変換し、結合
    CombinedData = Table.Combine(List.Transform(Sheets[Data], each 整形関数(_)))

参考
List.Transform : https://learn.microsoft.com/ja-jp/powerquery-m/list-transform

4.2.2 Excelフォルダから:"結合フォルダ"連結クエリ

(1) フォルダの取得 : [データ]-[データの取得v]-[ファイルから]-[フォルダーから] でフォルダのパスを選択、[開く]-[データの変換]、プロパティの名前を"結合フォルダ"に変更。

(2) 整形関数を呼び出して整形 : 以下、[ホーム]-詳細エディタで下記コードを記述する。そして、ソースの行の最後に","を付け、inの下のステップ名をCombineDataに変更する。

// フォルダの取得
    ソース = Folder.Files("フォルダのパス(例:C:\Sample)"),
// 整形関数を呼び出して整形
    // Excelファイルのみをフィルタリング ⇒ 全Excelファイル別のレコードになる

    ExcelFiles = Table.SelectRows(ソース, each Text.EndsWith([Extension], ".xlsx") or Text.EndsWith([Extension], ".xls")),
    // 各ExcelファイルからExcelの内容を取り出し、新しい列に追加
    AddCustom = Table.AddColumn(ExcelFiles, "Custom", each Excel.Workbook(File.Contents([Folder Path] & [Name]), null, true)),
    // Excelの内容の内"Name"と"Data"を展開 ⇒ 全Excelファイルのシート別のレコードになる
    ExpandCustom = Table.ExpandTableColumn(AddCustom, "Custom", {"Name", "Data"}, {"SheetName", "Data"}),

    // [Data]列を整形関数で変換
    ApplyTransform = Table.TransformColumns(ExpandCustom, {"Data", each 整形関数(_)}),
    // 整形した[Data]列の各テーブルを結合
    CombineData = Table.Combine(ApplyTransform[Data])

参考
Table.TransformColumns : https://learn.microsoft.com/ja-jp/powerquery-m/table-transformcolumns

4. サンプルファイル

C:\Users\Public\Documents\Sampleフォルダが無い状態で、C:\Users\Public\Documentsの下に展開してください。

ダウンロード - 250220xlcombine2.zip

下記のファイルが入っています。
"C:\Users\Public\Documents\250220xlcombine2.xlsx" : 集計ファイル
"C:\Users\Public\Documents\Sample\250220xlsheet.xlsx" : データファイル
"C:\Users\Public\Documents\Sample\250220xlsheet2.xlsx" : データファイル


■ 参考(外部サイト)

【Excel】マウス操作だけのパワークエリからレベルアップ!カスタム関数クエリでワンランク上のデータ整形
https://www.youtube.com/watch?v=jDIwpBCVibY

【Excel パワークエリ】これを見ても、まだコピペでいいなんて言うヤツ……いねぇよなぁ
https://www.youtube.com/watch?v=UapvFSxv2mE

【有料級】パワークエリでExcel全シートをまとめる秘訣を大公開
https://www.youtube.com/watch?v=ZgpRmxpKZFM

Power Query(パワークエリ)で同一ブック内の複数シートを結合
https://detail.chiebukuro.yahoo.co.jp/qa/question_detail/q13312576643?sort=1&page=1

パワークエリはデータクリーニングにて最強【EXCEL Power BI Power Query Data Cleaning】
https://www.youtube.com/watch?v=22o2pCVCLFE
ヘルパークエリで複数のファイルの異なるシート名のピボットを解除して結合する。 ⇒ この方法だとヘルパークエリのサンプルファイルの変換で修正が可能なので、本ブログ記事より保守性が良いかもしれない?


■ 関連記事

Power Queryで複数の表を結合する方法(まとめ)
https://bigarrow.tea-nifty.com/blog/2025/11/post-341d5f.html

Power Queryで表をリスト形式に整形する方法(まとめ):2025/11/16:当ブログ
https://app.cocolog-nifty.com/cms/blogs/632678/entries/95708707

複数ブック、複数シートをPower Queryで1つの表にする方法
https://bigarrow.tea-nifty.com/blog/2025/10/post-4ad540.html

1レコードが1シート複数行複数列から成る複数シートを1つのデータベース形式にする
https://bigarrow.tea-nifty.com/blog/2025/09/post-4beb66.html

Excel Power Query : フォルダ指定でブックとシートを取得しシートを整形結合
https://bigarrow.tea-nifty.com/blog/2024/08/post-9c6b07.html

| | コメント (0)

Excel Power Queryで横項目3段のクロス表をリスト形式にする方法

Yahoo!知恵袋にクロス表の変形の質問がありました。
https://detail.chiebukuro.yahoo.co.jp/qa/question_detail/q14310854964
INDEX(),MATCH()を用いた回答が付いています。これが最も簡単そうではあります。

ところで、このような表をデータベース形式に整形すると、更新が楽になり、色々な項目軸での集計が簡単にできるようになります。
そこで、Power Queryを使用して成形する方法をメモしました。

1. 元の表と整形後の表

C:\Public\Documents\250217xl3row.xlsx 'Sheet1'

元の表(Sheet1)
2025年 1F 2F
1月 AM PM AM PM
日 Gr 品名 個数 Gr 品名 個数 Gr 品名 個数 Gr 品名 個数
1日 A りんご 3 B キウイ 5 B 醤油 1 A 塩 2
2日 A みかん 5 B キウイ 5 B ソース 2 A 醤油 2
3日                        
4日 B みかん 11 A キウイ 5 A 塩 1 B ソース 1

表の特徴と整形目標
1) 横の列項目が3行(段)のクロス表になっている。
2) 横の列項目はセル結合により左端のセルのみ項目名が入っていて、他のセルは空白になっている。
⇒ 各項目が1列のリストに整形する。

3) 左端の日の列の値は、日のみで年月が無い。年月はクロス表の左上コーナー部に記載されている。
⇒ リストに年、月、年月日の項目列を追加する。

データベース形式に整形した表(Total)
年月日 年 月 日 1段目 2段目 Gr 品名 個数
2025/01/01 2025年 1月 1日 1F AM A りんご 3
2025/01/01 2025年 1月 1日 1F PM B キウイ 5
2025/01/01 2025年 1月 1日 2F AM B 醤油 3
以下省略                

これをピボットテーブルに読み込み、Grをスライサーに設定することで、Gr別の表を作成します。

2. Power Queryの解説

3つのクエリを作成し、その内2つのクエリをデータモデルに追加します。後でデータモデルをPower Pivotで処理します。
クエリは殆どクリックで式を生成します。一部で自動生成された式の列名を修正します。
左辺はクリック操作で自動的に付く名前にしているので、左辺の名前からクリック操作を推定することができます。
緑字が具体的な操作です。青字がPower Query Mのコードです。赤字が変更する部分です。

2.1 Sheet1クエリ

データの取込みと途中までの整形をします。

let
1) ブックを読み込み、シートを取り出す。(Excel:[データ]-[データの取得]-[ファイルから]-[Excelブックから]-[*.xlsx]-[インポート]-[Sheet1]-[データの変換])
    ソース = Excel.Workbook(File.Contents("C:\Public\Documents\250217xl3row.xlsx"), null, true),
    Sheet1_Sheet = ソース{[Item="Sheet1",Kind="Sheet"]}[Data],

2) 横の列項目の1段目(1F,2F)と2段目(AM,PM)の横方向の空白を左の値で埋める。
(1) フィル機能で埋めるため、表を縦横入れ替え(転置)する。([変換]-[入れ替え])
    転置されたテーブル = Table.Transpose(Sheet1_Sheet),

Column1 Column2 Column3 Column4 Column5 Column6 Column7
2025年 1月 日 1日 2日 3日 4日
1F AM Gr A A null B
null null 品名 りんご みかん null みかん
null null 個数 3 5 null 11
null PM Gr B B null A
以下省略            

(2) 1段目(1F,2F)と2段目(AM,PM)の空白を上の値で埋める。(Column1,Column2を選択し、[変換]-[フィルv]-[下へ])
    下方向へコピー済み = Table.FillDown(転置されたテーブル,{"Column1", "Column2"}),

3) 元表の横項目は後でピボット解除することで値を縦に並べる。ピボット解除はヘッダーの項目名に対して行うので、元表の横項目を結合して1列にし、後で行に転置して1行目をヘッダーとして使用する。
(1) 元表の横項目を結合する。(Column1~Column3を選択し、[変換]-[列のマージ])
    結合された列 = Table.CombineColumns(下方向へコピー済み,{"Column1", "Column2","Column3"},Combiner.CombineTextByDelimiter(";", QuoteStyle.None),"結合済み"),

結合済み  Column4 Column5 Column6 Column7
2025年;1月;日 1日 2日 3日 4日
1F;AM;Gr A A null B
1F;AM;品名 りんご みかん null みかん
1F;AM;個数 3 5 null 11
1F;PM;Gr B B null A
以下省略        

(2) 元表の横項目を項目名にするため、縦横入れ替え(転置)して元に戻す。([変換]-[入れ替え])
    転置されたテーブル1 = Table.Transpose(結合された列),

Column1 Column2 Cloumn3 Column4 Column5 Column6 Column7 Column8 Column9 Column10 Column11 Column12 Column13
2025年;1月;日 1F;AM;Gr 1F;AM;品名 1F;AM個数 1F;PM;Gr 1F;PM;品名 1F;PM;個数 2F;AM;Gr 2F;AM;品名 2F;AM;個数 2F;PM;Gr 2F;PM;品名 2F;PM;個数
1日 A りんご 3 B キウイ 5 B 醤油 1 A 塩 2
2日 A みかん 5 B キウイ 5 B ソース 2 A 醤油 2
3日 null null null null null null null null null null null null
4日 B みかん 11 A キウイ 5 A 塩 1 B ソース 1

4) 左上の年月から年、月、日の列を生成する。(横項目の整形のみの場合は、5)に進んでください。)
(1) 左端の列で年月だけを新しい列に抜き出す。([列の追加]-[カスタム列]で'新しい列名'に"年月"、'カスタム列の式'に下記式の"each"と")"の間の式を入力し、[OK])
    追加されたカスタム = Table.AddColumn(転置されたテーブル1, "年月", each if Text.Contains([Column1], "年") then [Column1] else null),

(2) 抜き出した年月を下方のレコードにコピーする。(年月を選択し、[変換]-[フィルv]-[下へ])
    下方向へコピー済み1 = Table.FillDown(追加されたカスタム,{"年月"}),

(3) 後で1行目をヘッダーにするので、左端の列の1行目にしか年の文字が無いことを利用して、1行目のみ値を"年;月:日"に変更する。([列の追加]-[カスタム列]で'新しい列名'に"カスタム"、'カスタム列の式'に下記式の"each"と")"の間の式を入力し、[OK])
    追加されたカスタム1 = Table.AddColumn(下方向へコピー済み1, "カスタム", each if [Column1] = [年月] then "年;月;日" else [年月]),

(4) 年月日の列に分割する。(カスタムを選択し、[変換]-[列の分割]で、'区切り記号'を"セミコロン"、詳細オプションの'分割の方向'を"列"、'分割後の列数'を"3"、'引用符文字'を"なし"にし、[OK])
    区切り記号による列の分割 = Table.SplitColumn(追加されたカスタム1, "カスタム", Splitter.SplitTextByDelimiter(";", QuoteStyle.None), {"カスタム.1", "カスタム.2", "カスタム.3"}),

(5) 左端の日の列の1行目のみ"月"が含まれていることを利用して、左端の日の列から1行目が項目名、2行目以降が日の値になる列を生成する。([列の追加]-[カスタム列]で'新しい列名'に"年月"、'カスタム列の式'に下記式の"each"と")"の間の式を入力し、[OK])
    追加されたカスタム2 = Table.AddColumn(区切り記号による列の分割, "カスタム", each if Text.Contains([Column1], "月") then "日" else [Column1]),

Column1 Column2 Cloumn3 Column4   途中略   Column13 年月 カスタム.1 カスタム.2 カスタム.3 カスタム
2025年;1月;日 1F;AM;Gr 1F;AM;品名 1F;AM個数       2F;PM;個数 2025年;1月;日 年 月 日 日
1日 A りんご 3       2 2025年;1月;日 2025年 1月 日 1日
2日 A みかん 5       2 2025年;1月;日 2025年 1月 日 2日
3日 null null null       null 2025年;1月;日 2025年 1月 日 3日
4日 B みかん 11       1 2025年;1月;日 2025年 1月 日 4日

(6) 不要になった列を削除する。(Column1,年月,カスタム.3を選択し、[ホーム]-[列の削除])
    削除された列 = Table.RemoveColumns(追加されたカスタム2,{"Column1", "年月", "カスタム.3"}),

5) 3段の横項目をヘッダーに昇格する。([変換]-[1行目をヘッダーとして使用])
    昇格されたヘッダー数 = Table.PromoteHeaders(削除された列, [PromoteAllScalars=true]),

6) 後で日を文字列として扱うので、型変更をする。(日を選択し、[変換]-[データ型]-[テキスト])
    変更された型 = Table.TransformColumnTypes(挿入された日付,{{"日", type text}})

7) 年月日列を追加する。日だけだと並び替えで各月の1日が最初に並んでしまい年月順にならないので、年月日の列を作る。([列の追加]-[カスタム列]で'新しい列名'に"年月日"、'カスタム列の式'に下記式の"each"と")"の間の式を入力し、[OK])
    挿入された日付 = Table.AddColumn(昇格されたヘッダー数, "年月日", each Date.From([年]&[月]&[日]), type date),

in
    挿入された日付

1F;AM;Gr 1F;AM;品名 1F;AM;個数   途中略   2F;PM;Gr 2F;PM;品名 2F;PM;個数 年 月 日 年月日
A りんご 3       A 塩 2 2025年 1月 1日 2025/1/1
A みかん 5       A 醤油 2 2025年 1月 2日 2025/1/2
null null null       null null null 2025年 1月 3日 2025/1/3
B みかん 11       B ソース 1 2025年 1月 4日 2025/1/4

Power Query Editorの[ホーム]-[閉じて読み込むv]-[閉じて次に読み込む...]で、"接続の作成のみ"を選択し、"このデータをデータモデルに追加する"のチェックを外し、[OK]。
250214xl1

2.2 Totalクエリ

データの表を作成します。

let
1) ソースの取込み(クエリSheet1を右クリックし、[参照]。名前を"Total"に変更。)
    ソース = Sheet1,

2) 横項目をピボット解除して縦1列に並べます。(年, 月, 日,年月日を選択し、[変換]-[列のピボット解除v]-[その他の列のピボット解除])
    ピボット解除された他の列 = Table.UnpivotOtherColumns(ソース, {"年", "月", "日", "年月日"}, "属性", "値"),

年 月 日 年月日 属性 値
2025年 1月 1日 2025/1/1 1F;AM;Gr A
2025年 1月 1日 2025/1/1 1F;AM;品名 りんご
2025年 1月 1日 2025/1/1 1F;AM;個数 3
2025年 1月 1日 2025/1/1 1F;PM;Gr B
以下省略          

3) 横項目を分割します。(属性を選択し、[変換]-[列の分割]で、'区切り記号'を"セミコロン"、詳細オプションの'分割の方向'を"列"、'分割後の列数'を"3"、'引用符文字'を"なし"にし、[OK]。生成された式の列名を下記のように変更。)
    区切り記号による列の分割 = Table.SplitColumn(ピボット解除された他の列, "属性", Splitter.SplitTextByDelimiter(";", QuoteStyle.None), {"1段目", "2段目", "3段目"}),

年 月 日 年月日 1段目 2段目 3段目 値
2025年 1月 1日 2025/1/1 1F AM Gr A
2025年 1月 1日 2025/1/1 1F AM 品名 りんご
2025年 1月 1日 2025/1/1 1F AM 個数 3
2025年 1月 1日 2025/1/1 1F AM Gr B
以下省略              

4) 縦に並んだ横項目を横にピボットするために、縦項目と横項目と値を各1列の全体で3列にします。次に横項目を横にピボットします。
(1) 縦項目を文字列で結合するために、文字列に型変更します。(年月日を選択し、[変換]-[データ型]-[テキスト])
    変更された型 = Table.TransformColumnTypes(区切り記号による列の分割,{{"年月日", type text}}),

(2) 縦項目を1列に結合します。(年月日,年,月,日,1段目,2段目を選択し、[変換]-[列のマージ])
    結合された列 = Table.CombineColumns(変更された型,{"年月日", "年", "月", "日","1段目","2段目"},Combiner.CombineTextByDelimiter(";", QuoteStyle.None),"結合済み"),

結合済み 3段目 値
2025/01/01;2025年;1月;1日;1F;AM Gr A
2025/01/01;2025年;1月;1日;1F;AM 品名 りんご
2025/01/01;2025年;1月;1日;1F;AM 個数 3
2025/01/01;2025年;1月;1日;1F;AM Gr B
以下省略    

(3) 横項目をピボットします。(3段目を選択し、[変換]-[列のピボット]、'値列'に"値"、'詳細設定オプション'の'値の集計関数'に[集計しない]、[OK]。)
    ピボットされた列 = Table.Pivot(結合された列, List.Distinct(結合された列[#"3段目"]), "3段目", "値"),

結合済み Gr 品名 個数
2025/01/01;2025年;1月;1日;1F;AM A りんご 3
2025/01/01;2025年;1月;1日;1F;AM B キウイ 5
2025/01/01;2025年;1月;1日;1F;AM B 醤油 1
2025/01/01;2025年;1月;1日;1F;AM A 塩 2
以下省略      

5) 結合していた横項目を分割します。(結合済みを選択し、[変換]-[列の分割]で、'区切り記号'を"セミコロン"、詳細オプションの'分割の方向'を"列"、'分割後の列数'を"6"、'引用符文字'を"なし"にし、[OK]。生成された式の列名を下記のように変更。 )
    区切り記号による列の分割1 = Table.SplitColumn(ピボットされた列, "結合済み", Splitter.SplitTextByDelimiter(";", QuoteStyle.None), {"年月日", "年", "月", "日", "1段目", "2段目"}),

6) 型変更をします。(年月日を選択し、[変換]-[データ型]-[日付]。Gr,品名を選択し、[変換]-[データ型]-[テキスト]。個数を選択し、[変換]-[データ型]-[整数]。)
   変更された型1 = Table.TransformColumnTypes(区切り記号による列の分割1,{{"年月日", type date}, {"Gr", type text}, {"品名", type text}, {"個数", Int64.Type}})
in
    変更された型1

年月日 年 月 日 1段目 2段目 Gr 品名 個数
2025/01/01 2025年 1月 1日 1F AM A りんご 3
2025/01/01 2025年 1月 1日 1F PM B キウイ 5
2025/01/01 2025年 1月 1日 2F AM B 醤油 1
2025/01/01 2025年 1月 1日 2F PM A 塩 2
以下省略                

※ Calendarクエリに年、月、日があるので、Totalの年、月、日は不要であり削除しても良い。逆にTotalに残すのであれば、Calendarクエリは不要で、Totalだけでピボットテーブルを作ることができる。

Power Query Editorの[ホーム]-[閉じて読み込むv]-[閉じて次に読み込む...]で、"ピボットテーブルレポート"を選択し、"このデータをデータモデルに追加する"のチェックを付けて、[OK]。
250214xl2

2.3 Calendarクエリ
カレンダーの表を作成します。

let
1) ソースの取込み。(クエリSheet1を右クリックし、[参照]。名前を"Calendar"に変更。)
    ソース = Sheet1,

2) カレンダ以外の列を削除する。(年,月,日,年月日を選択し、[ホーム]-[列の削除v]-[他の列の削除])
    削除された他の列 = Table.SelectColumns(ソース,{"年", "月", "日", "年月日"})
in
    削除された他の列

年 月 日 年月日
2025年 1月 1日 2025/01/01
2025年 1月 2日 2025/01/02
2025年 1月 3日 2025/01/03
2025年 1月 4日 2025/01/04

Power Query Editorの[ホーム]-[閉じて読み込むv]-[閉じて次に読み込む...]で、"接続の作成のみ"を選択し、"このデータをデータモデルに追加する"のチェックを付けて、[OK]。


3. リレーション (Power Pivot for Excel)

後で、データが無い日を表示するために、データが存在するCalendarをピボットテーブルに展開します。そこで、CalendarとTotalの年月日のリレーションを取ります。この時、Calendar側が1、Total側が多(*)になるようにします。([Power Pivot]-[管理]-[ホーム]-[ダイアグラムビュー]で、’Data'[年月日]を'Calendar'[年月日]にドラッグする。)

'Total'[年月日] *:1 'Calendar'[年月日]
250214xl3_20250301154501

4. メジャー

ピボットテーブルではΣ値(クロス点)に表の項目を集計した値=数値を表示します。しかし、メジャーは既に集計された値であるので、そのまま表示されます。そこで、品名をそのまま表示するメジャーを作成します。

メジャーの作成操作 : ピボットテーブルを選択し、ピボットフィールドの"Total"を右クリックし、[メジャーの追加]で、データの名前に"M品名"、数式に下記の=からの式を入力します。

M品名:
=IF(ISFILTERED(Calendar[年月日]) && ISFILTERED('Total'[1段目]), MAX('Total'[品名]))
 
年月日と1段目のフィルタコンテキストがある場合は、品名が1つになるので、それをIF文で確認し、品名を表示します。
メジャーで項目値を参照する場合は、集計が必要なのでMAXを使用していますが、1つしかないのでMINでもOKです。
250214xl4_20250301154601


5. ピボットテーブル

Gr別に表を分ける場合は、Grをスライサーかフィルターに設定して絞り込み表示をします。そして、シートをコピーして別のGrを絞り込み表示します。
250214xl5_20250301154601
 
1) ピボットテーブルのフィールド
列 : 1段目,2段目,Σ値
行 : 'Calendar'[年月日]
Σ値 : M品名,個数

2) スライサー
Grを右クリックし、[スライサーとして追加]。
[スライサー]-[列数]でレイアウトを変更。

3) 列幅の固定
[ピボットテーブル分析]-[ピボットテーブルv]-[オプション]-[レイアウトと書式]-[更新時に列幅を自動調整する]のチェックを外す。

参考 : [更新時にセル書式を保持する]は、Σ値のみに有効で、項目名には適用されないようです。

4) 集計値や表の形式の変更
[デザイン]-[レポートのレイアウト]、[小計]、[総計]などで表の形を変更します。

6. サンプルファイル

C:\Users\Public\Documents に格納してください。

ダウンロード - 250217xl3row.xlsx

 

7. 参考情報(他サイト)

8. 参考記事(当ブログ)

| | コメント (0)

aerofly RC10 + Quest 3 + OBS

aerofly RC10 を Quest3 に映してみました。
PCモニタより画質が落ちます。若干フレーム落ちします。
Quest 3からPCにCastした画像をOBSで録画したのですが、画質が劣化しています。
実際のQuest 3の画質はこれより良いです。
https://youtu.be/wb7kDjEhJo4


1. Quest 3での使用感

Pros
- 遠近感と視点の移動が現実に近い。
HDMはStereoscopic表示になるので近くのものは遠近感が出ます。この為、着陸はやりやすくなります。

Cons
- 解像度感がPCモニタより悪い。72fpsでも少しフレーム落ちする。
(PCの画像を圧縮してHDMに送るため、画質の劣化は避けられないようです。単にPC上で録画しても劣化します。環境や設定の影響もあると思いますが、改善する方法が分かりませんでした。逆に言うと細かい設定はあまり画質に影響しないということです。)
- 起動までの準備が面倒。
- HDMが重い。
- 設定画面でQuest 3のコントローラーが使用できず、PCのマウスを使用する必要がある。


2. RealFlightとの比較

1) Quest 3
PC上でもaeroflyの方が画質が良いので、Quest 3ではRealFlightより若干フレーム落ちが多いような気がします。

2) 着陸
aerofly RCの方が実機に近いように感じます。RealFlightは、失速しにくく、バウンドしにくく、壊れにくいので実機より着陸が容易です。

3) コントローラの設定
スイッチ関係の設定の自由度が不足しています。例えば、リバースがありません。

4) カスタマイズ
独自の機体や飛行場の作成が困難です。この点はRealFlightの方が良いです。


3. Setting

1) aerofly RC 10 Graphics setting
- Resplution : 2560x1440
- Frame rate limit : 120 fps
- Quarity preset : High

その他の設定は、下記記事を参照してください。

4. RealFlight Evolution + Quest 3の記事

PC Cast OBS 録画 : https://bigarrow.tea-nifty.com/blog/2025/02/post-a679e6.html
Quest 3 本体録画 : https://bigarrow.tea-nifty.com/blog/2025/02/post-89d797.html

5. VR Problem

UpdateでVRの画面が黒くなり表示されなくなりました。次の記事の情報により、「stable_v10010509 Version 10.01.05.09」に戻したところ、表示されました。

260531aerofly

[2026/06/24]追加情報

OpenXR RuntimeをSteamVRからVDXR(VirtualDesktopXR)に変更すると最新の2026/06/11バージョンでもVR表示できました。

| | コメント (0)

Meta Quest 3 の動画キャプチャーの画質

Quest 3のPC VRの録画画質が悪いです。
  Quest 3本体録画 : https://youtube.com/shorts/oJm9Yzj7cvs

そこでPCで録画をしてみました。改善はしましたが、まだQuest 3の画質と比べると劣化が大きいです。
  今回PCで録画した動画 : https://youtu.be/VrgdAZNbaNk

画質の順位(下線が本記事の動画)
PC > Vitual Desktop + Cast + OBS > SteamLink + Quest 3 Camera > Virtual Desktop + Quest 3 Camera

1. 録画対象
ソース : PC上のSteamアプリ RealFlight Evolution
PC-HDM間の経路 : Steamアプリ/PC --> SteamVR/PC --> Vertual Desktop Streamer/PC --> WiFi --> Vertual Desktop/HDM -->HDM

Vertual Desktop の Show performance overlay, SteamVR の Show GPU Performance Graph in Headset で確認すると、PC側よりHMD側の余力が少なそうです。WiFiは780Mbpsで余力はありそうです。

そこで余力がありそうなPC側にHDMの映像をUSBで送り返し、PC側で録画をしてみました。

2. Quest 3の画面をQuest3で静止画/動画記録する方法 : 前回の方法。動画画質が悪いです。
1) 記録開始方法
- 静止画
Metaマークボタンを押しながら右トリガーを短く押して離します。少し間をおいてシャッター音がして、保存メッセージが表示されます。
(トリガーを押す時間が短すぎると反応せず、長すぎると動画記録になってしまいます。)

- 動画
Metaマークボタンを押しながら右トリガーを長押します。音とともに右上に赤●が表示されたら、ボタンを離します。

2) ファイルの保存場所
- 静止画 : Quest 3/内部共有ストレージ/Oculus/Screenshots
- 動画 : Quest 3/内部共有ストレージ/Oculus/VideoShots

3) 画質
- 静止画 : 問題ないです。
- 動画 : 最高設定の20Mbpsでも中心部の画質が非常に悪いです。周辺部は良いのでバグがあるような気がします。Meta OS v72です。

Video : 1024x1024p, 約18Mbs, 30.00 fps. Audio : 128kbps, 48kHzで録画されました。

3. Meta Quest Developer Hub : 今回の方法
Meta Quest Developer HubはPCからQuest 3を操作する開発者用のツールです。
https://developers.meta.com/horizon/downloads/package/oculus-developer-hub-win/

※ 注意
Status : Update Requiredが出ていても、BETA版は不具合が多いのでテスト目的で無ければUpdateしないほうが良いです。

1) Device Managerの録画機能 : 画質はQuest 3 Cameraとあまり変わらず悪いです。
[Device Manager]-[Record]で録画ができますが、Quest 3本体で記録するものです。
録画開始は[Record]ボタンではなく、Quest本体側で行います(メタマークボダンを押しながら右トリガーを長押し)。
1024x1024p(デフォルト)の25Mbpsで録画したところ、中心部の解像度が低く画質改善しませんでした。
ファイルの保存場所 : Quest 3/内部共有ストレージ/Oculus/VideoShots

Video : 1024x1024p, 約18Mbps, 30.00 fps. Audio : 128kbos, 48kHz で録画されました。

※ 録画解像度を変更するとQuest側のビデオが再起動するようなのですが、HMD画面がちらつくことがあり不安定です。(OS v72)

2) キャスト機能 : 今回の方法
[Device Manager]-[Cast]でPC画面にQuest 3の画像をUSB経由で表示することができます。
PCとQuest 3のQuest Linkアプリの起動は不要です。
Quest 3側のLinkはPCに接続しておく必要があります。

キャスト画面の録画方法は、Quest本体とPC側でキャプチャーする方法があります。

(1) Quest本体側で録画:今回の方法ではありません。画質はQuest 3 Cameraよりは良いです。
- 録画開始方法 : PCのCastウィンドウの録画ボタンをクリック
- ファイルの保存場所 : Quest 3/内部共有ストレージ/Documents

[Cast]録画設定
- サイズ : オリジナル(1:1)
- 目標ビットレート : 自動⇒20Mbps
- キャプチャー方式 : 自動
- 目標フレームレート : 自動⇒30 FPS

Video : 1346x1440, 約15Mbps, 29.82 fps. Audio : 159kHz 48kHz で録画されました。

(2) PCでCast画面を録画:今回の方法です。
色々な方法がありますが、この記事ではOBS Sudioを使用しました。

[Cast]ミラーリング設定
- サイズ : トリミング(16:9)
オリジナル(1:1)は使用しているWQHDのモニタからはみ出てしまい、OBSでキャプチャーできませんでした。
トリミング(16:9)は1920x1080でキャプチャーできました。なおこのサイズにはCastのメニューバーも含まれます。
- 目標ビットレート : 20Mbs
- 最大解像度 : 1920x1080
- 最大フレームレート : 30 FPS
60fpsではフレーム落ちが多くなりました。
- Selected Eye : Left Eye
- 適応的にフレームをスキップ : On
- Image Stabilization : On
- 録画時に一時停止 : Off

Video : 1300x1372, 約8~17Mbps, 30.000300 fps. Audio : 48kHz で録画されました。

※ Vertual DesktopのBGMは記録されませんでした。

4. OBS Studio
Cast画面をOBSで録画しました。
https://obsproject.com/ja/

* 設定/出力/詳細/録画
- 録画フォーマット : Matroska Video (.mkv)
- 映像エンコーダ : NVIDIA NVENC H.264
- 出力をリスケールする : 無効, 1920x1080
- レート制御 : 目標品質による可変ビットレート(CQVBR)
- 品質目標 : 20
- 最大ビットレート : 20000 Kbps
- キーフレーム間隔 : 2 s
- プリセット : P5 Slow(高品質)
- チューニング : 高品質
- マルチパスモード : 2パス(1/4解像度)
- プロファイル : high
- Look-ahead : On
- 適応量子化 : On
- Bフレーム : 2

* 設定/出力/詳細/録画
- 音声ビットレート : 128

* 音声
サンプルレート : 48kHz
デスクトップ音声 : 規定

* 映像
- 基本(キャンパス)解像度 : 1920x1080
- 出力(スケーリング)解像度 : 1920x1080
- FPS共通値 : 30

* 詳細設定
- プロセスの優先度 : 高

* ソース/ウィンドウキャプチャ/設定
- ウィンドウ : [Casting.exe]: Casting
- キャプチャ方法 : Windows 10(1903以降)
- ウィンドウの一致有線順位 : タイトルに一致、そうでなけれあ同じ種類のウィンドウを見つける
- 音声をキャプチャ(ベータ版) : On
- カーソルをキャプチャする : Off
- クライアント領域 : Off
- SDRを強制する : Off

その他の設定は、前回と同じです。

5. 関連記事
前回記事(RealFlight + Quest 3をQuest 3本体で録画) : https://bigarrow.tea-nifty.com/blog/2025/02/post-89d797.html

| | コメント (0)

RealFlight Evolution + Quest 3

RealFlight EvolutionをQuest 3のVRで使用してみました。

https://youtu.be/B6Rbbpzc0PA
このYoutubeの画質は実際のQuest 3の画質よりかなり悪いです。これはQuest 3のレコーダの特性によるものです。
Vertual Desktop の VR Passthrough, Show performance overlay, SteamVR の Show GPU Performance Graph in Headset を Off にしても画質は悪いです。

以下の設定や手順は最適ではない可能性があります。

1. Conclusion
Pros
- 遠近感と視点の移動が現実に近い。
HDMはStereoscopic表示になるので近くのものは遠近感が出ます。この為、着陸はやりやすくなります。

Cons
- 解像度感がPCモニタより悪い。72fpsでも少しフレーム落ちする。
(PCのDisplay PortはWQHD/120Hzで11.59Gbpsと圧倒的に情報量が多いので、WiFiやUSB入力のQuest 3では歯が立たないです。でもDisplay Port入力のあるHDMは選択肢が少ないです。)
(PCの画像を圧縮してHDMに送るため、画質の劣化は避けられないようです。単にPC上で録画しても劣化します。環境や設定の影響もあると思いますが、改善する方法が分かりませんでした。逆に言うと細かい設定はあまり画質に影響しないということです。)
- 起動までの準備が面倒。
- HDMが重い。

2. Environment
1) Hardware
- CPU : Intel Core i9-12900
- Motherboard : ASUS PRIME Z690-P
- Memory : 32GB[16GB*2] DDR5-4800 Dual Channel
- SSD : Crucial P5 Plus CT1000P5PSSD8 M.2 PCI-E GEN4 SSD 1TB
- GPU : ASUS TUF RTX3070-O8G-V2-GAMING (Driver 560.94)
- PC Monitor : Dell S2721DGF 27inch WQHD 2560x1440 144Hz対応ですが120Hzで使用
- LAN : 1000BASE-T
- WiFi : NEC Aterm WG2600HP3 IEEE802.11ac/Quad Channel(WiFi5)
- HMD : Meta Quest 3 512GB

2) Software
- Windows 11 Home 23H2
- RealFlight Evolution publicbeta_directx11 Version 10.10.131
- SteamVR 2.8.8 2024-12-10
- Vertual Desktop Streamer Version 1.33.4 - 72
- Meta OS : v72 Build 5047332.16210.510 2025/01/26

3. Settings
1) RealFlight
* Simulation/Graphins
- Graphics Quality Settings : HIGHEST
* Simulation/Settings/Graphics/Quarity
- Post-processing:Bloom : No *
* Simulation/Setting/Graphics/Hardware
- Vertical Sync : No
- Image Quality : Highest *

2) SteamVR HMD
- レンダリング解像度 : Auto
- Steam VR Home : Off *
- 高度なスーパーサンプリングフィルタ : On
- オーバーレイスーパーサンプリングフィルター : Auto(High)
- GPUバスのモニタリング : Off

3) NVIDIA Conrol Panel 3D設定の管理
* "C:\Program Files (x86)\Steam\steamapps\common\SteamVR\bin\win64\vrmonitor.exe"
- アンチエイリアシング - FXAA : On *
- アンチエイリアシング - ガンマ補正 : On
- アンチエイリアシング - トランスペアレンシー : Off
- アンチエイリアシング - モード : アプリケーションによるコントロール
- バーチャルリアリティレンダリング前フレーム数 : 3Dアプリケーション設定を使用する
- マルチフレームサンプリング(MFAA) : Off
- モニターテクノロジ : 固定リフレッシュレート [*1]
- 低遅延モード : Ultra *
- 優先的に使用するリフレッシュモード : アプリケーションによるコントロール
- 垂直同期 : 3Dアプリケーション設定を利用する
- 最大フレームレート : Off [*1]

[*1] この設定にしないと激しくフレーム落ちする。

* "C:\Program Files (x86)\Steam\steamapps\common\RealFlight Evolution\RealFlight64.exe"
(この設定はPCモニタで2D表示するときに使われ、SteamVRから起動したときは使われない。)
- アンチエイリアシング - FXAA : On *
- アンチエイリアシング - ガンマ補正 : On
- アンチエイリアシング - トランスペアレンシー : Off
- モニターテクノロジ : G-SYNCとの互換性 *
- 低遅延モード : Off
- 優先的に使用するリフレッシュモード : アプリケーションによるコントロール
- 垂直同期 : 3Dアプリケーション設定を利用する
- 最大フレームレート : 120FPS *

4) VertualDesktop Streamer Options
(DefaultsからAllow remote connectionとStart with WindowsをUncheckedにしただけです。)
- Prefferd Codec : Automatic (HEVC 10-bit)
- Adaptive quantization : On
- OpenXR Runtime : Automatic
- Audio Streaming : VR headset only
- Use virtual audio driver : On
- VoiceMeeter mode : Off
- Automatically adjust bitrate : On
- Boost game priority : Off

5) Vertual Desktop Client HMD
当方の環境ではWiFi5 5GHz 780Mbpsでつながっています。電波は弱いです。(WiFi Router:1F、HDM:2F)
* INPUT
- Hand tracking : On
* Input Options
- Hands interact with desktop
* SETTINGS
- Use optional resolution : Off
* STREAMING
- VR Graphics Quality : High (RTX 3070 / RX 6800-XT)
- VR Frame Rate : 72fps
- VR Bitrate : 200Mbps(Max)
- Synchronous Spacewarp (SSW) : Disabled
- Snapdragon(R) Game Super Resolution (SGSR) : On
- Video bufferring : On
- Sharpening : 75%
- VR Passthrough Configure : Red:0, Green: 153, Blue: 51,
Similarity : 20%, Smoothness : 10%, Passthrougfh Opacity : 100%, Show boundary : On
- Tracked Keyboard : On *
- Hands : On *

6) Steam PC
* Remote Play : On(SteamLinkを使用しない時はOffでもよい)
- 直接接続を許可(IP共有): 自動(有効)
- ホストの詳細設定を有効化 : Off
- ストリーミングクライアントに合わせてデスクトップ解像度を変更 : Off
- NVIDIA GPUのNVFBCキャプチャーを使用 : Off
https://ja.gouma.org/Streaming/How-to-Set-Up-and-Optimize-the-Steam-Link-for-In-Home-Game-Streaming/
- ハードウェアエンコードを有効にする : On
- ソフトウェアエンコードのスレッドの数 : 自動

3. 起動手順(Vertual Desktop)
0) 直接起動しないもの
- RealFlight Evolution/PC: SteamVRから起動する。
- SteamVR/PC : Vertual Desktopから起動する。

1) HMDのVertual Desktopの起動前に起動しておいた方が良いもの
- Steam/PC: Vertual DesktopからSteamVRを起動すると自動的に開くので、事前起動は必須ではないが、先に起動しておいた方が速い。
([表示]-[スモールモード]にすると、Steamのウィンドウが小さい。)

2) HMDのVertual Desktopの起動前に起動しておくもの
- Vertual Desktop Streamer/PC : タスクトレイにあれば、ウィンドウは閉じてOK。

3) Vertual Desktop HDMの起動前に切っておくもの
- Airlink/HDM
- SteamLink/HDM
※ Vertual DesktopのWiFi通信との競合を回避するため、切っておく。

4) Vertual Desktop/HDMを起動。PC接続されるまでしばらく待つ。
(エラーが出ても1分くらい待つ。)
5) 左コントローラーのメニューボタン[≡]を押して、Vertual Desktopのメニューを表示する。
6) [Launch SteamVR]を選択。
7) Steamのプレイエリア画面で左コントローラーのメニューボタン[≡]を押す。
8) SteamのライブラリからRealFlight Evolutionを起動。


4. Recorder
HMD Meta Quest 3のカメラで録画しました。
* 設定/システム/カメラ
- アスペクト比 1080x1080
- 30fps
- 20Mbps
HMDの画像を録画すると画質がかなり劣化します。特に中心部の劣化が激しいです。静止画はきれいです。
画質の順番 : PC画像(Mono) > HDM画像(VR) > HMD録画画像(片目)


5. PC VRの種類と起動手順
複数の方法があります。Oculus VR以外は、どれも似たような画質です。またどれも若干フレーム落ちが発生します。

- 起動手順が簡単なのは、SteamLinkです。
- パススルーができるのは、Virtual Desktopです。
- 有線接続は、Quest Linkです。(画質やフレームレートはWiFiとそれほど差はありません。)

■ Steam Link (WiFi)
1) Steam/PCを起動。(Steam/PCの設定でRemoteをOnにしておく。)
2) Steam Link/HDMを起動。(自動的にSteamVR/HDMが起動する。)
3) SteamVR/HDMからRealFlightを起動。

Pros
- 起動手順が簡単で起動時間も短い。

Cons
- パススルーができないので、USBコントローラへの持ち替えやキーボード操作が手探りになる。

■ Quest Link/Steam VR (USB)
1) Meta Quest Link/PCを起動。
2) Link/HDMを起動し、デスクトップ(PC)を開く。
3) SteamVR/PCを起動。(自動的にSteam/PCとSteamVR/HDMが起動する。)
※ ここでRealFlight/PCのショートカットからSteam VRモードでRealFlightを起動を選ぶと、HDMに表示されない。
4) 左コントローラのメニュー[≡]を押し、ライブラリを表示。
5) RealFlightを起動する。

Cons
- パススルーができないので、USBコントローラへの持ち替えやキーボード操作が手探りになる。

▼ Quest Link/Oculus VR (USB)
1) Meta Quest Link/PCを起動。
2) Quest Link/HDMを起動し、デスクトップ(PC)を開く。
3) RealFlight/PCのショートカットからOculus VRモードでRealFlightを起動。

Problem
- 色が薄いです。使えないレベルです。

■ Air Link (WiFi)
1) Meta Quest Link/PCを起動。
2) LinkのAir Link/HDMを起動し、デスクトップ(PC)を開く。
以降、Quest Linkと同じ操作。

■ Virtual Desktop (WiFi)
1) Vertual Desktop Streamer/PCを起動。
2) Vertual Desktop/HDMを起動。
3) SteamVRを起動。(自動的にSteam/PCとSteam/HDMが起動。)
4) Steam/HDMからRealFlightを起動。
※ Reva Tuner Statistics Serverは終了するかDetection levelをNoneにしてください。
FAQ参照 : https://www.vrdesktop.net/

Pros
- キーボードやコントローラのパススルーができるので、コントローラの持ち替えが容易。また、RealFlightでキーボード操作ができる。

Cons
- 有料アプリです。
- PC接続に時間が掛かる。


6. 余談
- Quest Link、SteamVRでもパススルーができるようにして欲しいものです。
- 似たような設定が各所にあり、その関係(優先順位、有効無効など)が分かりにくいです。中の人が解説してくれると嬉しいのですが。
- SteamVR/HDMは、VR設定を開くと左のメニューが反応しません。

RTX5080に変更しました。VRの録画画質は、ゴーグルの画面よりは落ちるものの、RTX3070よりは改善しました。RTX5080でSteamVRで解像度を300~400%にすると改善します。

7. その後
1) Quest 3のCameraでの録画は、Virtual Desktop より SteamLink を使用したほうが画質が良いです。

8. 関連動画
PCで録画 : https://bigarrow.tea-nifty.com/blog/2025/02/post-a679e6.html

| | コメント (0)

Excelグループ化時に新しい列を横展開、ピボットテーブルのΣ値にテキストを表示

題記のようなExcelの表の整形に関するメモです。

0. 背景
Yahoo!知恵袋でグループ化した集計列を横に展開する質問がありました。
    https://detail.chiebukuro.yahoo.co.jp/qa/question_detail/q13310178813
    VBAの回答が付いています。VBAですので、セルアドレス依存があります。
セルアドレス依存を無くすため、これをモダンExcelで行いました。

1. 元のテーブル:Table1

Table1
ID Name Mail Week Job1 Job2
1 N1 M1 Tue2 A B
2 N2 M2 Tue2 A B
2 N2 M2 Wed1 C D
2 N2 M2 Tue3 E F
3 N3 M3 Fri2 F G
3 N3 M3 Fri3 K L

想定シーン:担当者別のタスクデータベース。

2. 整形後の表(ピボットテーブル)

Pivot table
ID Name No. 1 2 3
Mail Week Job1 Job2 Week Job1 Job2 Week Job1 Job2
1 N1 M1 Tue2 A B            
2 N2 M2 Tue2 A B Wed1 C D Tue3 E F
3 N3 M3 Fri2 F G Fri3 K L      

想定シーン:各担当者ごとにタスクを1行にまとめて表示する。これにより、一覧表で各担当者のタスクを見やすくする。

特徴
1) 元データによって列の数が増減する。(例ではNo.1~3)
2) レコードによっては、増減した列の値が無いものがある。(例ではID1はNo.1しかない)
3) 増減する列の値は、数値ではなく文字列である。(例ではWeeK、Job1、Job2の値は曜日やJobの記号)

3. 整形手順の概要

1) ChatGPT、Copilotでは、増減する列とグループ集計するPower Queryの解を得られませんでした。そこで、Power Queryでは、グループ毎のインデックスを付加するところまでで留め、ピボット展開はピボットテーブルを使用することにしました。
2) ピボットテーブルのΣ値では、数値の集計しかできません。そこで、メジャーを作成してΣ値で文字列を表示するようにしました。

4. 結果:Table1クエリのピボットテーブル

25205xl

5. Power Query

このクエリは、元の表にグループ毎にリセットされるインデックス列を付加します。

let
    ソース = Excel.CurrentWorkbook(){[Name="Table1"]}[Content],
    型変更 = Table.TransformColumnTypes(ソース,
        {{"ID", Int64.Type}, {"Name", type text}, {"Mail", type text}, {"Week", type text}, {"Job1", type text}, {"Job2", type text}}),
    グループ化 = Table.Group(型変更, {"ID", "Name", "Mail"},
        {"データ", each Table.AddIndexColumn(_, "インデックス", 1, 1), type table}
    ),
    展開 = Table.ExpandTableColumn(グループ化, "データ", {"インデックス", "Week", "Job1", "Job2"}),
    変更された型 = Table.TransformColumnTypes(展開,{{"インデックス", Int64.Type}, {"Week", type text}, {"Job1", type text}, {"Job2", type text}})
in
    変更された型

【作成方法】
Power Queryエディタのクリック操作で上記コードを生成します。
但し、グループ化のインデックス列を追加する式はクリック操作で生成できないので[*1]、グループ化する列を選択してグループ化をクリックした後に、[ホーム]-[詳細エディタ]で上記のようにコードを修正して下さい。

【解説】
グループ化のTable.AddIndexColumn()で、グループ化する各行("_")にインデックス列を追加し、グループ化の新しい列としてテーブルを格納しています。"_"は元テーブル(=前のステップ)から同一グループに属するレコードを抽出したテーブルです。同一グループのレコードに1から1ずつ加算した連番が追加されます。
例えばID=3は3レコードあるので3レコードのテーブルになります。
これを次の展開ステップで展開すると、3レコードが3行に展開され、グループ前の行数に戻ります。

【参考】

[*1]下記の方法だと1ステップ増えますが、クリック操作で作成できます。
【Power Query】グループごとに連番をつける方法
https://blog.truestar.co.jp/microsoft/power-query/20210707/45919/

Table.AddIndexColumn
https://learn.microsoft.com/ja-jp/powerquery-m/table-addindexcolumn
Table.AddIndexColumn(table as table, newColumnName as text, optional initialValue as nullable number, optional increment as nullable number, optional columnType as nullable type) as table

ときおり出てくる "each" キーワードと "_" (アンダースコア) とは
https://qiita.com/PowerBIxyz/items/6668537a576592a04bcf

6. メジャー

テーブルの項目をΣ値に設定すると、数値での集計になります。
そこでメジャーを使うことで、テキストをΣ値に表示できるようにします。

MWeek:=IF(ISFILTERED('Table1'[ID]) && ISFILTERED('Table1'[インデックス]), MAX('Table1'[Week]))
MJob1:=IF(ISFILTERED('Table1'[ID]) && ISFILTERED('Table1'[インデックス]), MAX('Table1'[Job1]))
MJob2:=IF(ISFILTERED('Table1'[ID]) && ISFILTERED('Table1'[インデックス]), MAX('Table1'[Job2]))

IF文は、合計では意味のないテキストを表示させないためのものです。ピボットテーブルで合計を出さないのであれば、メジャーのIF文は不要で、MAXのみ記述すればよいです。

Week,Job1,Job2はテキストであり演算するものではない。
これらの項目は合計等の算術計算ができない。
ピボットテーブルのフィルタコンテキストが1つの値しか返さない場合に、このメジャーが有効である。
値が1つであれば、MAXやMINの集計関数をΣ値で使用することによりテキスト値日付などを表示することができる。

7. サンプルファイル

ダウンロード - 250205xl.xlsm

注意 : Yahoo!知恵袋のVBAのコードを貼り付けましたが、セルアドレスが異なるので正常に動きません。


8. 参考

【Excel】自分で数式を作れば、ピボットテーブルで文字列をまとめることもできる。
https://www.youtube.com/watch?v=jC7POhejvPk


| | コメント (0)

MOS Excel 365の出題範囲の不思議

つぶやきです。

■ MOS Excel 出題範囲
モダンExcelで分からないことをWeb検索していると、とある質問サイトでMOS(Microsoft Office Specialist)の質問を目にします。上級クラス(エキスパート)は難しいらしいです。当方、受ける気は全く無いのですが、LAMBDA関数とそのヘルパー関数などは使ったことが無いので、MOSは難しいのだろうと思っていました。
そこで、Excelの出題範囲を調べて見ました。
https://mos.odyssey-com.co.jp/outline/excel365_ex.html

■ 出題範囲の不思議
2025年2月時点では、出題範囲にPower Query、Power Pivot、メジャーがありません。
最近の集計業務では、Power Query、Power Pivot、メジャーの使用頻度が高いように思うのですが?

Power QueryやPower Pivotの基本操作は殆どクリック操作であり、簡単ではあります。
でも、データ整形、マージ、リレーション、カスタム列などの多少のPower Query M言語、メジャーで使うDAXなどの知識は必要であり、MOSの範囲に含めたほうが集計業務の役に立つ気がします。

難解と言われているDAX(CALCULATE関数など)の理解度を試す内容になれば、MOS(Specialist)の価値も上がるかもしれませんね。

■ Excelの習得の順番
昔は、数式 ⇒ ワークシート関数 ⇒ グラフ ⇒ VBA ⇒ ピボットテーブル だったように思います。
今は、数式 ⇒ ワークシート関数 ⇒ グラフ ⇒ ピボットテーブル ⇒ Power Query/Power Pivot ⇒ VBAのような気がするのですが?

■ 関連記事
Excelの学習順序で思うこと(VBAより先にPower Query)
https://bigarrow.tea-nifty.com/blog/2025/05/post-1cec1d.html

■ 余談
ついでにPower BIでPythonやR言語を用いたデータ分析も範囲に入れれば、MOSの株も上がりそうな気がしますが、Excelとは関係ないので、範囲外ですね。でも、Python in Excelは範囲内ですね。冗談です。

⇒[2026/02/03] Power BIのPhytonやRビジュアルのサポートが無くなるらしいです。
Power BI 2025:進化と終幕、その先にある「Fabric+Al」時代の幕開け : R / Python ビジュアル(Embed for Customers での廃止)/ 
テクテク日記
https://marshal115.hatenablog.com/entry/2025/12/19/002040

| | コメント (0)

« 2025年1月 | トップページ | 2025年3月 »