s_lin_ols Subroutine

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

Multiple Linear Ordinary Least Squares (OLS) Regression with intercept. NOTE: OLS could be wrapper for ridge (with lambda = 0 or presence checks if mande an optional argument). However, it would increase computation slightly and make code less readable. OLS is often used in teaching and therefore, an easily readable standalone is kept.

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

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(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_ols~~CallsGraph proc~s_lin_ols s_lin_ols eigh eigh proc~s_lin_ols->eigh proc~f_sts_mean_core f_sts_mean_core proc~s_lin_ols->proc~f_sts_mean_core proc~s_err_print s_err_print proc~s_lin_ols->proc~s_err_print proc~f_utl_r2c f_utl_r2c proc~s_err_print->proc~f_utl_r2c

Called by

proc~~s_lin_ols~~CalledByGraph proc~s_lin_ols s_lin_ols interface~fsml_ols fsml_ols interface~fsml_ols->proc~s_lin_ols

Source Code

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

! ==== Description
!! Multiple Linear Ordinary Least Squares (OLS) Regression with intercept.
!! NOTE: OLS could be wrapper for ridge (with lambda = 0 or presence checks
!! if mande an optional argument). However, it would increase computation
!! slightly and make code less readable. OLS is often used in teaching and
!! therefore, an easily readable standalone is kept.
!!
!! Computes:
!!  - Intercept b0 (scalar)
!!  - Predictor coefficients b(nv)
!!  - Coefficient of determination R²
!!  - Standard errors se(nv)
!!  - Covariance matrix of predictors covb(nv,nv)

! ==== 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(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_i(nv+1,nv+1)  !! inverse of XᵗX
  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)                           :: ew(nv+1)          !! eigenvalues
  real(wp)                           :: ev(nv+1,nv+1)     !! eigenvectors
  real(wp)                           :: ew_diag(nv+1,nv+1)!! diag matrix of 1/λ
  integer(i4)                        :: i, j              !! loop counters

! ==== Instructions

  ! ---- validate input
  if (nd .le. nv + 1) then
     call s_err_print("[fsml error] OLS: Number of observations must&
                    & exceed number of predictors + intercept.")
     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)

  ! eigendecomposition of s_pool
  call eigh(xtx, ew, vectors=ev)

  ! check result and return sentinel if needed
  do i = 1, nv+1
     if (ew(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 = 0.0_wp
  do i = 1, nv+1
     ew_diag(i,i) = 1.0_wp / ew(i)
  enddo

  ! invert XᵗX
  xtx_i = matmul(ev, matmul(ew_diag, transpose(ev)))

  ! ---- 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_i(1,:), xty)

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

  ! ---- predicted values and residuals
  y_hat = matmul(x1, [b0, b])  ! [b0, b] concatenates b0 and b
  res   = y - y_hat

  ! ---- 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 of full coefficients
  if (present(cov_b)) then
     cov_b = xtx_i(2:nv+1, 2:nv+1) * (sse / real(nd - nv - 1, kind=wp))
  endif

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

end subroutine s_lin_ols