Skip to content

Fix single-column inputs in tpls() and facewise products - #12

Open
kimanh-lecao wants to merge 2 commits into
brendanlu:mainfrom
kimanh-lecao:pr/02-tpls-single-response
Open

kimanh-lecao wants to merge 2 commits into
brendanlu:mainfrom
kimanh-lecao:pr/02-tpls-single-response

Conversation

@kimanh-lecao

Copy link
Copy Markdown
Collaborator

Builds on #11; only the last commit is new.

When a response has one column (q = 1), R's [ drops the length-1 dimension and several steps
break. This PR keeps those dimensions in four places.

1. tpls(), regression and canonical modes (R/tens.tpls.R)

set.seed(1)
x <- array(rnorm(12 * 5 * 4), dim = c(12, 5, 4))
y <- array(rnorm(12 * 4), dim = c(12, 1, 4))
tpls(x, y, ncomp = 2)
#> Error in yhat[, , k_t_top[2]] %*% curr_y_loadings : non-conformable arguments

yhat[, , f] becomes a length-n vector and curr_y_loadings a length-1 vector, which %*%
rejects. Fixed by keeping the face as matrix(yhat[, , f], nrow = n).

2. Facewise product, %fp% / facewise_product() (.binary_facewise() in R/tens.mproduct.R)

a[, , i] %*% b[, , i] has the same problem whenever a face has a single row or column:

  • an n x 1 by 1 x 1 product fails, which is why tpls(mode = "tsvdm") fails for q = 1;
  • an n x 1 by 1 x n product returns a wrong result without any error: both faces drop to
    length-n vectors, so %*% computes their inner product, and that single number is recycled
    across the n x n face instead of the outer product.
a <- array(1:6, dim = c(3, 1, 2)); b <- array(1:6, dim = c(1, 3, 2))
(a %fp% b)[, , 1]   # every entry is 14 (= 1*1 + 2*2 + 3*3), not outer(1:3, 1:3)

Fixed by wrapping both faces in matrix(..., nrow, ncol), in the for-loop and the BiocParallel
branch. None of the package's own functions produce the silently wrong shape: in 13 typical calls
to tpca(), tpls() (all modes), tplsda(), block_tpls() and block_tplsda(), no facewise
product had a length-1 face dimension. It affects direct calls to %fp% and
facewise_product().

3. BiocParallel branch of the facewise product (same function)

simplify2array() flattens the result to a plain vector when every face is 1 x 1. The branch now
builds the array from the known dimensions instead.

4. .extract_tensor_columns() (R/tens.tpca.R)

apply() returns a vector instead of a matrix when the tensor has one row, so with q = 1
tpls(mode = "tsvdm") returned y_loadings as a vector rather than a 1 x ncomp matrix as the
other modes do. The matrix shape is now restored only in that case; other output is unchanged.

Checks.

  • Inputs without length-1 dimensions give identical() results before and after: 18 tpls() fits
    (q = 2, q = 3 and a subject-fixed q = 2; all three modes; with and without centring) and 7 results
    from tpca(), tplsda(), block_tpls(), block_tplsda() and a cumulative facewise_product().
  • New tests: in test-tens.mproduct.R, n x 1 by 1 x 1, n x 1 by 1 x n and 1 x p by p x 1 faces
    against a direct per-face product, on both branches; in test-tens.tpls.R, q = 1 runs in all
    three modes with the expected dimensions and finite output. Both fail on the current main, and
    reverting any one of the four changes makes at least one of them fail.
  • The full test suite passes.

Closes #2.

Copilot AI balanced review requested due to automatic review settings October 16, 2026 00:01

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Single-column inputs: tpls() fails for q = 1, and %fp% can return a wrong result

2 participants