s_lin_ridge Subroutine

public subroutine s_lin_ridge(x, y, nd, nv, lambda, b0, b, r2, y_hat, se, cov_b)

Multiple Linear Ridge Regression (λ >= 0) with intercept.

Computes: - Intercept b0 (scalar) - Predictor coefficients b(nv) - Coefficient of determination R² - Standard errors se(nv) (ridge-adjusted) - Covariance matrix of predictors covb(nv,nv) (ridge-adjusted)

Notes: - When lambda (λ) = 0, this reduces to ordinary least squares (OLS). - Ridge modifies the variance-covariance formula: cov(β) = σ² (XᵀX + λI)⁻¹ XᵀX (XᵀX + λI)⁻¹ This shrinks coefficients and affects SEs.

Arguments

Type IntentOptional Attributes Name
real(kind=wp), intent(in) :: x(nd,nv)

predictor data matrix (no intercept column)

real(kind=wp), intent(in) :: y(nd)

response vector

integer(kind=i4), intent(in) :: nd

number of datapoints

integer(kind=i4), intent(in) :: nv

number of predictors (excluding intercept)

real(kind=wp), intent(in) :: lambda

ridge penalty parameter (≥ 0, non-optional)

real(kind=wp), intent(out) :: b0

intercept coefficient

real(kind=wp), intent(out) :: b(nv)

predictor coefficients

real(kind=wp), intent(out) :: r2

coefficient of determination R²

real(kind=wp), intent(out), optional :: y_hat(nd)

predicted y values

real(kind=wp), intent(out), optional :: se(nv)

standard errors of predictor coefficients

real(kind=wp), intent(out), optional :: cov_b(nv,nv)

covariance matrix of predictor coefficients


Calls

proc~~s_lin_ridge~~CallsGraph proc~s_lin_ridge s_lin_ridge eigh eigh proc~s_lin_ridge->eigh proc~f_sts_mean_core f_sts_mean_core proc~s_lin_ridge->proc~f_sts_mean_core proc~s_err_print s_err_print proc~s_lin_ridge->proc~s_err_print proc~f_utl_r2c f_utl_r2c proc~s_err_print->proc~f_utl_r2c

Called by

proc~~s_lin_ridge~~CalledByGraph proc~s_lin_ridge s_lin_ridge interface~fsml_ridge fsml_ridge interface~fsml_ridge->proc~s_lin_ridge

Source Code

subroutine s_lin_ridge(x, y, nd, nv, lambda, b0, b, r2, y_hat, se, cov_b)

! ==== Description
!! Multiple Linear Ridge Regression (λ >= 0) with intercept.
!!
!! Computes:
!!  - Intercept b0 (scalar)
!!  - Predictor coefficients b(nv)
!!  - Coefficient of determination R²
!!  - Standard errors se(nv)   (ridge-adjusted)
!!  - Covariance matrix of predictors covb(nv,nv) (ridge-adjusted)
!!
!! Notes:
!!  - When lambda (λ) = 0, this reduces to ordinary least squares (OLS).
!!  - Ridge modifies the variance-covariance formula:
!!       cov(β) = σ² (XᵀX + λI)⁻¹ XᵀX (XᵀX + λI)⁻¹
!!    This shrinks coefficients and affects SEs.

! ==== Declarations
  integer(i4), intent(in)            :: nd                     !! number of datapoints
  integer(i4), intent(in)            :: nv                     !! number of predictors (excluding intercept)
  real(wp)   , intent(in)            :: x(nd,nv)               !! predictor data matrix (no intercept column)
  real(wp)   , intent(in)            :: y(nd)                  !! response vector
  real(wp)   , intent(in)            :: lambda                 !! ridge penalty parameter (≥ 0, non-optional)
  real(wp)   , intent(out)           :: b0                     !! intercept coefficient
  real(wp)   , intent(out)           :: b(nv)                  !! predictor coefficients
  real(wp)   , intent(out)           :: r2                     !! coefficient of determination R²
  real(wp)   , intent(out), optional :: y_hat(nd)              !! predicted y values
  real(wp)   , intent(out), optional :: se(nv)                 !! standard errors of predictor coefficients
  real(wp)   , intent(out), optional :: cov_b(nv,nv)           !! covariance matrix of predictor coefficients
  real(wp)                           :: x1(nd,nv+1)            !! matrix with intercept column
  real(wp)                           :: xt(nv+1,nd)            !! transpose of x1
  real(wp)                           :: xtx(nv+1,nv+1)         !! XᵀX matrix
  real(wp)                           :: xtx_ridge(nv+1,nv+1)   !! ridge-adjusted XᵀX matrix
  real(wp)                           :: xtx_ridge_i(nv+1,nv+1) !! inverse of ridge matrix
  real(wp)                           :: xty(nv+1)              !! Xᵀy
  real(wp)                           :: res(nd)                !! residuals
  real(wp)                           :: sse                    !! sum of squared errors
  real(wp)                           :: sst                    !! total sum of squares
  real(wp)                           :: y_bar                  !! mean of y
  real(wp)                           :: s2                     !! residual variance estimate
  real(wp)                           :: ew_r(nv+1)             !! eigenvalues (ridge)
  real(wp)                           :: ev_r(nv+1,nv+1)        !! eigenvectors (ridge)
  real(wp)                           :: ew_diag_r(nv+1,nv+1)   !! diag matrix of 1/λ (ridge)
  real(wp)                           :: cov(nv+1,nv+1)         !! covariance matrix including intercept
  integer(i4)                        :: i, j                   !! loop counters

