Nothing
#' @importFrom ggplot2 theme_bw
#' @importFrom reticulate conda_binary py_available py_module_available conda_install
#' @importFrom utils compareVersion
#' @importFrom tensorflow install_tensorflow
NULL
#' Getter function for the cell composition matrix
#'
#' Getter function for the cell composition matrix. This function allows to
#' access to the cell composition matrix of simulated training or test
#' pseudo-bulk RNA-Seq data.
#'
#' @param object \code{\linkS4class{DigitalDLSorter}} object with
#' \code{prob.cell.types} slot.
#' @param type.data Subset of data to e shown: \code{train} or \code{test}.
#'
#' @return A matrix object with the desired cell proportion matrix.
#'
#' @export
#'
#' @seealso \code{\link{generateBulkCellMatrix}}
#'
#'
getProbMatrix <- function(object, type.data) {
if (!is(object, "DigitalDLSorter")) {
stop("Provided object is not a DigitalDLSorter object")
} else if (!any(type.data == c("train", "test"))) {
stop("'type.data' argument must be 'train' or 'test'")
} else if (is.null(prob.cell.types(object))) {
stop("'prob.cell.types' slot is empty")
} else if (is.null(prob.cell.types(object, type.data))) {
stop(paste("No", type.data, "data in 'prob.cell.types' slot"))
}
return(prob.cell.types(object, type.data)@prob.matrix)
}
#' Show distribution plots of the cell proportions generated by
#' \code{\link{generateBulkCellMatrix}}
#'
#' Show distribution plots of the cell proportions generated by
#' \code{\link{generateBulkCellMatrix}}. These frequencies will determine the
#' proportion of different cell types used during the simulation of pseudo-bulk
#' RNA-Seq samples. There are 6 subsets of proportions generated by different
#' approaches that can be visualized in three ways: box plots, violin plots and
#' lines plots. You can also plot the probabilities based on the number of
#' different cell types present in the samples by setting \code{type.plot =
#' 'nCellTypes'}.
#'
#' These plots are only for diagnostic purposes. This is the reason because they
#' are generated without any parameter introduced by the user.
#'
#' @param object \code{\linkS4class{DigitalDLSorter}} object with
#' \code{prob.cell.types} slot with \code{plot} slot.
#' @param type.data Subset of data to show: \code{train} or \code{test}.
#' @param set Integer determining which of the 6 different subsets to display.
#' @param type.plot Character determining which type of visualization to
#' display. It can be \code{'boxplot'}, \code{'violinplot'}, \code{'linesplot'} or
#' \code{'ncelltypes'}. See Description for more information.
#'
#' @return A ggplot object.
#'
#' @export
#'
#' @seealso \code{\link{generateBulkCellMatrix}}
#'
#' @examples
#' # simulating data
#' set.seed(123) # reproducibility
#' sce <- SingleCellExperiment::SingleCellExperiment(
#' assays = list(
#' counts = matrix(
#' rpois(100, lambda = 5), nrow = 40, ncol = 30,
#' dimnames = list(paste0("Gene", seq(40)), paste0("RHC", seq(30)))
#' )
#' ),
#' colData = data.frame(
#' Cell_ID = paste0("RHC", seq(30)),
#' Cell_Type = sample(x = paste0("CellType", seq(4)), size = 30,
#' replace = TRUE)
#' ),
#' rowData = data.frame(
#' Gene_ID = paste0("Gene", seq(40))
#' )
#' )
#' DDLS <- loadSCProfiles(
#' single.cell.data = sce,
#' cell.ID.column = "Cell_ID",
#' gene.ID.column = "Gene_ID"
#' )
#' probMatrix <- data.frame(
#' Cell_Type = paste0("CellType", seq(4)),
#' from = c(1, 1, 1, 30),
#' to = c(15, 15, 50, 70)
#' )
#' DDLS <- generateBulkCellMatrix(
#' object = DDLS,
#' cell.ID.column = "Cell_ID",
#' cell.type.column = "Cell_Type",
#' prob.design = probMatrix,
#' num.bulk.samples = 60
#' )
#' lapply(
#' X = 1:6, FUN = function(x) {
#' showProbPlot(
#' DDLS,
#' type.data = "train",
#' set = x,
#' type.plot = "boxplot"
#' )
#' }
#' )
#'
showProbPlot <- function(
object,
type.data,
set,
type.plot = "boxplot"
) {
if (!is(object, "DigitalDLSorter")) {
stop("Provided object is not a DigitalDLSorter object")
} else if (is.null(object@prob.cell.types) ||
(length(object@prob.cell.types) == 0)) {
stop("'prob.cell.types' slot is empty")
} else if (!any(type.data == c("train", "test"))) {
stop("'type.data' argument must be 'train' or 'test'")
} else if (length(object@prob.cell.types[[type.data]]) == 0) {
stop(paste0("ProbMatrixCellTypes object does not have plots (", type.data, " data)"))
} else if (set < 1 || set > 6) {
stop("'set' argument must be an integer between 1 and 6")
} else if (!any(type.plot == c("violinplot", "boxplot", "linesplot", "ncelltypes"))) {
stop("'type.plot' argument must be one of the next options: 'violinplot', ",
"'boxplot', 'linesplot' or 'ncelltypes'")
}
return(prob.cell.types(object, type.data)@plots[[set]][[type.plot]])
}
#' Prepare \code{\linkS4class{DigitalDLSorter}} object to be saved as an RDA
#' file
#'
#' Prepare a \code{\linkS4class{DigitalDLSorter}} object that has a
#' \code{\linkS4class{DigitalDLSorterDNN}} object with a trained DNN model.
#' \code{keras} models cannot be stored natively as R objects (e.g. RData or RDS
#' files). By saving the structure as a JSON-like character object and the
#' weights as a list, it is possible to retrieve the model and make predictions.
#' \strong{Note:} with this option, the state of optimizer is not saved, only
#' the architecture and weights.
#'
#' It is possible to save the entire model as an HDF5 file with the
#' \code{\link{saveTrainedModelAsH5}} function and to load it into a
#' \code{\linkS4class{DigitalDLSorter}} object with the
#' \code{\link{loadTrainedModelFromH5}} function.
#'
#' It is also possible to save a \code{\linkS4class{DigitalDLSorter}} object as
#' an RDS file with the \code{saveRDS} function without any preparation.
#'
#' @param object \code{\linkS4class{DigitalDLSorter}} object with the
#' \code{trained.data} slot.
#'
#' @return A \code{\linkS4class{DigitalDLSorter}} or
#' \code{\linkS4class{DigitalDLSorterDNN}} object with its trained keras model
#' transformed from a \code{keras.engine.sequential.Sequential} class into a
#' \code{list} with the architecture as a JSON-like character object and the
#' weights as a list.
#'
#' @export
#'
#' @seealso \code{\link{saveRDS}} \code{\link{saveTrainedModelAsH5}}
#'
preparingToSave <- function(
object
) {
# check if python dependencies are covered
.checkPythonDependencies(alert = "error")
if (!is(object, "DigitalDLSorter") && !is(object, "DigitalDLSorterDNN")) {
stop("Provided object is not a DigitalDLSorter object")
}
if (is.null(trained.model(object))) {
stop("Provided object has not a DigitalDLSorterDNN object. It is not necessary ",
"to prepare this object to save it to disk")
} else if (length(trained.model(object)@model) == 0) {
stop("Provided object has not a trained DNN model. It is not necessary ",
"to prepare the object to save it to disk")
}
if (!is(trained.model(object)@model, "list"))
trained.model(object) <- .saveModelToJSON(trained.model(object))
return(object)
}
# core of barplots of deconvolution results
.barPlot <- function(
data,
colors,
color.line = NA,
x.label = "Bulk samples",
rm.x.text = FALSE,
title = "Results of deconvolution",
legend.title = "Cell types",
angle = 90,
theme = NULL
) {
df.res <- reshape2::melt(data * 100, value.name = "Proportion")
p <- ggplot(
data = df.res, aes(
x = .data[["Var1"]], y = .data[["Proportion"]], fill = .data[["Var2"]]
)
) + geom_bar(stat = "identity", color = color.line) + theme
if (!missing(colors) && !is.null(colors)) {
if (length(colors) < length(unique(df.res$Var2)))
stop("Number of provided colors is not enough for the number of cell types")
} else {
colors <- default.colors()
}
p <- p + scale_fill_manual(values = colors) + DigitalDLSorterTheme()
if (is.null(x.label)) {
p <- p + theme(axis.title.x = element_blank())
} else {
p <- p + xlab(x.label)
}
p <- p + ggtitle(title) + ggplot2::labs(fill = legend.title) + theme(
plot.title = element_text(face = "bold", hjust = 0.5),
axis.title = element_text(size = 12),
axis.text.x = element_text(angle = angle, hjust = 1, vjust = 0.5),
legend.title = element_text(face = "bold")
)
if (rm.x.text) {
p <- p + theme(axis.ticks.x = element_blank(),
axis.text.x = element_blank())
}
return(p)
}
################################################################################
####################### Utils functions related to DNN #########################
################################################################################
#' Save a trained \code{\linkS4class{DigitalDLSorter}} Deep Neural Network model
#' to disk as an HDF5 file
#'
#' Save a trained \code{\linkS4class{DigitalDLSorter}} Deep Neural Network model
#' to disk as an HDF5 file. Note that this function does not save the
#' \code{\linkS4class{DigitalDLSorterDNN}} object, but the trained keras model.
#' This is the alternative to the \code{\link{saveRDS}} and
#' \code{\link{preparingToSave}} functions if you want to keep the state of the
#' optimizer.
#'
#' @param object \code{\linkS4class{DigitalDLSorter}} object with
#' \code{trained.model} slot.
#' @param file.path Valid file path where to save the model to.
#' @param overwrite Overwrite file if it already exists.
#'
#' @return No return value, saves a keras DNN trained model as HDF5 file on
#' disk.
#'
#' @export
#'
#' @seealso \code{\link{trainDigitalDLSorterModel}}
#' \code{\link{loadTrainedModelFromH5}}
#'
saveTrainedModelAsH5 <- function(
object,
file.path,
overwrite = FALSE
) {
# check if python dependencies are covered
.checkPythonDependencies(alert = "error")
if (!is(object, "DigitalDLSorter")) {
stop("Provided object is not a DigitalDLSorter object")
} else if (is.null(trained.model(object))) {
stop("'trained.model' slot is empty")
} else if (length(trained.model(object)@model) == 0) {
stop("There is not a model to save on disk. First, train a model with ",
"'trainDigitalDLSorterModel' function")
}
if (file.exists(file.path)) {
if (overwrite) {
message(paste(file.path, "file already exists. Since 'overwrite' argument is",
"TRUE, it will be overwritten"))
} else {
stop(paste(file.path, "file already exists"))
}
}
if (is(trained.model(object)@model, "list")) {
warning(paste(
"Trained model is not a keras object, but a R list with",
"architecture of network and weights. The R object will be",
"compiled and saved as HDF5 file, but the optimizer state",
"will not be saved\n\n"
))
model <- .loadModelFromJSON(trained.model(object))
model <- model(model)
} else {
model <- trained.model(object)@model
}
tryCatch(
expr = {
save_model_hdf5(
object = model, filepath = file.path,
overwrite = overwrite, include_optimizer = TRUE
)
},
error = function(cond) {
message(paste("\nProblem during saving", file.path))
stop(cond)
}
)
}
#' Load from an HDF5 file a trained Deep Neural Network model into a
#' \code{\linkS4class{DigitalDLSorter}} object
#'
#' Load from an HDF5 file a trained Deep Neural Network model into a
#' \code{\linkS4class{DigitalDLSorter}} object. Note that HDF5 file must be a
#' valid trained model (\pkg{keras} object).
#'
#' @param object \code{\linkS4class{DigitalDLSorter}} object with
#' \code{trained.model} slot.
#' @param file.path Valid file path where the model are stored.
#' @param reset.slot Deletes \code{trained.slot} if it already exists. A new
#' \code{\link{DigitalDLSorterDNN}} object will be formed, but will not
#' contain other slots (\code{FALSE} by default).
#'
#' @return \code{\linkS4class{DigitalDLSorter}} object with \code{trained.model}
#' slot with the new keras DNN model incorporated.
#'
#' @export
#'
#' @seealso \code{\link{trainDigitalDLSorterModel}}
#' \code{\link{deconvDigitalDLSorterObj}} \code{\link{saveTrainedModelAsH5}}
#'
loadTrainedModelFromH5 <- function(
object,
file.path,
reset.slot = FALSE
) {
# check if python dependencies are covered
.checkPythonDependencies(alert = "error")
if (!is(object, "DigitalDLSorter")) {
stop("Provided object is not a DigitalDLSorter object")
} else if (!file.exists(file.path)) {
stop(paste(file.path, "file does not exist. Please, provide a valid file path"))
}
if (!is.null(trained.model(object))) {
slot.exists <- TRUE
message("'trained.model' slot is not empty:")
if (reset.slot) {
message(" 'reset.slot' is TRUE, 'trained.model' slot will be restart")
} else {
message(" 'reset.slot' is FALSE, just 'model' slot of DigitalDLSorterDNN",
"object will be overwritten")
}
} else {
slot.exists <- FALSE
}
tryCatch(
expr = {
loaded.model <- load_model_hdf5(filepath = file.path, compile = FALSE)
},
error = function(cond) {
message(paste("\n", file.path, "file provided is not a valid Keras model:"))
stop(cond)
}
)
if (!slot.exists) {
model <- new(Class = "DigitalDLSorterDNN",
model = loaded.model)
} else {
if (reset.slot) {
model <- new(Class = "DigitalDLSorterDNN",
model = loaded.model)
} else {
model(object@trained.model) <- loaded.model
return(object)
}
}
trained.model(object) <- model
return(object)
}
#' Plot training history of a trained DigitalDLSorter Deep Neural Network model
#'
#' Plot training history of a trained DigitalDLSorter Deep Neural Network model.
#'
#' @param object \code{\linkS4class{DigitalDLSorter}} object with
#' \code{trained.model} slot.
#' @param title Title of plot.
#' @param metrics Metrics to be plotted. If \code{NULL} (by default), all
#' metrics available in the \code{\linkS4class{DigitalDLSorterDNN}} object
#' will be plotted.
#'
#' @return A ggplot object with the progression of the selected metrics during
#' training.
#'
#' @export
#'
#' @seealso \code{\link{trainDigitalDLSorterModel}}
#' \code{\link{deconvDigitalDLSorterObj}}
#'
plotTrainingHistory <- function(
object,
title = "History of metrics during training",
metrics = NULL
) {
# check if python dependencies are covered
.checkPythonDependencies(alert = "error")
if (!is(object, "DigitalDLSorter")) {
stop("Provided object is not of DigitalDLSorter class")
} else if (is.null(trained.model(object))) {
stop("'trained.model' slot is empty")
} else if (is.null(trained.model(object)@training.history)) {
stop("There is no training history in provided object")
}
if (!is.null(metrics)) {
if (!all(metrics %in% names(trained.model(object)@training.history$metrics))) {
stop("None of the given metrics are in the provided object")
}
}
plot(
trained.model(object)@training.history,
metrics = metrics, method = "ggplot2"
) + ggtitle(title) + DigitalDLSorterTheme()
}
# custom ggplot2 theme
DigitalDLSorterTheme <- function() {
digitalTheme <- ggplot2::theme_bw() + theme(
plot.title = element_text(face = "bold", hjust = 0.5),
legend.title = element_text(face = "bold")
)
}
################################################################################
##################### Functions to transform list into DDLS ####################
################################################################################
#' Transform DigitalDLSorterDNN-like list into an actual DigitalDLSorterDNN
#' object
#'
#' Transform DigitalDLSorterDNN-like list into an actual
#' \code{DigitalDLSorterDNN} object. This function allows to use pre-trained
#' models in the \pkg{digitalDLSorteR} package. These models are stored in the
#' digitalDLSorteRmodels package.
#'
#' @param listTo A list in which each element must correspond to each slot of an
#' \code{DigitalDLSorterDNN} object. The names must be the same as the slot
#' names.
#'
#' @return \code{DigitalDLSorterDNN} object with the data provided in the
#' original list.
#'
#' @export
#'
#' @seealso \code{\link{listToDDLS}}
#'
listToDDLSDNN <- function(listTo) {
if (any(!names(listTo) %in% c(
"model", "training.history", "test.metrics", "test.pred",
"cell.types", "features", "test.deconv.metrics"
))) {
stop("The list provided is not valid to create a DigitalDLSorterDNN object")
}
return(
digitalDLSorteR::DigitalDLSorterDNN(
model = listTo$model,
training.history = listTo$training.history,
test.metrics = listTo$test.metrics,
test.pred = listTo$test.pred,
cell.types = listTo$cell.types,
features = listTo$features,
test.deconv.metrics = listTo$test.deconv.metrics
)
)
}
#' Transform DigitalDLSorter-like list into an actual DigitalDLSorterDNN object
#'
#' Transform DigitalDLSorter-like list into an actual \code{DigitalDLSorter}
#' object. This function allows to generate the examples and the vignettes of
#' \pkg{digitalDLSorteR} package as these are the data used. These data are
#' stored in the digitalDLSorteRdata package.
#'
#' @param listTo A list in which each element must correspond to each slot of an
#' \code{DigitalDLSorter} object. The names must be the same as the slot
#' names.
#'
#' @return \code{DigitalDLSorter} object the data provided in the original list.
#'
#' @export
#'
#' @seealso \code{\link{listToDDLSDNN}}
#'
listToDDLS <- function(listTo) {
if (any(!names(listTo) %in% c(
"single.cell.real", "zinb.params", "single.cell.simul",
"prob.cell.types", "bulk.simul", "trained.model", "deconv.data",
"deconv.results", "project", "version"
))) {
stop("The list provided is not valid to create a DigitalDLSorter object")
}
# for prob.cell.types slot: ProbMatrixCellTypes
if (is.null(listTo$prob.cell.types)){
prob.cell.types <- NULL
} else if (is(listTo$prob.cell.types, "list")) {
prob.cell.types <- list(
train = ProbMatrixCellTypes(
prob.matrix = listTo$prob.cell.types$train$prob.matrix,
cell.names = listTo$prob.cell.types$train$cell.names,
set.list = listTo$prob.cell.types$train$set.list,
set = listTo$prob.cell.types$train$set,
plots = listTo$prob.cell.types$train$plots,
type.data = listTo$prob.cell.types$train$type.data
),
test = ProbMatrixCellTypes(
prob.matrix = listTo$prob.cell.types$test$prob.matrix,
cell.names = listTo$prob.cell.types$test$cell.names,
set.list = listTo$prob.cell.types$test$set.list,
set = listTo$prob.cell.types$test$set,
plots = listTo$prob.cell.types$test$plots,
type.data = listTo$prob.cell.types$test$type.data
)
)
}
# for trained.model slot: DigitalDLSorterDNN
if (is.null(listTo$trained.model)){
trained.model <- NULL
} else if (is(listTo$trained.model, "list")) {
trained.model <- listToDDLSDNN(listTo$trained.model)
}
return(
DigitalDLSorter(
single.cell.real = listTo$single.cell.real,
zinb.params = listTo$zinb.params,
single.cell.simul = listTo$single.cell.simul,
prob.cell.types = prob.cell.types,
bulk.simul = listTo$bulk.simul,
trained.model = trained.model,
deconv.data = listTo$deconv.data,
deconv.results = listTo$deconv.results,
project = listTo$project,
version = listTo$version
)
)
}
################################################################################
############################# Python dependencies ##############################
################################################################################
.isConda <- function() {
conda <- tryCatch(
reticulate::conda_binary("auto"), error = function(e) NULL
)
!is.null(conda)
}
.isPython <- function() {
tryCatch(
expr = reticulate::py_available(initialize = TRUE),
error = function(e) FALSE
)
}
.isTensorFlow <- function() {
tfAvailable <- reticulate::py_module_available("tensorflow")
if (tfAvailable) {
tfVersion <- tensorflow::tf$`__version__`
tfAvailable <- utils::compareVersion("2.2", tfVersion) <= 0
}
return(tfAvailable)
}
# alert parameter: c("none", "error", "warn", "message", "startup")
.checkPythonDependencies <- function(
alert = "error"
) {
# turn off reticulate autoconfigure
ac_flag <- Sys.getenv("RETICULATE_AUTOCONFIGURE")
on.exit(Sys.setenv(RETICULATE_AUTOCONFIGURE = ac_flag))
Sys.setenv(RETICULATE_AUTOCONFIGURE = FALSE)
dependencies <- c(python = .isPython(), tf = .isTensorFlow())
if (!all(dependencies)) {
messageT <- c(
"There is no a Python interpreter with all the digitalDLSorteR \
dependencies covered available. Please, look at \
https://diegommcc.github.io/digitalDLSorteR/articles/kerasIssues.html \
or see ?installPythonDepend"
)
warningT <- c(
"There is no a Python interpreter with all the digitalDLSorteR \
dependencies covered available. Please, look at \
https://diegommcc.github.io/digitalDLSorteR/articles/kerasIssues.html \
or see ?installPythonDepend"
)
errorT <- c(
"There is no a Python interpreter with all the digitalDLSorteR \
dependencies covered available. Please, look at \
https://diegommcc.github.io/digitalDLSorteR/articles/kerasIssues.html \
or see ?installPythonDepend"
)
switch(
alert,
error = stop(errorT, call. = FALSE),
warn = warning(warningT, call. = FALSE),
message = message(messageT),
startup = packageStartupMessage(messageT),
none = NULL
)
}
return(invisible(all(dependencies)))
}
#' Install Python dependencies for digitalDLSorteR
#'
#' This is a helper function to install Python dependencies needed: a Python
#' interpreter with TensorFlow Python library and its dependencies. It is
#' performed using the \pkg{reticulate} package and the installer of the
#' \pkg{tensorflow} R package. The available options are virtual or conda
#' environments. The new environment is called digitaldlsorter-env. In any case,
#' this installation can be manually done as it is explained in
#' \url{https://diegommcc.github.io/digitalDLSorteR/articles/kerasIssues.html},
#' but we recommend using this function.
#'
#' This function is intended to make easier the installation of the requirements
#' needed to use \pkg{digitalDLSorteR}. It will automatically install Miniconda
#' (if wanted, see Parameters) and create an environment called
#' 'digitaldlsorter-env'. If you want to use other python/conda environment, see
#' \code{?tensorflow::use_condaenv} and/or the vignettes.
#'
#' @param conda Path to a conda executable. Use \code{"auto"} (by default)
#' allows \pkg{reticulate} to automatically find an appropriate conda binary.
#' @param install.conda Boolean indicating if install miniconda automatically
#' using \pkg{reticulate}. If \code{TRUE}, \code{conda} argument is ignored.
#' \code{FALSE} by default.
#' @param miniconda.path If \code{install.conda} is \code{TRUE}, you can set the
#' path where miniconda will be installed. If \code{NULL}, conda will find
#' automatically the proper place.
#'
#' @return No return value, called for side effects: installation of conda
#' environment with a Python interpreter and Tensorflow
#'
#' @export
#'
#' @examples
#' \dontrun{
#' notesInstallation <- installTFpython(
#' method = "auto", conda = "auto", install.conda = TRUE
#' )
#' }
#'
installTFpython <- function(
conda = "auto",
install.conda = FALSE,
miniconda.path = NULL
) {
if ((!.isConda())) {
if (!install.conda) {
stop("No miniconda detected, but 'install.conda' is FALSE. Please, set ",
"'install.conda = TRUE' to install miniconda." )
}
message("=== No miniconda detected, installing through the reticulate R package")
if (is.null(miniconda.path)) {
miniconda.path <- reticulate::miniconda_path()
}
status1 <- tryCatch(
reticulate::install_miniconda(path = miniconda.path),
error = function(e) {
return(TRUE)
}
)
if (isTRUE(status1)) {
stop(
"Error during the installation. Please see the website of the ",
"package and/or the vignettes for more details",
call. = FALSE
)
}
}
dirConda <- reticulate::conda_binary("auto")
message("\n=== Creating digitaldlsorter-env environment")
status2 <- tryCatch(
reticulate::conda_create(
envname = "digitaldlsorter-env",
packages = "python==3.7.11"
),
error = function(e) {
return(TRUE)
}
)
if (isTRUE(status2)) {
stop(
"Error during the installation. Please see the website of the ",
"package and/or the vignettes for more details",
call. = FALSE
)
}
message("\n=== Installing tensorflow in digitaldlsorter-env environment")
status3 <- tryCatch(
tensorflow::install_tensorflow(
version = "2.5-cpu",
method = "conda",
conda = dirConda,
envname = "digitaldlsorter-env"
),
error = function(e) {
return(TRUE)
}
)
if (isTRUE(status3)) {
stop(
"Error during the installation. Please see the website of the ",
"package and/or the vignettes for more details",
call. = FALSE
)
}
message("Installation complete!")
message(c("Restart R and load digitalDLSorteR. If you find any problem, \
see ?tensorflow::use_condaenv and kerasIssues.Rmd vignette"))
}
Any scripts or data that you put into this service are public.
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.