notebook

都内でWEB系エンジニアやってます。

jqのreduceを使ってカジュアルに集計する

今回はjqのreduceを使ってさくさく集計を行ってみます

サンプル

  • hoge.json
[
  {
    "value": 1,
    "label": "hoge"
  },
  {
    "value": 3,
    "label": "fuga"
  },
  {
    "value": 2,
    "label": "hoge"
  },
  {
    "value": 2,
    "label": "hoge"
  }
]

サンプルのjson

APIのレスポンスなどでもよくあるパターン

ゴールはlabelごとに集計結果を出力するところまで

reduce

まずはreduceの基本的な使い方

$ echo '[1,2,3,4]' | jq 'reduce .[] as $n (0; . + $n)'
10

.[] as $nの部分はループ中で参照するための変数を定義しています、いわゆるcurrentととらえることができそうです

0の部分が初期値、;で区切ってループの中で行う処理を書いていきます

.はいわゆるaccumulatorととらえることができそうです

  • valueの値の合計を求める
$ cat hoge.json | jq 'reduce .[] as $row (0; . + $row.value)'
8

いきなりいろんな要素が入った感じで出されると「うっ」となるが順を追ってひとつずつ理解していけば特に変な感じにはならない

特定キー毎に合計を計算する

$ cat hoge.json | jq 'reduce .[] as $row ({}; .[$row.label] += $row.value)'
{
  "hoge": 5,
  "fuga": 3
}

.labelをキーとしてキーに対して.valueを足していく

keyごとに初期値を指定する必要がなければこの方法でlabelごとの合計が計算できる

平均

$ cat hoge.json | jq 'reduce .[] as $row ({}; .[$row.label] += [$row.value])| . as $tmp| .'
{
  "hoge": [
    1,
    2,
    2
  ],
  "fuga": [
    3
  ]
}

$ cat hoge.json | jq 'reduce .[] as $row ({}; .[$row.label] += [$row.value])| . as $tmp| keys | reduce .[] as $key ({}; .[$key] = ($tmp[$key]|add)/($tmp[$key]|length))'
{
  "fuga": 3,
  "hoge": 1.6666666666666667
}
  • 平均を計算するには合計とデータ数が必要なので1つ目のreduceで計算対象のvalueのみ抜き出す
  • 結果を変数に格納し2つめのreduceでkeyごとに計算させる
  • addは合計、lengthはデータの個数

最大

合計、平均と同様関数に渡してあげるだけでOK

cat hoge.json | jq 'reduce .[] as $row ({}; .[$row.label] += [$row.value])| . as $tmp| keys | reduce .[] as $key ({}; .[$key] = ($tmp[$key]|max))'
{
  "fuga": 3,
  "hoge": 2
}

まとめ

  • reduce便利
  • 変数格納を駆使すればある程度何でもできそう
  • awkを使わなくても集計できる
  • 簡単な内容であればわざわざコード書かずともワンライナーで処理できる

この要領でいろいろ集計できそう

実現方法はほかにもいろいろありそうだがサクッと集計したいときには使えるように覚えておく