テーマ
「期間Aと期間Bで単語の出現頻度の差を算出する」
- if-idf的なことをやるためのお試しといった感じです
- 出現頻度:その単語を含む記事数
方針
- terms aggregation → 単語毎の出現頻度を数える
- filter aggregation → 期間Aと期間Bに分割
- bucket script aggregation → 期間Aと期間Bを比較
設定
elasticsearch.yml
script.engine.groovy.inline.aggs: on
query
bucket-script-agg.json
GET _search?search_type=count
{
  "query": {
    "match_all": {}
  },
  "aggs": { // 1.terms aggregation
    "term": {
      "terms": {
        "field": "text",
        "size": 5
      },
      "aggs": { // 2.filter aggregation
        "dateRange1": { // 期間A
          "filter": {
            "range": {
              "datetime": {
                "gte": "2014-04-01T00:00:00+0900",
                "lte": "2014-04-10T23:59:59+0900"
              }
            }
          }
        },
        "dateRange2": { // 期間B
          "filter": {
            "range": {
              "datetime": {
                "gte": "2014-04-11T00:00:00+0900",
                "lte": "2014-04-20T23:59:59+0900"
              }
            }
          }
        },
        "tf-idf": { // 語弊あり
          "bucket_script": { // 3.bucket script aggregation
            "buckets_path": {
              "range1": "dateRange1._count", //doc_countを取り出す
              "range2": "dateRange2._count"
            },
            "script": "range2 - range1" //簡単なscript
          }
        }
      }
    }
  }
}
結果
response.json
{
  "took": 133,
  "timed_out": false,
  "_shards": {
    "total": 7,
    "successful": 7,
    "failed": 0
  },
  "hits": {
    "total": 48762,
    "max_score": 0,
    "hits": []
  },
  "aggregations": {
    "term": {
      "doc_count_error_upper_bound": 6688,
      "sum_other_doc_count": 904138,
      "buckets": [
        {
          "key": "い", // term(analyzerの設定が適当なので変な感じ)
          "doc_count": 23490, // hits全体での件数
          "dateRange2": {
            "doc_count": 7642 // 期間Aでの件数
          },
          "dateRange1": {
            "doc_count": 8270 // 期間Bでの件数
          },
          "if-idf": {
            "value": -628 // 期間B - 期間A の結果
          }
        }
...
      ]
    }
  }
}
まとめと感想
- かなり柔軟に処理を書けそう
- scriptで扱うbucketの数が特定できないと使えないような気が
- bucketからドキュメント件数を取り出すには{backet_path}._countを使う
- ひとまず思ったとおりのことができた
- bucketを任意の条件でfilterするaggregationの構想もあるらしいので期待
- (20151112追記) bucket selector aggregationと言うのがそれらしい
 
- Senseが超便利
課題
- 期間Aと期間Bそれぞれの全ドキュメント数で正規化するような処理を入れたい
- terms aggregationとfilter aggregationの順序を逆に出来るだろうか