2-class multivariate Linear Discriminant Analysis (LDA)
Performs classification and returns: - Standardised discriminant coefficients - Reclassification accuracy - Mahalanobis distance - Discriminant criterion
Type | Intent | Optional | Attributes | Name | ||
---|---|---|---|---|---|---|
real(kind=wp), | intent(in) | :: | x(nd,nv,nc) |
input data (nd samples × nv variables × nc classes) |
||
integer(kind=i4), | intent(in) | :: | nd |
number of datapoints per class |
||
integer(kind=i4), | intent(in) | :: | nv |
number of variables |
||
integer(kind=i4), | intent(in) | :: | nc |
number of classes (must be 2) |
||
real(kind=wp), | intent(out) | :: | sa(nv) |
standardised discriminant coefficients |
||
real(kind=wp), | intent(out) | :: | g |
discriminant criterion |
||
real(kind=wp), | intent(out) | :: | score |
classification score |
||
real(kind=wp), | intent(out), | optional | :: | mh |
Mahalanobis distance |
subroutine s_lin_lda_2c(x, nd, nv, nc, sa, g, score, mh) ! ==== Description !! 2-class multivariate Linear Discriminant Analysis (LDA) !! !! Performs classification and returns: !! - Standardised discriminant coefficients !! - Reclassification accuracy !! - Mahalanobis distance !! - Discriminant criterion ! ==== Declarations integer(i4), intent(in) :: nc !! number of classes (must be 2) integer(i4), intent(in) :: nv !! number of variables integer(i4), intent(in) :: nd !! number of datapoints per class real(wp) , intent(in) :: x(nd,nv,nc) !! input data (nd samples × nv variables × nc classes) real(wp) , intent(out) :: score !! classification score real(wp) , intent(out) :: sa(nv) !! standardised discriminant coefficients real(wp) , intent(out) :: g !! discriminant criterion real(wp) , intent(out), optional :: mh !! Mahalanobis distance real(wp) :: xmv(nv,nc) !! group mean vectors real(wp) :: s_g(nv,nv,nc) !! group covariance matrices real(wp) :: s_pool(nv,nv) !! pooled covariance matrix real(wp) :: s_pool_i(nv,nv) !! inverse of pooled covariance matrix real(wp) :: a(nv) !! discriminant vector real(wp) :: d_pool(nc*nd) !! pooled data for std calc real(wp) :: tmpv(nd) !! temporary vector real(wp) :: tmp !! temporary scalars integer(i4) :: i, j, k !! loop counters real(wp) :: ew(nv) !! eigenvalues of pooled covariance real(wp) :: ev(nv,nv) !! eigenvectors of pooled covariance real(wp) :: ew_diag(nv,nv) !! diagonal matrix of inverted eigenvalues ! ==== Instructions ! ---- validate inputs if (nc .ne. 2) then call s_err_print("[fsml error] 2-class LDA: Number of classes must be 2.") error stop endif if (nv .lt. 2) then call s_err_print("[fsml error] 2-class LDA: 2+ variables required.") error stop endif ! ---- compute group means and covariance matrices xmv = 0.0_wp s_g = 0.0_wp do i = 1, nc do j = 1, nv tmpv(:) = x(:,j,i) xmv(j,i) = f_sts_mean_core(tmpv) enddo do j = 1, nv do k = 1, nv ! get sample covariance (ddf set to 1) s_g(k,j,i) = f_sts_cov_core(x(:,j,i), x(:,k,i), ddf=1.0_wp) enddo enddo enddo ! ---- invert pooled covariance matrix using eigen-decomposition ! compute pooled covariance matrix (equal nd → unweighted average) s_pool = 0.5_wp * (s_g(:,:,1) + s_g(:,:,2)) ! eigendecomposition of s_pool call eigh(s_pool, ew, vectors=ev) ! set returns to sentinel do i = 1, nv if (ew(i) .le. 0.0_wp) then if (present(mh)) mh = c_sentinel_r g = c_sentinel_r sa(:) = c_sentinel_r score = c_sentinel_r return endif enddo ! construct diagonal matrix from inverted eigenvalues ew_diag = 0.0_wp do i = 1, nv ew_diag(i,i) = 1.0_wp / ew(i) enddo ! compute inverse of pooled covariance matrix: V * D⁻¹ * Vᵗ s_pool_i = matmul(ev, matmul(ew_diag, transpose(ev))) ! ---- discriminant coefficients ! compute discriminant vector a = S_pool⁻¹ * (μ1 - μ2) a = matmul(s_pool_i, xmv(:,1) - xmv(:,2)) ! standardise coefficients do i = 1, nv d_pool(1:nd) = x(1:nd,i,1) d_pool(nd+1:nc*nd) = x(1:nd,i,2) ! get sample variance (ddf set to 1) tmp = f_sts_var_core(d_pool, ddf=1.0_wp) sa(i) = a(i) * sqrt(tmp) enddo ! ---- compute Mahalanobis distance if (present(mh)) then mh = sqrt( dot_product( xmv(:,1) - xmv(:,2), & & matmul( s_pool_i, xmv(:,1) - xmv(:,2) ) ) ) endif ! ---- compute discriminant criterion g g = 0.5_wp * (dot_product(a, xmv(:,1)) + dot_product(a, xmv(:,2))) ! ---- re-classification and scoring score = 0.0_wp do i = 1, nd do j = 1, nc tmp = dot_product(a, x(i,:,j)) if ((tmp .ge. g .and. j .eq. 1) .or. & & (tmp .lt. g .and. j .eq. 2)) then score = score + 1.0_wp endif enddo enddo score = score / real(nc * nd, kind=wp) end subroutine s_lin_lda_2c