diff --git a/DESCRIPTION b/DESCRIPTION index 9c13ddb..3f16680 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -2,7 +2,7 @@ Package: SpatialExperiment Version: 1.11.2 Title: S4 Class for Spatially Resolved -omics Data Description: Defines an S4 class for storing data from spatial -omics experiments. - The class extends SingleCellExperiment to + The class extends SingleCellExperiment to support storage and retrieval of additional information from spot-based and molecule-based platforms, including spatial coordinates, images, and image metadata. A specialized constructor function is included for data diff --git a/R/AllGenerics.R b/R/AllGenerics.R index b4d9cbc..1024ca0 100644 --- a/R/AllGenerics.R +++ b/R/AllGenerics.R @@ -51,7 +51,7 @@ setGeneric("spatialDataNames<-", function(x, value) standardGeneric("spatialData setGeneric("spatialCoords", function(x, ...) standardGeneric("spatialCoords")) #' @export -setGeneric("spatialCoords<-", function(x, value) standardGeneric("spatialCoords<-")) +setGeneric("spatialCoords<-", function(x, ..., value) standardGeneric("spatialCoords<-")) #' @export setGeneric("spatialCoordsNames", function(x) standardGeneric("spatialCoordsNames")) diff --git a/R/SpatialExperiment-combine.R b/R/SpatialExperiment-cbind.R similarity index 95% rename from R/SpatialExperiment-combine.R rename to R/SpatialExperiment-cbind.R index 7cd18b3..e59aa78 100644 --- a/R/SpatialExperiment-combine.R +++ b/R/SpatialExperiment-cbind.R @@ -103,6 +103,11 @@ setMethod("cbind", "SpatialExperiment", function(..., deparse.level=1) { out <- do.call( callNextMethod, c(args, list(deparse.level=1))) + if (any(duplicated(colnames(out)))) { + n <- vapply(args, ncol, integer(1)) + n <- rep.int(seq_along(args), n) + colnames(out) <- paste(n, colnames(out), sep="_") + } # merge 'imgData' from multiple samples if (!is.null(imgData(args[[1]]))) { diff --git a/R/SpatialExperiment-methods.R b/R/SpatialExperiment-methods.R index 3091c3f..1648c9e 100644 --- a/R/SpatialExperiment-methods.R +++ b/R/SpatialExperiment-methods.R @@ -23,7 +23,14 @@ #' identifier(s) for \code{scaleFactors}. Default = \code{TRUE} (all samples). #' @param image_id Logical value or character vector specifying image #' identifier(s) for \code{scaleFactors}. Default = \code{TRUE} (all images). +#' @param withDimnames Logical value indicating whether dimnames of the +#' \code{spatialExperiment} should be applied or checked against. +#' If \code{withDimnames=TRUE}, non-\code{NULL} \code{rownames(value)} +#' are checked against \code{colnames(x)}, and an error occurs if these +#' don't match. Else, discrepancies in rownames are ignored. +#' (see also \code{\link[SingleCellExperiment]{reducedDims}}) #' @param name The name of the \code{colData} column to extract. +#' @param ... Further arguments passed to and from other methods. #' #' @details #' Additional details for each type of data attribute are provided below. @@ -190,69 +197,6 @@ setReplaceMethod("spatialDataNames", } ) -# spatialCoords ---------------------------------------------------------------- - -#' @rdname SpatialExperiment-methods -#' @importFrom SingleCellExperiment int_colData<- -#' @export -setMethod("spatialCoords", - "SpatialExperiment", - function(x) int_colData(x)$spatialCoords) - -#' @rdname SpatialExperiment-methods -#' @importFrom SingleCellExperiment int_colData<- -#' @export -setReplaceMethod("spatialCoords", - c("SpatialExperiment", "matrix"), - function(x, value) { - stopifnot( - is.numeric(value), - nrow(value) == ncol(x)) - int_colData(x)$spatialCoords <- value - return(x) - } -) - -#' @rdname SpatialExperiment-methods -#' @export -setReplaceMethod("spatialCoords", - c("SpatialExperiment", "NULL"), - function(x, value) { - value <- matrix(numeric(), ncol(x), 0) - `spatialCoords<-`(x, value) - } -) - -# spatialCoordsNames ----------------------------------------------------------- - -#' @rdname SpatialExperiment-methods -#' @importFrom SingleCellExperiment int_colData -#' @export -setMethod("spatialCoordsNames", - "SpatialExperiment", - function(x) colnames(int_colData(x)$spatialCoords)) - -#' @rdname SpatialExperiment-methods -#' @importFrom SingleCellExperiment int_colData<- -#' @export -setReplaceMethod("spatialCoordsNames", - c("SpatialExperiment", "character"), - function(x, value) { - colnames(int_colData(x)$spatialCoords) <- value - return(x) - } -) - -#' @rdname SpatialExperiment-methods -#' @export -setReplaceMethod("spatialCoordsNames", - c("SpatialExperiment", "NULL"), - function(x, value) { - value <- character() - `spatialCoordsNames<-`(x, value) - } -) - # scaleFactors ----------------------------------------------------------------- #' @rdname SpatialExperiment-methods diff --git a/R/read10xVisium.R b/R/read10xVisium.R index 0ef83d2..b21872c 100644 --- a/R/read10xVisium.R +++ b/R/read10xVisium.R @@ -195,13 +195,11 @@ read10xVisium <- function(samples="", cnms <- c( "barcode", "in_tissue", "array_row", "array_col", "pxl_row_in_fullres", "pxl_col_in_fullres") - df <- lapply(seq_along(x), function(i) - { - df <- read.csv(x[i], - header=!grepl("list", x[i]), - row.names=1, col.names=cnms) + df <- lapply(seq_along(x), function(i) { + df <- read.csv(x[i], header=!grepl("list", x[i]), col.names=cnms) if (length(x) > 1) rownames(df) <- paste(i, rownames(df), sep="_") if (!is.null(names(x))) cbind(sample_id=names(x)[i], df) + rownames(df) <- df$barcode df }) df <- do.call(rbind, df) diff --git a/R/spatialCoords.R b/R/spatialCoords.R new file mode 100644 index 0000000..c22c0ac --- /dev/null +++ b/R/spatialCoords.R @@ -0,0 +1,77 @@ + +# spatialCoords ---------------------------------------------------------------- + +#' @rdname SpatialExperiment-methods +#' @importFrom SingleCellExperiment int_colData<- +#' @export +setMethod("spatialCoords", + "SpatialExperiment", + function(x, withDimnames=TRUE, ...) { + out <- int_colData(x)$spatialCoords + if (withDimnames) + rownames(out) <- colnames(x) + return(out) + }) + +#' @rdname SpatialExperiment-methods +#' @importFrom SingleCellExperiment int_colData<- +#' @export +setReplaceMethod("spatialCoords", + c("SpatialExperiment", "matrix"), + function(x, withDimnames=TRUE, ..., value) { + stopifnot( + is.numeric(value), + nrow(value) == ncol(x)) + new <- rownames(value) + if (!is.null(new) && withDimnames) { + if (!identical(new, colnames(x))) { + stop("Non-NULL 'rownames(value)' should be the", + " same as 'colnames(x)' for 'spatialCoords<-'.", + " Use 'withDimnames=FALSE' to force replacement.") + } + } + int_colData(x)$spatialCoords <- value + return(x) + } +) + +#' @rdname SpatialExperiment-methods +#' @export +setReplaceMethod("spatialCoords", + c("SpatialExperiment", "NULL"), + function(x, withDimnames=TRUE, ..., value) { + `spatialCoords<-`(x, + withDimnames=withDimnames, ..., + value=matrix(numeric(), ncol(x), 0)) + } +) + +# spatialCoordsNames ----------------------------------------------------------- + +#' @rdname SpatialExperiment-methods +#' @importFrom SingleCellExperiment int_colData +#' @export +setMethod("spatialCoordsNames", + "SpatialExperiment", + function(x) colnames(int_colData(x)$spatialCoords)) + +#' @rdname SpatialExperiment-methods +#' @importFrom SingleCellExperiment int_colData<- +#' @export +setReplaceMethod("spatialCoordsNames", + c("SpatialExperiment", "character"), + function(x, value) { + colnames(int_colData(x)$spatialCoords) <- value + return(x) + } +) + +#' @rdname SpatialExperiment-methods +#' @export +setReplaceMethod("spatialCoordsNames", + c("SpatialExperiment", "NULL"), + function(x, value) { + value <- character() + `spatialCoordsNames<-`(x, value) + } +) diff --git a/inst/NEWS b/inst/NEWS index 977438f..f1f7683 100644 --- a/inst/NEWS +++ b/inst/NEWS @@ -8,6 +8,12 @@ changes in version 1.9.5 (2023-03-02) + bugfix for tissue positions read in incorrect order with read10xVisium() in datasets with multiple samples (bug introduced in version 1.7.1) +changes in version 1.9.4 (2022-11-24) ++ avoid duplicated colnames when cbinding SPEs ++ read10xVisium keeps original barcodes as colData ++ added withDimnames argument for spatialCoords/<- + (analogous to SCE's reducedDim(s)/<-) + changes in version 1.7.2 (2022-10-07) + support for seeing colData names with $ in RStudio diff --git a/man/SpatialExperiment-combine.Rd b/man/SpatialExperiment-combine.Rd index 976ea05..b44c2db 100644 --- a/man/SpatialExperiment-combine.Rd +++ b/man/SpatialExperiment-combine.Rd @@ -1,5 +1,5 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/SpatialExperiment-combine.R +% Please edit documentation in R/SpatialExperiment-cbind.R \name{SpatialExperiment-combine} \alias{SpatialExperiment-combine} \alias{cbind,SingleCellExperiment-method} diff --git a/man/SpatialExperiment-methods.Rd b/man/SpatialExperiment-methods.Rd index 965e3f9..5688283 100644 --- a/man/SpatialExperiment-methods.Rd +++ b/man/SpatialExperiment-methods.Rd @@ -1,5 +1,6 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/SpatialExperiment-methods.R, R/imgData.R +% Please edit documentation in R/SpatialExperiment-methods.R, R/imgData.R, +% R/spatialCoords.R \name{SpatialExperiment-methods} \alias{SpatialExperiment-methods} \alias{spatialData} @@ -19,17 +20,17 @@ \alias{spatialDataNames,SpatialExperiment-method} \alias{spatialDataNames<-,SpatialExperiment,character-method} \alias{spatialDataNames<-,SpatialExperiment,NULL-method} +\alias{scaleFactors,SpatialExperiment-method} +\alias{$,SpatialExperiment-method} +\alias{imgData,SpatialExperiment-method} +\alias{imgData<-,SpatialExperiment,DataFrame-method} +\alias{imgData<-,SpatialExperiment,NULL-method} \alias{spatialCoords,SpatialExperiment-method} \alias{spatialCoords<-,SpatialExperiment,matrix-method} \alias{spatialCoords<-,SpatialExperiment,NULL-method} \alias{spatialCoordsNames,SpatialExperiment-method} \alias{spatialCoordsNames<-,SpatialExperiment,character-method} \alias{spatialCoordsNames<-,SpatialExperiment,NULL-method} -\alias{scaleFactors,SpatialExperiment-method} -\alias{$,SpatialExperiment-method} -\alias{imgData,SpatialExperiment-method} -\alias{imgData<-,SpatialExperiment,DataFrame-method} -\alias{imgData<-,SpatialExperiment,NULL-method} \title{Methods for spatial attributes} \usage{ \S4method{spatialData}{SpatialExperiment}(x) @@ -44,27 +45,27 @@ \S4method{spatialDataNames}{SpatialExperiment,`NULL`}(x) <- value -\S4method{spatialCoords}{SpatialExperiment}(x) +\S4method{scaleFactors}{SpatialExperiment}(x, sample_id = TRUE, image_id = TRUE) -\S4method{spatialCoords}{SpatialExperiment,matrix}(x) <- value +\S4method{$}{SpatialExperiment}(x, name) -\S4method{spatialCoords}{SpatialExperiment,`NULL`}(x) <- value +\S4method{imgData}{SpatialExperiment}(x) -\S4method{spatialCoordsNames}{SpatialExperiment}(x) +\S4method{imgData}{SpatialExperiment,DataFrame}(x) <- value -\S4method{spatialCoordsNames}{SpatialExperiment,character}(x) <- value +\S4method{imgData}{SpatialExperiment,`NULL`}(x) <- value -\S4method{spatialCoordsNames}{SpatialExperiment,`NULL`}(x) <- value +\S4method{spatialCoords}{SpatialExperiment}(x, withDimnames = TRUE, ...) -\S4method{scaleFactors}{SpatialExperiment}(x, sample_id = TRUE, image_id = TRUE) +\S4method{spatialCoords}{SpatialExperiment,matrix}(x, withDimnames = TRUE, ...) <- value -\S4method{$}{SpatialExperiment}(x, name) +\S4method{spatialCoords}{SpatialExperiment,`NULL`}(x, withDimnames = TRUE, ...) <- value -\S4method{imgData}{SpatialExperiment}(x) +\S4method{spatialCoordsNames}{SpatialExperiment}(x) -\S4method{imgData}{SpatialExperiment,DataFrame}(x) <- value +\S4method{spatialCoordsNames}{SpatialExperiment,character}(x) <- value -\S4method{imgData}{SpatialExperiment,`NULL`}(x) <- value +\S4method{spatialCoordsNames}{SpatialExperiment,`NULL`}(x) <- value } \arguments{ \item{x}{A \code{\link{SpatialExperiment}} object.} @@ -78,6 +79,15 @@ identifier(s) for \code{scaleFactors}. Default = \code{TRUE} (all samples).} identifier(s) for \code{scaleFactors}. Default = \code{TRUE} (all images).} \item{name}{The name of the \code{colData} column to extract.} + +\item{withDimnames}{Logical value indicating whether dimnames of the +\code{spatialExperiment} should be applied or checked against. +If \code{withDimnames=TRUE}, non-\code{NULL} \code{rownames(value)} +are checked against \code{colnames(x)}, and an error occurs if these +don't match. Else, discrepancies in rownames are ignored. +(see also \code{\link[SingleCellExperiment]{reducedDims}})} + +\item{...}{Further arguments passed to and from other methods.} } \value{ Return value varies depending on method, as described below. diff --git a/tests/testthat/test_SpatialExperiment-cbind.R b/tests/testthat/test_SpatialExperiment-cbind.R index 5100a6c..762a23c 100644 --- a/tests/testthat/test_SpatialExperiment-cbind.R +++ b/tests/testthat/test_SpatialExperiment-cbind.R @@ -8,7 +8,6 @@ test_that("duplicated sample_ids are made unique with a message", { expect_true(nrow(new) == nrow(spe)) expect_true(ncol(new) == 2*ncol(spe)) expect_identical(rownames(new), rownames(spe)) - expect_setequal(colnames(new), colnames(spe)) }) test_that("imgData are combined correctly", { @@ -33,3 +32,15 @@ test_that("imgData are combined correctly", { expect_identical(imgData(spe3)[one, ], imgData(spe1)) expect_identical(imgData(spe3)[two, ], imgData(spe2)) }) + +test_that("unique colnames are left asis,", { + tmp <- spe + colnames(tmp) <- paste0(colnames(tmp), "x") + out <- cbind(spe, tmp) + expect_false(any(duplicated(colnames(out)))) +}) + +test_that("duplicated colnames are made unique", { + out <- cbind(spe, spe) + expect_false(any(duplicated(colnames(out)))) +}) \ No newline at end of file diff --git a/tests/testthat/test_SpatialExperiment-methods.R b/tests/testthat/test_SpatialExperiment-methods.R index ffe1b46..c863c05 100644 --- a/tests/testthat/test_SpatialExperiment-methods.R +++ b/tests/testthat/test_SpatialExperiment-methods.R @@ -45,28 +45,6 @@ test_that("spatialDataNames()<-,NULL", { expect_identical(new, character(0)) }) -test_that("spatialCoordsNames()", { - expect_identical( - spatialCoordsNames(spe), - colnames(int_colData(spe)$spatialCoords)) -}) - -test_that("spatialCoordsNames<-,character", { - old <- spatialCoordsNames(spe) - new <- sample(letters, length(old)) - spatialCoordsNames(spe) <- new - expect_identical(spatialCoordsNames(spe), new) - expect_identical(spatialCoordsNames(spe), - colnames(int_colData(spe)$spatialCoords)) -}) - -test_that("spatialCoordsNames<-,NULL", { - old <- spatialCoords(spe) - spatialCoordsNames(spe) <- NULL - expect_null(spatialCoordsNames(spe)) - expect_equivalent(spatialCoords(spe), old) -}) - test_that("scaleFactors()", { sfs <- scaleFactors(spe, sample_id=TRUE, image_id=TRUE) expect_is(sfs, "numeric") diff --git a/tests/testthat/test_SpatialExperiment-spatialCoords.R b/tests/testthat/test_SpatialExperiment-spatialCoords.R new file mode 100644 index 0000000..6f84597 --- /dev/null +++ b/tests/testthat/test_SpatialExperiment-spatialCoords.R @@ -0,0 +1,36 @@ +example(read10xVisium, echo = FALSE) + +test_that("spatialCoordsNames()", { + expect_identical( + spatialCoordsNames(spe), + colnames(int_colData(spe)$spatialCoords)) +}) + +test_that("spatialCoordsNames<-,character", { + old <- spatialCoordsNames(spe) + new <- sample(letters, length(old)) + spatialCoordsNames(spe) <- new + expect_identical(spatialCoordsNames(spe), new) + expect_identical(spatialCoordsNames(spe), + colnames(int_colData(spe)$spatialCoords)) +}) + +test_that("spatialCoordsNames<-,NULL", { + old <- spatialCoords(spe) + spatialCoordsNames(spe) <- NULL + expect_null(spatialCoordsNames(spe)) + expect_equivalent(spatialCoords(spe), old) +}) + +test_that("spatialCoords<-,matrix", { + # shuffle coordinates + xyz <- spatialCoords(spe) + xyz <- xyz[sample(ncol(spe)), ] + # rownames(value) != colnames(x) fails + expect_error(spatialCoords(spe) <- xyz) + # but passes with 'withDimnames=FALSE' + expect_silent(spatialCoords(spe, withDimnames=FALSE) <- xyz) + # no 'rownames(value)' passes + rownames(xyz) <- NULL + expect_silent(spatialCoords(spe) <- xyz) +})