2
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【Power Query】「連続値の区分け」で毎度M言語を修正するの嫌だ。List.Accumulateで「テーブルを育てる」とうまくいく

2
Last updated at Posted at 2026-02-08

動機

Power Queryでデータを加工していると、連続した値をある程度の区間に分けて(「連続値の区分け(ビニング)」)、その区間でGROUP BYして集計したくなります。

  • 「年齢」を「10代, 20代...」に分けたい
  • 「点数」を「赤点, 合格...」に分けたい
  • 「売上」を「ランクA, B...」に分けたい

で、これが起きるたびに、毎度詳細エディタを開いて、Table.AddColumnif 文を書いたり、マスタとのマージ処理を書き足していてメンドクサイなぁと。

設定テーブル駆動によるアプローチ

「区分けのルールが増えるたびに、クエリ自体を修正しないですむ方法はあるか?」 と考えまして、先の【Power Query】集計定義をExcelシートに外出してTable.Groupをクエリの外から制御すると同じ考え方で、設定テーブルを軸にして、「設定」と「ロジック」を分離する方式を取りました。

この検討の中で、List.Accumulate という関数が今回の主役なのですが、結構面白いところがありまして、今回の投稿になりました。

1. 用意したテーブル構造

まず、以下のような「設定テーブル( T_BIN_CONFIG )」をExcel上に作ります。これが今回の司令塔です。

対象列 マスタテーブル名 新しい列名
年齢 T_M_年齢区分 年齢層
点数 T_M_点数区分 成績
  • 対象列: 元データのどの列を区分けするか
  • マスタテーブル名: どの境界値テーブル(0~30は"小"など)を使うか
  • 新しい列名: 結果を何という列名で出力するか

この行を増やすことで、Power Queryが自動的に列を追加してくれるようにします。

2. 実装したM言語( List.Accumulate

今回の主役は List.Accumulate です。検索エンジンで検索すると、通常は「数値の合計」などの例で語られることが多い関数なのですが、今回は 「テーブル変数の更新」 に使います。

let
    // 1. 各種データの読み込み
    Source = Excel.CurrentWorkbook(){[Name="T_テスト結果"]}[Content],
    Config = Excel.CurrentWorkbook(){[Name="T_BIN_CONFIG"]}[Content],

    // 2. ループ処理の実行 (List.Accumulate)
    // Configテーブルを1行ずつ読み込みながら、テーブル(state)を育てていく
    ResultTable = List.Accumulate(
        Table.ToRecords(Config), // ループするリスト(設定情報)
        Source,                  // 初期状態(種となるテーブル)
        
        // --- ループ処理の中身 (state:現在のテーブル, currentConfig:今の設定行) ---
        (state, currentConfig) =>
            let
                // A. マスタテーブルを名前から動的に読み込む
                // Excelブック内から "T_M_年齢区分" などの名前でテーブルを探す
                RawRangeTable = Excel.CurrentWorkbook(){[Name=currentConfig[マスタテーブル名]]}[Content],
                RangeTable = Table.Buffer(RawRangeTable), // 高速化のためバッファ必須

                // B. 列の追加(ビニング処理)
                AddedColumn = Table.AddColumn(state, "TempBin", (record) =>
                    // マスタから条件に合う行を探す (>= 下限 and < 上限)
                    Table.SelectRows(RangeTable, each 
                        Record.Field(record, currentConfig[対象列]) >= [下限] and 
                        Record.Field(record, currentConfig[対象列]) < [上限]
                    )
                ),

                // C. テーブルを展開して値を確定させる
                Expanded = Table.ExpandTableColumn(AddedColumn, "TempBin", {"区分名"}),
                
                // D. 列名をコンフィグ指定の名前に変更 (例: "区分名" -> "年齢層")
                Renamed = Table.RenameColumns(Expanded, {{"区分名", currentConfig[新しい列名]}})
            in
                Renamed
    )
in
    ResultTable

実行結果はこんな感じ

image.png

面白い発見:reduce List.Accumulateで「テーブルを育てる」という感覚

この実装を通じて面白かったのは、List.AccumulateAccumulateプリミティブな値だけ でなく テーブルでもよい という点です。
List.Accumulateは、javascriptなどの一般的な関数型プログラミング言語の reduce 関数(数値の合計など)に相当する関数です。

reduce関数は、こんな感じのバケツリレーするようなイメージです。

  • 初期値 01 を足す → 12 を足す → 結果は 3 (数値)

で、今回の実装ではバケツの中身(Accumulator)が 「テーブルそのもの」 なのです。

  1. 初期状態: [名前, 年齢, 点数] の3列
  2. 1周目: 設定1行目(年齢)を読み込み、 列を追加
    [名前, 年齢, 点数, *年齢層*] に成長
  3. 2周目: 設定2行目(点数)を読み込み、 さらに列を追加
    [名前, 年齢, 点数, 年齢層, *成績*] に成長

状態(State)としてのテーブル

図にすると、前のループで作られたテーブルが、次のループの入力(currentTable)としてバケツリレーされていることがわかります。

M言語で関数型プログラミングするのって、結構面白い?!

出発点は「M言語を毎回修正したくない」という、毎度のメンドクサイのをどうにかしたいでしたが、 累積器 (イミュータブルなのに変数というのはおかしな感じがなので、累積器 と呼びます)に、数値などの単純な値を累積するだけでなく、テーブルそのものを累積できる という、関数型言語らしい発見があって、面白かったです。

2
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?