Nothing
#' Mixed Linear Model Evaluation with RRPP performed within subjects
#'
#' Function performs a mixed linear model fit over many random permutations of
#' data, using a randomized residual permutation procedure restricted to
#' subjects. This function is likely to evolve greater flexibility in the future.
#'
#' The function fits a mixed linear model using ordinary least squares (OLS) or
#' generalized least squares (GLS) estimation of coefficients for fixed effects, and
#' maximum likelihood (ML) or restricted ML (REML) for random effects, over any
#' number of random permutations of the data, but the permutations are mostly
#' restricted to occur with subject blocks for any model terms other than subjects.
#' Most functionality should resemble that of \code{\link{lm.rrpp}} and \code{\link{lm.rrpp.ws}},
#' with some caveats. First, estimation of random effects uses the methodology of the
#' \code{\link[lme4]{lmer}} function (Bates et al., 2015),
#' for only one random term based on a subjects designation.
#' Second, this means that a "random slopes" model involves an interaction between subjects and a numeric
#' variable (not a categorical variable). Third, although any number of fixed effects can be used,
#' only one fixed effect can interaction with subjects. Finally, the function is intended to allow
#' analysis of variance (ANOVA), like with \code{\link{lm.rrpp.ws}}, but not treated random
#' effects as fixed.
#' There is not a coefficients test associated with this function, as with other \code{\link{lm.rrpp}}
#' that include only fixed effects.
#'
#' Although this function allows covariance matrices to be used for generalized least squares (GLS)
#' estimation of fixed effects, it does not currently
#' expand the covariance mnatrix based only on subjects, as does
#' \code{\link{lm.rrpp.ws}}. However, that function can be used to obtain a covariance matrix for this
#' function. The covariance structure (Lambda matrix) among subjects is estimated via ML or REML (Bates et al., 2015) and
#' is not impacted by the expected covariances among fixed effects, however, the expected covariances
#' will influence interactions between fixed and random effects. The Lambda matrix is estimated as a single matrix,
#' even for multivariate data. This matrix is obtained by projecting data onto the first component of
#' a singular value decomposition of the product between the data and a hat matrix of the the fixed effects
#' (basically projection following a principal component analysis of model fitted values). This is a computationally
#' proficient step that is similar to averaging covariance parameters (theta) over all variables.
#' If independent Lambda matrices are desired, then this function can be used on, e.g., each principal
#' component of data, followed by combining the coefficients. For ANOVA, the precise estimation of Lambda
#' is not as important for tests of fixed effects, as it means a linear transformation of coefficients
#' that would be consistent across random permutations of RRPP. It might be more important for model
#' comparison and selection, in which case whether to use LS, ML, REML, and whether to use separate Lambda matrices
#' is a much deeper consideration.
#'
#' A data frame (preferably an \code{\link{rrpp.data.frame}} object) must be
#' used for this function. Not all "downstream" functions (e.g., logLik, predict) are guaranteed
#' to work as expected, as this function is still very much under development.
#'
#' The \code{\link{lm.rrpp}} arguments not available for this function include:
#' full.resid, block, and SS.type. These arguments are fixed because of
#' the within-subject blocking for tests, plus the requirement for type II SS
#' for within-subject effects.
#'
#' @param fixed A formula for the fixed effects of linear model (e.g., y~x1+x2).
#' @param subjects A variable that can be found in the data frame indicating the research subjects
#' for the analysis. This variable must be in the data frame. Is can be either numeric
#' (if its slot in the data frame is known) or a character, e.g., "sub_id". It is imperative that
#' it is ordered the same as the data but that the data do not have row names the same as subjects.
#' For example, the subjects variable in the data frame might be sub_id: sub1, sub1, sub1, sub2,
#' sub2, sub2, ... and the row names of the data might be obs1, obs2, obs3, obs4, obs5, obs6, ...
#' The data do not need to have row names but the subjects variable has to be provided.
#' @param type An indication whether to use random intercepts ("intercepts") or random intercepts
#' and slopes ("slopes") for random effects associated with subjects.
#' @param slopeTerm An optional character value (e.g., "mass") indicating which term in the data frame is to be
#' used in the estimation of random slopes. This term should also be in the fixed formula.
#' @param estimation One of "LS", "ML", or "REML", for guiding how random effects should be estimated.
#' @param iter Number of iterations for significance testing
#' @param data A data frame for the function environment, see
#' \code{\link{rrpp.data.frame}}. A data frame is required for this analysis.
#' @param ... Arguments typically used in \code{\link{lm}}, \code{\link{lm.rrpp}},
#' or \code{\link{lm.rrpp.ws}} such as weights or offset, seed for permutations, etc., passed on to
#' \code{lm.rrpp.ws} for estimation of coefficients.
#' @keywords analysis
#' @export
#' @author Michael Collyer
#' @return An object of class \code{lmm.rrpp} is a list containing the
#' following
#' \item{call}{The matched call.}
#' \item{LM}{Linear Model objects, including data (Y), coefficients,
#' design matrix (X), sample size
#' (n), number of dependent variables (p), dimension of data space (p.prime),
#' QR decomposition of the design matrix, fitted values, residuals,
#' weights, offset, model terms, data (model) frame, random coefficients
#' (through permutations),
#' random vector distances for coefficients (through permutations),
#' whether OLS or GLS was performed,
#' and the mean for OLS and/or GLS methods. Note that the data returned
#' resemble a model frame rather than
#' a data frame; i.e., it contains the values used in analysis, which
#' might have been transformed according to
#' the formula. The response variables are always labeled Y.1, Y.2, ...,
#' in this frame. Most important objects for mixed models are also found here. }
#' \item{ANOVA}{Analysis of variance objects, including the SS type,
#' random SS outcomes, random MS outcomes,
#' random R-squared outcomes, random F outcomes, random Cohen's f-squared
#' outcomes, P-values based on random F
#' outcomes, effect sizes for random outcomes, sample size (n), number of
#' variables (p), and degrees of freedom for
#' model terms (df). These objects are used to construct ANOVA tables.}
#' \item{PermInfo}{Permutation procedure information, including the number
#' of permutations (perms), The method
#' of residual randomization (perm.method), and each permutation's sampling
#' frame (perm.schedule), which
#' is a list of reordered sequences of 1:n, for how residuals were
#' randomized.}
#' \item{devcomp}{Deviance components, a list of components used
#' in deviance and likelihood calculations.}
#' @seealso \code{\link{lm.rrpp}}; \code{\link{lm.rrpp.ws}}; \code{\link{measurement.error}}
#' @references Douglas Bates, Martin Maechler, Ben Bolker, Steve Walker (2015). Fitting Linear
#' Mixed-Effects Models Using lme4. Journal of Statistical Software, 67(1), 1-48.
#' doi:10.18637/jss.v067.i01.
#'
#' \subsection{Limitations}{
#' This function currently only works for "Random Intercepts" and "Random slopes" models,
#' where the intercepts or slopes of a single subject term (random effect) are free to vary.
#' If a Random Slopes model is used, a single covariate (numeric variable) must be specified for slopes.
#' Generally, this is term that is also among the fixed effects. These limitations are imposed
#' to allow ML or REML estimation. More complex models can be entertained with \code{lmm.rrpp.ws},
#' but using only "LS" estimation. If "LS" estimation is used in this function, results should be the same
#' with a comparable formula, using \code{lmm.rrpp.ws}
#'
#' If a ML or REML fit is near singular (one or more covariance parameters are 0), the analysis will be arrested
#' rather than continued with some random effects rendered as 0. This should be an indication that the model
#' is over-fit with the type of estimation used.
#'
#' These limitations might be addressed with future versions of this function, as theoretical advances are made.
#' }
#' @examples
#' \dontrun{
#' # Random intercepts models
#'
#' data(ReptAbund)
#' fit_ls <- lmm.rrpp(fixed = reptile_abundance ~ Year,
#' subjects = "Site",
#' type = "intercepts",
#' estimation = "LS",
#' data = ReptAbund)
#'
#' fit_ml <- lmm.rrpp(fixed = reptile_abundance ~ Year,
#' subjects = "Site",
#' type = "intercepts",
#' estimation = "ML",
#' data = ReptAbund)
#'
#' fit_reml <- lmm.rrpp(fixed = reptile_abundance ~ Year,
#' subjects = "Site",
#' type = "intercepts",
#' estimation = "REML",
#' data = ReptAbund)
#'
#' anova(fit_ls, error = c("Site", "Residuals"))
#' anova(fit_ml, error = c("Site", "Residuals"))
#' anova(fit_reml, error = c("Site", "Residuals"))
#'
#' coef(fit_ls, type = "list")
#' coef(fit_ml, type = "list")
#' coef(fit_reml, type = "list")
#'
#' # Random slopes models
#' # none should not work!
#'
#' fit_ls <- lmm.rrpp(fixed = reptile_abundance ~ Year,
#' subjects = "Site",
#' type = "slopes",
#' slopeTerm = "Year",
#' estimation = "LS",
#' data = ReptAbund)
#'
#' fit_ml <- lmm.rrpp(fixed = reptile_abundance ~ Year,
#' subjects = "Site",
#' type = "slopes",
#' slopeTerm = "Year",
#' estimation = "ML",
#' data = ReptAbund)
#'
#'
#' fit_reml <- lmm.rrpp(fixed = reptile_abundance ~ Year,
#' subjects = "Site",
#' type = "slopes",
#' slopeTerm = "Year",
#' estimation = "REML",
#' data = ReptAbund)
#'
#' anova(fit_ls, error = c("Year:Site", "Year:Site", "Residuals"))
#' anova(fit_ml, error = c("Year:Site", "Year:Site", "Residuals"))
#'
#' coef(fit_ls, type = "list")
#' coef(fit_ml, type = "list")
#' }
lmm.rrpp <- function(fixed,
subjects,
type = c("intercepts", "slopes"),
slopeTerm = NULL,
estimation = c("REML", "ML", "LS"),
iter = 999,
data,
...) {
estimation <- match.arg(estimation)
dots <- list(...)
largs <- as.vector(names(formals(lm.rrpp.ws)))
lm.rrpp.args <- dots[names(dots) %in% largs]
names(lm.rrpp.args) <- lm.rrpp.args[largs %in% names(dots)]
response <- fixed[[2]]
trms <- attr(terms(fixed, keep.order = TRUE), "term.labels")
form <- if(type == "intercepts")
reformulate(response = response,
termlabels = c(trms, subjects)) else
reformulate(response = response,
termlabels = c(trms, subjects,
paste(slopeTerm, subjects, sep = ":")))
useLS <- (estimation == "LS")
form <- formula(terms(form, keep.order = TRUE),
keep.order = TRUE)
lm.rrpp.args$f1 <- form
lm.rrpp.args$subjects <- subjects
lm.rrpp.args$verbose <- FALSE
lm.rrpp.args$data <- data
if(isTRUE(dots$print.progress)){
cat("\nInitial lm.rrpp.ws fit:\n ")
}
lm.rrpp.args$int.first <- TRUE
if(useLS){
fit <- suppressWarnings(do.call(lm.rrpp.ws,
lm.rrpp.args))
} else {
lm.rrpp.args$iter <- 0
fit <- suppressWarnings(do.call(lm.rrpp.ws,
lm.rrpp.args))
lm.rrpp.args$iter <- iter
}
B <- fit$LM$coefficients
lm.rrpp.args$f1 <- fixed
fixed.fit <- suppressWarnings(do.call(lm.rrpp.ws,
lm.rrpp.args))
fit$call <- match.call()
dat <- fit$LM$data
dat$resp <- as.matrix(dat$Y[, 1, drop = FALSE])
Y <- dat$Y
dims <- dim(Y)
n <- dims[1]
p <- dims[2]
Xs <- suppressWarnings(
getModels(fixed.fit, "qr"))
k <- length(Xs$full)
QR <- Xs$full[[k]]
rands <- if(type == "intercepts")
paste("(1|", subjects, ")", sep = "") else
paste("(", slopeTerm, "|", subjects, ")", sep = "")
randform <- reformulate(response = response,
termlabels = c(trms, rands))
randform <- update(randform, resp ~ .)
init.rand.fit <- suppressMessages(
suppressWarnings(lmer(randform,
REML = (estimation == "REML"),
data = dat)))
Zall <- getME(init.rand.fit,
"Z")
if(type == "slopes"){
nzc <- ncol(Zall)
Zslope <- Zall[, seq(2, nzc, 2), drop = FALSE]
Zsub <- Zall[, seq(1, nzc - 1, 2), drop = FALSE]
} else {
Zsub <- Zall
Zslope <- NULL
}
nsub <- NCOL(Zsub)
Xff <- model.matrix(fixed.fit)
kf <- NCOL(Xff)
Snames <- rand.coef.names <- dimnames(Zsub)[[2]]
if(type == "slopes"){
a <- rep(Snames, each = 2)
b <- rep(c("(Intercept)", slopeTerm), length(Snames))
rand.coef.names <- paste(a, b, sep = ":")
}
sub1 <- paste(subjects, levels(fit$subjects), sep = "")
sub2 <- if(type == "slopes")
paste(slopeTerm, sub1, sep = ":") else NULL
xnames <- c(colnames(Xff), sub1, sub2)
Bfill <- matrix(0, nrow = length(xnames),
ncol = p)
dimnames(Bfill) <- list(xnames, colnames(dat$resp))
Bfill[rownames(Bfill) %in% rownames(B), ] <- B
B <- Bfill
rm(Bfill)
if(type == "slopes"){
Bff <- as.matrix(B[1:kf, , drop = FALSE])
Bs <- as.matrix(B[-(1:kf), , drop = FALSE])
ks <- NROW(Bs)
sseq <- NULL
for(i in 1:(ks/2)) sseq <- c(sseq, c(i, i + ks/2))
Bs <- as.matrix(Bs[sseq, , drop = FALSE])
B <- as.matrix(rbind(Bff, Bs))
}
if(p > 1){
H <- tcrossprod(QR$Q)
yp <- ordinate(Y, A = H, rank. = 1)$x
ft <- refit(init.rand.fit, yp)
} else ft <- init.rand.fit
if(isSingular(ft, tol = 1e-7))
stop("Model is near-singular.\n",
"Try a different estimation method or a simpler model.\n",
call. = FALSE)
Lambda <- getME(ft, "Lambda")
Zall <- Zall %*% Lambda
if(type == "slopes"){
nzc <- ncol(Zall)
Zslope <- Zall[, seq(2, nzc, 2), drop = FALSE]
Zsub <- Zall[, seq(1, nzc - 1, 2), drop = FALSE]
} else {
Zsub <- Zall
Zslope <- NULL
}
if(!useLS){
if(fit$LM$gls) {
if(!is.null(fit$LM$Cov)) {
Pcov <- getModelCov(fit, "Pcov")
Y <- Pcov %*% Y
} else {
w <- sqrt(fit$LM$weights)
Y <- Y * w
}
}
subjFactor <- unlist(dat[[which(names(dat) == subjects)]])
ns <- nlevels(subjFactor)
subjLevels <- levels(subjFactor)
if(type == "slopes") {
slopeVar <- unlist(dat[[which(names(dat) == slopeTerm)]])
if(is.factor(slopeVar) && estimation != "LS"){
stop("\nThe slope term must be numeric for ML or REML fits...",
"\nThis is a requirement of the lme4::lmer function dependency. ",
"\nConsider using dummy (binary) variables for two-group ",
"comparisons. \nSee the lme4::dummy function for details. \n",
call. = FALSE)
}
}
for(i in 1:2){
reduced <- lapply(Xs$reduced,
function(x) as.matrix(x$X))
full <- lapply(Xs$full,
function(x) as.matrix(x$X))
}
Xs$reduced <- reduced
Xs$full <- full
rm(reduced, full)
Xs$reduced[[k + 1]] <- Xs$full[[k]]
Xs$full[[k + 1]] <- Xs$full[[k]]
names(Xs$reduced)[[k + 1]] <-
names(Xs$full)[[k + 1]] <- subjects
if(type == "slopes"){
Xs$reduced[[k + 2]] <- Xs$full[[k]]
Xs$full[[k + 2]] <- Xs$full[[k]]
names(Xs$reduced)[[k + 2]] <-
names(Xs$full)[[k + 2]] <- paste(
slopeTerm, subjects, sep = ":")
}
Hbs.reduced <- Hbs.full <-
XZs.reduced <- XZs.full <- list()
for(i in 1:k){
Hbs.reduced[[i]] <- getLMM_Hb(Xs$reduced[[i]], Zsub)
Hbs.full[[i]] <- getLMM_Hb(Xs$full[[i]], Zsub)
XZs.reduced[[i]] <- cbind(Xs$reduced[[i]], Zsub)
XZs.full[[i]] <- cbind(Xs$full[[i]], Zsub)
}
# If Intercepts model
if(type == "intercepts"){
Hbs.reduced[[k+1]] <- getLMM_Hb(Xs$full[[k]])
XZs.reduced[[k+1]] <- Xs$full[[k]]
Hbs.full[[k+1]] <- getLMM_Hb(Xs$full[[k]], Zsub)
XZs.full[[k+1]] <- cbind(Xs$full[[k]], Zsub)
}
# If slopes model
if(type == "slopes"){
Hbs.reduced[[k+1]] <- getLMM_Hb(Xs$full[[k]], Zslope)
XZs.reduced[[k+1]] <- cbind(Xs$full[[k]], Zslope)
Hbs.full[[k+1]] <- getLMM_Hb(Xs$full[[k]], Zall)
XZs.full[[k+1]] <- cbind(Xs$full[[k]], Zall)
Hbs.reduced[[k+2]] <- getLMM_Hb(Xs$full[[k]], Zsub)
XZs.reduced[[k+2]] <- cbind(Xs$full[[k]], Zsub)
Hbs.full[[k+2]] <- getLMM_Hb(Xs$full[[k]], Zall)
XZs.full[[k+2]] <- cbind(Xs$full[[k]], Zall)
}
Hs.full <- Map(function(x, h){
x %*% h
}, XZs.full, Hbs.full)
Hs.reduced <- Map(function(x, h){
x %*% h
}, XZs.reduced, Hbs.reduced)
Fitted <- lapply(Hs.reduced, function(h){
as.matrix(h %*% Y)
})
Resid <- lapply(Fitted, function(f) Y - f)
### start RRPP
ind <- perm.index(n, iter, block = subjFactor,
seed = dots$seed)
ind_s <- perm.index(n, iter, block = NULL,
seed = dots$seed)
perms <- length(ind)
kk <- length(XZs.full)
XZ_full <- XZs.full[[kk]]
XZ_null <- XZ_full[, 1, drop = FALSE] # need to consider no intercept
Hb_full <- Hbs.full[[kk]]
QRnull <- QRforX(matrix(1, n, 1))
Hb_null <- getHb(QRnull)
Qnull <- QRnull$Q
B <- as.matrix(Hb_full %*% Y)
dimnames(B) <- list(c(colnames(fixed.fit$LM$X),
rand.coef.names),
colnames(Y))
U <- as.matrix(B[-seq_len(ncol(fixed.fit$LM$X)), , drop = FALSE])
UtU_det <- try(det(crossprod(U)), silent = TRUE)
if(inherits(UtU_det, "try-error"))
UtU_det <- NA
sub_no <- which(names(Xs$full) == subjects)
Result <- as.list(array(NA, perms))
names(Result) <- names(ind)
H_residual <- XZ_full %*% Hb_full
H_residual <- H_residual - diag(n)
ss <- function(hf, hr, y) {
ssr <- sum(crossprod(hr, y)^2)
ssf <- sum(crossprod(hf, y)^2)
r <- H_residual %*% y
ssm <- sum(r^2)
c(ssf - ssr, ssm)
}
trmnms <- names(Xs$full)
for(i in 1:perms){
s <- ind[[i]]
Y_i <- lapply(1:kk, function(j){
sj <- if(j == sub_no) ind_s[[i]] else s
Fitted[[j]] + as.matrix(Resid[[j]][sj, , drop = FALSE])
})
yy <- Y[s, , drop = FALSE]
res <- vapply(1:max(1, kk), function(j){
ss(Hs.full[[j]], Hs.reduced[[j]], Y_i[[j]])
}, numeric(2))
dimnames(res) <- list(c("SS", "RSS"), trmnms)
Ralls <- Map(function(y){
H_residual %*% y}, Y_i)
RSS <- sapply(Ralls, function(r) sum(r^2))
Rm <- H_residual %*% yy
RSS.model <- sum(Rm^2)
F0 <- fastFit(Qnull, yy, n, p)
R0 <- yy - F0
TSS <- sum(R0^2)
Result[[i]] <- list(
SS = res,
RSS.model = RSS.model,
TSS = TSS
)
}
SS <- sapply(Result, function(x) x$SS[1,])
RSS <- sapply(Result, function(x) x$SS[2,])
RSS.model <- sapply(Result, function(x) x$RSS.model)
RSS.model <- matrix(RSS.model, kk, perms, byrow = TRUE)
TSS <- sapply(Result, function(x) x$TSS)
TSS <- matrix(TSS, kk, perms, byrow = TRUE)
dimnames(TSS) <- dimnames(RSS.model) <- dimnames(SS)
Df <- fit$ANOVA$df
MS <- SS / Df[1:kk]
Rsq <- SS / TSS
fit$ANOVA$SS <- SS
fit$ANOVA$RSS <- RSS
fit$ANOVA$RSS.model <- RSS.model
fit$ANOVA$TSS <- TSS
fit$ANOVA$Rsq <- Rsq
fit$PermInfo$perms <- perms
}
fit$LM$LMM <- TRUE
fit$LM$X <- fixed.fit$LM$X
fit$LM$Z <- getME(init.rand.fit, "Z")
fit$LM$Lambda <- Lambda
fit$LM$QR <- NULL
fit$LM$coefficients <- B
fit$LM$coef.fixed <- seq_len(ncol(fixed.fit$LM$X))
fit$LM$coef.random <- seq_len(NROW(B))[-fit$LM$coef.fixed]
if(!useLS){
if(fit$LM$gls) {
fit$L$gls.fitted <- XZ_full %*% fit$LM$coefficients
fit$L$gls.residuals <- Y - fit$LM$gls.fitted
} else {
fit$LM$fitted <- XZ_full %*% fit$LM$coefficients
fit$LM$residuals <- Y - fit$LM$fitted
}
}
fit$LM$fixed <- fixed
fit$LM$estimation <- estimation
fit$LM$ranef.type <- type
fit$LM$ranef.slopeTerm <- slopeTerm
fit$LM$lm_form <- form
fit$LM$lmer_form <- randform
fit$LM$cnms <- init.rand.fit@cnms
# deviance component list
fit$devcomp <- list()
fit$devcomp$UtU_det <- if(!useLS)
UtU_det else NA
X <- QRforX(fit$LM$X)$X
nX <- NCOL(X)
if(!is.null(fit$LM$weights)) X <- X / sqrt(fit$LM$weights)
Pcov <- try(getModelCov(fit, "Pcov"), silent = TRUE)
if(!inherits(Pcov, "try-error")) X <- Pcov %*% X
ZX_full <- cbind(fit$LM$Z %*% fit$LM$Lambda, X)
R <- resid(fit)
if(!inherits(Pcov, "try-error")) R <- Pcov %*% R
if(!is.null(fit$LM$weights)) R <- R / sqrt(fit$LM$weights)
Omega <- if(!inherits(Pcov, "try-error"))
fast.solve(tcrossprod(Pcov)) else
if(!is.null(fit$LM$weights)) diag(fit$LM$weights) else
diag(n)
ldO <- log(det(Omega))
nZX <- ncol(ZX_full)
nZ <- nZX - nX
RtR_det <- try(det(crossprod(R)), silent = TRUE)
if(inherits(RtR_det, "try-error")) RtR_det <- NA
fit$devcomp$RtR_det <- RtR_det
cpZX_full <- crossprod(ZX_full)
cpZX_full[1:nZ, 1:nZ] <- cpZX_full[1:nZ, 1:nZ] +
diag(nZ)
Ch <- try(chol(cpZX_full), silent = TRUE)
if(inherits(Ch, "try-error")) Ch <- NA
if(!all(is.na(Ch))) {
fit$devcomp$ldLZ <- 2 * log(det(Ch[1:nZ, 1:nZ]))
cpX <- crossprod(ZX_full[, -(1:nZ)]) -
crossprod(Ch[1:nZ, -(1:nZ)])
fit$devcomp$ldLX <- log(det(cpX))
} else fit$devcomp$ldLZ <- fit$devcomp$ldLX <- NA
fit$devcomp$ldO <- ldO
# out
class(fit) <- c("lmm.rrpp", class(fit))
fit$call <- match.call()
fit
}
getLMMQ <- function(XZ, Hb){
QR <- QRforX(XZ %*% Hb)
Q <- QR$Q[, 1:QR$rank, drop = FALSE]
Q
}
# Not used but could in the future
getSubBlocks <- function(flmer){
Zt <- getME(flmer, "Ztlist")[[1]]
block <- as.factor(Zt@i + 1)
block
}
# Not used but could in the future
indexZtoX <- function(X, Z){
Z <- Z0 <- as.matrix(Z)
kx <- ncol(X)
kz <- ncol(Z)
nx <- nrow(X)
nz <- nrow(Z)
if(kx != kz || nx != nz)
stop("Dimensions of matrices do not match.\n",
call. = FALSE)
res <- array(NA, kx)
z.index <- 1:kz
for(i in seq_len(kx)){
x <- X[, i, drop = FALSE]
r <- sapply(1:kz, function(j){
identical(x, Z[, j])
})
a <- which(r)
res[i] <- z.index[a]
Z <- as.matrix(Z[, -a, drop = FALSE])
z.index <- z.index[-a]
kz <- kz - 1
}
res
}
Any scripts or data that you put into this service are public.
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.