Chapter 4 Server
Once you:
1. Have created a dataset description des_df
object
2. Have changed the directory of the .db
to the one you have stored your data in
…you can start running the app.
(but lots of details are provided below so you can dive into the code)
4.1 Requirements
For the Server.R
page to run, you will need to create a des_df
, a description df that describes your datasets. An example is shown below:
- Dataset: The name of the dataset
- condition: The experimental disease group name
- species: the species in the experiment
- count: the name of the count matrix
- colDatas: The name of experimental design
- deg_df_name: the name of differential expression data
- type: Name of the RNA sequencing technique; either bulk or single
- page_id: This identifier is useful for loading description files specific for each experiment. E.g. subtype.Rhtml . Name your description files and add their names to this column.
- include_subtypes: This specifies if there are different subtypes of samples in the experiment. E.g. different species strains. If TRUE, there will be an additional line plot showing subtype-specific changes in gene expression.
- include_degs: This specifies if there are differential expression data files for this experiment. If TRUE, the visualisation page will yield differential expression dot plots and volcano plots.
### example with one bulk dataset
= data.frame(
des_df Dataset = c("Subtype DRG (Barry)"),
condition = c("ipsi"),
Species = c("mouse"),
count = c("bulkseq_mat"),
include_subtypes = c(TRUE),
include_degs = c(TRUE),
page_id = c("subtype"),
deg_df_name = c("subtype_deg_df"),
colDatas = c("bulkseq_colData"),
type = c("bulk"),
Tissue = c("DRG"))
### example with bulk and single cell datasets
= data.frame(
des_df Dataset = c("Subtype DRG (Barry)", "Mouse DRG Bulk", "Human DRG spatial-seq"),
condition = c("ipsi", "SNI", "N/A"),
Species = c("mouse", "mouse", "human"),
count = c("bulkseq_mat", "TPM_mouse", ""),
include_subtypes = c(TRUE, FALSE, FALSE),
include_degs = c(TRUE, TRUE, FALSE),
page_id = c("subtype", "mouse", "scrna"),
deg_df_name = c("subtype_deg_df", "mouse_deg_df", ""),
colDatas = c("bulkseq_colData", "TPM_mouse_colData", ""),
type = c("bulk","bulk", "single"),
Tissue = c("DRG", "DRG", "DRG"))
kbl(des_df %>% head) %>% kable_styling(bootstrap_options = c("striped", "hover", "condensed", "responsive"), font_size = 12, latex_options = "scale_down") %>% scroll_box(width = "100%", height = "500px")
Dataset | condition | Species | count | include_subtypes | include_degs | page_id | deg_df_name | colDatas | type | Tissue |
---|---|---|---|---|---|---|---|---|---|---|
Subtype DRG (Barry) | ipsi | mouse | bulkseq_mat | TRUE | TRUE | subtype | subtype_deg_df | bulkseq_colData | bulk | DRG |
Mouse DRG Bulk | SNI | mouse | TPM_mouse | FALSE | TRUE | mouse | mouse_deg_df | TPM_mouse_colData | bulk | DRG |
Human DRG spatial-seq | N/A | human | FALSE | FALSE | scrna | single | DRG |
The table names of countdata
and colData
for each experiment will be written. This allows the app to directly query those tables from the database object. In addition, you will need to specify directory that storing your data
## directory for scRNA sequencing data;
{= NULL # leave it for NULL if none
scRNA_dir = NULL # leave it for NULL if none
scRNA_names = list()
scRNA_dir = c(scRNA_dir, list("drg.combined.rds")) #set for single cell data
scRNA_dir = c("Human DRG spatial-seq") #names for single cell data
scRNA_names
names(scRNA_dir) = scRNA_names
if (length(scRNA_dir) != 0) {
library(Seurat) # for scRNA; only load library if scRNA data is present
}
}
## RData that contains DEG files for network analysis
= "data/network.RData"
network_dir
## directory for the sql database
= "test.db" sql_dir
4.2 Server Overview
You don’t need to modify this, but is provided as an overview of the app structure.
server()
function is invoked each time a new session starts. When the server function is called it creates a new local environment that is independent of every other invocation of the function. This allows each session to have a unique state, as well as isolating the variables created inside the function.
Server functions take three parameters: input
, output
, and session
. The input argument is a list-like object that contains all the input data sent from ui.R
, named according to the input ID.
4.2.1 Side bar
Update the gene symbols entered or uploaded in files into reactive variables.
# gene search bar
::updateSelectizeInput(session,
shinyinputId = "geneid",
label = "Search Genes:",
choices = allgenes,
server = TRUE,
selected = c("Tubb3","Gap43","Atf3") ## can change to relevant genes
)
# a reactive variable that records whether a file is uploaded.
<- reactiveValues(
rv clear = FALSE,
data = FALSE
)
# read in files uploaded
::observeEvent(input$file, {
shiny$clear <- FALSE
rv$data = TRUE
rvpriority = 1000)
},
# clear the file space when files are removed
::observeEvent(input$reset, {
shiny$clear = TRUE
rv$data = FALSE
rv
})
# read into gene ids from files
<- reactive({
file_input if (rv$clear == TRUE) {
return(NULL)
}if(rv$clear==FALSE && rv$data == TRUE) {
= read.table(input$file$datapath)
goi rownames(goi) <- goi[,1]
<- goi[which(rownames(goi) %in% allgenes==TRUE),]
goi return(goi)}
})
= reactive({
genes if (is.null(file_input())) {
= input$geneid
genes
}else {
= file_input()
genes
} })
4.2.3 Grouped comparisons
By using the same visualization tools and methods across studies, it becomes easier to compare and integrate results from different studies.
- Render a table for datasets included
$meta_table = DT::renderDataTable({
output::datatable(
DTc("Dataset", "Pain_Model", "Species", "Tissue")], ## can select relevant columns
bulk_df[width = 6,
class = 'nowrap',
options = list(scrollX = TRUE, scrollY = TRUE, pageLength = 5),
selection = "none"
# selection = list(mode = "multiple", selected = list(rows = c(1:nrow(bulk_df)), cols = c(1:4)), target = "row+column")
) })
- Generate homepage plots
- A combined gene expression dot plots
- A scRNA dotplot based on a
Seurat
object - A differential expression dot plot that allows visualisation of the significance level of differential gene expression
### this doesn't need to be modified
# plot in reponse to the 'load' button
observeEvent(input$load, {
# load scRNA data if it exists
if (is.null(scRNA_dir) == FALSE) {
for (c in scRNA_dir) {
$pbmc <- c(variables$pbmc, readRDS(scRNA_dir[[1]]))
variables
}names(variables$pbmc) = scRNA_names
}
# read geneids from file if it exists
<- reactive({
file_input if (rv$clear == TRUE) {
return(NULL)
}if(rv$clear==FALSE && rv$data == TRUE) {
= read.table(input$file$datapath)
goi rownames(goi) <- goi[,1]
<- goi[which(rownames(goi) %in% allgenes==TRUE),]
goi return(goi)}
})
if (is.null(file_input())) {
= input$geneid
genes
}else {
= file_input()
genes
}
# plot homepage dot plots and DEG plots
if (nrow(bulk_df) != 0) {
# include all bulk datasets in the combined plots
= c(1:nrow(bulk_df))
selected_data
= list()
df_list for (i in c(1:nrow(bulk_df))){
= bulk_df[i,]
curr = curr$count
d
# if using sql
if (!is.null(sql_dir)) {
if (curr$Species == "human"){
= paste("SELECT * FROM", d, "WHERE mgi_symbol = ?")
sql
}else {
= paste("SELECT * FROM", d, "WHERE symbol = ?")
sql
}= as.data.frame(dbGetPreparedQuery(conn, sql, bind.data=data.frame(symbol=genes)))
count_data
}
# if using RData
if (!is.null(rdata_dir)) {
= mat[[d]]
ct if (curr$Species == "human"){
= ct[ct$mgi_symbol %in% genes,]
count_data
}else {
= ct[ct$symbol %in% genes,]
count_data
}
}
# a list of filtered count data
= append(df_list, list(count_data))
df_list
}
= generate_combine_dataset(df_list, genes, input$sex, col_list[selected_data], des_df$condition[selected_data], des_df$Species[selected_data],
final_df
expression, Condition, Population, symbol, Dataset, Species)
# homepage dotplot
= plotcombine_server("dot", final_df, input$sex, genes, Population, symbol, expression, "Dataset") #dotplot
combine_dot
# deg plot
# first, convert gene symbols to species specific
= human_gene_data[human_gene_data$mgi_symbol %in% genes,]$hgnc_symbol
hgenes = rat_gene_data[rat_gene_data$mgi_symbol %in% genes,]$rgd_symbol
rgenes
# filter differential expression data
= degrat[degrat$symbol %in% rgenes,]
degr = degmouse[degmouse$symbol %in% genes,]
degm = deghuman[deghuman$symbol %in% hgenes,]
degh
= deg_combine_server("deg_plot",degr, degm, degh, Population,symbol, log2FoldChange,sig, "Dataset")
combine_degplot
}
# give scRNA dot plots if data exists
if (is.null(variables$pbmc) != TRUE){
= plothomescdot_server("homespat", variables$pbmc, genes)
scrna_dot
}
# download all plots into a zip
$combineplots <- downloadHandler(
outputfilename = function() {
paste("combined", "zip", sep=".")
},
content = function(file){
# Set temporary working directory
<- setwd(tempdir())
owd on.exit(setwd(owd))
= c()
fs
# Save the plots
ggsave('combine_deg.png', plot = combine_degplot, width = 14, height = 6, dpi = 300, units = "in", device='png')
ggsave('combine_dot.png', plot = combine_dot, width = 14, height = 6, dpi = 300, units = "in", device='png')
ggsave('scrna_dot.png', plot = scrna_dot, width = 10, height = 8, dpi = 300, units = "in", device='png')
= c(fs, 'combine_deg.png')
fs = c(fs, 'combine_dot.png')
fs = c(fs, 'scrna_dot.png')
fs
# csv files for gene count data
write.csv(final_df,"result_table.csv", row.names = FALSE, col.names = TRUE)
= c(fs, "result_table.csv")
fs
zip(file, fs)
}
)
})
4.2.4 Individual Analysis
First, a table that contains dataset description is rendered at the top.
$dataset_table = DT::renderDataTable({
output::datatable(
DTc("Dataset", "Pain_Model", "Species", "Tissue")], ## can select relevant columns
des_df[width = 6,
class = 'nowrap',
options = list(scrollX = TRUE, scrollY = TRUE, pageLength = 5), selection = "single"
) })
Next, the ui for each independent dataset is rendered in response to row selected from the table. This is passed by the input$dataset_table_rows_selected
parameter and stored into a reactive variable indrows. The parameters required for calling the shinypageUI
function is retrieved from the des_df
.
= reactiveValues({
variables = NULL
indrows
})
::observe({
shiny$indrows =input$dataset_table_rows_selected
variables
})
$shinypages = shiny::renderUI({
output= input$dataset_table_rows_selected
j = des_df[j,]
select
if (select$type == "single"){
shinyscrnapageUI("spatpage", select$Dataset)
}
else {
= select$page_id
page_id shinypageUI(page_id, select$Dataset,
$include_degs,
select$include_subtypes, des_dir = NULL)
select
}
})
The server functions for each study-specific ui called is shown below. The shinypage server module contains code for generating plots and result tables.
::observe({
shiny= input$dataset_table_rows_selected
i
if (is.null(i) == FALSE) {
= des_df[i,]
selected = selected$page_id
page_id
# bulk rnaseq
if (selected$type == "bulk") {
::callModule(shinypage, page_id, input$sex, selected$count, as.data.frame(col_list[i]),
shiny$Species, selected$condition, reactive({genes()}), deg_df_name = selected$deg_df_name,
selecteddataset = selected$Dataset, parent = session)
}
# single rnaseq
if (selected$type == "single"){
if (is.null(variables$pbmc[[selected$Dataset]]) == TRUE) {
# make sure that scRNA data is already read into reactive variables
for (c in scRNA_dir) {
$pbmc <- c(variables$pbmc, readRDS(scRNA_dir[[1]]))
variables
}names(variables$pbmc) = scRNA_names
}
::callModule(shinyscrna, "spatpage", reactive({genes()}), variables$pbmc[[selected$Dataset]])
shiny
}
}
})
4.3 Network Analysis
Network integration can give context when working with large datasets, and provide a platform to integrate information from multiple sources.
Here, networks are built using protein–protein interaction data taken from the STRING database. The network can be enriched in various ways. As an example, we have enriched the STRING networks in our example by:
- An Enrichment Score, an integrated statistic using data from all experiments.
- Differential expression in an individual experiment.
- Genes of interest, as a list
- Here, we used known pain-associated genes from various sources
# update choice
::updateSelectizeInput(session,
shinyinputId = "gene_symbols",
label = "Search Genes:",
choices = human_gene_data$hgnc_symbol,
server = TRUE,
selected = c("ATF3") ## choose your favourite
)
4.3.1 Integration with STRING DB
Node and edge data is extracted in real-time from STRING https://string-db.org/ and reconstructed into a network based on user input.
For reference only, you don’t need to modify this.
### this doesn't need to be modified
# generates network when the submit button is clicked
observeEvent(input$submit, {
# Query gene interactions
= as.vector(input$gene_symbols)
genes_list = paste(genes_list, collapse = "%0d")
genes <- query_interactions(genes, 10)
interactions = unique(c(unique(interactions$preferredName_A), unique(interactions$preferredName_B)))
proteins = data.frame()
nodes = unique(c(unique(interactions$stringId_A), unique(interactions$stringId_B)))
id = as.data.frame(id)
nodes$label = proteins
nodes= mutate(nodes, shape = ifelse(nodes$label %in% input$gene_symbols, "diamond", "circle"))
nodes
if (input$pop == "Composite Enrichment Score") {
= "meta"
mode = score_df
metrics = score_df[score_df$symbol %in% proteins,]
dt
}else {
= "indiv"
mode # the dict contains deg_df based on the name of datasets
# res = network_df[network_df$experiment_id == input$pop,]
= degall[degall$Dataset == input$pop,]
res = subset(res, !duplicated(symbol))
res = mutate(res, sig=ifelse((res$padj<0.05), "SIG", "NS"))
res = res
metrics
# account for species differences
if (unique(res$Species) == "mouse") {
= human_gene_data[human_gene_data$hgnc_symbol %in% proteins,]$mgi_symbol
proteins
}if (unique(res$Species) == "rat") {
= human_gene_data[human_gene_data$hgnc_symbol %in% proteins,]$mgi_symbol
proteins
}= filter(res, symbol %in% proteins) # this means the proteins must be in human symbol
dt
# remove duplicate
rownames(dt) = dt$symbol
= dt[c("log2FoldChange","padj", "sig")]
dt
}
# network construction
= get_nodes(interactions, genes_list, metrics, mode=mode, snps, pg1, pg2)
nodes <- data.frame(from = interactions$stringId_A, to = interactions$stringId_B, width = interactions$score,
edges color = "#444444")
<- data.frame(shape = c( "icon"),
lnodes icon.color = c("#961252", "#d8aec4ff", "lightgrey", "#aed5eaff","#0277bd"),
icon.code = c("f35b", "f111", "f111", "f111", "f358"),
icon.size = 30)
# network visualisation
= visNetwork::visNetwork(nodes, edges, height = "500px", width = "100%") %>%
nw visPhysics(enabled = FALSE,solver = "forceAtlas2Based",
forceAtlas2Based = list(avoidOverlap = 1)) %>%
visEdges(smooth = FALSE) %>%
visInteraction(navigationButtons = TRUE, zoomSpeed = 0.6) %>%
visLegend(addNodes = lnodes,
#main = list(text = "Enrichment", style = "font-family:Gadugi"),
useGroups = FALSE,
width = 0.2,
position = "right",
ncol = 1,
stepY = 50,
#stepX = 50,
zoom = FALSE)
$network <- renderVisNetwork({nw})
output
$downloadnet <- downloadHandler(
outputfilename = "graph.html",
content = function(file) {
visSave(nw, file)
}
)
$contrast_table <- DT::renderDataTable({
output::datatable(
DT
dt,width = 6,
class = 'nowrap',
options = list(scrollX = TRUE, pageLength = 8)
)
})
# a table containing description and information of genes included
$protein_table <- DT::renderDataTable({
output::datatable(
DTquery_information(proteins),
width = 6,
class = "wrap",
options = list(scrollX = TRUE, pageLength = 8)
)
})
})
4.4 Functions
An run-down of functions used throughout the app. For basic applications these don’t need to be modified. A function can be called and reused multiple times by passing parameters. For this example database, functions are written for rendering plots. To illustrate them, we will use an example count dataset.
4.4.1 Data processing
#' a function that gets the median expression of a count data
<- function(df, sex, var, ...){
get_median ifelse(sex=="Both", data <- df %>% dplyr::group_by(...) %>%
::summarise(expression=median(as.double({{var}}))), df <- tcounts %>% dplyr::group_by(..., Sex) %>%
dplyr::summarise(expression=median(as.double({{var}}))))
dplyrreturn(data)
}
= c("Atf3", "Msh6", "Dlst", "Slc2a9")
genes = "Both"
sex
# load data
= read.csv("./data/CountTables/TPM_mouse.csv", header = TRUE)
count = read.csv(paste0("./data/colData/", "TPM_mouse_colData.csv"), header = TRUE)
colData rownames(colData) = colData$X
# filter data
= count[count$symbol %in% genes,]
filt = filt[,!(names(filt) %in% c("symbol"))]
matfilt
# rename the first column
colnames(matfilt) = c("gene", colnames(matfilt)[2:length(colnames(matfilt))])
rownames(matfilt) = matfilt$gene
# group based on experimental conditions
<- t(matfilt) %>%
tcounts ::merge(colData, ., by="row.names") %>%
base::gather(gene, expression, (ncol(.)-nrow(matfilt)+1):(ncol(.)))
tidyr
$symbol = filt[filt$X %in% tcounts$gene,]$symbol
tcounts
# get median expression
= get_median(tcounts, sex, expression, Condition, Population, symbol, Dataset, Species)
tcounts_med <- tcounts_med[!tcounts_med$Condition %in% "SNI", ] # remove pain samples tcounts_med
4.4.2 Dot plots
- median counts (here, transcripts per million (TPM))
<- function(data, x_var, y_var, col_var, facet_var, sex){
plot_dotplot = enquo(x_var)
x_var = enquo(y_var)
y_var = enquo(col_var)
col_var
= ggplot2::ggplot(data = data, aes(x=!!x_var, y = !!y_var)) +
g scale_colour_viridis_c(option = "magma", end = .90) +
geom_point(aes(col=log(!!col_var), size=log(!!col_var))) +
facet_grid(.~.data[[facet_var]], scales = "free", space="free") +
scale_x_discrete(labels=population_labels) + labs(col="log(TPM)", size = "") +
guides(size = "none")
# add themes
= g + theme_bw() + theme(panel.grid = element_blank(),
g axis.title = element_blank(),
axis.text.x = element_text(size=8, angle = 45, hjust= 1),
axis.text.y = element_text(size=10),
axis.ticks.x = element_blank(),
axis.ticks.y = element_blank(), legend.justification = c(0,0.3),
strip.text.x = element_text(size = 9, margin = margin(6, 2, 6, 2)),
panel.spacing = unit(0.25, "mm"))
if (sex == "Separate") {
= g + facet_wrap(~Sex, labeller = labeller(Sex = sexlabels), scales ="free_x")
g
}return(g)
}
= plot_dotplot(tcounts_med, Population, symbol, expression, "Dataset", sex)
dotplot print(dotplot)
4.4.3 Line plots
- Median gene expression in case and control conditions
- Linetype is encoded by the timepoint of treatment group (if exists)
#' @param group_vars symbol, Timepoint
#' @param linetype_var Timepoint
#'
<- function(data,sex, x_var, y_var, col_var, linetype_var, ...){
plot_lineplot = enquo(x_var)
x_var = enquo(y_var)
y_var = enquo(col_var)
col_var = enquos(...)
group_vars = enquo(linetype_var)
linetype_var
= theme_bw() +
theme_line theme(panel.grid = element_blank(),
axis.title = element_text(size=12),
axis.text.x = element_text(size=10, angle = 45, hjust= 1),
axis.text.y = element_text(size=10),
axis.ticks.x = element_blank(),
axis.ticks.y = element_blank(), legend.justification = c(0,0.3))
= ggplot2::ggplot(data, aes(x=!!x_var, y=!!y_var, group=interaction(!!!group_vars))) +
lineplot scale_colour_viridis(discrete=TRUE, end = .80) +
geom_line(aes(color=!!col_var, linetype=!!linetype_var)) + geom_point(aes(col=!!col_var)) + theme_line + ylab("Expression") +
guides(linetype=guide_legend(label.hjust=0.5))
if (sex =='Separate'){
= lineplot + facet_wrap(~Sex, labeller = labeller(Sex = sexlabels), scales = "free_x") + labs(col="")
lineplot
}
return(lineplot)
}
= get_median(tcounts, sex, var = expression, Condition, symbol, Timepoint, Dataset, Species)
tcounts_m
= plot_lineplot(tcounts_m, sex, Condition, expression, symbol, Timepoint, symbol, Timepoint)
lineplot print(lineplot)
4.4.4 A differential expression dot plot
- Colour intensity encodes the log2 fold change
- Dot represents if the change is significant (filled) or not (unfilled).
<- function(data, x_var, y_var, col_var, shape_var){
plot_degplot
= enquo(x_var)
x_var = enquo(y_var)
y_var = enquo(col_var)
col_var = enquo(shape_var)
shape_var
= ggplot2::ggplot(data, aes(x=interaction(!!x_var), y = !!y_var,
degplot text = paste('padj: ',padj))) +
scale_colour_viridis_c(option = "viridis", end = .90) +
geom_point(aes(col=!!col_var, shape=!!shape_var, size=0.5)) + scale_shape_manual(values=c(1, 19)) +
labs(shape = "", size = "") + scale_x_discrete(labels=subpopulation_labels) +
guides(size = "none")
= degplot + theme_bw() + theme(panel.grid = element_blank(),
degplot axis.title = element_blank(),
axis.text.x = element_text(size=8, angle = 45, hjust= 1),
axis.text.y = element_text(size=10),
axis.ticks.x = element_blank(),
axis.ticks.y = element_blank(), legend.justification = c(0,0.3),
strip.text.x = element_text(size = 9, margin = margin(6, 2, 6, 2)),
panel.spacing = unit(0.25, "mm"))
return(degplot)
}
= plot_degplot(deg_df, Population, symbol, log2FoldChange, sig)
degplot print(degplot)
4.4.5 A volcano plot
- showing case vs control samples
- genes of interest are labelled using colour.
<- function(data, il_genes, x_var, y_var, label_var){
plot_volcanoplot
= enquo(x_var)
x_var = enquo(y_var)
y_var = enquo(label_var)
label_var
= ggplot2::ggplot(data = data, aes(x = !!x_var, y = !!y_var)) +
volcanoplot geom_point(colour = "grey", alpha = 0.5) +
geom_point(data = il_genes, # New layer containing data subset il_genes
size = 2,
shape = 21,
fill = "firebrick",
colour = "black") +
geom_hline(yintercept = -log10(0.05),
linetype = "dashed") +
geom_vline(xintercept = c(log2(0.5), log2(2)),
linetype = "dashed") +
geom_label_repel(data = il_genes,
aes(label = !!label_var),
force = 2,
nudge_y = 1) +
# scale_colour_manual(values = cols) +
scale_x_continuous(breaks = c(seq(-10, 10, 2)),
limits = c(-10, 10)) +
# theme_line +
labs(y="-log10(FDR)")
return(volcanoplot)
}
= mutate(deg, log10fdr=-log10(padj))
res = res[res$symbol %in% genes,]
volc_df = plot_volcanoplot(res, volc_df, log2FoldChange, log10fdr, symbol)
volcanoplot print(volcanoplot)
## Warning: Removed 31279 rows containing missing values (`geom_point()`).
4.4.6 Other
A downloadable R Markdown report can be generated by clicking the “Generate Code” button, as shown below.
# download a R markdown report
$combineplot <- downloadHandler(
outputfilename = "RNAseq_homeplot.html",
content = function(file) {
<- list(matrix = final_df, genes = genes, sex = input$sex, scRNA = variables$pbmc, ratdeg = degr,
params mousedeg = degm, humandeg = degh,
human_gene_data = human_gene_data)
::withProgress(value = 0,
shinymessage = 'Rendering plotting report',
detail = 'This might take several minutes.',{
::render(input = "reports/combineplot.Rmd",
rmarkdownparams = params,
output_file = file,
envir = new.env(parent = globalenv()))
})
}# closure for download handler )
The plots can also be downloaded as a zip file via a download button “Download Data”.
# download all plots into a zip
$combineplots <- downloadHandler(
outputfilename = function() {
paste("combined", "zip", sep=".")
},
content = function(file){
# Set temporary working directory
<- setwd(tempdir())
owd on.exit(setwd(owd))
= c()
fs
# Save the plots
ggsave('combine_deg.png', plot = combine_degplot, width = 14, height = 6, dpi = 300, units = "in", device='png')
ggsave('combine_dot.png', plot = combine_dot, width = 14, height = 6, dpi = 300, units = "in", device='png')
ggsave('scrna_dot.png', plot = scrna_dot, width = 10, height = 8, dpi = 300, units = "in", device='png')
= c(fs, 'combine_deg.png')
fs = c(fs, 'combine_dot.png')
fs = c(fs, 'scrna_dot.png')
fs
# csv files for gene count data
write.csv(final_df,"result_table.csv", row.names = FALSE, col.names = TRUE)
= c(fs, "result_table.csv")
fs
zip(file, fs)
} )