Skip to content

Commit

Permalink
discovered the issue with population cosinor through a default return…
Browse files Browse the repository at this point in the history
… class from sapply in the chain, which should now be resolved, however need to test coefficients in additional dataframes #1
  • Loading branch information
Anish S. Shah committed Oct 14, 2024
1 parent 270ebe1 commit 6c4adde
Show file tree
Hide file tree
Showing 4 changed files with 76 additions and 22 deletions.
2 changes: 1 addition & 1 deletion R/cosinor-constructor.R
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand Down
57 changes: 43 additions & 14 deletions R/cosinor-fit.R
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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))

Expand Down Expand Up @@ -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)
Expand All @@ -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)))
Expand Down Expand Up @@ -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)

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -448,15 +477,15 @@ 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)
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)
names(seVector)[length(seVector)] <- paste0("phi", i)
}

# Name the columns of ciMatrix according to confidence levels
Expand Down
6 changes: 0 additions & 6 deletions R/globals.R
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
33 changes: 32 additions & 1 deletion tests/testthat/test-cosinor-fit.R
Original file line number Diff line number Diff line change
Expand Up @@ -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")

})

0 comments on commit 6c4adde

Please sign in to comment.