Splunk: 上位〇位のランキングを出力する7の方法


###### 実施環境： Splunk Free 8.2.2

### 0. 概要

 Splunk のデータ処理で、上位〇位のランキングを作成したいことがたまにあります。
一見簡単にできそうな内容ですが、同率〇位の扱いを考えるとそう単純にはいかない場合があります。
今回はこのランキング作成の方法について考えていきたいと思います。

例となる元データは、以下の CSV データをルックアップファイルとして登録して使用します。

| name | num |
|:-|:-|
| A | 50 |
| B | 80 |
| C | 10 |
| D | 100 |
| E | 0 |
| F | 10 |
| G | 100 |
| H | 30 |
| I | 30 |
| J | 0 |

```Splunk:Splunk
| inputlookup "test_rank.csv"
```

![WS000116.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1465018/a8dbe47d-2145-5c0c-8778-9c69d6f7afee.jpeg "元データ")

このデータについて、「 num の値の上位5位」を抽出することを考えます。

### 1. 単純に最上位から5個を抽出、コマンド2個

まずは単純に num の値の順番に並べ替え、上から5個をとってくることを考えます。
このとき、まずぱっと思い浮かぶのは sort コマンドと head コマンドを使う方法です。
 sort コマンドはデータを並び替えるコマンド、 head コマンドは先頭から指定した行数のデータを抽出するコマンドで、この2つを組み合わせると上位〇位が抽出できます。

```Splunk:Splunk
| inputlookup "test_rank.csv"
| sort - num
| head 5
```

![WS000117.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1465018/1ba6054a-8c6a-d715-8dcd-3d6786884cc1.jpeg "sort+head")

### 2. 単純に最上位から5個を抽出、コマンド1個

ただ実は、 head コマンドがなくても sort コマンドだけで同様の結果は取得できます。
 sort コマンドには limit というオプションがあり、これは head と同じような役割を持ちます。

```Splunk:Splunk
| inputlookup "test_rank.csv"
| sort limit=5 - num
```

![WS000118.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1465018/a8e42bcd-40a2-1603-b7bc-4629fedab69a.jpeg "sort+limit")

 sort コマンドだけで処理することができました。

ちなみに sort コマンドの limit オプションは実はデフォルトでも10000に設定されています(性能への悪影響を避けるため)。
大量のデータについてどうしてもソート結果全てを出したい場合は、 limit オプションに明示的に 0 を入れる必要があります。

### 3. 最上位から5個を抽出し、単純に先頭から順位を付与

「順位」を付与したい場合は、 streamstats コマンドを使用します。
これは「現在処理している行を含みそれより前の行のみを対象として集計し、その結果を新しいフィールドとして各行に追加する」コマンドです。
このコマンドで count 関数を使用すると「現在処理している行を含みそれより前の行の行数」を算出し、その結果は連番と同等となります。

![WS000121.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1465018/0fb3334d-eb8c-3bdd-2d9f-16802a955fab.jpeg "rank")

```Splunk:Splunk
| inputlookup "test_rank.csv"
| sort limit=5 - num
| streamstats count AS rank
```

![WS000119.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1465018/82ccc82a-f92e-80f4-5cc4-0905233737ff.jpeg "streamstats")

### 4. 最上位から5個を抽出し、同率〇位を考慮した順位をスキップなしで付与

順位の付与について、これまでは同率〇位については考えてきませんでした。
ですが、実際は同率〇位の取り扱いについて考える必要があることが多いかと思います。
なのでまずは、「同率〇位考慮、スキップなし」の順位を付与することを考えます。

いくつか方法はありますが、最もシンプルなのは count 関数の代わりに distinct_count 関数を使用する方法です。
これは重複を排除したユニーク数、すなわち「値が何種類あるか」をカウントする関数なので、この関数を streamstats コマンドで使用すれば「同率〇位考慮、スキップなし」の順位になります。

![WS000123.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1465018/bcd67bf6-f36e-5265-9ac2-5cb294d0d665.jpeg "rank_uniq")

```Splunk:Splunk
| inputlookup "test_rank.csv"
| sort limit=5 - num
| streamstats distinct_count(num) AS rank
```

![WS000124.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1465018/f74a6ef5-8778-a228-4215-f14203d2b603.jpeg "distinct_count")

### 5. 同率〇位を考慮した順位をスキップなしで付与し、上位〇位を抽出

さて、上のように「まずデータを抽出し、次に順位を付与」という順番で同率〇位を処理すると、最終順位が本来想定していた値(今回は5)とずれてしまいます。
同率〇位を処理して上位〇位を正しく抽出するには、処理の順番を入れ替え、「まず順位を付与し、次にデータを抽出」という形にする必要があります。
そのため、 limit オプションを0にして sort コマンドからデータ抽出を削除し、 streamstats の後にデータ抽出処理を追加します。
このとき、抽出条件とするべきものは「単純に先頭から何件目か」ではなく「順位が〇位より上か」ですので、 head コマンドではなく where コマンドを使用して抽出します。

```Splunk:Splunk
| inputlookup "test_rank.csv"
| sort limit=0 - num
| streamstats distinct_count(num) AS rank
| where rank <= 5
```

![WS000125.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1465018/9aa46f71-41fc-788c-3034-3ee3dac7350f.jpeg "sort+where")

### 6. 最上位から5個を抽出し、同率〇位を考慮した順位をスキップありで付与

ここまで同率〇位が発生したとき次の順位はそのままスキップなしで連番にしていましたが、実際は次の順位について1→1→3のようにスキップすることも多いかと思います。

こちらもいくつか方法はありますが、今回は eventstats コマンドと first 関数を使用します。
 eventstats コマンドは streamstats コマンドとは少し異なり、「全ての行のみを対象として集計し、その結果を新しいフィールドとして各行に追加する」コマンドです。
 first 関数は「指定したフィールドで最初に現れた値」を返す関数で、これと BY 句を組み合わせて eventstats コマンドに適用することで、「値で分けたグループ内で最初に現れた順位」を出力することが可能となります。

![WS000134.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1465018/9e057ece-3d80-0f88-2a8f-4a53bd6f7f57.jpeg "rank_first")

```Splunk:Splunk
| inputlookup "test_rank.csv"
| sort limit=5 - num
| streamstats count(num) AS rank_c
| eventstats first(rank_c) AS rank BY num
```

![WS000132.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1465018/3967b697-46fb-a36e-a63a-97e81a6787d7.jpeg "eventstats")

### 7. 同率〇位を考慮した順位をスキップありで付与し、上位〇位を抽出

スキップありについても同率〇位を処理して上位〇位を正しく抽出するには、先ほどと同様コマンドを入れ替えて where コマンドを付与します。

```Splunk:Splunk
| inputlookup "test_rank.csv"
| sort limit=0 - num
| streamstats count(num) AS rank_c
| eventstats first(rank_c) AS rank BY num
| where rank <= 5
```

![WS000133.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1465018/ade73f75-b881-02fb-f041-33e94f847f0b.jpeg "eventstats+where")