重み付きk係数を理解する

# 何をしたいか
重み付きk係数を理解したい。

# k係数とはなにか
観察者間の一致度を測る評価指標の一つ。Cohen's kappa（コーエンのk係数）と呼ばれたり、カッパ係数と呼ばれたりする。正解データと、AIの推測値の一致度を評価することもできる。

```math
 \begin{align}
k&=\dfrac{\Sigma _{i}O_{i,i}-\Sigma _{i}E_{i･,･i}}{1-\Sigma _{i}E_{i･,･i}}\\&=1-\dfrac{1-\Sigma _{i}O_{i,i}}{1-\Sigma _{i}E_{i･,･i}}
 \end{align}

```

## 変数の説明
病気をGrade1〜3で評価した、以下のようなデータを仮定する。

- Grade1 -> Grade2 -> Grade3の順番に程度が悪化していく。
- 横方向が医師A、縦方向が医師Bの判断。

|    | Grade1 | Grade2 | Grade3 |合計|
|-----|-----|---- |-----|-----|
| Grade1 | 32  |  0  |  5  | 37  |
| Grade2 | 0  | 19 | 1  | 20  |
| Grade3 | 9  | 0 | 34  | 43  |
| 合計 | 41  | 19 | 40 | 100  |

#### O (Observed)
 このケースにおいて、医師Aの判断が医師Bの判断と一致した割合

 ```math
 \begin{align}
 \Sigma _{i}O_{i,i} &= \frac{32}{100} + \frac{19}{100} + \frac{34}{100}\\ &= 0.85
 \end{align}
 ```

#### E (Expected)
 このケースに限らず、医師Aの判断が医師Bの判断と一致するであろう確率

 ```math
 \begin{align}
 \Sigma _{i}E_{i･,･i} &= \frac{37}{100} \times \frac{41}{100} + \frac{20}{100} \times \frac{19}{100} + \frac{43}{100} \times \frac{40}{100}\\&= 0.36
 \end{align}
 ```

## k係数を計算する
　したがって、

```math
 \begin{align}
 k&=\dfrac{\Sigma _{i}O_{i,i}-\Sigma _{i}E_{i･,･i}}{1-\Sigma _{i}E_{i･,･i}}\\&=\dfrac{0.85-0.36}{1-0.36}\\&=0.77
 \end{align}
```

## k係数の評価

Landis and Kochの基準では、
>0.0〜0.2: わずかに一致(slight agreement)
0.21〜0.40 まずまずの一致(fair agreement)
0.41〜0.60 中等度の一致(moderate agreement)
0.61〜0.80 かなりの一致(substantial agreement)
0.81〜1.0 ほぼ完全、完全一致(almost perfect or perfect agreement)

Krippendorffの基準では

>0.67未満　評価しない(discounted)
0.67〜0.80　不確かな結果(conclusions tentatively)
0.80以上　明確な結果(definite conclusions)

