Skip to content

Commit

Permalink
reorg and plotting
Browse files Browse the repository at this point in the history
  • Loading branch information
drmowinckels committed Feb 29, 2024
1 parent 1d00617 commit 55d0fea
Show file tree
Hide file tree
Showing 13 changed files with 293 additions and 101 deletions.
3 changes: 2 additions & 1 deletion .Rbuildignore
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
^freesurfer\.lme\.Rproj$
^neuromat\.Rproj$
^\.Rproj\.user$
^LICENSE\.md$
^README\.Rmd$
^\.github$
^_pkgdown\.yml$
^docs$
^pkgdown$
^man-roxygen$
26 changes: 17 additions & 9 deletions DESCRIPTION
Original file line number Diff line number Diff line change
@@ -1,18 +1,26 @@
Package: freesurfer.lme
Title: Create Freesurfer QDEC Files for Linear Mixed Effects Models
Package: neuromat
Title: Create Model Matrices for Neuroimaging Analyses
Version: 0.0.0.9000
Authors@R:
person("Athanasia Mo", "Mowinckel", , "a.m.mowinckel@psykologi.uio.no", role = c("aut", "cre"),
comment = c(ORCID = "0000-0002-5756-0223"))
Description: Running vertex-wise linear mixed effects models
in Freesurfer <https: https://surfer.nmr.mgh.harvard.edu/fswiki/LinearMixedEffectsModels> requires a specific file format. This package
provides functions to create these files from R.
person(
given = "Athanasia Mo",
family = "Mowinckel",
email = "a.m.mowinckel@psykologi.uio.no",
role = c("aut", "cre"),
comment = c(ORCID = "0000-0002-5756-0223"))
Description: Neuroimaging software often requires the specification
of model matrices to run analyses. These can be difficult to
correctly. This package helps neuroscientists to create such
This includes creating qdec-files for Freesurfer Linear Mixes
Effects models.
License: MIT + file LICENSE
Encoding: UTF-8
Roxygen: list(markdown = TRUE)
RoxygenNote: 7.2.3
Imports:
grDevices,
methods,
stats,
utils
URL: https://github.com/capro-uio/freesurfer.lme, http://www.capro.dev/freesurfer.lme/
BugReports: https://github.com/capro-uio/freesurfer.lme/issues
URL: https://github.com/capro-uio/neuromat, http://www.capro.dev/neuromat/
BugReports: https://github.com/capro-uio/neuromat/issues
9 changes: 8 additions & 1 deletion NAMESPACE
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
# Generated by roxygen2: do not edit by hand

