Fortran - 重回帰分析・自由度調整済み決定係数の計算!

Updated:


重回帰分析における自由度調整済み決定係数の計算を Fortran 95 で行ってみました。

前回、同じことを Ruby で Array クラスを拡張する方法で実装しています。

0. 前提条件

  • Debian GNU/Linux 10.3 (64bit) での作業を想定。
  • GCC 9.2.0 (GFortran 9.2.0) でのコンパイルを想定。

1. 自由度調整済み決定係数について

決定係数は説明変数(独立変数)の数が増えるほど 1 に近づくという性質を持っている。そのため、説明変数の数が多い(重回帰分析)場合には、補正した自由度調整済み決定係数(自由度修正済み決定係数)を使う。

計算式は次のとおり。

\[\begin{eqnarray*} 自由度調整済み決定係数 R^2_f = 1 - \frac{\frac{S_E}{N-p-1}}{\frac{S_{y^2}}{N-1}} \end{eqnarray*}\]

但し、

\[\begin{eqnarray*} 標本値の変動 &=& \sum_{i=1}^{N}(y_i - \bar{y})^2 = S_{y^2} \\ 残差の変動 &=& \sum_{i=1}^{N}(y_i - Y_i)^2 = S_E \\ p &:& 説明(独立)変数の個数 \\ N &:& データ数 \end{eqnarray*}\]

2. ソースコードの作成

File: adjusted_coefficient_of_determination.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
!****************************************************
! 重回帰分析(説明変数2個)決定係数計算
!
!   DATE          AUTHOR          VERSION
!   2019.12.18    mk-mode.com     1.00 新規作成
!
! Copyright(C) 2019 mk-mode.com All Rights Reserved.
!****************************************************
!
module const
  ! SP: 単精度(4), DP: 倍精度(8)
  integer,     parameter :: SP = kind(1.0)
  integer(SP), parameter :: DP = selected_real_kind(2 * precision(1.0_SP))
end module const

module comp
  use const
  implicit none
  private
  public :: calc_reg_multi

contains
  ! 重回帰式計算
  ! * 説明変数2個限定
  !
  ! :param(in)  real(8) x(:, 2): 説明変数配列
  ! :param(in)  real(8)    y(:): 目的変数配列
  ! :param(out) real(8)       c: 定数
  ! :param(out) real(8)    v(2): 係数
  subroutine calc_reg_multi(x, y, c, v)
    implicit none
    real(DP), intent(in)  :: x(:, :), y(:)
    real(DP), intent(out) :: c, v(2)
    integer(SP) :: s_x1, s_x2, s_y
    real(DP)    :: sum_x1, sum_x1x1, sum_x1x2
    real(DP)    :: sum_x2, sum_x2x1, sum_x2x2
    real(DP)    :: sum_y, sum_x1y, sum_x2y
    real(DP)    :: mtx(3, 4)

    s_x1 = size(x(:, 1))
    s_x2 = size(x(:, 2))
    s_y  = size(y)
    if (s_x1 == 0 .or. s_x2 == 0 .or. s_y == 0) then
      print *, "[ERROR] array size == 0"
      stop
    end if
    if (s_x1 /= s_y .or. s_x2 /= s_y) then
      print *, "[ERROR] size(X) != size(Y)"
      stop
    end if

    sum_x1   = sum(x(:, 1))
    sum_x2   = sum(x(:, 2))
    sum_x1x1 = sum(x(:, 1) * x(:, 1))
    sum_x1x2 = sum(x(:, 1) * x(:, 2))
    sum_x2x1 = sum_x1x2
    sum_x2x2 = sum(x(:, 2) * x(:, 2))
    sum_y    = sum(y)
    sum_x1y  = sum(x(:, 1) * y)
    sum_x2y  = sum(x(:, 2) * y)
    mtx(1, :) = (/real(s_x1, DP),   sum_x1,   sum_x2,   sum_y/)
    mtx(2, :) = (/        sum_x1, sum_x1x1, sum_x1x2, sum_x1y/)
    mtx(3, :) = (/        sum_x2, sum_x2x1, sum_x2x2, sum_x2y/)
    call gauss_e(3, mtx)
    c = mtx(1, 4)
    v = mtx(2:3, 4)
  end subroutine calc_reg_multi

  ! Gaussian elimination
  !
  ! :param(in)    integer(4)     n: 元数
  ! :param(inout) real(8) a(n,n+1): 係数配列
  subroutine gauss_e(n, a)
    implicit none
    integer(SP), intent(in)    :: n
    real(DP),    intent(inout) :: a(n, n + 1)
    integer(SP) :: i, j
    real(DP)    :: d

    ! 前進消去
    do j = 1, n - 1
      do i = j + 1, n
        d = a(i, j) / a(j, j)
        a(i, j+1:n+1) = a(i, j+1:n+1) - a(j, j+1:n+1) * d
      end do
    end do

    ! 後退代入
    do i = n, 1, -1
      d = a(i, n + 1)
      do j = i + 1, n
        d = d - a(i, j) * a(j, n + 1)
      end do
      a(i, n + 1) = d / a(i, i)
    end do
  end subroutine gauss_e