なので、0.77は比較的まともな数値に見えます。([参考２](http://ides.hatenablog.com/entry/20170417/1492409363))
しかし、Grade1とGrade3の取り違えはかなりおかしい。

## k係数の問題
- クラスが3以上ある場合に、少しでも値がずれていたらハズレとしてしまうと、近い値をとった場合も、遠い値をとった場合も、同様にハズレとして評価されてしまい望ましくない。医師がGrade1だGrade2だと争うよりも、Grade1だGrade3だと争うほうが一致率は低いのに、これが反映されにくい。

- 遠い値をとった場合(k=0.76)

|    | Grade1 | Grade2 | Grade3 |合計|
|-----|-----|---- |-----|-----|
| Grade1 | 32  |  0  |  5(*)  | 37  |
| Grade2 | 0  | 19 | 1  | 20  |
| Grade3 | 9(*)  | 0 | 34  | 43  |
| 合計 | 41  | 19 | 40 | 100  |

- 近い値をとった場合(k=0.77)

|    | Grade1 | Grade2 | Grade3 |合計|
|-----|-----|---- |-----|-----|
| Grade1 | 32  | 5(*) | 0  | 37  |
| Grade2 | 0   | 19   | 1  | 20  |
| Grade3 | 0   | 9(*) | 34 | 43  |
| 合計 | 32  | 34 | 35 | 100  |


# 重み付きk係数とはなにか
単純なk係数で現れる問題を解消するための工夫として重みを使った評価指標。kaggleのEvaluationで説明されていたり、医療の分野で見られたりと、比較的有名。Weighted Kappa。特に3クラス以上あるとき、セルの重みをquadraticに見る場合、Quadratic Weighted Kappaとも。

```math
k=1-\dfrac{\Sigma _{i,j}{w_{i,j}x_{i,j}}}{\Sigma _{i,j}{w_{i,j}m_{i,j}}}
```
## 変数の説明
「遠い値をとった場合」のマトリックスを使って考える。

#### w (weight)
　マトリックスのそれぞれのセルの重み。kはクラス数。（[注1](https://qiita.com/tan826/items/4d1fb2045f2b48d21b7d#注)）

```math
w_{i,j}=(i - j)^2
```

 例えば、医師AがGrade1、医師BがGrade3と判断したセルの重みは、

```math
 \begin{align}
w_{1,3}&=(1 - 3)^2\\&=4
 \end{align}
```

これを表にすると、

|    | Grade1 | Grade2 | Grade3 |
|-----|-----|---- |-----|
| Grade1 | 0  | 1 | 4  |
| Grade2 | 1   | 0   | 1  |
| Grade3 | 4   | 1 | 0 |

#### x (Observed)
 このケースにおいて、医師Aの判断が医師Bの判断と一致した割合。医師AがGrade1、医師BがGrade３と判断した割合は、

```math
x_{1,3}=\dfrac{5}{100}
```

#### m (Expected)
　このケースに限らず、医師Aの判断が医師Bの判断と一致するであろう確率。医師AがGrade1、医師BがGrade3と判断する確率は、

```math
\begin{align}
m_{1,3}&=\dfrac{37}{100}\times\dfrac{40}{100}\\&=\dfrac{1480}{10000}
\end{align}
```

## k係数を計算する

それぞれのケースで計算する。

- 遠い値をとった場合(k=0.51)

|    | Grade1 | Grade2 | Grade3 |合計|
|-----|-----|----   |-----|-----|
| Grade1 | 32(×0)   | 0(×1) |  5(×4)  | 37 |
| Grade2 | 0(×1) | 19(×0)   | 1(×1)  | 20 |
| Grade3 | 9(×4)    | 0(×1) | 34(×0) | 43 |
| 合計    | 41       | 19      | 40      | 100  |

- 近い値をとった場合(k=0.89)

|    | Grade1 | Grade2 | Grade3 |合計|
|-----|-----|---- |-----|-----|
| Grade1 | 32(×0)  | 5(×1) | 0(×4)  |37 |
| Grade2 | 0(×1)   | 19(×0)   | 1(×1)  |20|
| Grade3 | 0(×4)   | 9(×1) | 34(×0) |43|
| 合計    | 32       | 33      | 35      | 100  |

　遠い値をとっている場合を、より低い値として評価できています。

# 実装してみた

```wkappa.py
import numpy as np

a = [2, 0, 2, 2, 0, 1]
b = [0, 0, 2, 2, 0, 2]


def main():
    assert_len(a, b)
    cls_cnt = count_class(a, b)
    total = count_total(a)
    weight = get_weight(cls_cnt)
    matrix = get_matrix(a, b, cls_cnt)
    x = calc_x(matrix, total)
    m = calc_m(matrix, total)
    score = get_score(weight, x, m)
    print(score)


def assert_len(a, b):
    assert len(a) == len(b)


def count_class(a, b):
    return len(set(a+b))


def count_total(a):
    return len(a)


def get_weight(cls_cnt):
    weight = np.zeros((cls_cnt, cls_cnt), dtype=np.float32)
    for i in range(cls_cnt):
        for j in range(cls_cnt):
            weight[i][j] = np.square(i - j)
    return weight


def get_matrix(a, b, cls_cnt):
    matrix = np.zeros((cls_cnt, cls_cnt), dtype=np.float32)
    for i, j in zip(a, b):
        matrix[i][j] += 1
    return matrix


def calc_x(matrix, total):
    return matrix / total


def calc_m(matrix, total):
    sum_a = np.sum(matrix, axis=1)
    sum_b = np.sum(matrix, axis=0)  # weightが対称行列なので本当は軸の順番はどうでもいい
    m = np.outer(sum_a, sum_b) / np.square(total)
    return m


def get_score(weight, x, m):
    return 1 - np.sum(weight * x) / np.sum(weight * m)


if __name__ == '__main__':
    main()
```

# Scikit-Learnに実装されてます
- こっちを使ったほうがいいです。
 - [3.3.2.4. Cohen’s kappa](https://scikit-learn.org/stable/modules/model_evaluation.html#cohen-s-kappa)
 - [sklearn.metrics.cohen_kappa_score](https://scikit-learn.org/stable/modules/generated/sklearn.metrics.cohen_kappa_score.html#sklearn.metrics.cohen_kappa_score)

```
>>> from sklearn.metrics import cohen_kappa_score
>>> y_true = [2, 0, 2, 2, 0, 1]
>>> y_pred = [0, 0, 2, 2, 0, 2]
>>> cohen_kappa_score(y_true, y_pred)
0.4285714285714286
```
このcohen_kappa_score関数に、[weights="quadratic"](https://github.com/scikit-learn/scikit-learn/blob/7389dba/sklearn/metrics/classification.py#L360)と引数を加えるといい。

# 最後に
- 評価値が連続した性質のものであれば、重み付きk係数を使うことは有効だが、各クラスが完全に独立していると意味不明な出力になってしまう。
- 連続変数だとしても、それぞれのクラスの隔たりの程度は同じでない場合もあると思う。例えば、果物の腐り具合を、腐っている部位の割合で表現した場合を考える。1割くらい腐っていたら食べられたものではなく、  

 - 腐っていない（腐っている部位0%）
 - 腐りかけ(腐っている部位10%)
 - 腐っている(腐っている部位100%)  

などとクラス分けする可能性があるかもしれない。そんな場合は重みの計算をアレンジしたほうが良いかと思う。

- 頑張って調べたが、結局[wiki](https://en.wikipedia.org/wiki/Cohen%27s_kappa)が一番丁寧だった。

# 注
参考(1)のページは、参考(2)に従って重みをつけている。

```math
w_{i,j}=\dfrac{(i - j)^2}{(k-1)^2}
```
これに対して、scikit-learnの実装は、Weighted Kappaのオリジナルである参考(6)（リンク切れで確認できず）を参考にしている。このページでは、参考(6)に従った。

# 注２
実はWeighted Kappaのオリジナルは参考(7)であって、Kappaのオリジナルである参考(6)ではない。

# 参考
(1) [κ係数によるreproducibilityの評価 | 大阪大学腎臓内科](http://www.med.osaka-u.ac.jp/pub/kid/clinicaljournalclub12.html)
(2) [Measurement of Observer Agreement
](https://www.ncbi.nlm.nih.gov/pubmed/12819342?dopt=Citation)
(3) [重み付きカッパ係数―順序尺度の場合のカッパ係数
](https://bellcurve.jp/statistics/blog/14200.html)(4) [コーエンのκ係数](http://ides.hatenablog.com/entry/20170417/1492409363)
(5) [信頼性評価指標としてのk係数の性質について](http://www.math.chuo-u.ac.jp/~sugiyama/08/08-02.pdf)
(6) J. Cohen (1960). "A coefficient of agreement for nominal scales". Educational and Psychological Measurement 20(1):37-46. doi:10.1177/001316446002000104.
(7) Cohen, J. (1968). "Weighed kappa: Nominal scale agreement with provision for scaled disagreement or partial credit". Psychological Bulletin. 70 (4): 213–220. doi:10.1037/h0026256. PMID 19673146.