Power Query の List.Accumulate 関数ってなんだよー

お題の例として累積値を求めようかなと。Excel だとピボットテーブル使えやー、Power BI だとメジャーを書けやーってことではあるんだけど、そういうこともあるよねって思いなおしたので。
Power Query エディターでデータを揉むとき、テーブルでいろいろ考えてしまいがちなんだけど、行(record)、列(list) という見方もしていかないとならないのです、結果的に遠回りってこともありますからね。

# お題
Excel ワークシート上のテーブルにある[列1]の値の累積値を追加する感じで。要は行をまたいで列の集計結果を追加したいということ。

|列1|累計|
|--:|--:|
|1|1|
|2|3|
|3|5|
|..|..|
|10000|50005000|
|..|..|

# 見かけた方法
別にネガティブな評価をしているのではないです。ただ、テーブルでいろいろ考えてしまってるのでもったいないなぁということだけです。

```text:街で見かけた方法
let
    Source = Excel.CurrentWorkbook(){[Name="Table1"]}[Content],
    ChangedType = Table.TransformColumnTypes(
        Source,
        {"列1", Int64.Type}
    ),
    AddedIndex = Table.AddIndexColumn(
        ChangedType,
        "Index",
        1,
        1
    ),
    AddedCustom = Table.AddColumn(
        AddedIndex,
        "累計",
        each
            List.Sum(
                List.FirstN(ChangedType[列1], [Index])
            ),
        Int64.Type
    ),
    RemovedOtherColumns = Table.SelectColumns(AddedCustom,{"列1", "累計"})
in
    RemovedOtherColumns
```

たぶん3000行くらいからヤバい感じになるかなと。同じ計算を繰り返しているのも改善点かな。
Table.AddColumn はその行の範囲だけで処理が吉なんだけど、Mashup Engine が悲鳴をあげてるかも。

**すぐ遅くなるよね。なぜなのか？**
端的に表現すると Mashup Engine (Power Queryの中の人ね)の得意とするところを手当なく無慈悲に否定しているからで、本題にそれるので少しだけ。例えば、

```text
let
    Source = Table1,
    Added列2 = Table.AddColumn(Source, "列2", each [列1] + 1, Int64.Type),
    Added列3 = Table.AddColumn(Added列2, "列3", each [列2] * 10, Int64.Type)
in
    Added列3
```
というクエリがあったとして。[列1] + 1 の評価結果を [列2] として追加し、[列2] * 10 の評価結果を [列3] として追加するシンプルな内容。このとき、入力である Table1 の行に対しどのような処理がされているか。
1. すべての行に対し[列2]追加し終わってから、
2. すべての行に[列3] を追加する
という動作ではなくて、ある程度の行数の処理が完了したら、できた分を小出しに次の処理に回すスタイル(stream)になっていて、手持ちのリソースだけでできるだけ多くの行を処理するという感じだから。なので、できるだけムチャ振りしない。

# お題の解決(できるだけ)
## 本題の List.Accumulate
>**List.Accumulate**
リスト内のアイテムから要約値を収集します。
<code>accumulator</code> を使用して、リスト <code>list</code> 内のアイテムから要約値を収集します。省略可能なシード パラメーター <code>seed</code> を設定することもできます。
((state, current) => state + current ) を使用して、リスト {1, 2, 3, 4, 5} 内のアイテムから要約値を収集します。
List.Accumulate({1, 2, 3, 4, 5}, 0, (state, current) => state + current) = 15 // true

ちょっとわかりにくいなと。
ソースになる list の順番にリストアイテム(current)を accumulator に渡し、繰り返しの処理をしてくれる。List.Transform と違うのは状態(state)を持っていて、List.Accumulate の結果は最後の状態(state)。


||state|current|次の state|
|:--:|:--:|:--:|:--:|
|#1|0(seed)|1| 0 + 1|
|#2|1|2|1 + 2|
|#3|3|3|3 + 3|
|#4|6|4|6 + 4|
|#5|10|5|10 + 5|
|return|15||

### サンプル
List.Accumulate が扱う値は数値(number) である必要はないので、list を扱う方法にする。得られた list を列として テーブルに追加すればよいではないか。

