diff --git a/.Rprofile b/.Rprofile deleted file mode 100644 index 8196ebc..0000000 --- a/.Rprofile +++ /dev/null @@ -1,4 +0,0 @@ -# Set up devtools -if (interactive()) { - suppressMessages(require(devtools)) -} diff --git a/DESCRIPTION b/DESCRIPTION index 479cd89..8299c0d 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -2,12 +2,12 @@ Package: card Type: Package Title: Cardiovascular Applications in Research Data Version: 0.1.0.9000 -Authors@R: c( - person("Anish S. Shah", - email = "ashah282@uic.edu", - role = c("aut", "cre", "cph"), - comment = c(ORCID = "0000-0002-9729-1558")) - ) +Authors@R: + person(given = "Anish S.", + family = "Shah", + role = c("aut", "cre", "cph"), + email = "shah.in.boots@gmail.com", + comment = c(ORCID = "0000-0002-9729-1558")) Description: A collection of cardiovascular research datasets and analytical tools, including methods for cardiovascular procedural data, such as electrocardiography, echocardiography, and catheterization data. Additional @@ -17,7 +17,7 @@ URL: https://cran.r-project.org/package=card BugReports: https://github.com/shah-in-boots/card/issues Encoding: UTF-8 LazyData: true -RoxygenNote: 7.3.1 +RoxygenNote: 7.3.2 Roxygen: list(markdown = TRUE) Depends: R (>= 4.1), diff --git a/R/cosinor-constructor.R b/R/cosinor-constructor.R index 70998af..e906308 100644 --- a/R/cosinor-constructor.R +++ b/R/cosinor-constructor.R @@ -7,8 +7,8 @@ #' linearization of the parameters to assess their statistics and #' distribution. #' -#' @param t Represents the _ordered_ time indices that provide the positions for the -#' cosine wave. Depending on the context: +#' @param t Represents the _ordered_ time indices that provide the positions for +#' the cosine wave. Depending on the context: #' #' - A `data frame` of a time-based predictor/index. #' diff --git a/R/cosinor-fit.R b/R/cosinor-fit.R index c210104..727c7e5 100644 --- a/R/cosinor-fit.R +++ b/R/cosinor-fit.R @@ -165,7 +165,7 @@ cosinor_pop_impl <- function(predictors, outcomes, tau, population) { # Remove patients with only p observations (will cause a det ~ 0 error) counts <- by(df, df[, "population"], nrow) - lowCounts <- as.numeric(names(counts[counts <= 2*p + 1])) + lowCounts <- as.numeric(names(counts[counts <= 2 * p + 1])) df <- subset(df, !(population %in% lowCounts)) # Message about population count removal @@ -315,71 +315,159 @@ confint.cosinor <- function(object, parm, level = 0.95, ...) { } switch( - object$type, - Population = { - # Message - message("Confidence intervals for amplitude and acrophase for population-mean cosinor use the methods described by Fernando et al 2004, which may not be applicable to multiple-components.") - - # Freedom by number of individuals - k <- nrow(object$xmat) - - # Standard errors - SE_mesor <- sd(xmat[, "mesor"]) / sqrt(k) - for(i in 1:p) { - assign(paste0("SE_amp", i), (sd(xmat[, paste0("amp", i)]) / sqrt(k))) - assign(paste0("SE_phi", i), (sd(xmat[, paste0("phi", i)]) / sqrt(k))) - assign(paste0("SE_beta", i), (sd(xmat[, paste0("beta", i)]) / sqrt(k))) - assign(paste0("SE_gamma", i), (sd(xmat[, paste0("gamma", i)]) / sqrt(k))) - } + object$type, + Population = { + # Message + message("Confidence intervals for amplitude and acrophase for population-mean cosinor use the methods described by Fernández et al. 2004.") - # Save SE - se <- list() - for(i in 1:p) { - se[[i]] <- c( - paste0("SE_amp", i), - paste0("SE_phi", i), - paste0("SE_beta", i), - paste0("SE_gamma", i) - ) - } - se <- c(SE_mesor, unlist(mget(unlist(se)))) - names(se)[1] <- "SE_mesor" - names(se) <- gsub("SE_", "", names(se)) + # Number of individuals + k <- nrow(xmat) - # Confidence intervals - tdist <- stats::qt(1 - a/2, df = n - k) - confints <- list() - for(i in 1:p) { - confints[[i]] <- - c( - # Amp - get(paste0("amp", i)) - tdist * get(paste0("SE_amp", i)), - get(paste0("amp", i)) + tdist * get(paste0("SE_amp", i)), - # Phi - get(paste0("phi", i)) - tdist * get(paste0("SE_phi", i)), - get(paste0("phi", i)) + tdist * get(paste0("SE_phi", i)) - ) - } + # Extract parameters from xmat + params <- as.data.frame(xmat) - df <- rbind( - c(mesor - tdist*SE_mesor, mesor + tdist*SE_mesor), - matrix(unlist(confints), ncol = 2, byrow = TRUE) - ) - rnames <- list() - for(i in 1:p) { - rnames[[i]] <- c(paste0("amp", i), paste0("phi", i)) - } - rownames(df) <- c("mesor", unlist(rnames)) - colnames(df) <- c(paste0(100*(a/2),"%"), paste0(100*(1-a/2), "%")) + # Convert column names to lowercase + colnames(params) <- tolower(colnames(params)) - # Returned - estimates <- list( - ci = df, - se = se - ) - return(estimates) + popParams <- colMeans(params) + varCovMat <- cov(params) + + # Degrees of freedom + df <- k - 1 - }, + # Critical value from chi-squared distribution + cValue <- qchisq(level, df = 2) + + # Initialize data structures for output + ciMatrix <- matrix(nrow = 0, ncol = 2) + colnames(ciMatrix) <- c( + sprintf("%.1f%%", a / 2 * 100), + sprintf("%.1f%%", (1 - a / 2) * 100) + ) + + seVector <- numeric() + names(seVector) <- character() + + # MESOR confidence interval + seMesor <- sqrt(varCovMat["mesor", "mesor"] / k) + tValue <- qt(1 - a / 2, df) + ciMesor <- c( + popParams["mesor"] - tValue * seMesor, + popParams["mesor"] + tValue * seMesor + ) + + # Add MESOR to outputs + ciMatrix <- rbind(ciMatrix, ciMesor) + rownames(ciMatrix)[nrow(ciMatrix)] <- "mesor" + + seVector <- c(seVector, seMesor) + names(seVector)[length(seVector)] <- "mesor" + + # For each component + for (i in 1:p) { + # Get beta and gamma parameter names + betaName <- paste0("beta", i) + gammaName <- paste0("gamma", i) + + # Extract individual estimates + beta_i <- params[[betaName]] + gamma_i <- params[[gammaName]] + + # Compute sample means + betaBar <- mean(beta_i) + gammaBar <- mean(gamma_i) + + # Compute covariance matrix for beta and gamma + covMat <- cov(cbind(beta_i, gamma_i)) + + # Eigen decomposition of covariance matrix + eig <- stats::eigen(covMat) + V <- eig$vectors + D <- diag(eig$values) + + # Square root of covariance matrix + rootCov <- V %*% sqrt(D) %*% t(V) + + # Generate ellipse points + thetaSeq <- seq(0, 2 * pi, length.out = 1000) + ellipsePoints <- sqrt(cValue) * (rootCov %*% rbind(cos(thetaSeq), sin(thetaSeq))) + + betaTheta <- betaBar + ellipsePoints[1, ] + gammaTheta <- gammaBar + ellipsePoints[2, ] + + # Compute amplitude and acrophase + # Make sure acrophase is within the unit circle + amplitudeTheta <- sqrt(betaTheta^2 + gammaTheta^2) + acrophaseTheta <- atan2(-gammaTheta, betaTheta) + acrophaseTheta <- (acrophaseTheta + pi) %% (2 * pi) - pi + + # Compute confidence intervals for amplitude and acrophase + ampLower <- min(amplitudeTheta) + ampUpper <- max(amplitudeTheta) + + phiLower <- min(acrophaseTheta) + phiUpper <- max(acrophaseTheta) + + # Compute standard errors for beta and gamma + seBeta <- sqrt(var(beta_i) / k) + seGamma <- sqrt(var(gamma_i) / k) + + # Compute t-value for beta and gamma + tValueBetaGamma <- qt(1 - alpha / 2, df) + + # Confidence intervals for beta and gamma + betaLower <- betaBar - tValueBetaGamma * seBeta + betaUpper <- betaBar + tValueBetaGamma * seBeta + + gammaLower <- gammaBar - tValueBetaGamma * seGamma + gammaUpper <- gammaBar + tValueBetaGamma * seGamma + + # Add beta to outputs + ciMatrix <- rbind(ciMatrix, c(betaLower, betaUpper)) + rownames(ciMatrix)[nrow(ciMatrix)] <- paste0("beta", i) + + seVector <- c(seVector, seBeta) + names(seVector)[length(seVector)] <- paste0("beta", i) + + # Add gamma to outputs + ciMatrix <- rbind(ciMatrix, c(gammaLower, gammaUpper)) + rownames(ciMatrix)[nrow(ciMatrix)] <- paste0("gamma", i) + + seVector <- c(seVector, seGamma) + names(seVector)[length(seVector)] <- paste0("gamma", i) + + # Add amplitude to outputs + ciMatrix <- rbind(ciMatrix, c(ampLower, ampUpper)) + rownames(ciMatrix)[nrow(ciMatrix)] <- paste0("amplitude", i) + + # Standard error for amplitude + amplitude_i <- sqrt(beta_i^2 + gamma_i^2) + seAmplitude <- sqrt(var(amplitude_i) / k) + seVector <- c(seVector, seAmplitude) + names(seVector)[length(seVector)] <- paste0("amplitude", i) + + # Add acrophase to outputs + ciMatrix <- rbind(ciMatrix, c(phiLower, phiUpper)) + rownames(ciMatrix)[nrow(ciMatrix)] <- paste0("acrophase", i) + + # Standard error for acrophase + acrophase_i <- atan2(-gamma_i, beta_i) + acrophase_i <- (acrophase_i + pi) %% (2 * pi) - pi + # Compute angular standard deviation + seAcrophase <- sqrt(-2 * log(abs(mean(exp(1i * acrophase_i))))) + seVector <- c(seVector, seAcrophase) + names(seVector)[length(seVector)] <- paste0("acrophase", i) + } + + # Name the columns of ciMatrix according to confidence levels + colnames(ciMatrix) <- c( + sprintf("%.1f%%", a / 2 * 100), + sprintf("%.1f%%", (1 - a / 2) * 100) + ) + + # Return the output as a list + return(list(ci = ciMatrix, se = seVector)) + }, Individual = { # Nummber of parameters @@ -467,6 +555,9 @@ confint.cosinor <- function(object, parm, level = 0.95, ...) { } + + + ## Zero Amplitude Test #' @title Zero Amplitude Test diff --git a/tests/testthat/test-cosinor-fit.R b/tests/testthat/test-cosinor-fit.R index 9723491..d55624e 100644 --- a/tests/testthat/test-cosinor-fit.R +++ b/tests/testthat/test-cosinor-fit.R @@ -13,7 +13,7 @@ test_that("models can be generally fit", { # Harmonic checks expect_gt(length(mcos$tau), 1) expect_equal(max(mcos$tau) %% min(mcos$tau), 0) - expect_message(cosinor_features(mcos)) + expect_warning(cosinor_features(mcos)) # Confidence intervals expect_type(confint(scos), "list") @@ -21,10 +21,11 @@ test_that("models can be generally fit", { test_that("population cosinors can be fit", { + # Single population cosinor f <- sDYX ~ hour data <- twins population = "patid" - cosinor(formula = f, data = data, tau = 24, population = population) + m <- cosinor(formula = f, data = data, tau = 24, population = population) }) diff --git a/tests/testthat/test-cosinor-plots.R b/tests/testthat/test-cosinor-plots.R index b68be0b..1ce6d88 100644 --- a/tests/testthat/test-cosinor-plots.R +++ b/tests/testthat/test-cosinor-plots.R @@ -2,7 +2,7 @@ test_that("ggcosinor makes a ggplot", { data("twins") scos <- cosinor(rDYX ~ hour, twins, 24) mcos <- cosinor(rDYX ~ hour, twins, c(24, 12)) - pcos <- cosinor(rDYX ~ hour, twins, 24, "patid") - g <- ggcosinor(mcos) + pcos <- expect_message(cosinor(rDYX ~ hour, twins, 24, "patid")) + g <- expect_warning(ggcosinor(mcos)) expect_s3_class(g, "ggplot") -}) \ No newline at end of file +})