From 6c4adde76389690c7827a999e2c311ed4f444bb1 Mon Sep 17 00:00:00 2001 From: "Anish S. Shah" Date: Sun, 13 Oct 2024 21:48:56 -0600 Subject: [PATCH] discovered the issue with population cosinor through a default return class from sapply in the chain, which should now be resolved, however need to test coefficients in additional dataframes #1 --- R/cosinor-constructor.R | 2 +- R/cosinor-fit.R | 57 +++++++++++++++++++++++-------- R/globals.R | 6 ---- tests/testthat/test-cosinor-fit.R | 33 +++++++++++++++++- 4 files changed, 76 insertions(+), 22 deletions(-) diff --git a/R/cosinor-constructor.R b/R/cosinor-constructor.R index e906308..3d1f8a9 100644 --- a/R/cosinor-constructor.R +++ b/R/cosinor-constructor.R @@ -172,7 +172,7 @@ cosinor_bridge <- function(processed, tau, population, data, ...) { } else if (length(population) == length(predictors)) { - # Modified function, usings cosinor_impl internally + # Modified function, using `cosinor_impl()` internally fit <- cosinor_pop_impl(predictors, outcomes, tau, population) type <- "Population" diff --git a/R/cosinor-fit.R b/R/cosinor-fit.R index 727c7e5..275099a 100644 --- a/R/cosinor-fit.R +++ b/R/cosinor-fit.R @@ -33,20 +33,31 @@ cosinor_impl <- function(predictors, outcomes, tau) { # y(t) = M + sum_j[beta_j * x_j + gamma_j * z_j] + error(t) # Number of parameters will be the number of taus - # e.g. single component = 3 components, where 3 = 2p + 1 (p = 1 component) + # e.g. single component = 3 components, where 3 = 2p + 1 (p = 1 component) p <- length(tau) + # Create null variables + mesor <- NULL + for (i in 1:p) { + assign(paste0("x", i), NULL) + assign(paste0("z", i), NULL) + assign(paste("amp", i), NULL) + assign(paste("phi", i), NULL) + assign(paste("beta", i), NULL) + assign(paste("gamma", i), NULL) + } + # Single parameters y <- outcomes t <- predictors n <- length(t) # Normal equation for 3 components - # Normal equations (where M, beta, gamma are the coefficients to solve for) - # sum(y) = M*n + beta*sum(x) + gamma*sum(z) - # sum(y*x) = M*sum(x) + beta*sum(x^2) + gamma*sum(x*z) - # sum(y*z) = M*sum(z) + beta*sum(x*z) + gamma*sum(z^2) - # d = Su (for single component, 3 equations with 3 unknowns) + # Normal equations (where M, beta, gamma are the coefficients to solve for) + # sum(y) = M*n + beta*sum(x) + gamma*sum(z) + # sum(y*x) = M*sum(x) + beta*sum(x^2) + gamma*sum(x*z) + # sum(y*z) = M*sum(z) + beta*sum(x*z) + gamma*sum(z^2) + # d = Su (for single component, 3 equations with 3 unknowns) # For multiple components, the matrix must be expanded @@ -160,6 +171,17 @@ cosinor_pop_impl <- function(predictors, outcomes, tau, population) { # Period p <- length(tau) # Number of parameters ... single cosinor ~ 2p + 1 = 3 + # Create null variables based on number of parameters + mesor <- NULL + for (i in 1:p) { + assign(paste0("x", i), NULL) + assign(paste0("z", i), NULL) + assign(paste("amp", i), NULL) + assign(paste("phi", i), NULL) + assign(paste("beta", i), NULL) + assign(paste("gamma", i), NULL) + } + # Create data frame for split/apply approach df <- na.omit(data.frame(predictors, outcomes, population)) @@ -193,15 +215,19 @@ cosinor_pop_impl <- function(predictors, outcomes, tau, population) { # Create matrix that we can apply cosinor to subgroups kCosinors <- with( df, - by(df, population, function(x) { - cosinor_impl(x$predictors, x$outcomes, tau) + by(df, population, function(.x) { + cosinor_impl(.x$predictors, .x$outcomes, tau) }) ) ### Coefficients # Fits of individual cosinors - kfits <- sapply(kCosinors, stats::fitted) + # Must be a data frame to have column names + kfits <- sapply(kCosinors, stats::fitted, USE.NAMES = TRUE) + if (inherits(kfits, "matrix")) { + kfits <- as.data.frame(kfits) + } fits <- data.frame( population = rep(names(kfits), sapply(kfits, length)), yhat = unlist(kfits) @@ -223,7 +249,10 @@ cosinor_pop_impl <- function(predictors, outcomes, tau, population) { assign(paste0("gamma", i), unname(coefs[paste0("gamma", i)])) # Amplitude - assign(paste0("amp", i), sqrt(get(paste0("beta", i))^2 + get(paste0("gamma", i))^2)) + # Currently using the population mean method + # Eventually will require implementation of trigonometric method + #assign(paste0("amp", i), sqrt(get(paste0("beta", i))^2 + get(paste0("gamma", i))^2)) + assign(paste0("amp", i), unname(coefs[paste0("amp", i)])) # Phi / acrophase sb <- sign(get(paste0("beta", i))) @@ -381,7 +410,7 @@ confint.cosinor <- function(object, parm, level = 0.95, ...) { covMat <- cov(cbind(beta_i, gamma_i)) # Eigen decomposition of covariance matrix - eig <- stats::eigen(covMat) + eig <- eigen(covMat) V <- eig$vectors D <- diag(eig$values) @@ -413,7 +442,7 @@ confint.cosinor <- function(object, parm, level = 0.95, ...) { seGamma <- sqrt(var(gamma_i) / k) # Compute t-value for beta and gamma - tValueBetaGamma <- qt(1 - alpha / 2, df) + tValueBetaGamma <- qt(1 - a / 2, df) # Confidence intervals for beta and gamma betaLower <- betaBar - tValueBetaGamma * seBeta @@ -448,7 +477,7 @@ confint.cosinor <- function(object, parm, level = 0.95, ...) { # Add acrophase to outputs ciMatrix <- rbind(ciMatrix, c(phiLower, phiUpper)) - rownames(ciMatrix)[nrow(ciMatrix)] <- paste0("acrophase", i) + rownames(ciMatrix)[nrow(ciMatrix)] <- paste0("phi", i) # Standard error for acrophase acrophase_i <- atan2(-gamma_i, beta_i) @@ -456,7 +485,7 @@ confint.cosinor <- function(object, parm, level = 0.95, ...) { # 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) + names(seVector)[length(seVector)] <- paste0("phi", i) } # Name the columns of ciMatrix according to confidence levels diff --git a/R/globals.R b/R/globals.R index 911544e..9e1d01e 100644 --- a/R/globals.R +++ b/R/globals.R @@ -37,12 +37,6 @@ utils::globalVariables(c( "models", "mesor", "x", - paste0("x", 1:3), - paste0("z", 1:3), - paste0("beta", 1:3), - paste0("gamma", 1:3), - paste0("amp", 1:3), - paste0("phi", 1:3), paste0("acro", 1:3), "glabs", "term", diff --git a/tests/testthat/test-cosinor-fit.R b/tests/testthat/test-cosinor-fit.R index d55624e..81efb27 100644 --- a/tests/testthat/test-cosinor-fit.R +++ b/tests/testthat/test-cosinor-fit.R @@ -25,7 +25,38 @@ test_that("population cosinors can be fit", { f <- sDYX ~ hour data <- twins population = "patid" - m <- cosinor(formula = f, data = data, tau = 24, population = population) + m1 <- cosinor(formula = f, data = data, tau = 24, population = population) + m2 <- cosinor(formula = f, data = data, tau = c(24, 12,8), population = population) +}) + + +test_that("kfits dataframe is appropriate for population mean", { + + df <- as.data.frame(matrix(NA,3120,3)) # data frame for data + names(df) <- c("time","subject", "HR") # variable names + t <- c(1:520) # time + df[,1] <- rep(1:520,6) # six subjects + df[,2] <- rep(1:6,520)[order(rep(1:6,520))] # time for each subject + set.seed(1) # seed for rnd + + # generates six different signals with some noise + for (i in 1:6){ + M <- rnorm(1, mean=70, sd=5) + A <- rnorm(1, mean=3, sd=0.1) + phi <- rnorm(1, mean=60, sd=10) + e <- rnorm(c(1:520), mean=0, sd=5) + hr.curve <- M + A*cos((2*pi/260)*t+phi)+e + df[520*(i-1)+(1:520),3] <- hr.curve + #print(plot(t,hr.curve)) + } + + formula <- HR ~ time + data <- df + tau <- 260 + population <- "subject" + # Was erroring in the past because of a sapply leading to a matrix + m <- cosinor(formula = formula, data = data, tau = tau, population = population) + expect_s3_class(m, "cosinor") })