end module comp

program adjusted_coefficient_of_determination
  use const
  use comp
  implicit none
  character(9), parameter :: F_INP = "input.txt"
  integer(SP),  parameter :: UID   = 10
  real(DP)      :: c, v(2)
  real(DP)      :: y_b, s_r, s_e, r_2
  integer(SP)   :: n, p, i
  character(20) :: f
  real(DP), allocatable :: x(:, :), y(:), y_e(:)

  ! IN ファイル OPEN
  open (UID, file = F_INP, status = "old")

  ! データ数読み込み
  read (UID, *) n

  ! 配列メモリ確保
  allocate(x(n, 2))
  allocate(y(n))
  allocate(y_e(n))

  ! データ読み込み
  do i = 1, n
    read (UID, *) x(i, :), y(i)
  end do
  write (f, '("(A, ", I0, "F8.2, A)")') n
  print f, "説明変数 X(1) = (", x(:, 1), ")"
  print f, "説明変数 X(2) = (", x(:, 2), ")"
  print f, "目的変数 Y    = (", y, ")"
  print '(A)', "---"

  ! IN ファイル CLOSE
  close (UID)

  ! 重回帰式
  call calc_reg_multi(x, y, c, v)
  print '(A, F12.8)', "    定数項 = ", c
  print '(A, F12.8)', "    係数-1 = ", v(1)
  print '(A, F12.8)', "    係数-2 = ", v(2)

  ! 自由度調整済み決定係数
  y_e = c + v(1) * x(:, 1) + v(2) * x(:, 2)   ! 推定値配列
  y_b = sum(y) / size(y)   ! 標本値 Y (目的変数)の平均
  p = size(x(1, :))
  n = size(y)
  s_r = sum((y - y_b)**2)  ! 推定値の変動
  s_e = sum((y - y_e)**2)  ! 残差の変動
  r_2 = 1.0_DP - (s_e / (n - p - 1)) / (s_r / (n - 1))
  print '(A)', "自由度調整済み決定係数"
  print '(A, F20.16)', "       R2f = ", r_2

  ! 配列メモリ解放
  deallocate(x)
  deallocate(y)
  deallocate(y_e)
end program adjusted_coefficient_of_determination

3. ソースコードのコンパイル

$ gfortran -Wall -O2 -o adjusted_coefficient_of_determination adjusted_coefficient_of_determination.f95

4. 動作確認

まず、以下のような入力ファイルを用意する。
(先頭行:要素数、2行目以降:各要素(x1, x2, y)

File: input.txt

1
2
3
4
5
6
7
8
9
10
11
10
17.5 30 45
17.0 25 38
18.5 20 41
16.0 30 34
19.0 45 59
19.5 35 47
16.0 25 35
18.0 35 43
19.0 35 54
19.5 40 52

そして、実行。

$ ./adjusted_coefficient_of_determination
説明変数 X(1) = (   17.50   17.00   18.50   16.00   19.00   19.50   16.00   18.00   19.00   19.50)
説明変数 X(2) = (   30.00   25.00   20.00   30.00   45.00   35.00   25.00   35.00   35.00   40.00)
目的変数 Y    = (   45.00   38.00   41.00   34.00   59.00   47.00   35.00   43.00   54.00   52.00)
---
    定数項 = -34.71293084
    係数-1 =   3.46981263
    係数-2 =   0.53300948
自由度調整済み決定係数
       R2f =   0.8176337138681722

以上。





 

Sponsored Link

 

Comments