TL;DR
Spearman 相関係数を計算するときには、順位タイの変数値に気をつけましょう。
Spearman 相関係数とは
確率変数 $X, Y$ の相関を測る際に使われる指標として Pearson 相関係数と Spearman 相関係数が有名です。
Pearson 相関係数は $X, Y$ が間隔尺度の場合に使われるのに対して、 Spearman 相関係数は順位尺度に使われます。
- 順序尺度 : 値の大小関係や順位には意味があるが、値の間隔には意味がない変数(例:五段階の商品レビュー、地震の震度)
- 間隔尺度 : 値の間隔に意味がある変数(例:温度、テストの点数)
実数値変数 $X, Y$ の Spearman 相関係数は、 $X, Y$ の順位変数に対する Pearson 相関係数として定義されます:
x = [5.0, 1.9, 3.4, 1.1, 0.3]
y = [4.8, 1.4, 4.0, 1.5, 0.9]
rank_x = [0, 2, 1, 3, 4]
rank_y = [0, 3, 1, 2, 4]
spearman(x, y) = pearson(rank_x, rank_y)
この定義から明らかにわかるように、順位関係を保つような変換を $X, Y$ に適用しても Spearman 相関係数の値は変化しません。
落とし穴 : 変数の値に順位タイが存在する場合
下記のように変数の中に同一の値が複数含まれている場合、順位変数の振り方としていくつか異なる方法が考えられ、その選択次第で Spearman 相関係数の値が全く変わってしまいます。
x = [5.0, 5.0, 5.0, 0.4, 2.2]
y = [4.8, 1.4, 4.0, 1.5, 0.9]
一見重箱の隅を突くような話に思われるかもしれませんが、これは実際のデータセットでもよく起こり得ることです。
特に $n$ 段階のスコアを人手でつけたデータセットの場合、サンプル数に対して値のバリエーションが少ないため、非常に高頻度で順位タイが発生します。
ナイーブな方法
例えば、同一の値に対して出現順に順位を振ると、次の例のように変数の並び替えに対して Spearman 相関係数が不変ではなくなってしまうため良くありません。
x = [5.0, 5.0, 5.0, 0.4, 2.2]
y = [4.8, 1.4, 4.0, 1.5, 0.9]
rank_x = [0, 1, 2, 4, 3]
rank_y = [0, 3, 1, 2, 4]
spearman(x, y) = 0.5
x = [5.0, 5.0, 5.0, 0.4, 2.2]
y = [1.4, 4.0, 4.8, 1.5, 0.9]
rank_x = [0, 1, 2, 4, 3]
rank_y = [3, 1, 0, 2, 4]
spearman(x, y) = 0.1
スタンダードな方法
scipy.stats.spearmanr
の実装1や英 Wikipedia 記事2でスタンダードとされている方法では、同一の値全てに対して平均化された順位変数を割り当てます。
次の例で言うと、x
の値 5.0
は順位 0, 1, 2, 3
を占めているため、平均化された順位は (0 + 1 + 2 + 3) / 4 = 1.5
になります。
x = [5.0, 5.0, 5.0, 5.0, 0.4, 2.2]
y = [1.4, 4.0, 4.8, 3.9, 1.5, 0.9]
rank_x = [1.5, 1.5, 1.5, 1.5, 5, 4]
rank_y = [4, 1, 0, 2, 3, 5]
Spearman 相関係数の不適切な実装例 (TensorFlow r1.14)
TensorFlow の Estimator
に metric を設定する場合には streaming タイプの値しか受け付けてくれません。
Pearson 相関係数であれば streaming_pearson_correlation
が用意されていますが Spearman 相関係数に関しては自前で実装する必要があります。
次のコードはネット上で実際に見かけたものですが、順位タイの場合の処理が適切にできていません。
上述したように同一の値に対して別々の順位変数が割り当てられてしまうためです。
import tensorflow as tf
size = tf.size(x)
_, indice_of_ranks_x = tf.nn.top_k(x, k=size)
_, indice_of_ranks_y = tf.nn.top_k(y, k=size)
_, rank_x_int = tf.nn.top_k(-indice_of_ranks_x, k=size)
_, rank_y_int = tf.nn.top_k(-indice_of_ranks_y, k=size)
rank_x = tf.to_float(rank_x_int)
rank_y = tf.to_float(rank_y_int)
spearman = tf.contrib.metrics.streaming_pearson_correlation(rank_x, rank_y)
-
spearman 関数の中で順位変数を計算するために rankdata 関数が使われている。 rankdata 関数で順位タイを処理するメソッドのデフォルトは
average
↩