! ==== Instructions

  ! ---- validate input
  if (nd .le. nv + 1) then
     call s_err_print("[fsml error] Ridge: Number of observations must&
                    & exceed number of predictors + intercept.")
     error stop
  endif
  if (lambda .lt. 0.0_wp) then
     call s_err_print("[fsml error] Ridge: lambda must be non-zero positive.")
     error stop
  endif

  ! ---- construct matrix with intercept column (first column = 1.0)
  do i = 1, nd
     x1(i,1) = 1.0_wp
     do j = 1, nv
        x1(i,j+1) = x(i,j)
     enddo
  enddo

  ! ---- eigen-decomposition for inversion of XᵗX

  ! compute transposed matrix and XᵀX
  xt   = transpose(x1)
  xtx  = matmul(xt, x1)

  ! build ridge-penalised XᵀX (lambda added to diagonal, except intercept term)
  ! (remains unadjusted OLS matrix if lamnbda = 0)
  xtx_ridge = xtx
  do i = 2, nv+1
     xtx_ridge(i,i) = xtx_ridge(i,i) + lambda
  enddo

  ! eigen-decomposition for inversion of ridge-adjusted XᵀX
  call eigh(xtx_ridge, ew_r, vectors=ev_r)

  ! check result and return sentinel if needed
  do i = 1, nv+1
     if (ew_r(i) .le. 0.0_wp) then
        b0        = c_sentinel_r
        b(:)      = c_sentinel_r
        r2        = c_sentinel_r
        if (present(y_hat)) y_hat = c_sentinel_r
        if (present(se))       se = c_sentinel_r
        if (present(cov_b)) cov_b = c_sentinel_r
        return
     endif
  enddo

  ! ---- construct diagonal matrix from inverted eigenvalues
  ew_diag_r = 0.0_wp
  do i = 1, nv+1
     ew_diag_r(i,i) = 1.0_wp / ew_r(i)
  enddo

  ! invert ridge-adjusted XᵀX
  xtx_ridge_i = matmul(ev_r, matmul(ew_diag_r, transpose(ev_r)))

  ! ---- compute coefficients: full vector including intercept
  xty = matmul(xt, y)
  b0  = 0.0_wp
  b   = 0.0_wp

  ! get intercept coefficient
  b0  = dot_product(xtx_ridge_i(1,:), xty)

  ! get predictor coefficients
  do j = 1, nv
     b(j) = dot_product(xtx_ridge_i(j+1,:), xty)
  enddo

  ! ---- predicted values and residuals
  if (present(y_hat)) then
     y_hat = matmul(x1, [b0, b])
     res   = y - y_hat
  else
     res   = y - matmul(x1, [b0, b])
  endif

  ! ---- R² calculation

  ! sum of squared errors
  sse   = sum(res**2)

  ! mean of y
  y_bar = f_sts_mean_core(y)

  ! total sum of squares
  sst   = sum((y - y_bar)**2)

  ! R²
  r2    = 1.0_wp - sse / sst

  ! ---- covariance matrix (ridge-adjusted)

  ! calculate only when needed; reduces to OLS covariance when lambda = 0.
  if (present(cov_b) .or. present(se)) then
     s2  = sse / real(nd - nv - 1, kind=wp)
     cov = matmul(xtx_ridge_i, matmul(xtx, xtx_ridge_i)) * s2
  endif

  ! ---- covariance matrix of full coefficients
  if (present(cov_b)) cov_b = cov(2:nv+1, 2:nv+1)

  ! ---- standard errors for predictors (exclude intercept)
  if (present(se)) then
     do i = 1, nv
        se(i) = sqrt(cov(i+1, i+1))
     enddo
  endif

end subroutine s_lin_ridge