Power Query歴、約3年の狸です。
@PowerBIxyz さんの名記事がある中で今更何を書くか、ということなんですが、ともかくも、基本的なところを書いてみました。
Power Queryでの関数の書き方なんか知らん、という方は実例に行く前に、準備事項をお読みください。
#List.Generateでできること
##存在意義
Power Queryには、For文、Do ~ Loop文と言ったものがありません。
代わりに、いくつかの関数1で、ループ処理を行うことができます。
その中でも、List.Generateは最も自由度が高い計算と言えます。2
##戻り値
この関数の戻り値は、ループ各回の実行結果を全部を含んだリストです。
過程の結果が要らない場合は、リストの最後の要素を指定すればよいです。
例えば、SomeList
というリストを得た場合に、下記のように書きます。
List.Last(SomeList)
もしくは
List.Reverse(SomeList){0}
###リストとは
Power Queryでいうリストというのは配列みたいなものです。
ただし、Power Queryでは一度、値を定義したら、その値を変更することができません。(immutable)
つまり、「リストaの中の値を変更する」ことはできないので、「リストaの中の値を変更して、リストbとして定義する」ことになります。
a={1,3,5,7,9} //要素が5つのリストにaという名前を付ける
a{0} //=1
公式reference
リスト
不変性(immutability)
##再帰との関係
Power Queryでは再帰関数の定義もできます。ただ、末尾再帰の最適化がされていないようです。
なので、全般、List.Generateで書いた方がよいと考えています。(今後は分かりませんが、今のところ)
※再帰関数を書く場合は、関数の名前の前に@
をつける記法となります。
詳しくは公式referenceを読んでください。この記事では触れません。
#準備事項 関数の定義ができること
最低限、Power Queryでの関数の定義ができないといけないです。
List.Generateを使う上では、引数0個と引数1個の2種類の関数を作れる必要があります。
##引数が1個の関数
###定義例
下記3つは同じ意味で、「引数に1を加えて返す」関数です。
(x)=> x + 1
(_)=> _ + 1
each _ + 1
実行した結果も確認したい方はこちらをクリック。
※引数名の1字目に数字を使うとか、引数名の中に記号を使ったりすると、特殊な記法が必要になりますので、避けます。
##引数が0個の関数
やや引っかかりやすいのですが、引数0個の関数というものも定義できます。
1個の時の違って、書き方はこれ1通りです。
each
と書くと、関数ではあっても、引数0にはならないので注意してください。
()=> 100 //OK:引数0の関数
each 100 //NG:引数1の関数!(引数を計算に使ってないだけ)
↓初期値をeachで書くとこうなります。「Expression.Error: 0 引数は、1 を必要とする関数に渡されました。」というエラーメッセ―ジが出ます。定義した関数1個の引数を必要とするが、何も渡されなかったから実行できず、エラーになったということです。
※List.Generateでなくても、引数を渡さなければ同じエラーが出ます。
#例1:もっとも単純な例
関数の定義自体がよく分からない場合は、準備事項 関数の定義ができることから読んでください。
##実例
List.Generateじゃなくてもできるじゃないか、ということは置いといて、簡単な計算を書いています。
なお、便宜的に各引数に日本語を当てていますが、説明の便宜上の呼び名で、公式なものではありません。
List.Generate(
()=> 0, //---初期値(initial)
(x)=> x <= 10, //---継続条件(condition)
(x)=> x + 1 //---各回の処理(next)
)
##解説
###初期値(initial)
引数なしの関数を入れます。何も受けとる引数がないからです。だからといって、関数でない固定値を入れてしまうと、下記のようなエラーになります。
###継続条件(condition)
次に生成しようとする値(予定値)を引数として受け取り、true/falseを返す関数を書きます。
trueである場合は予定値が値として確定し、次の予定値の計算に進みます。3
falseの場合は予定値は捨てられ、その前までの値でリストが確定します。
初期値も継続条件を満たさないといけないことに注意してください。
###各回の処理(next)
確定済みの、現在の値を引数として受け取り、何らかの値を返す関数を書きます。
###実行順序
例1の場合は下記のように実行されます。
- 「初期値」関数を実行して、予定値
0
を得る。 - 予定値
0
を「継続条件」関数に入れ、真偽値として、true
を得る。 - 予定値
0
が確定値になる。 - 確定値
0
を「各回の処理」関数に入れ、予定値1
を得る。 - 予定値
1
を「継続条件」関数に入れ、真偽値として、true
を得る。 - 予定値
1
が確定値になる。
以下略
#例2:よくある例
レコードを使えば、欲しい値と別の情報を持つことができ、書きやすくなります。
##レコードについて
レコード(record)というのはいわゆる連想配列のようなもので、名前の付いた値の集まりのことです。
名前と値の1セットをフィールドと呼びます。
b=[年=2021,月=12,日=25] //レコードにbという名前を付ける。改行も可能。
b[年] //=2021
公式reference(レコードについて)
##実例
レコードを使うと、計算内容をフィールドに分けることができるので、各回での実施内容を、複雑な計算を読みやすく記述することができます。
List.Generate(
()=> [i = 0, value = 1], //初期値
(x)=> x[i] <= 10, //継続条件
//各回の処理
(x)=> [
i = x[i] + 1,
value = x[value] + Number.Power(2,i)
],
(x)=> x[value] //最終出力値
)
※実行結果(クリックで開きます)
##実例の解説
基本的な3引数については例1で説明していますので、重複する部分は省きます。
###初期値(initial)
引数なしで、レコードを返す関数を入れます。
決まり、というより、レコード処理をする上で、後続の処理がレコードを前提としていますので、それと同じ構造でないと、計算できないためです。
###継続条件(condition)
引数として渡される予定値はレコードですので、今回は、レコードを引数に取る関数を書かないといけません。
(x)=> x[i] <= 10
という関数は、引数x
がレコードであり、その中のフィールドi
について、10以下かどうかについてtrue/falseを返す、という意味です。
###各回の処理(next)
こちらも同様に、引数として渡される前回の確定値はレコードですので、レコードを引数に取る関数を書かないといけません。
(x)=> [
i = x[i] + 1,
value = x[value] + Number.Power(2,i)
]
フィールドvalue
は、前回の確定値のフィールドvalue
に2の累乗を足す計算をしています。2の累乗の指数としてi
(つまり、x[i] + 1
)を指定しています。
x[i]
とi
を混同しないよう注意してください。
-
x[i]
:前回の確定値のフィールドi
-
i
:今作成しようとしているレコードのフィールドi
###最終出力値(selector)
今回は例1で紹介した3引数に加え、「最終出力値」という引数を使いました。レコードを使って書く場合は知っておくべき引数です。
今回のようにレコードの中の出力するフィールドを指定する、というのが主な使い方になります。
(x)=> x[value]
とすることで、各回の確定値のレコードの中のフィールドvalue
だけが戻り値となります。
なお、無い場合はエラーになるわけではなく、リストの要素となる戻り値がレコードになるだけです。一度試してみるとよいと思います。
###実行順序
レコード値を使うと、計算の様子が例1とは異なってきます。
これはPower Queryで遅延評価(lazy evaluation)が行われることに起因する、と僕は解釈しています。
- 「初期値」関数の中のフィールド
i
を計算して、予定値0
を得る。 - フィールド
i
の予定値0
を「継続条件」関数に入れ、真偽値として、true
を得る。 - 0番目の値のフィールド
i
の確定値が0
になる。 - 「初期値」関数の中のフィールド
value
を計算して、0番目の値のフィールドvalue
の確定値が1
になる。 - 0番目の確定値(レコード)を「各回の処理」関数に入れ、予定値
1
を得る。 - 予定値
1
を「継続条件」関数に入れ、真偽値として、true
を得る。 - 予定値
1
が確定値になる。
#試した環境
- MS365のExcel:バージョン2109
- Power Queryのバージョン:2.97.321.0 64 ビット
#その他公式reference
https://docs.microsoft.com/ja-jp/powerquery-m/understanding-power-query-m-functions
-
他にはList.Accumulate関数、List.Transform関数があります。これらは計算対象のリストを引数とし、そのリストの各要素を引数として計算するfor each的な処理となります。また、Table.TransformRows関数、Table.TransformColumns関数なども、テーブルの各行、各列を処理するもので、ループ処理の一種と言えます。 ↩
-
@PowerBIxyz さんのList.Generateの記事「Power Query の List.Generate 関数ってなんだよー」では、List.Accumulateとの比較もされています。 ↩