diff --git a/.travis.yml b/.travis.yml
index ee6fa1db..2c2a458a 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -6,33 +6,35 @@ sudo: required
os:
- linux
+dist:
+ - xenial
+
r:
- - oldrel
- release
- devel
-env:
- global:
- - PATH="$HOME/miniconda2/bin:$HOME/miniconda3/bin:$PATH"
- - RETICULATE_PYTHON="$HOME/miniconda2/bin/python"
+cache:
+ - packages
+ - apt
+ - directories:
+ - $HOME/.cache/pip
before_install:
- - chmod +x travis_setup.sh; ./travis_setup.sh
- - $HOME/miniconda2/bin/pip install -q phate
- - $HOME/miniconda3/bin/pip install -q phate
- - R -e "install.packages(c('reticulate', 'devtools', 'readr', 'phateR', 'Matrix', 'ggplot2', 'viridis'), quiet=TRUE)"
+ - curl https://bootstrap.pypa.io/get-pip.py -o get-pip.py
+ - sudo python3 get-pip.py
+ - sudo pip3 install --upgrade pip
+ - pip3 install --user -q phate
install:
- - cd python; $HOME/miniconda2/bin/pip install -q .
- - $HOME/miniconda3/bin/pip install -q .
- - cd ../Rmagic; R CMD INSTALL .; cd ..
+ - cd python; pip3 install --user -q .; cd ..
+ - cd Rmagic; R -e 'install.packages("devtools", repos="http://cloud.r-project.org")'
+ - R -e "devtools::install_github(repo = 'satijalab/seurat', ref = 'release/3.0')"
+ - R -e 'devtools::install_deps(dep = T)'; cd ..
script:
- cd Rmagic; R CMD build .
- - travis_wait 30 R CMD check *tar.gz
- - cd ../python; $HOME/miniconda2/bin/pip install -q .[test,doc]
- - $HOME/miniconda3/bin/pip install -q .[test,doc]
- - python2 setup.py test
+ - R CMD check *tar.gz
+ - cd ../python; pip3 install --user -q .[test,doc]
- python3 setup.py test
- cd doc; make html
diff --git a/README.md b/README.md
index fc599f73..f99ef509 100644
--- a/README.md
+++ b/README.md
@@ -13,6 +13,15 @@ Markov Affinity-based Graph Imputation of Cells (MAGIC) is an algorithm for deno
MAGIC has been implemented in Python, Matlab, and R.
+#### To get started immediately, check out our tutorials:
+##### Python
+* [Epithelial-to-Mesenchymal Transition Tutorial](http://nbviewer.jupyter.org/github/KrishnaswamyLab/MAGIC/blob/master/python/tutorial_notebooks/emt_tutorial.ipynb)
+* [Bone Marrow Tutorial](http://nbviewer.jupyter.org/github/KrishnaswamyLab/MAGIC/blob/master/python/tutorial_notebooks/bonemarrow_tutorial.ipynb)
+##### R
+* [Epithelial-to-Mesenchymal Transition Tutorial](http://htmlpreview.github.io/?https://github.com/KrishnaswamyLab/MAGIC/blob/master/Rmagic/inst/examples/emt_tutorial.html)
+* [Bone Marrow Tutorial](http://htmlpreview.github.io/?https://github.com/KrishnaswamyLab/MAGIC/blob/master/Rmagic/inst/examples/bonemarrow_tutorial.html)
+
+
diff --git a/Rmagic/DESCRIPTION b/Rmagic/DESCRIPTION
index 817e8d63..2f19b0d1 100644
--- a/Rmagic/DESCRIPTION
+++ b/Rmagic/DESCRIPTION
@@ -16,10 +16,11 @@ Imports:
reticulate (>= 1.4),
ggplot2
Suggests:
- readr,
+ Seurat (>= 3.0.0),
+ readr,
viridis,
- phateR
+ phateR
License: GPL-2 | file LICENSE
LazyData: true
-RoxygenNote: 6.1.0
+RoxygenNote: 6.1.1
Encoding: UTF-8
diff --git a/Rmagic/NAMESPACE b/Rmagic/NAMESPACE
index 6185b2be..8614b652 100644
--- a/Rmagic/NAMESPACE
+++ b/Rmagic/NAMESPACE
@@ -3,6 +3,9 @@
S3method(as.data.frame,magic)
S3method(as.matrix,magic)
S3method(ggplot,magic)
+S3method(magic,Seurat)
+S3method(magic,default)
+S3method(magic,seurat)
S3method(print,magic)
S3method(summary,magic)
export(install.magic)
diff --git a/Rmagic/R/magic.R b/Rmagic/R/magic.R
index 85ddddf0..def8c2b6 100644
--- a/Rmagic/R/magic.R
+++ b/Rmagic/R/magic.R
@@ -37,6 +37,7 @@
#' For n_jobs below -1, (n.cpus + 1 + n.jobs) are used. Thus for
#' n_jobs = -2, all CPUs but one are used
#' @param seed int or `NULL`, random state (default: `NULL`)
+#' @param ... Arguments passed to and from other methods
#'
#' @return If a Seurat object is passed, a Seurat object is returned. Otherwise, a "magic" object containing:
#' * **result**: matrix containing smoothed expression values
@@ -74,126 +75,222 @@
#' }
#'
#' @export
-magic <- function(data,
- genes=NULL,
- k = 10,
- alpha = 15,
- t = 'auto',
- npca=100,
- init=NULL,
- t.max=20,
- knn.dist.method='euclidean',
- verbose=1,
- n.jobs=1,
- seed=NULL) {
+#'
+magic <- function(data, ...) {
+ UseMethod(generic = 'magic', object = data)
+}
+
+#' @rdname magic
+#' @export
+#'
+magic.default <- function(
+ data,
+ genes = NULL,
+ k = 10,
+ alpha = 15,
+ t = 'auto',
+ npca = 100,
+ init = NULL,
+ t.max = 20,
+ knn.dist.method = 'euclidean',
+ verbose = 1,
+ n.jobs = 1,
+ seed = NULL,
+ ...
+) {
# check installation
if (!reticulate::py_module_available(module = "magic")) {
load_pymagic()
}
- tryCatch(pymagic, error = function(e) load_pymagic())
- k <- as.integer(k)
- t.max <- as.integer(t.max)
- n.jobs <- as.integer(n.jobs)
-
- if (is.numeric(npca)) {
- npca <- as.integer(npca)
- } else if (!is.null(npca) && is.na(npca)) {
+ tryCatch(expr = pymagic, error = function(e) load_pymagic())
+ k <- as.integer(x = k)
+ t.max <- as.integer(x = t.max)
+ n.jobs <- as.integer(x = n.jobs)
+ if (is.numeric(x = npca)) {
+ npca <- as.integer(x = npca)
+ } else if (!is.null(x = npca) && is.na(x = npca)) {
npca <- NULL
}
- if (is.numeric(alpha)) {
- alpha <- as.double(alpha)
- } else if (!is.null(alpha) && is.na(alpha)) {
+ if (is.numeric(x = alpha)) {
+ alpha <- as.double(x = alpha)
+ } else if (!is.null(x = alpha) && is.na(x = alpha)) {
alpha <- NULL
}
- if (is.numeric(t)) {
- t <- as.integer(t)
- } else if (is.null(t) || is.na(t)) {
+ if (is.numeric(x = t)) {
+ t <- as.integer(x = t)
+ } else if (is.null(x = t) || is.na(x = t)) {
t <- 'auto'
}
- if (is.numeric(seed)) {
- seed <- as.integer(seed)
- } else if (!is.null(seed) && is.na(seed)) {
+ if (is.numeric(x = seed)) {
+ seed <- as.integer(x = seed)
+ } else if (!is.null(x = seed) && is.na(x = seed)) {
seed <- NULL
}
- if (is.numeric(verbose)) {
- verbose <- as.integer(verbose)
+ if (is.numeric(x = verbose)) {
+ verbose <- as.integer(x = verbose)
}
- use_seurat <- FALSE
- if (methods::is(data, "seurat")) {
- seurat_obj <- data
- use_seurat <- TRUE
- data <- t(data@data)
- } else if (!methods::is(data, "Matrix")) {
- data <- as.matrix(data)
+ if (!methods::is(object = data, "Matrix")) {
+ data <- as.matrix(x = data)
}
- if (is.null(genes) || is.na(genes)) {
+ if (is.null(x = genes) || is.na(x = genes)) {
genes <- NULL
- gene_names <- colnames(data)
- } else if (is.numeric(genes)) {
- gene_names <- colnames(data)[genes]
- genes <- as.integer(genes - 1)
- } else if (length(genes) == 1 && genes == "all_genes") {
- gene_names <- colnames(data)
- } else if (length(genes) == 1 && genes == "pca_only") {
+ gene_names <- colnames(x = data)
+ } else if (is.numeric(x = genes)) {
+ gene_names <- colnames(x = data)[genes]
+ genes <- as.integer(x = genes - 1)
+ } else if (length(x = genes) == 1 && genes == "all_genes") {
+ gene_names <- colnames(x = data)
+ } else if (length(x = genes) == 1 && genes == "pca_only") {
gene_names <- paste0("PC", 1:npca)
} else {
# character vector
- if (!all(genes %in% colnames(data))) {
- warning(paste0("Genes ", genes[!(genes %in% colnames(data))],
- " not found.", collapse=", "))
+ if (!all(genes %in% colnames(x = data))) {
+ warning(paste0("Genes ", genes[!(genes %in% colnames(data))], " not found.", collapse = ", "))
}
- genes <- which(colnames(data) %in% genes)
- gene_names <- colnames(data)[genes]
- genes <- as.integer(genes - 1)
+ genes <- which(x = colnames(x = data) %in% genes)
+ gene_names <- colnames(x = data)[genes]
+ genes <- as.integer(x = genes - 1)
}
-
# store parameters
- params <- list("data" = data, "k" = k, "alpha" = alpha, "t" = t,
- "npca" = npca, "knn.dist.method" = knn.dist.method)
+ params <- list(
+ "data" = data,
+ "k" = k,
+ "alpha" = alpha,
+ "t" = t,
+ "npca" = npca,
+ "knn.dist.method" = knn.dist.method
+ )
# use pre-initialized values if given
operator <- NULL
- if (!is.null(init)) {
+ if (!is.null(x = init)) {
if (!methods::is(init, "magic")) {
warning("object passed to init is not a phate object")
} else {
operator <- init$operator
- operator$set_params(k = k,
- a = alpha,
- t = t,
- n_pca = npca,
- knn_dist = knn.dist.method,
- n_jobs = n.jobs,
- random_state = seed,
- verbose = verbose)
+ operator$set_params(
+ k = k,
+ a = alpha,
+ t = t,
+ n_pca = npca,
+ knn_dist = knn.dist.method,
+ n_jobs = n.jobs,
+ random_state = seed,
+ verbose = verbose
+ )
}
}
- if (is.null(operator)) {
- operator <- pymagic$MAGIC(k = k,
- a = alpha,
- t = t,
- n_pca = npca,
- knn_dist = knn.dist.method,
- n_jobs = n.jobs,
- random_state = seed,
- verbose = verbose)
- }
- result <- operator$fit_transform(data,
- genes = genes,
- t_max = t.max)
- colnames(result) <- gene_names
- rownames(result) <- rownames(data)
- if (use_seurat) {
- seurat_obj@data <- t(result)
- return(seurat_obj)
- } else {
- result <- as.data.frame(result)
- result <- list("result" = result, "operator" = operator,
- "params" = params)
- class(result) <- c("magic", "list")
- return(result)
+ if (is.null(x = operator)) {
+ operator <- pymagic$MAGIC(
+ k = k,
+ a = alpha,
+ t = t,
+ n_pca = npca,
+ knn_dist = knn.dist.method,
+ n_jobs = n.jobs,
+ random_state = seed,
+ verbose = verbose
+ )
}
+ result <- operator$fit_transform(
+ data,
+ genes = genes,
+ t_max = t.max
+ )
+ colnames(x = result) <- gene_names
+ rownames(x = result) <- rownames(data)
+ result <- as.data.frame(x = result)
+ result <- list(
+ "result" = result,
+ "operator" = operator,
+ "params" = params
+ )
+ class(x = result) <- c("magic", "list")
+ return(result)
}
+#' @rdname magic
+#' @export
+#' @method magic seurat
+#'
+magic.seurat <- function(
+ data,
+ genes = NULL,
+ k = 10,
+ alpha = 15,
+ t = 'auto',
+ npca = 100,
+ init = NULL,
+ t.max = 20,
+ knn.dist.method = 'euclidean',
+ verbose = 1,
+ n.jobs = 1,
+ seed = NULL,
+ ...
+) {
+ results <- magic(
+ data = as.matrix(x = t(x = data@data)),
+ genes = genes,
+ k = k,
+ alpha = alpha,
+ t = t,
+ npca = npca,
+ init = init,
+ t.max = t.max,
+ knn.dist.method = knn.dist.method,
+ verbose = verbose,
+ n.jobs = n.jobs,
+ seed = seed
+ )
+ data@data <- t(x = as.matrix(x = results$result))
+ return(data)
+}
+
+#' @param assay Assay to use for imputation, defaults to the default assay
+#'
+#' @rdname magic
+#' @export
+#' @method magic Seurat
+#'
+magic.Seurat <- function(
+ data,
+ assay = NULL,
+ genes = NULL,
+ k = 10,
+ alpha = 15,
+ t = 'auto',
+ npca = 100,
+ init = NULL,
+ t.max = 20,
+ knn.dist.method = 'euclidean',
+ verbose = 1,
+ n.jobs = 1,
+ seed = NULL,
+ ...
+) {
+ if (!requireNamespace(package = 'Seurat', quietly = TRUE)) {
+ stop("Please install Seurat v3 to run MAGIC on new Seurat objects")
+ }
+ if (is.null(x = assay)) {
+ assay <- Seurat::DefaultAssay(object = data)
+ }
+ results <- magic(
+ data = t(x = Seurat::GetAssayData(object = data, slot = 'data', assay = assay)),
+ genes = genes,
+ k = k,
+ alpha = alpha,
+ t = t,
+ npca = npca,
+ init = init,
+ t.max = t.max,
+ knn.dist.method = knn.dist.method,
+ verbose = verbose,
+ n.jobs = n.jobs,
+ seed = seed
+ )
+ data[[paste0('MAGIC_', assay)]] <- Seurat::CreateAssayObject(data = t(x = as.matrix(x = results$result)))
+ Seurat::Tool(object = data) <- results[c('operator', 'params')]
+ return(data)
+}
#' Print a MAGIC object
#'
diff --git a/Rmagic/R/utils.R b/Rmagic/R/utils.R
index 8ae098a2..81b87f29 100644
--- a/Rmagic/R/utils.R
+++ b/Rmagic/R/utils.R
@@ -10,18 +10,25 @@ null_equal <- function(x, y) {
}
load_pymagic <- function(delay_load = FALSE) {
+ # load
if (is.null(pymagic)) {
+ # first time load
result <- try(pymagic <<- reticulate::import("magic", delay_load = delay_load))
} else {
+ # already loaded
result <- try(reticulate::import("magic", delay_load = delay_load))
}
+ # check
if (methods::is(result, "try-error")) {
+ # failed load
if ((!delay_load) && length(grep("ModuleNotFoundError: No module named 'magic'", result)) > 0 ||
length(grep("ImportError: No module named magic", result)) > 0) {
+ # not installed
if (utils::menu(c("Yes", "No"), title="Install MAGIC Python package with reticulate?") == 1) {
install.magic()
}
} else if (length(grep("r\\-reticulate", reticulate::py_config()$python)) > 0) {
+ # installed, but envs sometimes give weird results
message("Consider removing the 'r-reticulate' environment by running:")
if (grep("virtualenvs", reticulate::py_config()$python)) {
message("reticulate::virtualenv_remove('r-reticulate')")
@@ -29,6 +36,18 @@ load_pymagic <- function(delay_load = FALSE) {
message("reticulate::conda_remove('r-reticulate')")
}
}
+ } else if (!delay_load) {
+ # successful load
+ version <- strsplit(pymagic$`__version__`, '\\.')[[1]]
+ major_version <- 1
+ minor_version <- 4
+ if (as.integer(version[1]) < major_version) {
+ stop(paste0("Python MAGIC version ", pymagic$`__version__`, " is out of date (recommended: ",
+ major_version, ".", minor_version, "). Please update with pip or Rmagic::install.magic()."))
+ } else if (as.integer(version[2]) < minor_version) {
+ warning(paste0("Python MAGIC version ", pymagic$`__version__`, " is out of date (recommended: ",
+ major_version, ".", minor_version, "). Consider updating with pip or Rmagic::install.magic()."))
+ }
}
}
diff --git a/Rmagic/man/magic.Rd b/Rmagic/man/magic.Rd
index 092079f6..68cf2756 100644
--- a/Rmagic/man/magic.Rd
+++ b/Rmagic/man/magic.Rd
@@ -2,15 +2,32 @@
% Please edit documentation in R/magic.R
\name{magic}
\alias{magic}
+\alias{magic.default}
+\alias{magic.seurat}
+\alias{magic.Seurat}
\title{Perform MAGIC on a data matrix}
\usage{
-magic(data, genes = NULL, k = 10, alpha = 15, t = "auto",
- npca = 100, init = NULL, t.max = 20,
+magic(data, ...)
+
+\method{magic}{default}(data, genes = NULL, k = 10, alpha = 15,
+ t = "auto", npca = 100, init = NULL, t.max = 20,
+ knn.dist.method = "euclidean", verbose = 1, n.jobs = 1,
+ seed = NULL, ...)
+
+\method{magic}{seurat}(data, genes = NULL, k = 10, alpha = 15,
+ t = "auto", npca = 100, init = NULL, t.max = 20,
+ knn.dist.method = "euclidean", verbose = 1, n.jobs = 1,
+ seed = NULL, ...)
+
+\method{magic}{Seurat}(data, assay = NULL, genes = NULL, k = 10,
+ alpha = 15, t = "auto", npca = 100, init = NULL, t.max = 20,
knn.dist.method = "euclidean", verbose = 1, n.jobs = 1,
- seed = NULL)
+ seed = NULL, ...)
}
\arguments{
-\item{data}{input data matrix}
+\item{data}{input data matrix or Seurat object}
+
+\item{...}{Arguments passed to and from other methods}
\item{genes}{character or integer vector, default: NULL
vector of column names or column indices for which to return smoothed data
@@ -53,6 +70,14 @@ For n_jobs below -1, (n.cpus + 1 + n.jobs) are used. Thus for
n_jobs = -2, all CPUs but one are used}
\item{seed}{int or `NULL`, random state (default: `NULL`)}
+
+\item{assay}{Assay to use for imputation, defaults to the default assay}
+}
+\value{
+If a Seurat object is passed, a Seurat object is returned. Otherwise, a "magic" object containing:
+ * **result**: matrix containing smoothed expression values
+ * **operator**: The MAGIC operator (python magic.MAGIC object)
+ * **params**: Parameters passed to magic
}
\description{
Markov Affinity-based Graph Imputation of Cells (MAGIC) is an
diff --git a/Rmagic/tests/test_magic.R b/Rmagic/tests/test_magic.R
index b3cb5088..88132a3d 100644
--- a/Rmagic/tests/test_magic.R
+++ b/Rmagic/tests/test_magic.R
@@ -28,10 +28,11 @@ test_magic <- function() {
test_seurat <- function() {
data(magic_testdata)
- seurat_obj <- Seurat::CreateSeuratObject(raw.data=t(magic_testdata))
+ # seurat_obj <- Seurat::CreateSeuratObject(raw.data=t(magic_testdata))
+ seurat_obj <- Seurat::CreateSeuratObject(counts = t(x = magic_testdata))
# run MAGIC
- data_MAGIC <- magic(magic_testdata, seed = 42)
- seurat_MAGIC <- magic(seurat_obj, seed = 42)
- stopifnot(all(data_MAGIC$result == t(seurat_MAGIC@data)))
+ data_MAGIC <- magic(data = magic_testdata, seed = 42)
+ seurat_obj <- magic(data = seurat_obj, seed = 42)
+ stopifnot(all(data_MAGIC$result == t(x = Seurat::GetAssayData(object = seurat_obj, slot = 'data', assay = 'MAGIC'))))
}
diff --git a/python/README.rst b/python/README.rst
index cdb6546c..08cb5ac3 100644
--- a/python/README.rst
+++ b/python/README.rst
@@ -50,7 +50,7 @@ Installation with pip
To install with ``pip``, run the following from a terminal::
- pip install --user git+git://github.com/KrishnaswamyLab/MAGIC.git#subdirectory=python
+ pip install --user magic-impute
Installation from GitHub
------------------------
diff --git a/python/doc/source/requirements.txt b/python/doc/source/requirements.txt
index d61c6428..a86ec7c5 100644
--- a/python/doc/source/requirements.txt
+++ b/python/doc/source/requirements.txt
@@ -4,7 +4,7 @@ pandas>=0.21.0
scipy>=1.1.0
matplotlib>=2.0.1
future
-graphtools>=0.1.8
-scprep>=0.7.1
+graphtools>=1.0.0
+scprep>=0.10.0
sphinx
sphinxcontrib-napoleon
diff --git a/python/magic/magic.py b/python/magic/magic.py
index f8dcfe66..7393dd5a 100644
--- a/python/magic/magic.py
+++ b/python/magic/magic.py
@@ -41,10 +41,10 @@ class MAGIC(BaseEstimator):
Parameters
----------
- k : int, optional, default: 10
+ knn : int, optional, default: 10
number of nearest neighbors on which to build kernel
- a : int, optional, default: 15
+ decay : int, optional, default: 15
sets decay rate of kernel tails.
If None, alpha decaying kernel is not used
@@ -60,9 +60,9 @@ class MAGIC(BaseEstimator):
roughly log(n_samples) time.
knn_dist : string, optional, default: 'euclidean'
- recommended values: 'euclidean', 'cosine'
- Any metric from `scipy.spatial.distance` can be used
- distance metric for building kNN graph.
+ Distance metric for building kNN graph. Recommended values:
+ 'euclidean', 'cosine'. Any metric from `scipy.spatial.distance` can be
+ used. Custom distance functions of form `f(x, y) = d` are also accepted
n_jobs : integer, optional, default: 1
The number of jobs to use for the computation.
@@ -79,6 +79,10 @@ class MAGIC(BaseEstimator):
verbose : `int` or `boolean`, optional (default: 1)
If `True` or `> 0`, print status messages
+ k : Deprecated for `knn`
+
+ a : Deprecated for `decay`
+
Attributes
----------
@@ -126,11 +130,15 @@ class MAGIC(BaseEstimator):
`Cell `__.
"""
- def __init__(self, k=10, a=15, t='auto', n_pca=100,
+ def __init__(self, knn=10, decay=15, t='auto', n_pca=100,
knn_dist='euclidean', n_jobs=1, random_state=None,
- verbose=1):
- self.k = k
- self.a = a
+ verbose=1, k=None, a=None):
+ if k is not None:
+ knn = k
+ if a is not None:
+ decay = a
+ self.knn = knn
+ self.decay = decay
self.t = t
self.n_pca = n_pca
self.knn_dist = knn_dist
@@ -166,8 +174,8 @@ def _check_params(self):
------
ValueError : unacceptable choice of parameters
"""
- utils.check_positive(k=self.k)
- utils.check_int(k=self.k,
+ utils.check_positive(knn=self.knn)
+ utils.check_int(knn=self.knn,
n_jobs=self.n_jobs)
# TODO: epsilon
utils.check_between(v_min=0,
@@ -175,16 +183,17 @@ def _check_params(self):
utils.check_if_not(None, utils.check_positive, utils.check_int,
n_pca=self.n_pca)
utils.check_if_not(None, utils.check_positive,
- a=self.a)
+ decay=self.decay)
utils.check_if_not('auto', utils.check_positive, utils.check_int,
t=self.t)
- utils.check_in(['euclidean', 'cosine', 'correlation',
- 'cityblock', 'l1', 'l2', 'manhattan', 'braycurtis',
- 'canberra', 'chebyshev', 'dice', 'hamming', 'jaccard',
- 'kulsinski', 'mahalanobis', 'matching', 'minkowski',
- 'rogerstanimoto', 'russellrao', 'seuclidean',
- 'sokalmichener', 'sokalsneath', 'sqeuclidean', 'yule'],
- knn_dist=self.knn_dist)
+ if not callable(self.knn_dist):
+ utils.check_in(['euclidean', 'cosine', 'correlation',
+ 'cityblock', 'l1', 'l2', 'manhattan', 'braycurtis',
+ 'canberra', 'chebyshev', 'dice', 'hamming', 'jaccard',
+ 'kulsinski', 'mahalanobis', 'matching', 'minkowski',
+ 'rogerstanimoto', 'russellrao', 'seuclidean',
+ 'sokalmichener', 'sokalsneath', 'sqeuclidean', 'yule'],
+ knn_dist=self.knn_dist)
def _set_graph_params(self, **params):
try:
@@ -202,10 +211,10 @@ def set_params(self, **params):
Parameters
----------
- k : int, optional, default: 10
+ knn : int, optional, default: 10
number of nearest neighbors on which to build kernel
- a : int, optional, default: 15
+ decay : int, optional, default: 15
sets decay rate of kernel tails.
If None, alpha decaying kernel is not used
@@ -240,6 +249,10 @@ def set_params(self, **params):
verbose : `int` or `boolean`, optional (default: 1)
If `True` or `> 0`, print status messages
+ k : Deprecated for `knn`
+
+ a : Deprecated for `decay`
+
Returns
-------
self
@@ -253,14 +266,22 @@ def set_params(self, **params):
del params['t']
# kernel parameters
- if 'k' in params and params['k'] != self.k:
- self.k = params['k']
+ if 'k' in params and params['k'] != self.knn:
+ self.knn = params['k']
reset_kernel = True
del params['k']
- if 'a' in params and params['a'] != self.a:
- self.a = params['a']
+ if 'a' in params and params['a'] != self.decay:
+ self.decay = params['a']
reset_kernel = True
del params['a']
+ if 'knn' in params and params['knn'] != self.knn:
+ self.knn = params['knn']
+ reset_kernel = True
+ del params['knn']
+ if 'decay' in params and params['decay'] != self.decay:
+ self.decay = params['decay']
+ reset_kernel = True
+ del params['decay']
if 'n_pca' in params and params['n_pca'] != self.n_pca:
self.n_pca = params['n_pca']
reset_kernel = True
@@ -318,6 +339,9 @@ def fit(self, X, graph=None):
else:
n_pca = self.n_pca
+ tasklogger.log_info("Running MAGIC on {} cells and {} genes.".format(
+ X.shape[0], X.shape[1]))
+
if graph is None:
graph = self.graph
if self.X is not None and not \
@@ -332,7 +356,7 @@ def fit(self, X, graph=None):
elif graph is not None:
try:
graph.set_params(
- decay=self.a, knn=self.k + 1, distance=self.knn_dist,
+ decay=self.decay, knn=self.knn, distance=self.knn_dist,
n_jobs=self.n_jobs, verbose=self.verbose, n_pca=n_pca,
thresh=1e-4, random_state=self.random_state)
except ValueError as e:
@@ -341,7 +365,7 @@ def fit(self, X, graph=None):
"Reset graph due to {}".format(str(e)))
graph = None
else:
- self.k = graph.knn - 1
+ self.knn = graph.knn
self.alpha = graph.decay
self.n_pca = graph.n_pca
self.knn_dist = graph.distance
@@ -363,8 +387,8 @@ def fit(self, X, graph=None):
self.graph = graphtools.Graph(
X,
n_pca=n_pca,
- knn=self.k + 1,
- decay=self.a,
+ knn=self.knn,
+ decay=self.decay,
thresh=1e-4,
n_jobs=self.n_jobs,
verbose=self.verbose,
@@ -440,7 +464,7 @@ def transform(self, X=None, genes=None, t_max=20,
"using this method.")
store_result = True
- if X is not None and not utils.matrix_is_equivalent(X, self.X):
+ if X is not None and not utils.matrix_is_equivalent(X, self.graph.data):
store_result = False
graph = graphtools.base.Data(X, n_pca=self.n_pca)
warnings.warn(UserWarning, "Running MAGIC.transform on different "
diff --git a/python/magic/utils.py b/python/magic/utils.py
index 654bb83d..55593269 100644
--- a/python/magic/utils.py
+++ b/python/magic/utils.py
@@ -1,6 +1,8 @@
import numbers
import numpy as np
import pandas as pd
+import scprep
+from scipy import sparse
try:
import anndata
except (ImportError, SyntaxError):
@@ -107,8 +109,22 @@ def matrix_is_equivalent(X, Y):
"""
Checks matrix equivalence with numpy, scipy and pandas
"""
- return X is Y or (isinstance(X, Y.__class__) and X.shape == Y.shape and
- np.sum((X != Y).sum()) == 0)
+ if X is Y:
+ return True
+ elif X.shape == Y.shape:
+ if sparse.issparse(X) or sparse.issparse(Y):
+ X = scprep.utils.to_array_or_spmatrix(X)
+ Y = scprep.utils.to_array_or_spmatrix(Y)
+ elif isinstance(X, pd.DataFrame) and isinstance(Y, pd.DataFrame):
+ return np.all(X == Y)
+ elif not (sparse.issparse(X) and sparse.issparse(Y)):
+ X = scprep.utils.toarray(X)
+ Y = scprep.utils.toarray(Y)
+ return np.allclose(X, Y)
+ else:
+ return np.allclose((X - Y).data, 0)
+ else:
+ return False
def convert_to_same_format(data, target_data, columns=None, prevent_sparse=False):
diff --git a/python/magic/version.py b/python/magic/version.py
index 3e8d9f94..5b601886 100644
--- a/python/magic/version.py
+++ b/python/magic/version.py
@@ -1 +1 @@
-__version__ = "1.4.0"
+__version__ = "1.5.0"
diff --git a/python/requirements.txt b/python/requirements.txt
index 0621bdda..27f26d44 100644
--- a/python/requirements.txt
+++ b/python/requirements.txt
@@ -3,7 +3,7 @@ pandas>=0.21.0
scipy>=1.1.0
matplotlib
scikit-learn>=0.19.1
-graphtools>=0.1.8
future
-tasklogger>=0.2.1
-scprep>=0.7.1
+tasklogger>=0.4.0
+graphtools>=1.0.0
+scprep>=0.10.0
diff --git a/python/setup.py b/python/setup.py
index 5cffd732..0ef43cb0 100644
--- a/python/setup.py
+++ b/python/setup.py
@@ -8,9 +8,10 @@
'scipy>=1.1.0',
'matplotlib',
'scikit-learn>=0.19.1',
- 'tasklogger>=0.2.1',
- 'graphtools>=0.1.9',
- 'scprep>=0.7.1'
+ 'future',
+ 'tasklogger>=0.4.0',
+ 'graphtools>=1.0.0',
+ 'scprep>=0.10.0'
]
test_requires = [
@@ -25,8 +26,8 @@
'sphinxcontrib-napoleon',
]
-if sys.version_info[:2] < (2, 7) or (3, 0) <= sys.version_info[:2] < (3, 5):
- raise RuntimeError("Python version 2.7 or >=3.5 required.")
+if sys.version_info[:2] < (3, 5):
+ raise RuntimeError("Python version >=3.5 required.")
version_py = os.path.join(os.path.dirname(
__file__), 'magic', 'version.py')
diff --git a/python/tutorial_notebooks/bonemarrow_tutorial.ipynb b/python/tutorial_notebooks/bonemarrow_tutorial.ipynb
index 24fae056..3e0404ae 100644
--- a/python/tutorial_notebooks/bonemarrow_tutorial.ipynb
+++ b/python/tutorial_notebooks/bonemarrow_tutorial.ipynb
@@ -337,12 +337,24 @@
"outputs": [
{
"data": {
- "image/png": "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\n",
"text/plain": [
- "