[Power BI] RANKX関数を紐解く。JAPAN RUGBY LEAGUE ONE 選手たちの身長・体重で。

# はじめに

先日、JAPAN RUGBY LEAGUE ONE の開幕を記念して、選手の身長・体重を**Power Query**のカスタム関数を利用してPower BI Desktopに取り込み、可視化しました。

https://qiita.com/hanaseleb/items/ffb70fba16aa073a8814

## 今回取り組むこと

RANKX関数を使って身長、体重のランキングを作成します。
そのなかでRANKX関数の仕様を紐解いていきます。

https://docs.microsoft.com/ja-jp/dax/rankx-function-dax

## 作ったレポート

こんなものを作りました。

https://app.powerbi.com/view?r=eyJrIjoiOGMzYTNmMTAtMGY1MC00OGEwLWExOGQtOTgyZDdhNDdiNTU5IiwidCI6IjdkMDZmZWVlLWI3MzYtNDE3Zi1hMGUyLTE3NTZjOTdiMDU5YyJ9&pageName=ReportSection5ce04f9219a54552ce20

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/80467/b3f2477c-3c8d-a685-ce19-0dcc4c86a5e3.png)

## RANKX関数の構文

```datastudio
RANKX(<table>, <expression>[, <value>[, <order>[, <ties>]]])
```

## 身長のランキングを作成する

身長を並べるテーブルを作成します。ここにRANKX関数を書いて、ランキングをつけていきます。
200m以上の選手がこんなにいるんですね。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/80467/bb8d548b-3e30-77a3-1d71-e0c03ca9edd3.png)

最低限のRANKX関数を書いてみます。1つ目の引数には身長・体重が入っている`Height&Weight`テーブルを指定して、2個めの引数は単一の結果をだす式でないといけないので、今回はMAXX関数をつかっています。

```MAX_Height.dax
MAX_Height = MAXX('Height&Weight','Height&Weight'[Height])
```

`datastudio
HeightRank 0 = RANKX(
    'Height&Weight',
    [MAX_Height]
)
`

これを適用してみると↓のようになります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/80467/249398ef-9491-7439-8e3e-86763e79f59a.png)

期待した動きではないですよね。これは、RANKX関数が行ごとに評価をしていく仕組みになっているので、このままだと1行の中にある、１つの身長に対してランキングを返すだけの謎の関数になってしまっています。

## 改善１つ目

ということで、分母を表全体に変えたいと思います。そういうときはALL関数を使用します。
1つ目の引数に対してALL関数を適用して、

分母 → 表全体
分子 → 行ごとの身長

となるように書いていきます。

https://docs.microsoft.com/ja-jp/dax/all-function-dax

```datastudio
HeightRank 1 = RANKX(
    ALL('Height&Weight'),
    [MAX_Height]
)
```

### 結果

期待した結果になりました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/80467/d86e6403-0f75-2357-067f-4e53e0ec2442.png)

## 改善２つ目

Power BIは合計列も行もひとつとしてDAX関数の評価が行われているので注意が必要です。
いまは合計列にも数字が表示されてしまっているので、ここに数字が表示されないようにしたいです。今回はISINSCOPE関数を使用します。指定した階層にいるときだけ`true`を返します。


https://docs.microsoft.com/ja-jp/dax/isinscope-function-dax

```datastudio
HeightRank 2 = IF(
    ISINSCOPE( 'Height&Weight'[Name] ),
    RANKX( ALL( 'Height&Weight' ), [MAX_Height] )
)
```

### 結果

期待した通り、合計行の数字がなくなりました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/80467/4e470241-8bfd-e2c9-da14-1a2cd8c68a3d.png)

## 改善３つ目

次に考えるのは、選手個人名のスライサーを作成して数名を選んだときに、分母が**全員**のママなのでランキングが直感的ではなくなっている点です。選択した選手の中でだけランキングをされるように改善します。

この場合はALLSELECTED関数を使用します。名前の通り、スライサーで選択したものすべてを分母とするときに使用します。

