% Generated by roxygen2: do not edit by hand
% Please edit documentation in R/ComDim_y.R
\name{ComDim_y}
\alias{ComDim_y}
\title{ComDim_y - Extending PLS-like supervised methods to multi-block data.}
\usage{
ComDim_y(
  MB = MB,
  y = y,
  ndim = NULL,
  FUN = FUN,
  nort = 0L,
  type = c("regression", "discriminant")[1],
  decisionRule = c("fixed", "max")[2],
  normalise = FALSE,
  scale.y = FALSE,
  threshold = 1e-10,
  loquace = FALSE,
  method = "FUN",
  cv.k = 7,
  ...
)
}
\arguments{
\item{MB}{A MultiBlock object.}

\item{y}{The response: a numeric vector or matrix for regression
(\code{type = 'regression'}), or a class vector or dummy matrix for
discriminant analysis (\code{type = 'discriminant'}).}

\item{ndim}{Number of predictive Common Dimensions. If \code{NULL},
defaults to the number of blocks.}

\item{FUN}{The function used as the core of the ComDim analysis. It must
accept \code{W} (salience-weighted concatenated blocks, n x p matrix),
\code{y} (response), and \code{ndim} (number of components to compute)
as its first three named arguments, and return a named list with at least:
\describe{
  \item{scores}{X scores vector (length n) for the current component.}
  \item{P}{X loadings vector (length p) for the current component.}
  \item{W}{X weights vector (length p) for the current component.}
  \item{Q}{Y loadings vector for the current component.}
  \item{U}{Y scores vector (length n) for the current component.}
}
Optional return fields:
\describe{
  \item{y}{y as used internally by FUN (e.g. after centring/scaling),
    so that \code{ComDim_y} can detect automatic y-transformation and
    back-transform the Q loadings for correct B and B0 computation.}
  \item{orthoscores}{A numeric vector (length n) or single-column matrix
    of orthogonal X-scores. \strong{Required} when \code{nort = 1}.
    Only the first column is used. FUN does not need to change its
    behaviour between ort and predictive phases; if it always returns
    \code{orthoscores}, the framework uses it only during the ort phase
    and ignores it during the predictive phase.}
}}

\item{nort}{Number of orthogonal Common Dimensions to extract before the
predictive loop. Default \code{0} (no orthogonalization, pure PLS-like).
Only \code{nort = 0} or \code{nort = 1} are accepted; for multiple
orthogonal components use \code{ComDim_OPLS()}.
When \code{nort = 1} the function follows the same two-phase architecture
as \code{ComDim_OPLS}: the orthogonal component is removed from the
(lambda-weighted) concatenated blocks first, then predictive components are
computed from the orthogonally-deflated blocks. \code{FUN} must return
\code{output$orthoscores} (a numeric vector of length n, or a matrix whose
first column is used) when called with \code{ndim = 1}.
Cross-validation is automatically skipped when \code{nort > 0}.}

\item{type}{\code{'regression'} (default) or \code{'discriminant'}.}

\item{decisionRule}{Only used when \code{type = 'discriminant'}. If
\code{'fixed'}, samples are assigned to the class whose predicted score
exceeds \code{1/nclasses}; if \code{'max'}, the class with the highest
predicted score is chosen. Default \code{'max'}.}

\item{normalise}{To apply block normalisation. \code{FALSE} == no
(default), \code{TRUE} == yes.}

\item{scale.y}{Logical (default \code{FALSE}). When \code{TRUE} and
\code{type = 'regression'}, each column of Y is mean-centred and scaled to
unit variance before being passed to \code{FUN}. The stored B, B0, and
Ypred are back-transformed to the original Y scale, so downstream outputs
(R2Y, Q2, predictions) are always in the original units. Ignored when
\code{type = 'discriminant'} (dummy Y should not be scaled).}

\item{threshold}{Convergence threshold: iterations stop when the change
in the global score vector falls below this value (default \code{1e-10}).}

\item{loquace}{Display computation time at each step. \code{TRUE} == yes,
\code{FALSE} == no (default).}

\item{method}{A string label identifying the method (default: \code{'FUN'}).}

\item{cv.k}{Number of folds for k-fold cross-validation (default 7). Set
to 0 to skip CV. When \code{cv.k >= 2}, the \code{Q2} and \code{DQ2}
slots in the output reflect cross-validated predictive ability; otherwise
they reflect training-set fit. CV is skipped when \code{nort > 0}.}

\item{...}{Additional arguments passed to \code{FUN}.}
}
\value{
A \code{ComDim} object with the following slots:
\describe{
  \item{\code{Method}}{The label supplied via the \code{method} argument.}
  \item{\code{ndim}}{Number of predictive Common Dimensions extracted.}
  \item{\code{Q.scores}}{Global consensus scores matrix (\eqn{n \times
    ndim}).  Each column \eqn{\mathbf{q}_a} (unit-norm) is derived from
    the dominant left direction of FUN applied to the salience-weighted
    concatenated blocks.}
  \item{\code{T.scores}}{Named list of block-specific local scores
    (\eqn{n \times ndim} each).  Local loading
    \eqn{\mathbf{p}_{ba} = \tilde{\mathbf{X}}_b'\mathbf{q}_a} (computed
    on the ort-deflated block when \code{nort > 0}); local score
    \eqn{\mathbf{t}_{ba} =
    \tilde{\mathbf{X}}_b\,\mathbf{p}_{ba}(\mathbf{p}_{ba}'\mathbf{p}_{ba})^{-1}}.}
  \item{\code{P.loadings}}{Global loadings (\eqn{p_{tot} \times ndim}):
    \eqn{\mathbf{P} = \tilde{\mathbf{X}}'\mathbf{Q}}, where
    \eqn{\tilde{\mathbf{X}}} is the (optionally ort-deflated) mean-centred
    concatenated blocks.}
  \item{\code{Saliences}}{Block salience matrix (\eqn{ntable \times ndim}):
    \eqn{\lambda_{ba} =
    \mathbf{q}_a'\tilde{\mathbf{X}}_b\tilde{\mathbf{X}}_b'\mathbf{q}_a}.}
  \item{\code{R2X}}{Proportion of X variance captured by each predictive
    component (named vector, length \eqn{ndim}).  Let \eqn{\mathbf{t}_a}
    be the X-score vector returned by FUN for component \eqn{a}:
    \deqn{R2X_a = \|\mathbf{t}_a\|^4 \big/ \sum_k \|\mathbf{t}_k\|^4.}
    When \code{nort > 0}, the denominator also includes the orthogonal
    \eqn{\|\mathbf{t}_{ort,k}\|^4} terms, and the orthogonal R2X fractions
    are stored separately in \code{Orthogonal$R2X}.}
  \item{\code{R2Y}}{Cumulative Y-variance explained (named vector, length
    \eqn{ndim}):
    \deqn{R2Y_a = 1 - RSS_a / TSS_Y,}
    where \eqn{RSS_a} is the residual SS from an OLS regression of
    \eqn{\mathbf{Y}} on \eqn{[1, \mathbf{q}_1, \ldots, \mathbf{q}_a]}.
    \strong{Note:} \eqn{R2Y_a} is cumulative -- the total Y-variance
    explained by the first \eqn{a} components together, not the marginal
    contribution of component \eqn{a} alone.}
  \item{\code{Q2}}{Predictive Q2 per response column (regression) or per
    class (discriminant), named accordingly:
    \deqn{Q2 = 1 - PRESS / TSS_Y,}
    where \eqn{PRESS = \sum_i (\hat{y}_i - y_i)^2}.
    When \code{cv.k >= 2} and \code{nort = 0}: cross-validated (out-of-
    sample) predictions are used; otherwise training-set predictions.
    CV is automatically skipped when \code{nort > 0}.}
  \item{\code{DQ2}}{(Discriminant mode only) Discriminant Q2 per class,
    using only penalising residuals:
    \deqn{DQ2 = 1 - PRESSD / TSS_Y,}
    where \eqn{PRESSD} sums \eqn{\hat{y}_i^2} for class-0 samples with
    \eqn{\hat{y}_i > 0}, and \eqn{(\hat{y}_i - 1)^2} for class-1 samples
    with \eqn{\hat{y}_i < 1}.  Same cross-validation logic as \code{Q2}.}
  \item{\code{Singular}}{Squared L2 norm of the FUN X-score vector per
    component (\eqn{\|\mathbf{t}_a\|^2}), used to derive \code{R2X}.}
  \item{\code{VIP}}{Global total VIP (named vector, length \eqn{p_{tot}}):
    concatenation of \code{VIP.block[[b]]$tot} across blocks.  When
    \code{nort = 0}, uses the Wold formula; when \code{nort = 1}, tot
    combines predictive and orthogonal VIPs (see \code{VIP.block}).}
  \item{\code{VIP.block}}{Named list (one \code{data.frame} per block).
    When \code{nort = 0}: columns \code{p} and \code{tot} (= \code{p}),
    using the Wold formula:
    \deqn{VIPp_j = \sqrt{p_b \cdot
      \frac{\sum_a s_a \tilde{w}_{j,a}^2}{\sum_a s_a}},}
    where \eqn{s_a = \|\mathbf{t}_a\|^2\|\mathbf{q}_a\|^2} and
    \eqn{\tilde{w}_{j,a} = w_{j,a}/\|\mathbf{w}_a\|} is the L2-normalised
    \eqn{j}-th element of the \eqn{a}-th weight vector.
    When \code{nort = 1}: columns \code{p} (Wold, same as above),
    \code{o} (orthogonal VIP, loadings-based:
    \eqn{VIPo_j = \sqrt{p_b \cdot \sum_a s_{oa}\tilde{P}_{o,j,a}^2 /
    \sum_a s_{oa}}},
    where \eqn{s_{oa} = \|\mathbf{q}_{ort}[,a]\|^2} and
    \eqn{\tilde{\mathbf{P}}_o} is the column-L2-normalised block-slice of
    the ort loadings), and \code{tot}
    (\eqn{VIPtot_j = \sqrt{(VIPp_j^2 + VIPo_j^2)/2}}).
    Row names are variable names.}
  \item{\code{PLS.model}}{List with: \code{W} (X weight matrix collected
    from FUN, \eqn{p_{tot} \times ndim}); \code{B} (regression
    coefficients,
    \eqn{\mathbf{B} = \mathbf{W}(\mathbf{P}'\mathbf{W})^{-1}\mathbf{Q}'},
    in original Y units); \code{B0} (intercept,
    \eqn{\mathbf{B}_0 = \bar{\mathbf{y}} -
    \overline{\tilde{\mathbf{x}}}\mathbf{B}}); \code{Y} (original
    response matrix as supplied).
    Training-set predictions:
    \eqn{\hat{\mathbf{Y}} =
    \tilde{\mathbf{X}}\mathbf{B} + \mathbf{B}_0}.}
  \item{\code{cv}}{Cross-validation results when \code{cv.k >= 2} and
    \code{nort = 0} (empty list otherwise): \code{k}, \code{fold}
    (sample-to-fold vector), \code{Ypred} (\eqn{n \times ncol(Y)}
    out-of-sample predictions), \code{Q2} (CV Q2 per class/response),
    \code{DQ2} (mean CV DQ2, discriminant only),
    \code{DQ2.perclass} (CV DQ2 per class, discriminant only).}
  \item{\code{Orthogonal}}{When \code{nort > 0}: list with \code{nort},
    \code{Q.scores} (global ort scores, \eqn{n \times nort}, unit-norm),
    \code{T.scores} (block ort local scores, \eqn{n \times nort} each),
    \code{P.loadings.ort} (ort loadings, \eqn{p_{tot} \times nort}),
    \code{Saliences.ort} (\eqn{ntable \times nort}), and \code{R2X}
    (orthogonal X-variance fractions,
    \eqn{R2X_{ort,a} = \|\mathbf{t}_{ort,a}\|^4 / total}).
    Empty list when \code{nort = 0}.}
  \item{\code{Prediction}}{Training-set predictions: \code{Y.pred}
    (\eqn{n \times ncol(Y)}); for discriminant analysis also
    \code{decisionRule}, \code{trueClass}, \code{predClass} (data.frame),
    \code{Sensitivity} and \code{Specificity} (per class),
    \code{confusionMatrix} (named list of 2x2 matrices).}
  \item{\code{Mean}}{List with \code{MeanMB} (column means per block),
    \code{MeanY} (column means of Y), and \code{ScaleY} (column SDs of Y;
    all ones when \code{scale.y = FALSE}).}
  \item{\code{Norm}}{List with \code{NormMB}: Frobenius norms for block
    normalisation.}
  \item{\code{variable.block}}{Character vector (length \eqn{p_{tot}})
    mapping each row of \code{P.loadings} and each element of \code{VIP}
    to its block.}
  \item{\code{runtime}}{Total computation time in seconds.}
}
}
\description{
Extends any PLS-like method used for regression or discriminant purposes to
the multi-block field. The user provides a function (\code{FUN}) that
computes one predictive component from the salience-weighted concatenated
blocks; global scores, local scores, and loadings are then derived following
the traditional ComDim-PLS framework. Optionally, orthogonal components
returned by FUN (e.g. from an O-PLS wrapper) are captured. VIP scores and
k-fold cross-validation are also supported.
}
\examples{
b1 <- matrix(rnorm(500), 10, 50) # 10 samples, 50 variables
b2 <- matrix(rnorm(800), 10, 80) # 10 samples, 80 variables
mb <- MultiBlock(Data = list(b1 = b1, b2 = b2))

## Example 1: ComDim-PLS (regression) ---------------------------------------
# Single-step NIPALS PLS wrapper (one predictive component per call).
# Note: 'tx' is used instead of 't' to avoid shadowing base::t().
fun.PLS <- function(W, y, ndim, ...) {
  output <- list()
  w <- t(W) \%*\% y / as.numeric(t(y) \%*\% y) # X weight (u = y, 1 step)
  w <- w / sqrt(sum(w^2)) # L2 normalise
  tx <- W \%*\% w # X score
  p <- t(W) \%*\% tx / as.numeric(t(tx) \%*\% tx) # X loading
  q <- t(y) \%*\% tx / as.numeric(t(tx) \%*\% tx) # Y loading
  u <- y \%*\% q / as.numeric(t(q) \%*\% q) # Y score
  output$scores <- as.vector(tx)
  output$P <- as.vector(p)
  output$W <- as.vector(w)
  output$Q <- as.vector(q)
  output$U <- as.vector(u)
  return(output)
}

y <- c(1, 1, 1, 1, 1, 5, 5, 5, 10, 10)
resultsPLS <- ComDim_y(mb,
  y = y, ndim = 2,
  type = "regression",
  FUN = fun.PLS,
  method = "PLS",
  cv.k = 0
)

## Example 2: ComDim-OPLS-DA (discriminant, nort = 1) ----------------------
# Thin wrapper around OPLS_NIPALS_DNR(), the package's NIPALS OPLS engine.
# All inputs (W, y, and any extra args such as 'threshold') are forwarded
# directly via '...'. Use this pattern when nort > 0; for nort = 0 the
# simpler PLS wrapper in Example 1 is sufficient (no orthoscores needed).
fun.OPLS <- function(W, y, ndim, ...) {
  res <- OPLS_NIPALS_DNR(W = W, y = y, ...)
  list(
    scores      = as.vector(res$t_pred),
    P           = as.vector(res$p),
    W           = as.vector(res$w_pred),
    Q           = as.vector(res$q),
    U           = as.vector(res$u),
    orthoscores = matrix(res$t_ort, ncol = 1)
  )
}

groups <- c(rep("A", 5), rep("B", 5))
resultsOPLS <- ComDim_y(mb,
  y = groups, ndim = 1,
  nort = 1,
  type = "discriminant",
  FUN = fun.OPLS,
  method = "OPLS-DA",
  cv.k = 0
)

## Example 3 (not run): ComDim-OPLS-DA via ropls ---------------------------
# Wrapping ropls::opls is also possible. Key points:
#   - Use orthoI = 1 (fixed) instead of NA so the output is predictable.
#   - Always return output$orthoscores; ComDim_y ignores it in phases
#     where ort has already been removed.
#   - Expand the single ropls Q loading to match the ncol(y_dummy) width.
\donttest{
if (requireNamespace("ropls", quietly = TRUE)) {
fun.OPLSDA.ropls <- function(W, y, ndim, ...) {
  output <- list()
  # Convert dummy matrix to ropls-compatible -1/+1 vector
  Y <- c(-1, 1)[apply(y, 1, function(x) match(1, x))]
  result <- tryCatch(
    ropls::opls(
      x = W, y = Y, predI = 1, orthoI = 1,
      fig.pdfC = "none", info.txtC = "none"
    ),
    error = function(e) {
      ropls::opls(
        x = W, y = Y, predI = 1, orthoI = 0,
        fig.pdfC = "none", info.txtC = "none"
      )
    }
  )
  output$scores <- result@scoreMN[, 1]
  output$P <- result@loadingMN[, 1]
  output$W <- result@weightMN[, 1]
  output$U <- result@uMN[, 1]
  # Expand the single ropls Q loading to match the 2-column dummy matrix:
  # loadings for class1 and class2 are antisymmetric in binary PLS-DA.
  output$Q <- c(-result@cMN[, 1], result@cMN[, 1])
  output$y <- result@suppLs$yModelMN # internal y (for scaling detection)
  # Orthogonal scores (used during the ort pre-loop when nort > 0)
  if (!is.null(result@orthoScoreMN) && ncol(result@orthoScoreMN) > 0) {
    output$orthoscores <- result@orthoScoreMN   # n x k matrix; col jj used for jj-th ort
  } else {
    output$orthoscores <- matrix(0, nrow = nrow(W), ncol = 1)
  }
  return(output)
}

b1_r <- matrix(rnorm(8 * 30), 8, 30)
b2_r <- matrix(rnorm(8 * 20), 8, 20)
mb_r <- MultiBlock(Data = list(b1 = b1_r, b2 = b2_r))
resultsOPLSDA <- ComDim_y(mb_r,
  y = c(rep("NI", 4), rep("OFF", 4)),
  ndim = 1,
  nort = 1,
  type = "discriminant",
  FUN = fun.OPLSDA.ropls,
  method = "OPLS-DA(ropls)",
  cv.k = 0
)
}
}
}
