Fortran - スピアマン順位相関係数の計算!
Updated:
Fortran 95 でスピアマンの順位相関係数(Spearman’s Rank Correlation Coefficient)の計算をしてみました。
0. 前提条件
- Debian GNU/Linux 10.3 (64bit) での作業を想定。
- GCC 9.2.0 (GFortran 9.2.0) でのコンパイルを想定。
1. スピアマンの順位相関係数について
各変量を順位に変換してピアソンの積率相関係数(いわゆる相関係数)を求めたものを スピアマンの順位相関係数(Spearman’s Rank Correlation Coefficient) と呼ぶ。
実際にはまず、 \(n\) 対の変数 \(X, Y\) のそれぞれに順位をつける。但し、同順位(タイ)がある場合は中央(平均)順位(mid-rank) で順位をつける。
(e.g. 2位が3個ある場合、 \((2+3+4)/3=3\)。3位が2個ある場合、 \((3+4)/2=3.5\))
そして、次の式によりスピアマンの順位相関係数 \(r_s\)(または \(\rho\))を求める。
(1) 同順位(タイ)が存在しない場合、 \(\begin{eqnarray*} r_s = 1 - \frac{6}{n(n^{2} - 1)} \displaystyle \sum^{n}_{i=1}(X_i - Y_i)^2 \end{eqnarray*}\)
(2) 同順位(タイ)が存在する場合、 \(\begin{eqnarray*} r_s &=& \frac{T_x + T_y - \displaystyle \sum_{i=1}^{n}(X_i - Y_i)^2}{2\sqrt{T_xT_y}} \\ 但し、\ \ T_x &=& \left\{n(n^2 - 1) - \displaystyle \sum_{i=1}^{n_x}t_i({t_i}^2 - 1)\right\} /\ 12 \\ T_y &=& \left\{n(n^2 - 1) - \displaystyle \sum_{j=1}^{n_y}t_j({t_j}^2 - 1)\right\} /\ 12 \\ &&(n_x,\ n_y\ は同順位の数、\ t_i,\ t_j\ は同順位となる順位の個数) \end{eqnarray*}\)
(注) 同順位が存在しない場合は (2) の \(\sum t_i({t_i}^2 - 1),\ \sum t_j({t_j}^2 - 1)\) が \(0\) となり、結局 (1) になる。よって、同順位(タイ)が存在しない場合と存在する場合で場合分けをせず、全て (2) で計算しても、結果は同じになる。
また、スピアマンの順位相関係数は、値を順位に置き換えたもの(同順位(タイ)は中央順位法)の相関係数(ピアソンの積率相関係数)であるので、当然、以下の計算式でも計算できる。
\[\begin{eqnarray*} r_s = \frac{\displaystyle \sum_{i=1}^{n}(X_i - \overline{X})(Y_i - \overline{Y})}{\sqrt{\displaystyle \sum_{i=1}^{n}(X_i - \overline{X})^2 \displaystyle \sum_{i=1}^{n}(Y_i - \overline{Y})^2}} \end{eqnarray*}\]さらに、同順位(タイ)が存在する場合の計算式を以下のように説明している文献(特に海外の文献)も多い。(但し、前述の計算式で計算した結果と若干の差異がある)
\[\begin{eqnarray*} r_s &=& 1 - \frac{6}{n(n^2 - 1)}\left\{ \displaystyle\sum_{i=1}^{n}(X_i - Y_i)^2 + T_x + T_y \right\} \\ 但し、\ \ T_x &=& \displaystyle \sum_{i=1}^{n_x}\frac{t_i({t_i}^2-1)}{12} \\ T_y &=& \displaystyle \sum_{j=1}^{n_y}\frac{t_j({t_j}^2-1)}{12} \\ &&(n_x,\ n_y\ は同順位の数、\ t_i,\ t_j\ は同順位となる順位の個数) \end{eqnarray*}\]2. Fortran ソースコードの作成
- 計算の本質は
comp
モジュール。 - 使用する2つの配列
X
,Y
を適宜変更する。(calc_rcc_spearman
モジュール内)
File: calc_rcc_spearman.f95
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
!****************************************************
! スピアマンの順位相関係数の計算
!
! DATE AUTHOR VERSION
! 2019.12.12 mk-mode.com 1.00 新規作成
!
! Copyright(C) 2019 mk-mode.com All Rights Reserved.
!****************************************************
!
module cst
! SP: 単精度(4), DP: 倍精度(8)
integer, parameter :: SP = kind(1.0)
integer(SP), parameter :: DP = selected_real_kind(2 * precision(1.0_SP))
end module cst
module comp
use cst
implicit none
private
public :: rcc_spearman
contains
! スピアマンの順位相関係数の計算
!
! :param(in) real(8) xs
! :param(in) real(8) ys
! :return real(8) rcc
function rcc_spearman(xs, ys) result(rcc)
use cst
implicit none
real(DP), intent(in) :: xs(:), ys(:)
real(DP) :: rcc
integer(SP) :: i, n, n3
real(DP) :: s_x, s_y, t_x, t_y, s
real(DP), allocatable :: rank_x(:), rank_y(:)
real(DP), allocatable :: tai_x(:, :), tai_y(:, :)
rcc = -1.0_DP
n = size(xs)
if (size(ys) /= n) return
allocate(rank_x(n))
allocate(rank_y(n))
allocate(tai_x(n, 2))
allocate(tai_y(n, 2))
! ランク付け
call rank(xs, rank_x)
call rank(ys, rank_y)
! 同順位の数(インデックスが順位)
call tai(rank_x, tai_x)
call tai(rank_y, tai_y)
! Tx, Ty の sum 部分
call sum_t(tai_x, s_x)
call sum_t(tai_y, s_y)
! 相関係数
n3 = n * n * n - n
t_x = (n3 - s_x) / 12.0_DP
t_y = (n3 - s_y) / 12.0_DP
s = 0.0_DP
do i = 1, n
s = s + (xs(i) - ys(i)) * (xs(i) - ys(i))
end do
rcc = (t_x + t_y - s) / (2.0_DP * sqrt(t_x * t_y))
deallocate(rank_x)
deallocate(rank_y)
deallocate(tai_x)
deallocate(tai_y)
end function rcc_spearman
! ランク付け
!
! :param(in) real(8) src(:)
! :param(out) real(8) res(:)
subroutine rank(src, res)
implicit none
real(DP), intent(in) :: src(:)
real(DP), intent(out) :: res(:)
integer(SP) :: c, i, j, s
real(DP), allocatable :: wk(:)
s = size(src)
allocate(wk(s))
wk = 0.0_DP
do i = 1, s
c = 0
do j = 1, s
if (src(i) < src(j)) c = c + 1
end do
wk(i) = real(c + 1, DP)
end do
! 同順位を中央(平均)順位(mid-rank)に
do i = 1, s
c = 0
do j = 1, s
if (wk(i) == wk(j)) c = c + 1
end do
res(i) = sum((/(j, j=int(wk(i)),int(wk(i))+c-1)/)) / real(c, DP)
end do
deallocate(wk)
end subroutine rank
! 同順位の数
!
! :param(in) real(8) src(:)
! :param(out) real(8) res(:, 2)
subroutine tai(src, res)
implicit none
real(DP), intent(in) :: src(:)
real(DP), intent(out) :: res(:, :)
integer(SP) :: c, i, j, s
real(DP), allocatable :: wk(:)
s = size(src)
allocate(wk(s))
wk = src
res = 0.0_DP
do i = 1, s
c = 0
do j = i, s
if (wk(j) == 0.0_DP) cycle
if (wk(i) == wk(j)) then
c = c + 1
if (i /= j) wk(j) = 0.0_DP
end if
end do
res(i, :) = (/wk(i), real(c, DP)/)
end do
deallocate(wk)
end subroutine tai
! Tx, Ty の sum 部分
!
! :param(in) real(8) src(:, 2)
! :param(out) real(8) s_t
subroutine sum_t(src, s_t)
implicit none
real(DP), intent(in) :: src(:, :)
real(DP), intent(out) :: s_t
integer(SP) :: i, s
integer(DP) :: t
s = size(src) / 2
s_t = 0.0_DP
do i = 1, s
t = int(src(i, 2), DP)
if (t < 2) cycle
s_t = s_t + t * t * t - t
end do
endsubroutine sum_t
end module comp
program calc_rcc_spearman
use cst
use comp
implicit none
real(DP) :: rcc
! === タイ(同順位)が存在しない例
!real(DP), parameter :: X(10) = (/ &
! & 1.0_DP, 2.0_DP, 3.0_DP, 4.0_DP, 5.0_DP, &
! & 6.0_DP, 7.0_DP, 8.0_DP, 9.0_DP, 10.0_DP &
!& /)
!real(DP), parameter :: Y(10) = (/ &
! & 1.0_DP, 3.0_DP, 5.0_DP, 7.0_DP, 9.0_DP, &
! & 2.0_DP, 4.0_DP, 6.0_DP, 8.0_DP, 10.0_DP &
!& /)
! === タイ(同順位)が存在する例
real(DP), parameter :: X(10) = (/ &
& 1.0_DP, 2.0_DP, 3.0_DP, 4.0_DP, 5.0_DP, &
& 5.0_DP, 7.0_DP, 8.0_DP, 9.0_DP, 10.0_DP &
& /)
real(DP), parameter :: Y(10) = (/ &
& 1.0_DP, 3.0_DP, 5.0_DP, 6.0_DP, 9.0_DP, &
& 2.0_DP, 4.0_DP, 6.0_DP, 8.0_DP, 10.0_DP &
& /)
! === サイズが異なる例 ( => 実行時にエラー )
!real(DP), parameter :: X(10) = (/ &
! & 1.0_DP, 2.0_DP, 3.0_DP, 4.0_DP, 5.0_DP, &
! & 6.0_DP, 7.0_DP, 8.0_DP, 9.0_DP, 10.0_DP &
!& /)
!real(DP), parameter :: Y(9) = (/ &
! & 1.0_DP, 3.0_DP, 5.0_DP, 7.0_DP, 9.0_DP, &
! & 2.0_DP, 4.0_DP, 6.0_DP, 8.0_DP &
!& /)
! === X のサイズがゼロの例( => 当然、コンパイルエラー )
!real(DP), parameter :: X(0) = (//)
!real(DP), parameter :: Y(9) = (/ &
! & 1.0_DP, 3.0_DP, 5.0_DP, 7.0_DP, 9.0_DP, &
! & 2.0_DP, 4.0_DP, 6.0_DP, 8.0_DP, 10.0_DP &
!& /)
! === 数値以外のものが存在する例( => 当然、コンパイルエラー )
!real(DP), parameter :: X(10) = (/ &
! & 1.0_DP, 2.0_DP, 3.0_DP, 4.0_DP, 5.0_DP, &
! & 6.0_DP, 7.0_DP, 8.0_DP, 9.0_DP, 10.0_DP &
!& /)
!real(DP), parameter :: Y(10) = (/ &
! & 1.0_DP, 3.0_DP, 5.0_DP, 7.0_DP, 9.0_DP, &
! & "ABC", 4.0_DP, 6.0_DP, 8.0_DP, 10.0_DP &
!& /)
rcc = rcc_spearman(X, Y)
print '("X = ", 10F5.1)', X
print '("Y = ", 10F5.1)', Y
if (rcc == -1.0_DP) then
print *, "[ERROR]"
else
print '("Spearman''s RCC = ", F11.8)', rcc
end if
end program calc_rcc_spearman
3. コンパイル&リンク
$ gfortran -Wall -O2 -o calc_rcc_spearman calc_rcc_spearman.f95
4. プログラムの実行
$ ./calc_rcc_spearman
X = 1.0 2.0 3.0 4.0 5.0 5.0 7.0 8.0 9.0 10.0
Y = 1.0 3.0 5.0 6.0 9.0 2.0 4.0 6.0 8.0 10.0
Spearman's RCC = 0.70731707
以上。
Comments