https://docs.microsoft.com/ja-jp/dax/allselected-function-dax

```datastudio
HeightRank 3 = IF(
    ISINSCOPE( 'Height&Weight'[Name] ),
    RANKX( ALLSELECTED( 'Height&Weight' ), [MAX_Height] )
)
```

### 結果

期待したとおり、５人をスライサーで選択すると、１～５位までが表示されています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/80467/8db750cf-71a4-7c53-70f4-4f91466ac75b.png)

## 改善４つ目

レポートを作るに当たり、チームごとでのランキングも表示させたいなぁと思っています。いまの関数でチームの階層を加えると、選択したすべての選手が分母になっているので、チームをまたいでランキングが計算されています。これをチーム別にランキングが計算されるようにしていきます。

これを実現するために、**ALLEXCEPT関数**を使います。指定した列にかかっているフィルター以外はすべてなくなります。今回の場合、チーム名のフィルターコンテキストだけ残し、「選択した選手」フィルターは削除します。この関数を使用することで、分母がチーム全員、分子がその行の身長となり、期待したランキングを作成できます。

>指定した列に適用されているフィルターを除く、テーブル内のすべてのコンテキスト フィルターを削除します。

`ALLEXCEPT(<table>,<column>[,<column>[,…]])`

https://docs.microsoft.com/ja-jp/dax/allexcept-function-dax

```datastudio
HeightRank 4 =
IF (
    ISINSCOPE ( 'Height&Weight'[Name] ),
    RANKX (
        ALLEXCEPT (
            'Height&Weight',
            'Height&Weight'[Team_Name]
        ),
        [MAX_Height]
    )
)

```

### 結果

チームごとの選手数が分母になり、ランキングされているのがわかりますね。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/80467/8677de18-c2b3-2bd7-fc51-777f18325fda.png)

## 改善５つ目

チーム別にランキングが計算されるようになりましたが、さらにポジション別の階層も追加したいと思っています。そのときは、さきほどの**ALLEXCEPT関数**に`'Height&Weight'[Position]`カラムを追加してあげます。

```datastudio
HeightRank 5 =
IF (
    ISINSCOPE ( 'Height&Weight'[Name] ),
    RANKX (
        ALLEXCEPT (
            'Height&Weight',
            'Height&Weight'[Position],
            'Height&Weight'[Team_Name]
        ),
        [MAX_Height]
    )
)
```

### 結果

チームとポジションでフィルターをかけたときにも期待した動作になっています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/80467/f7dd8555-c650-a083-a3c9-d3772fdf8dc7.png)

ポジションを２つ選んでもそれぞれのポジションごとにランキングが計算されています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/80467/57dd8f5c-2b66-2b99-4b75-acb8bff9b584.png)

複数のチーム、複数のポジションを選んでも大丈夫。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/80467/4cf61b72-ecb4-ee24-7575-73fc1ffd2d3d.png)


## まとめ

**RANKX関数**単体ではあまり期待したランキングは作成できなさそう。**ALL関数、ALLSELECTED関数、ALLEXCEPT関数、ISINSCOPE関数**などを組み合わせて状況に合わせたDAXを書いてあげることが大事。

## 成果物

選手のポジションごとに身長/体重を可視化できました。ランキングも機能しています。

https://app.powerbi.com/view?r=eyJrIjoiOGMzYTNmMTAtMGY1MC00OGEwLWExOGQtOTgyZDdhNDdiNTU5IiwidCI6IjdkMDZmZWVlLWI3MzYtNDE3Zi1hMGUyLTE3NTZjOTdiMDU5YyJ9&pageName=ReportSection5ce04f9219a54552ce20

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/80467/b3f2477c-3c8d-a685-ce19-0dcc4c86a5e3.png)

スクラムハーフとプロップの体型の違いもばっちり可視化できています！

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/80467/7867b272-d33d-d5ba-021e-11c6e06d7a98.png)