Skip to content

Commit 071e2bb

Browse files
committed
sped up matrix (pseudo)inversion throughout -- related to #241 and #246
1 parent d51b5d9 commit 071e2bb

15 files changed

+73
-38
lines changed

DESCRIPTION

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ Suggests:
5858
Seurat,
5959
bluster,
6060
cluster,
61+
speedglm,
6162
rmarkdown,
6263
gridExtra,
6364
BiocStyle,

NAMESPACE

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,7 @@ importFrom(MASS,theta.mm)
3939
importFrom(Matrix,Matrix)
4040
importFrom(Matrix,bdiag)
4141
importFrom(Matrix,colSums)
42-
importFrom(Matrix,diag)
4342
importFrom(Matrix,rowMeans)
44-
importFrom(Matrix,solve)
4543
importFrom(Matrix,t)
4644
importFrom(Rcpp,sourceCpp)
4745
importFrom(RcppEigen,fastLmPure)

R/RcppExports.R

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,7 @@ eigenMapMatMult <- function(A, B, n_cores = 1L) {
99
.Call(`_scLANE_eigenMapMatMult`, A, B, n_cores)
1010
}
1111

12+
eigenMapPseudoInverse <- function(A, tolerance = 1e-6, n_cores = 1L) {
13+
.Call(`_scLANE_eigenMapPseudoInverse`, A, tolerance, n_cores)
14+
}
15+

