ElasticsearchのMetrics aggregationsは、その名の通り?統計的なAggregationsです。
機能充実はありたがたいもののその分だけ数も多いですね。
それぞれの名称、SQLや他の言語などでの関数名と類似性から、得られる値や使い方は同じような用法だろうなーと推測はされるものの、実際必要になるまでは試す気にもならずということで私自身も食わず嫌いではあるのですが、見た目から入って入門するというのが良いのではと思い、ちょいと変則的な切り口でまとめてみました... という記事です。
Metrics aggregationsのよくある「型」
さっそくですが、Metrics aggregationsらは、つまるところ集計関数です。
QueryDSLで得られた検索結果ドキュメント一覧に対して、なんらかの集計などを行います。
集計を行うからには、当然対象のフィールドをどれか指定することになり、おそらく数値型のフィールドを指定することになるでしょう。
実際、集計クエリの形としては、(全部ではありませんが)次のようなものになるものが大半です。
{ aggs: { [agg_label]: { [AGGNAME]: { field: fieldname, } } } }
クエリの種類数は多いですが、上記の見た目をとるものについては、fieldnameで指定されるフィールドに対し、AGGNAMEで示す統計的演算を施す...と見れば一網打尽ではないでしょうか。
現に、X-Packが必要ないもののうちで、AGGNAMEに指定できるものは次のようなものとなっています。
value_count,avg,max,min,sum,stats,extended_stats,cardinality,percentiles,median_absolute_deviation
名前だけで分からないものもありますが、agv、max,sum ...などという顔ぶれを見ればなんとなくわかったような気がしてきます。
では、次に上記にいろいろ当てはめて、試してみましょう。
試してみる
試してみましょうと言いましたが、実際のところ、上記のヒトたちは、名前と形から予想される動作となります。
よって、ひとつずつ試してみるというよりは、同じ形になるというところを示唆することも兼ねて、kibanaにコピペしやすいクエリ一覧をアドホックに出力するスニペットのご紹介で代用することにします。
データ登録用のポストデータ出力
import json ii = [10, 100, 1000, 10000,100000] a = [] num = 5 for i, v in enumerate(ii): idx = i + 1 x = [{'a':'a'+str(v),'b':v, 'c': v * 2, 'weight':idx,'loc':{'lat':35.5,'lon':float('145.'+str(idx))}} for _ in range(num) ] a.extend(x) for i in a: print('POST a_ex/_doc/') print(json.dumps(i, ensure_ascii=False))
などとすると下記が出力されます。
POST a_ex/_doc/ {"a": "a10", "b": 10, "c": 20, "weight": 1, "loc": {"lat": 35.5, "lon": 145.1}} POST a_ex/_doc/ {"a": "a10", "b": 10, "c": 20, "weight": 1, "loc": {"lat": 35.5, "lon": 145.1}} POST a_ex/_doc/ ...
a_exが今回お試し用のインデックスです。
あとで平均などの集計を行う際に、得られる値が予測しやすいようにラフな規則の値をPOSTすることをイメージした出力にしてあります。
逆にいうと、ここで登録するデータはなんらかのドメインとは全く関係ありませんので、数値傾向自体は何も意味をなさないことに注意ください。
なお今回は、locフィールドは使っていません。主に、bフィールドを練習に使います。
上記の出力をkibanaにコピペすると、データ登録されます。今回は明示的なmapping設定は行いません(ひとまず不要です)。
検索(aggsクエリ発行)
前項で登録したデータ(インデックス)をターゲットに、特にbフィールドを意識した、同じ形のクエリ一覧を生成します。 (例によって、直接クエリを発行するのではなく、kibanaに貼り付け用の例を出力する方式です。)
const h = _ => console.log('GET a_ex/_search?filter_path=agg*'); const aggs1 = (AGGNAME, fieldname) => { const agg_label = `my_${AGGNAME}`; return { aggs: { [agg_label]: { [AGGNAME]: { field: fieldname, } } } }; }; AGGNAMES1 = 'value_count,avg,max,min,sum,stats,extended_stats,cardinality,percentiles,median_absolute_deviation'.split(','); AGGNAMES1.forEach(a => { const f = 'b'; //console.log('GET a_ex/_search?filter_path=agg*'); h(); console.log(JSON.stringify(aggs1(a, f))); }); matstats = fields => { return { size: 0, aggs: { "mtrx_stats": { matrix_stats: { fields } } } } }; h(); console.log(JSON.stringify(matstats(['b', 'c']))); const pr = (field, values) => ({ size: 0, aggs: { "my_percentile_ranks": { percentile_ranks: { field, values, } } } }); h(); console.log(JSON.stringify(pr('b', [10, 1000]))); const wg = (f1, f2) => ({ // Σ (value * weight)/Σ(weight) size: 0, aggs: { "my_weighted_avg": { weighted_avg: { value: { field: f1 }, weight: { field: f2 } } } } }); h(); console.log(JSON.stringify(wg('b', 'idx')));
上記はJavaScriptのプログラムですが、例えば下記のように実行してください。
node fuga.js
あとは、kibanaでポチポチ実際に実行してみれば確かにそうだね...という感じの結果が得られます。
これを、先ほどのインデックスデータを登録した環境のkibanaでポチポチ実行してみてください。
先の話のとおり、この類の分野に慣れている方なら、ああ確かにこうなのねという結果が得られるのえですが、自分メモとして、上記の中では少しだけクセがある演算について以下のとおり、ひとこと補足しておきます。
- stats: avgやmaxなどの主要な基本統計量を一括で取得
- extended_stats: statsのさらに拡張
- median_absolute_deviation: いわゆるMADが取得できる(とのこと)
また、次は、複数パラメータを取る分、少しだけaggsクエリの形が違うのでやや別枠としましたが、本質的には、似た種類に属するので合わせてご紹介してあります。
- matrix_stats: 複数フィールドを指定すると、フィールド間の共分散等が取得できる。それ以外にももともと各フィールドのstatsおよび歪度と尖度が得られるので、指定フィールドを1つだけにしてこの数値を取得する命令と考えても良いかもシレナイ。
- percentile_ranks: 指定した数値が累積パーセンタイルのどこに属するかを表してくれる。
- weighted_avg: 各レコードの集計フィールドに対して、指定の別フィールドの値を重みとみなした平均
その他
Metrics aggregationsのうち、X-Pack以外のもので、残りは次のものです。 こちらは時間があればまた。
- top_hits
- geo_bounds
- geo_centroid