見出し画像

Power Query M 式言語 カスタム関数作成2(List.Generate使う)

いくつか練習で作成しました。具体的に使える状況が
どんなものかはよくわからないですが、なにかに使えるといいですね。(2022/3/28)
⑤だけ必要があって作りました。(2022/4/3)

①グループ内の最大値がある行だけにするカスタム関数
②(List.Generateを使って)今日から指定日数分の
 曜日付きカレンダーを作るカスタム関数
③指定テーブルの列並び順を逆転するカスタム関数
④指定テーブルの列名を左から昇順の番号にするカスタム関数
⑤指定テーブルを1行飛ばしに表示させるカスタム関数

①グループ内の最大値がある行だけにするカスタム関数

let
 Result = #table(
     type table[Info = text, ごはん = text, 価格 = number, Date = date],
     {
         {"A", "寿司(最古/最高)", 1000, #date(2018, 1, 1)},
         {"A", "焼き肉", 900, #date(2020, 1, 1)},
         {"A", "ラーメン(最新/最安)", 800, #date(2022, 1, 1)},
         {"B", "クッキー(最新/最高)", 700, #date(2019, 1, 1)},
         {"B", "ブールドネージュ(最古/最安)", 600, #date(2017, 1, 1)},
         {"C", "えび天(最古/最高)", 500, #date(2020, 1, 1)},
         {"C", "チョコレート", 400, #date(2023, 1, 1)},
         {"C", "茶碗蒸し", 300, #date(2025, 1, 1)},
         {"C", "プリン(最新/最安)", 200, #date(2027, 1, 1)},
         {"D", "餅", 100, #date(2000, 1, 1)}
     }
 )
in
 Result

ABCDでグループ化して、最高値のレコードを取る場合の関数
それぞれ(最高)がついているものが取得できれば成功です。
(Dは1個しかないので絶対に餅)

let
   /* **GetGroupMax**
     tblで指定したテーブルに、groupColumnNameという名前の列でグループ化した
     valueColumnName中のデータが最大の行を取得する
   */
  Result = (tbl as table, groupColumnName as text, valueColumnName as text) =>
  let
      Grouped = Table.Group(tbl, { groupColumnName }, {{ "Grouped", each List.Max(Table.Column(_, valueColumnName))}}),
      Merged = Table.NestedJoin(tbl, { groupColumnName, valueColumnName }, Grouped, { groupColumnName, "Grouped" }, "Merged", JoinKind.Inner),
      Cleaned = Table.RemoveColumns(Merged, { "Merged" })
   in
      Cleaned
in
   Result

画像1

取れました。(関数名はGetGroupMax)
List.Maxの代わりにList.Minを使えば最小値も取れました。

②今日から指定日数分の曜日付きカレンダーを作るカスタム関数

let
   Result = (dates as number) =>
   let
       firstDay = DateTime.Date(DateTime.LocalNow()),
       Lists = List.Generate(
       () => [dateNo = 0, day = null, weekday = null], // initial
       (row) => row[dateNo] <= dates, // condition
       (row) => [
           dateNo = row[dateNo] + 1,
           day = Date.AddDays(firstDay, row[dateNo]),
           weekday = Date.ToText(day, "ddd")
       ], // next 1日ずつ足していく。row[xx]にした場合は、前レコードでxxは同じレコード
       (row) => [ 日付 = row[day], 曜日 = row[weekday] ]
       ), // selector 番号はいらないので日付と曜日だけselect
       Result = Table.FromRecords(
           List.Skip(Lists, 1)
       )
   in
       Result
in
   Result

initialとnextに同じことを2回も書きたくなかったのでSkip使いました。他にいい方法ないのでしょうか。(Skip使わずに最初から詰めて作りたい)
結果はうまく取れていそうでした。(この部分を書いている日は2022/3/23)

画像2

③指定テーブルの列並び順を逆転するカスタム関数

let    // 適当な結果試し用のテーブル
    Result = #table(
        type table[あ = text, い = text, う = number, え = number],
        {
            {"A", "あ", 5, 50},
            {"A", "あ", 5, 50},
            {"A", "あ", 5, 50}
        }
    )
in
    Result
let
  /* **Table.ReverseColumns**
    tblで指定したテーブルの列を逆順にして返す
  */
   Result = (tbl as table) =>
   let
       columnNames = Table.ColumnNames(tbl),
       reverseColumnNames = List.Reverse(columnNames),
       ReverseColumns = Table.SelectColumns(tbl, reverseColumnNames)
   in
       ReverseColumns
in
   Result

画像3

④指定テーブルの列名を左から昇順の番号にするカスタム関数
※テーブルは③で作ったものを使用

let
  /* **Table.RenameColumnsByNumber**
    tblで指定したテーブルの列を番号にして返す(左から昇順)
  */
   Result = (tbl as table) =>
   let
       columns = Table.ColumnCount(tbl),
       names = Table.ColumnNames(tbl),
       numberList = List.Generate(() => [i = 0, columnNameFrom = "", columnNameTo = ""], // initial
       (row) => row[i] <= columns, // condition
       (row) => [
           i = row[i] + 1,
           columnNameFrom = names{i - 1},
           columnNameTo = "適当なPrefix" & Text.From(i) // ここを適当にいじればprefixもつけられる
       ], // next
       (row) => { row[columnNameFrom], row[columnNameTo] }), // selector
       Renamed = Table.RenameColumns(tbl, List.Skip(numberList, 1), MissingField.Ignore)
   in
       Renamed
in
   Result

画像4

⑤指定テーブルを1行飛ばしに表示させるカスタム関数

let
   Result = (tbl as table) =>
       let
           /* 1行ごとに挿入するための空のレコードを作る
              newキーワードみたいなの無いんですか?
            */
           colCount = Table.ColumnCount(tbl),
           nullValues = List.Generate(() =>
               [i = 0, value = null], // initial
               (row) => row[i] < colCount, // condition
               (row) => [
                   i = row[i] + 1,
                   value = null
               ], // next
               (row) => row[value] // selector
           ),
           nullRecord = Record.FromList(nullValues, Table.ColumnNames(tbl)),

           /* もとのテーブルの1行飛ばしで取得する
           */
           rowCount = Table.RowCount(tbl),
           lists = List.Generate(() =>
               [i = 0, rowNo = -1, rowData = null], // initial
               (row) => row[rowNo] < rowCount, // condition
               (row) => [
                   i = row[i] + 1,
                   rowNo = row[rowNo] + Number.Mod(i, 2),
                   rowData = 
                   if
                       Number.Mod(i, 2) = 0
                   then
                       nullRecord
                   else
                       tbl{rowNo}
                   
               ], // next
               (row) => row[rowData] // selector
           ),
           Result = Table.FromRecords(List.Skip(lists, 1))
       in
           Result
in
   Result

画像5

おわり。
row[i]…前の行のiで、i…今の行のiと、
[](フィールド名的なものでアクセスする際に使う)と
{}(行番号的なものでアクセスする際に使う)の使い分けは忘れそう…
なので今ここに書きました。

この記事が気に入ったらサポートをしてみませんか?