R/biasCorrectGEE.R

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
#' @description This functions implements several bias-correction methods for the GEE sandwich variance-covariance matrix; they are to be used when the number of subjects is small or the numer of timepoints per-subject is very large.
66
#' @importFrom stats fitted.values cor
77
#' @importFrom dplyr with_groups summarise
8-
#' @importFrom MASS ginv
98
#' @importFrom Matrix bdiag
109
#' @param fitted.model The fitted model of class \code{geem} returned by \code{\link{marge2}}. Defaults to NULL.
1110
#' @param correction.method A string specifying the correction method to be used. Currently supported options are "df" and "kc". Defaults to "kc".
@@ -121,9 +120,10 @@ biasCorrectGEE <- function(fitted.model = NULL,
121120
}
122121
sigma2 <- fitted.model$phi
123122
Var_Yi <- sigma2 * R_i
124-
Var_Yi_inv <- try({
125-
MASS::ginv(Var_Yi)
126-
}, silent = TRUE)
123+
Var_Yi_inv <- try({ eigenMapMatrixInvert(Var_Yi, n_cores = 1L) }, silent = TRUE)
124+
if (inherits(Var_Yi_inv, "try-error")) {
125+
Var_Yi_inv <- try({ eigenMapPseudoInverse(Var_Yi, n_cores = 1L) }, silent = TRUE)
126+
}
127127
if (inherits(Var_Yi_inv, "try-error")) {
128128
if (verbose) {
129129
warning(paste0("Covariance matrix inversion failed for subject ",
@@ -137,9 +137,9 @@ biasCorrectGEE <- function(fitted.model = NULL,
137137
W <- as.matrix(Matrix::bdiag(cov_matrices))
138138
X <- fitted.model$X
139139
XWX <- t(X) %*% W %*% X
140-
XWX_inv <- try({ eigenMapMatrixInvert(XWX, n_cores = 1) }, silent = TRUE)
140+
XWX_inv <- try({ eigenMapMatrixInvert(XWX, n_cores = 1L) }, silent = TRUE)
141141
if (inherits(XWX_inv, "try-error")) {
142-
XWX_inv <- MASS::ginv(XWX)
142+
XWX_inv <- eigenMapPseudoInverse(XWX, n_cores = 1L)
143143
}
144144
H <- X %*% XWX_inv %*% t(X) %*% W
145145
tr_H <- sum(diag(H))

R/marge2.R

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -853,7 +853,7 @@ marge2 <- function(X_pred = NULL,
853853
data = model_df,
854854
family = MASS::negative.binomial(theta_hat, link = "log"),
855855
trace = FALSE,
856-
model = TRUE,
856+
model = FALSE,
857857
y = FALSE,
858858
fitted = TRUE)
859859
}

R/plotModels.R

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,8 @@ plotModels <- function(test.dyn.res = NULL,
6464
# check inputs
6565
if (is.null(expr.mat) || is.null(pt) || is.null(gene) || is.null(test.dyn.res)) { stop("You forgot one or more of the arguments to plotModels().") }
6666
if ((is.gee || is.glmm) && is.null(id.vec)) { stop("id.vec must be provided to plotModels() when running in GEE or GLMM mode.") }
67+
cor.structure <- tolower(cor.structure)
68+
if (is.gee && !(cor.structure %in% c("ar1", "independence", "exchangeable"))) { stop("GEE models require a specified correlation structure.") }
6769
# get raw counts from SingleCellExperiment, Seurat, or CellDataSets object
6870
if (inherits(expr.mat, "SingleCellExperiment")) {
6971
expr.mat <- BiocGenerics::counts(expr.mat)

R/score_fun_gee.R

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
#' @author David I. Warton
66
#' @author Jack R. Leary
77
#' @importFrom RcppEigen fastLmPure
8-
#' @importFrom Matrix solve
98
#' @importFrom MASS ginv
109
#' @description Calculate the score statistic for a GEE model.
1110
#' @param Y The response variable. Defaults to NULL.
@@ -98,7 +97,7 @@ score_fun_gee <- function(Y = NULL,
9897
B = J21_transpose,
9998
n_cores = 1)
10099
Sigma <- Sigma22 - temp_prod_1 - temp_prod_2 + temp_prod_3
101-
Sigma_inv <- try({ Matrix::solve(Sigma) }, silent = TRUE)
100+
Sigma_inv <- try({ eigenMapMatrixInvert(Sigma, n_cores = 1L) }, silent = TRUE)
102101
if (inherits(Sigma_inv, "try-error")) {
103102
Sigma_inv <- MASS::ginv(Sigma)
104103
}

R/score_fun_glm.R

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,6 @@
55
#' @author David I. Warton
66
#' @author Jack R. Leary
77
#' @importFrom RcppEigen fastLmPure
8-
#' @importFrom Matrix solve
9-
#' @importFrom MASS ginv
108
#' @description Calculate the score statistic for a GLM model.
119
#' @param Y The response variable. Defaults to NULL.
1210
#' @param VS.est_list A product of matrices. Defaults to NULL.
@@ -54,9 +52,9 @@ score_fun_glm <- function(Y = NULL,
5452
B.est <- eigenMapMatMult(A = t(mu.est * VS.est_i),
5553
B = XA,
5654
n_cores = 1)
57-
XVX_22 <- try({ Matrix::solve(inv.XVX_22) }, silent = TRUE)
55+
XVX_22 <- try({ eigenMapMatrixInvert(inv.XVX_22, n_cores = 1L) }, silent = TRUE)
5856
if (inherits(XVX_22, "try-error")) {
59-
XVX_22 <- MASS::ginv(inv.XVX_22)
57+
XVX_22 <- eigenMapPseudoInverse(inv.XVX_22, n_cores = 1L)
6058
}
6159
temp_prod <- eigenMapMatMult(A = B.est,
6260
B = XVX_22,

R/stat_out_score_gee_null.R

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,8 @@
55
#' @author David I. Warton
66
#' @author Jack R. Leary
77
#' @importFrom geeM geem
8-
#' @importFrom MASS negative.binomial ginv
8+
#' @importFrom MASS negative.binomial
99
#' @importFrom stats fitted.values
10-
#' @importFrom Matrix solve
1110
#' @description A function that calculates parts of the score statistic for GEEs only (it is used for the full path for forward selection).
1211
#' @param Y The response variable Defaults to NULL.
1312
#' @param B_null The design matrix matrix under the null model Defaults to NULL.
@@ -73,9 +72,9 @@ stat_out_score_gee_null <- function(Y = NULL,
7372
V_est_i <- eigenMapMatMult(A = temp_prod,
7473
B = diag_sqrt_V_est,
7574
n_cores = 1)
76-
V_est_i_inv <- try({ Matrix::solve(V_est_i) }, silent = TRUE)
75+
V_est_i_inv <- try({ eigenMapMatrixInvert(V_est_i, n_cores = 1L) }, silent = TRUE)
7776
if (inherits(V_est_i_inv, "try-error")) {
78-
V_est_i_inv <- MASS::ginv(V_est_i)
77+
V_est_i_inv <- eigenMapPseudoInverse(V_est_i, n_cores = 1L)
7978
}
8079
S_est_i <- t(Y)[temp_seq_n] - mu_est_sub
8180
temp_prod <- eigenMapMatMult(A = S_est_i,
@@ -121,9 +120,9 @@ stat_out_score_gee_null <- function(Y = NULL,
121120
if (nrow(J11) == 1 && ncol(J11) == 1) {
122121
J11_inv <- 1 / J11
123122
} else {
124-
J11_inv <- try({ Matrix::solve(J11) }, silent = TRUE)
123+
J11_inv <- try({ eigenMapMatrixInvert(J11, n_cores = 1L) }, silent = TRUE)
125124
if (inherits(J11_inv, "try-error")) {
126-
J11_inv <- MASS::ginv(J11)
125+
J11_inv <- eigenMapPseudoInverse(J11, n_cores = 1L)
127126
}
128127
}
129128
temp_prod <- eigenMapMatMult(A = J11_inv,

R/stat_out_score_glm_null.R

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,6 @@
66
#' @author Jack R. Leary
77
#' @importFrom gamlss gamlss
88
#' @importFrom stats fitted.values
9-
#' @importFrom Matrix diag t
10-
#' @importFrom MASS ginv
119
#' @description A function that calculates parts of the score statistic for GLMs only (it is used for the full path for forward selection).
1210
#' @param Y : The response variable Defaults to NULL.
1311
#' @param B_null : Design matrix under the null model. Defaults to NULL.
@@ -29,8 +27,8 @@ stat_out_score_glm_null <- function(Y = NULL, B_null = NULL) {
2927
mu_est <- as.matrix(stats::fitted.values(ests))
3028
V_est <- mu_est * (1 + mu_est * sigma_est) # Type I NB variance = mu (1 + mu * sigma); sigma = 1 / theta
3129
VS_est_list <- (Y - mu_est) / V_est
32-
mu_V_diag <- Matrix::diag(c(mu_est^2 / V_est))
33-
temp_prod <- eigenMapMatMult(A = Matrix::t(B_null),
30+
mu_V_diag <- diag(c(mu_est^2 / V_est))
31+
temp_prod <- eigenMapMatMult(A = t(B_null),
3432
B = mu_V_diag,
3533
n_cores = 1)
3634
A_list_inv <- eigenMapMatMult(A = temp_prod,
@@ -39,12 +37,12 @@ stat_out_score_glm_null <- function(Y = NULL, B_null = NULL) {
3937
if (ncol(A_list_inv) == 1 && nrow(A_list_inv) == 1) {
4038
A_list <- 1 / A_list_inv
4139
} else {
42-
A_list <- try({ solve(A_list_inv) }, silent = TRUE)
40+
A_list <- try({ eigenMapMatrixInvert(A_list_inv, n_cores = 1L) }, silent = TRUE)
4341
if (inherits(A_list, "try-error")) {
44-
A_list <- MASS::ginv(A_list_inv)
42+
A_list <- eigenMapPseudoInverse(A_list_inv, n_cores = 1L)
4543
}
4644
}
47-
B1_list <- eigenMapMatMult(A = Matrix::t(B_null),
45+
B1_list <- eigenMapMatMult(A = t(B_null),
4846
B = mu_V_diag,
4947
n_cores = 1)
5048
res <- list(VS.est_list = VS_est_list,

R/waldTestGEE.R

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
#' @name waldTestGEE
44
#' @author Jack R. Leary
55
#' @description Performs a basic Wald test to determine whether an alternate model is significantly better than a nested null model. This is the GEE equivalent (kind of) of \code{\link{modelLRT}}. Be careful with small sample sizes.
6-
#' @importFrom MASS ginv
76
#' @importFrom stats pchisq
87
#' @param mod.1 The model under the alternative hypothesis. Must be of class \code{geem}. Defaults to NULL.
98
#' @param mod.0 The model under the null hypothesis. Must be of class \code{geem}. Defaults to NULL.
@@ -74,7 +73,10 @@ waldTestGEE <- function(mod.1 = NULL,
7473
}
7574
vcov_mat <- vcov_mat[coef_idx, coef_idx]
7675
wald_test_stat <- try({
77-
vcov_mat_inv <- eigenMapMatrixInvert(vcov_mat, n_cores = 1)
76+
vcov_mat_inv <- eigenMapMatrixInvert(vcov_mat, n_cores = 1L)
77+
if (inherits(vcov_mat_inv, "try-error")) {
78+
vcov_mat_inv <- eigenMapPseudoInverse(vcov_mat, n_cores = 1L)
79+
}
7880
as.numeric(crossprod(coef_vals, vcov_mat_inv) %*% coef_vals)
7981
}, silent = TRUE)
8082
if (inherits(wald_test_stat, "try-error")) {

src/RcppExports.cpp

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ Rcpp::Rostream<false>& Rcpp::Rcerr = Rcpp::Rcpp_cerr_get();
1212
#endif
1313

1414
// eigenMapMatrixInvert
15-
SEXP eigenMapMatrixInvert(const Eigen::Map<Eigen::MatrixXd> A, int n_cores);
15+
Eigen::MatrixXd eigenMapMatrixInvert(const Eigen::Map<Eigen::MatrixXd> A, int n_cores);
1616
RcppExport SEXP _scLANE_eigenMapMatrixInvert(SEXP ASEXP, SEXP n_coresSEXP) {
1717
BEGIN_RCPP
1818
Rcpp::RObject rcpp_result_gen;
@@ -24,7 +24,7 @@ BEGIN_RCPP
2424
END_RCPP
2525
}
2626
// eigenMapMatMult
27-
SEXP eigenMapMatMult(const Eigen::Map<Eigen::MatrixXd> A, const Eigen::Map<Eigen::MatrixXd> B, int n_cores);
27+
Eigen::MatrixXd eigenMapMatMult(const Eigen::Map<Eigen::MatrixXd> A, const Eigen::Map<Eigen::MatrixXd> B, int n_cores);
2828
RcppExport SEXP _scLANE_eigenMapMatMult(SEXP ASEXP, SEXP BSEXP, SEXP n_coresSEXP) {
2929
BEGIN_RCPP
3030
Rcpp::RObject rcpp_result_gen;
@@ -36,10 +36,24 @@ BEGIN_RCPP
3636
return rcpp_result_gen;
3737
END_RCPP
3838
}
39+
// eigenMapPseudoInverse
40+
Eigen::MatrixXd eigenMapPseudoInverse(const Eigen::Map<Eigen::MatrixXd> A, double tolerance, int n_cores);
41+
RcppExport SEXP _scLANE_eigenMapPseudoInverse(SEXP ASEXP, SEXP toleranceSEXP, SEXP n_coresSEXP) {
42+
BEGIN_RCPP
43+
Rcpp::RObject rcpp_result_gen;
44+
Rcpp::RNGScope rcpp_rngScope_gen;
45+
Rcpp::traits::input_parameter< const Eigen::Map<Eigen::MatrixXd> >::type A(ASEXP);
46+
Rcpp::traits::input_parameter< double >::type tolerance(toleranceSEXP);
47+
Rcpp::traits::input_parameter< int >::type n_cores(n_coresSEXP);
48+
rcpp_result_gen = Rcpp::wrap(eigenMapPseudoInverse(A, tolerance, n_cores));
49+
return rcpp_result_gen;
50+
END_RCPP
51+
}
3952

4053
static const R_CallMethodDef CallEntries[] = {
4154
{"_scLANE_eigenMapMatrixInvert", (DL_FUNC) &_scLANE_eigenMapMatrixInvert, 2},
4255
{"_scLANE_eigenMapMatMult", (DL_FUNC) &_scLANE_eigenMapMatMult, 3},
56+
{"_scLANE_eigenMapPseudoInverse", (DL_FUNC) &_scLANE_eigenMapPseudoInverse, 3},
4357
{NULL, NULL, 0}
4458
};
4559

src/eigenMapInvert.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33
// [[Rcpp::depends(RcppEigen)]]
44
// [[Rcpp::export]]
55

6-
SEXP eigenMapMatrixInvert(const Eigen::Map<Eigen::MatrixXd> A, int n_cores = 1){
6+
Eigen::MatrixXd eigenMapMatrixInvert(const Eigen::Map<Eigen::MatrixXd> A, int n_cores = 1){
77
Eigen::setNbThreads(n_cores);
88
Eigen::MatrixXd B = A.llt().solve(Eigen::MatrixXd::Identity(A.rows(), A.cols()));
9-
return Rcpp::wrap(B);
9+
return B;
1010
}

src/eigenMapMatMult.cpp

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,11 @@
33
// [[Rcpp::depends(RcppEigen)]]
44
// [[Rcpp::export]]
55

6-
SEXP eigenMapMatMult(const Eigen::Map<Eigen::MatrixXd> A,
7-
const Eigen::Map<Eigen::MatrixXd> B,
8-
int n_cores = 1) {
6+
Eigen::MatrixXd eigenMapMatMult(const Eigen::Map<Eigen::MatrixXd> A,
7+
const Eigen::Map<Eigen::MatrixXd> B,
8+
int n_cores = 1) {
99
Eigen::setNbThreads(n_cores);
1010
Eigen::MatrixXd C;
1111
C.noalias() = A * B;
12-
return Rcpp::wrap(C);
12+
return C;
1313
}

src/eigenMapPseudoInverse.cpp

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
#include <RcppEigen.h>
2+
3+
// [[Rcpp::depends(RcppEigen)]]
4+
// [[Rcpp::export]]
5+
6+
Eigen::MatrixXd eigenMapPseudoInverse(const Eigen::Map<Eigen::MatrixXd> A,
7+
double tolerance = 1e-6,
8+
int n_cores = 1) {
9+
Eigen::setNbThreads(n_cores);
10+
Eigen::JacobiSVD<Eigen::MatrixXd> svd(A, Eigen::ComputeThinU | Eigen::ComputeThinV);
11+
Eigen::MatrixXd singularValues = svd.singularValues();
12+
Eigen::MatrixXd singularValuesInv(A.cols(), A.rows());
13+
singularValuesInv.setZero();
14+
for (int i = 0; i < singularValues.size(); ++i) {
15+
if (singularValues(i) > tolerance) {
16+
singularValuesInv(i, i) = 1.0 / singularValues(i);
17+
}
18+
}
19+
return svd.matrixV() * singularValuesInv * svd.matrixU().transpose();
20+
}

0 commit comments

Comments
 (0)