```text
let
    Source = Excel.CurrentWorkbook(){[Name="Table1"]}[Content],
    ChangedType = Table.TransformColumnTypes(
        Source,
        {"列1", Int64.Type}
    ),
    KeptFirstRows = Table.FirstN(ChangedType, 50000),
    ColumnToList = List.Buffer(KeptFirstRows[列1]),
    Accumulated = List.Accumulate(
        ColumnToList,
        {0},
        (state, current) => {current + List.First(state)} & state
    ),
    Custom1 = Table.FromColumns(
        {
            ColumnToList,
            List.Skip(List.Reverse(Accumulated))
        },
        Value.Type(Table.AddColumn(ChangedType,"累積", each null, Int64.Type))
    ) 
in
    Custom1
```

#### 使った関数など
**List.Accumulate**

(state, current) => {current + List.First(state)} & state
でリストアイテム(current)をリスト(state)の先頭に詰んでいて、
List.Skip(List.Reverse(Accumulated))
でひっくり返しているのは、
(state, current) => state & {current + List.Last(state)}
リスト(state)の後ろにリストアイテム(current)追加するスタイルだと、List.Last が高コストぽい感じだから。

>**List.Buffer**
リストをバッファー処理します。
リスト <code>list</code> をメモリにバッファー処理します。この呼び出しの結果は、安定したリストです。

Mashup Engine が ヒャッハー。って感じになるのは、このバッファしたリストについては一括で処理を進めてよいよということで、ストリーミング処理でこのあとリストアイテムが追加されることがないから。

>**Table.FromColumns**
列と指定した値のリストからテーブルを作成します。
列名と値を含む入れ子になったリストを含むリスト <code>lists</code> から <code>columns</code> 型のテーブルを作成します。一部の列の値が他の列よりも多い場合、NULL 許容列では足りない値が既定値の 'NULL' で埋められます。

List.Zip と似た感じ。

#思ったこと🙄
Excel ワークシート関数と同じで、できるだけ多くの Power Query 関数を知らないとならないかなと。そして、それらをどう組み合わせるか。
Power Query で 累計を集計するのはちょいムチャな感じはあるけれども何とかなるかな。とはいえ、途中にスタックオーバーフローがチラチラ見えていたので、List.Accumulate じゃくて List.Generate の方がよいような気がしないでもない。

[Power Query の List.Generate 関数ってなんだよー](https://qiita.com/PowerBIxyz/items/ed6ebb547a9157561ad1)

# その他
- [Power BI 勉強会 - connpass](https://powerbi.connpass.com/)
- [Power BI Desktop を使い始めるには](http://qiita.com/PowerBIxyz/items/6c4735cb5b5712e70981)
- [Excel で Power Query を使い始めるには](http://qiita.com/PowerBIxyz/items/52e563037b855a099ca9)

#おまけ

列名に依存しない記述って大変だよね。
すべての列を列ごとに累積ってできるかな？と思っただけ。

```text:List.Accumulateを盛大に使ってみた
let
    Source = Excel.CurrentWorkbook(){[Name="テーブル1"]}[Content],
    ChangedType = Table.TransformColumnTypes(Source,{{"列1", Int64.Type}, {"列2", Int64.Type}, {"列3", Int64.Type}}),
    KeptFirstRows = Table.FirstN(ChangedType,20000),
    Seed = List.Accumulate(
        Table.ColumnNames(Source),
        [],
        (state, current) => Record.AddField(state, current, 0)
    ),
    BufferedRows = List.Buffer(Table.ToRecords(KeptFirstRows)),
    Custom1 = List.Accumulate(
        BufferedRows,
        {Seed}, 
        (Rows, CurrentRow) =>
            [
                PreviousItem = List.First(Rows),
                CurrentItem = List.Accumulate(
                    Record.FieldNames(Seed),
                    [],
                    (Fields, CurrentField) =>
                        Record.AddField(
                            Fields,
                            CurrentField,
                            Record.Field(PreviousItem, CurrentField) +
                            Record.Field(CurrentRow, CurrentField))
                ),
                Return = {CurrentItem} & Rows
            ][Return]
    ),
    Custom2 = Table.FromRecords(
        List.Skip(List.Reverse(Custom1)),
        Value.Type(KeptFirstRows)
    )
in
    Custom2
``