export(make_qdec)
S3method(plot,qdec)
export(make_fs_qdec)
export(qdec)
export(scale_vec)
importFrom(grDevices,hcl.colors)
importFrom(methods,is)
importFrom(stats,heatmap)
importFrom(stats,model.matrix)
importFrom(utils,write.csv)
159 changes: 114 additions & 45 deletions R/qdec.R
Original file line number Diff line number Diff line change
@@ -1,13 +1,26 @@
#' Create a QDEC file
#' Create a Freesurfer qdec
#'
#' Create a Freesurfer qdec from a model formula,
#' utilising R's robust model formula syntax. The function
#' also scales continuous variables, and can keep the original
#' data in the output.
#'
#' @details
#' The \code{\link{formula}} should in all likelihood also
#' include the \code{-1} to remove the intercept, as this
#' will provide a matrix where all levels of factor variables
#' have their own binary column. This is necessary to follow
#' the steps from the Freesurfer documentation.
#'
#' It is highly recommended to have the base-id's for Freesurfer
#' in their own column, and request to have this remain
#' in the qdec by using the id-column's name in the
#' \code{keep_orig} argument.
#'
#' @param data data.frame, list or environment
#' containing the data. Neither matrix nor an array
#' will be accepted.
#' @param formula a model \code{\link{formula}} or
#' \code{\link{terms}} object. For simple qdec file,
#' with binary columns for all levels of a factor,
#' make sure to include a formula with \code{-1} to
#' remove the intercept.
#' @template formula
#' @param path an option file path to write qdec to csv
#' @param keep_orig logical or vector of column names,
#' to keep the original data in the output.
Expand All @@ -16,75 +29,131 @@
#' @return data.frame with model matrix and
#' scaled continuous variables.
#' @export
#' @importFrom stats model.matrix
#' @importFrom utils write.csv
#' @importFrom methods is
#'
#' @examples
#' cars <- mtcars
#' cars$cyl <- as.factor(cars$cyl)
#' cars$gear <- as.factor(cars$gear)
#'
#' make_qdec(cars, mpg ~ cyl + hp)
#' make_fs_qdec(cars, mpg ~ cyl + hp)
#'
#' # Remove the intercept, necessary to follow
#' # steps from Freesurfer docs
#' make_qdec(cars, mpg ~ -1 + cyl + hp)
#' make_qdec(cars, mpg ~ -1 + cyl + hp + gear)
#' make_fs_qdec(cars, mpg ~ -1 + cyl + hp)
#' make_fs_qdec(cars, mpg ~ -1 + cyl + hp + gear)
#'
#' # Keep the original data also in the output
#' make_qdec(cars, mpg ~ -1 + cyl + hp, keep_orig = TRUE)
make_qdec <- function(data, formula,
#' make_fs_qdec(cars, mpg ~ -1 + cyl + hp, keep_orig = TRUE)
#'
#' # Keep the original data of specific columns
#' # Use a character vector
#' make_fs_qdec(cars, mpg ~ -1 + cyl + hp, keep_orig = c("mpg", "gear"))
#'
make_fs_qdec <- function(data,
formula,
path = NULL,
keep_orig = FALSE) {
if(all(
is(keep_orig, "character") &
keep_orig == ""
)){
stop("`keep_orig` cannot be ''. Use TRUE/FALSE or the names of columns.", call. = FALSE)
}

qdec <- qdec(data, formula)
vars <- attr(qdec, "vars")

if(is(keep_orig, "character")){
vars <- keep_orig
}
data <- data[ , vars, drop = FALSE]

add_orig <- suppressWarnings(
any(keep_orig, is.character(keep_orig))
)
if(add_orig){
qdec <- cbind(qdec, data)
}

# write to path if requested
if(!is.null(path)){
write.csv(qdec, path, row.names = FALSE)
}

qdec
}

#' Freesurfer qdec constructor
#'
#' Creates a qdec matrix from a model formula.
#'
#' @param data input data.frame
#' @template formula
#'
#' @return
#' @export
#'
#' @examples
qdec <- function(data, formula){
# extract variable names from formula
vars <- all.vars(formula)

# reduce data
data <- data[,vars]

# create model matrix
mm <- stats::model.matrix(formula, data)
mm <- model.matrix(formula, data)
mm <- as.data.frame(mm)
mm <- mm[, !names(mm) %in% names(data)]

# scale continuous variables
cl <- sapply(data, class)
dataz <- data[,which(cl %in% "numeric")]
dataz <- apply(dataz, 2, scale_vec)
dataz <- as.data.frame(dataz, check.names = FALSE)
names(dataz) <- paste0(names(dataz), "z")
dataz <- scale_num_data(data, vars)

# combine model matrix and scaled data
qdec <- cbind(mm, dataz)
qdec <- qdec[, !names(qdec) %in% names(data)]

if(inherits(class(keep_orig), "character")){
data <- data[, keep_orig, drop = FALSE]
qdec <- cbind(qdec, data)
}else if(keep_orig){
qdec <- cbind(qdec, data)
}

# write to path if requested
if(!is.null(path)){
utils::write.csv(qdec, path, row.names = FALSE)
}

return(qdec)
structure(
qdec,
class = c("qdec", "tbl", "data.frame"),
formula = formula,
vars = vars
)
}


#' Scale a numeric vector
#' Plot qdec matrix
#'
#' @param x numeric vector to scale
#' @param ... additional arguments to be passed to \code{\link{scale}}
#' Visualise a Freesurfer qdec matrix as returned
#' byt the \code{\link{make_fs_qdec}} function.
#'
#' @return scaled vector
#' @param x a qdec object
#' @param col a vector of colors to be used in the heatmap.
#' @param ... arguments to be passed to \code{\link{heatmap}}.
#' \code{scale}, \code{Rowv}, \code{Colv} already have
#' custom values that may not be overwritten for the sake
#' of a better visualisation.
#'
#' @return a heatmap of the qdec matrix
#' @export
#' @keywords internal
#' @importFrom grDevices hcl.colors
#' @importFrom stats heatmap
#' @examples
#' scale_vec(1:20)
scale_vec <- function(x, ...) {
# Error if x has more than one dimension
if(!is.null(dim(x))){
stop("Input `x` must be a vector", call. = FALSE)
#' cars <- mtcars
#' cars$cyl <- as.factor(cars$cyl)
#'
#' qdec <- make_fs_qdec(cars, mpg ~ -1 + cyl + hp)
#' plot(qdec)
plot.qdec <- function(x,
col = hcl.colors(12, "viridis"),
...) {
if(!inherits(x, "qdec")){
stop("Input `x` must be a qdec object", call. = FALSE)
}
as.numeric(scale(x, ...))
x <- x[, !names(x) %in% attr(x, "vars")]
x <- as.matrix(x)
x <- x[dim(x)[1]:1, ]
heatmap(x, scale = "none",
Rowv = NA, Colv = NA,
col = col,
...)
}
27 changes: 27 additions & 0 deletions R/utils.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
#' Scale a numeric vector
#'
#' @param x numeric vector to scale
#' @param ... additional arguments to be passed to \code{\link{scale}}
#'
#' @return scaled vector
#' @export
#' @keywords internal
#' @examples
#' scale_vec(1:20)
scale_vec <- function(x, ...) {
# Error if x has more than one dimension
if(!is.null(dim(x))){
stop("Input `x` must be a vector", call. = FALSE)
}
as.numeric(scale(x, ...))
}

scale_num_data <- function(data, formula){
# scale continuous variables
cl <- sapply(data, class)
dataz <- data[,which(cl %in% "numeric")]
dataz <- apply(dataz, 2, scale_vec)
dataz <- as.data.frame(dataz, check.names = FALSE)
names(dataz) <- paste0(names(dataz), "z")
dataz
}
5 changes: 5 additions & 0 deletions man-roxygen/formula.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#' @param formula a model \code{\link{formula}} or
#' \code{\link{terms}} object. For simple qdec file,
#' with binary columns for all levels of a factor,
#' make sure to include a formula with \code{-1} to
#' remove the intercept.
Binary file added man/figures/README-example4-1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
67 changes: 67 additions & 0 deletions man/make_fs_qdec.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 55d0fea

Please sign in to comment.