From 9bee120f1678d69ecca7c94249b63d2a77d91f21 Mon Sep 17 00:00:00 2001 From: farchaab Date: Mon, 9 Dec 2024 12:03:36 +0100 Subject: [PATCH 1/4] delete resources --- ressources/old/Dockerfile | 129 -- ressources/old/Dockerfile.validation | 15 - ressources/old/barplots.yml | 24 - ressources/old/build_n_validate_docker.sh | 22 - .../pipeline_visualization/DADA2_dag.pdf | Bin 35624 -> 0 bytes .../DADA2_rule_graph.pdf | Bin 16681 -> 0 bytes .../both_rule_graph.pdf | Bin 18585 -> 0 bytes .../pipeline_visualization/vsearch_dag.pdf | Bin 34846 -> 0 bytes .../vsearch_rule_graph.pdf | Bin 16429 -> 0 bytes .../create_phyloseq_from_metadata.R | 42 - ressources/r_scripts/importation/dada2_to_R.R | 134 -- .../importation/merge_at_given_taxa_rank.R | 18 - .../importation/reads_counts_df_fct.R | 17 - .../importation/reorder_metadata_levels.R | 66 - .../1_Reads/dna_quant_barplots_fct.R | 118 -- .../1_Reads/qPCR_dna_quant_barplots_fct.R | 111 -- .../1_Reads/reads_counts_barplot_fct.R | 98 -- .../2_Barplots/adapted_KRONA_fct.R | 30 - .../visualization/2_Barplots/barplots_fct.R | 408 ------ .../barplots_fct_filtration_at_tax_levels.R | 328 ----- .../3_Heatmaps/201901_WIP_heatmap.R | 368 ------ .../3_Heatmaps/comparative_heatmap_fct.R | 359 ------ .../comparative_heatmap_fct_serie1_2019_WIP.R | 359 ------ .../3_Heatmaps/merge_variants_heatmap_fct.R | 435 ------- .../sequence_variants_heatmap_fct.R | 436 ------- .../4_Diversity/modif_plot_richness_fct.R | 115 -- ressources/r_scripts/visualization/app.R | 1125 ----------------- .../16S_input_table_insilico.tsv | 20 - .../template_files/16S_validation_set.tsv | 7 - .../DB_snakemake_bash_command.sh | 6 - .../template_files/ITS_output_example.tsv | 9 - .../template_files/ITS_validation_set.tsv | 9 - .../basic_snakemake_bash_command copy.sh | 8 - ressources/template_files/cluster.json | 82 -- ressources/template_files/config.yaml | 65 - ressources/template_files/config_DB.yaml | 21 - .../config_in_silico_validation.yaml | 48 - .../template_files/example_local_samples.tsv | 41 - .../template_files/example_sra_samples.tsv | 5 - .../insilico_ITS_input_taxID.tsv | 9 - 40 files changed, 5087 deletions(-) delete mode 100644 ressources/old/Dockerfile delete mode 100644 ressources/old/Dockerfile.validation delete mode 100644 ressources/old/barplots.yml delete mode 100644 ressources/old/build_n_validate_docker.sh delete mode 100644 ressources/pipeline_visualization/DADA2_dag.pdf delete mode 100644 ressources/pipeline_visualization/DADA2_rule_graph.pdf delete mode 100644 ressources/pipeline_visualization/both_rule_graph.pdf delete mode 100644 ressources/pipeline_visualization/vsearch_dag.pdf delete mode 100644 ressources/pipeline_visualization/vsearch_rule_graph.pdf delete mode 100755 ressources/r_scripts/importation/create_phyloseq_from_metadata.R delete mode 100755 ressources/r_scripts/importation/dada2_to_R.R delete mode 100755 ressources/r_scripts/importation/merge_at_given_taxa_rank.R delete mode 100755 ressources/r_scripts/importation/reads_counts_df_fct.R delete mode 100755 ressources/r_scripts/importation/reorder_metadata_levels.R delete mode 100755 ressources/r_scripts/visualization/1_Reads/dna_quant_barplots_fct.R delete mode 100755 ressources/r_scripts/visualization/1_Reads/qPCR_dna_quant_barplots_fct.R delete mode 100755 ressources/r_scripts/visualization/1_Reads/reads_counts_barplot_fct.R delete mode 100755 ressources/r_scripts/visualization/2_Barplots/adapted_KRONA_fct.R delete mode 100755 ressources/r_scripts/visualization/2_Barplots/barplots_fct.R delete mode 100644 ressources/r_scripts/visualization/2_Barplots/barplots_fct_filtration_at_tax_levels.R delete mode 100644 ressources/r_scripts/visualization/3_Heatmaps/201901_WIP_heatmap.R delete mode 100755 ressources/r_scripts/visualization/3_Heatmaps/comparative_heatmap_fct.R delete mode 100755 ressources/r_scripts/visualization/3_Heatmaps/comparative_heatmap_fct_serie1_2019_WIP.R delete mode 100755 ressources/r_scripts/visualization/3_Heatmaps/merge_variants_heatmap_fct.R delete mode 100755 ressources/r_scripts/visualization/3_Heatmaps/sequence_variants_heatmap_fct.R delete mode 100755 ressources/r_scripts/visualization/4_Diversity/modif_plot_richness_fct.R delete mode 100644 ressources/r_scripts/visualization/app.R delete mode 100644 ressources/template_files/16S_input_table_insilico.tsv delete mode 100644 ressources/template_files/16S_validation_set.tsv delete mode 100644 ressources/template_files/DB_snakemake_bash_command.sh delete mode 100644 ressources/template_files/ITS_output_example.tsv delete mode 100644 ressources/template_files/ITS_validation_set.tsv delete mode 100644 ressources/template_files/basic_snakemake_bash_command copy.sh delete mode 100644 ressources/template_files/cluster.json delete mode 100644 ressources/template_files/config.yaml delete mode 100644 ressources/template_files/config_DB.yaml delete mode 100644 ressources/template_files/config_in_silico_validation.yaml delete mode 100644 ressources/template_files/example_local_samples.tsv delete mode 100644 ressources/template_files/example_sra_samples.tsv delete mode 100644 ressources/template_files/insilico_ITS_input_taxID.tsv diff --git a/ressources/old/Dockerfile b/ressources/old/Dockerfile deleted file mode 100644 index 597ba73a..00000000 --- a/ressources/old/Dockerfile +++ /dev/null @@ -1,129 +0,0 @@ -FROM ubuntu:18.04 - -############################## Install miniconda environement, from miniconda3 Dockerfile ############################## -# $ docker build . -t continuumio/miniconda3:latest -t continuumio/miniconda3:4.5.11 -# $ docker run --rm -it continuumio/miniconda3:latest /bin/bash -# $ docker push continuumio/miniconda3:latest -# $ docker push continuumio/miniconda3:4.5.11 - -ENV LANG=C.UTF-8 LC_ALL=C.UTF-8 -ENV PATH /opt/conda/bin:$PATH -ENV TZ Europe/Zurich - -########################### Install system libraries, PANDAseq (libltdl7) dependencies and a package required for png plotting (libcairo2) ########################### -RUN echo $TZ > /etc/timezone &&\ - ln -snf /usr/share/zoneinfo/$TZ /etc/localtime &&\ - apt-get update && \ - apt-get install -y wget bzip2 ca-certificates curl git git-lfs libltdl7 libcairo2-dev tzdata && \ - dpkg-reconfigure -f noninteractive tzdata && \ - rm /etc/localtime && \ - apt-get clean && \ - apt-get autoremove -y && \ - rm -rf /var/lib/apt/lists/* - -RUN wget --quiet https://repo.anaconda.com/miniconda/Miniconda3-4.6.14-Linux-x86_64.sh -O ~/miniconda.sh && \ - /bin/bash ~/miniconda.sh -b -p /opt/conda && \ - rm ~/miniconda.sh && \ - /opt/conda/bin/conda clean -tipsy && \ - ln -s /opt/conda/etc/profile.d/conda.sh /etc/profile.d/conda.sh && \ - echo ". /opt/conda/etc/profile.d/conda.sh" >> ~/.bashrc && \ - echo "conda activate base" >> ~/.bashrc - -ENV TINI_VERSION v0.16.1 -ADD https://github.com/krallin/tini/releases/download/${TINI_VERSION}/tini /usr/bin/tini -RUN chmod +x /usr/bin/tini - -############################## Create pipeline_user, set useful variables ############################## -RUN useradd -r -u 1080 pipeline_user -ENV main=/home/pipeline_user -WORKDIR $main -ENV pipeline_folder=${main}/microbiome16S_pipeline -ENV assembly_finder_folder=${main}/assembly_finder - -########################### Install java (needed for Qiime tax assignemnt), Snakemake and Simulate_PCR dependancies ############################## -RUN conda config --add channels defaults && \ - conda config --add channels bioconda && \ - conda config --add channels conda-forge && \ - conda install snakemake=5.5.4 blast=2.9.0 perl-lwp-simple perl-bioperl java-jdk conda=4.6.14 - - -## Set in path a patched version of simulate PCR, DOI: 10.1186/1471-2105-15-237 for amplicons validation -RUN wget --quiet https://github.com/metagenlab/updated_simulate_PCR/archive/v0.9.9.tar.gz -O simulate_PCR.tar.gz && \ - mkdir /opt/simulate_PCR && \ - tar xzf simulate_PCR.tar.gz -C /opt/simulate_PCR && \ - mv /opt/simulate_PCR/updated_simulate_PCR-0.9.9/code/simulate_PCR /opt/simulate_PCR && \ - rm simulate_PCR.tar.gz && \ - rm -R /opt/simulate_PCR/updated_simulate_PCR-0.9.9 && \ - chmod +x /opt/simulate_PCR/simulate_PCR - -ENV PATH="/opt/simulate_PCR:${PATH}" -ENV PERL5LIB="/opt/conda/lib/site_perl/5.26.2" -ENV PATH="/opt/simulate_PCR:${PATH}" - -############################## Get the pipeline through github ####################### -## Call the access token to reach the private github repo -ARG GITHUB_AT - -## Add cleaned ezbiolcoud201805 db. -### ezbiocloud2018 -COPY ./data/ezbiocloud201805/DADA2_DB_amp_taxonomy_* /home/pipeline_user/microbiome16S_pipeline/data/ezbiocloud201805/ -COPY ./data/ezbiocloud201805/DB_amp* /home/pipeline_user/microbiome16S_pipeline/data/ezbiocloud201805/ -COPY ./data/ezbiocloud201805/DB_amp* /home/pipeline_user/microbiome16S_pipeline/data/ezbiocloud201805/ -COPY ./data/ezbiocloud201805/Decipher_DB_amp_taxonomy_decipher_trained_tax.rds /home/pipeline_user/microbiome16S_pipeline/data/ezbiocloud201805/ - -### ezbiocloud2018.201909 -COPY ./data/ezbiocloud201805.201909/DADA2_DB_amp_taxonomy_* /home/pipeline_user/microbiome16S_pipeline/data/ezbiocloud201805.201909/ -COPY ./data/ezbiocloud201805.201909/DB_amp* /home/pipeline_user/microbiome16S_pipeline/data/ezbiocloud201805.201909/ -COPY ./data/ezbiocloud201805.201909/Decipher_DB_amp_taxonomy_decipher_trained_tax.rds /home/pipeline_user/microbiome16S_pipeline/data/ezbiocloud201805.201909/ -COPY ./data/ezbiocloud201805.201909/Readme.Md /home/pipeline_user/microbiome16S_pipeline/data/ezbiocloud201805.201909/ -COPY ./data/ezbiocloud201805.201909/config_db_ezbiocloud20190913.yaml /home/pipeline_user/microbiome16S_pipeline/data/ezbiocloud201805.201909/ - -### ezbiocloudSilva132.201908 -COPY ./data/Silva132.201908/DADA2_DB_amp_taxonomy_* /home/pipeline_user/microbiome16S_pipeline/data/Silva132.201908/ -COPY ./data/Silva132.201908/DB_amp* /home/pipeline_user/microbiome16S_pipeline/data/Silva132.201908/ -COPY ./data/Silva132.201908/Decipher_DB_amp_taxonomy_decipher_trained_tax.rds /home/pipeline_user/microbiome16S_pipeline/data/Silva132.201908/ -COPY ./data/Silva132.201908/Readme.Md /home/pipeline_user/microbiome16S_pipeline/data/Silva132.201908/ -COPY ./data/Silva132.201908/config_db_silva_20190826.yaml /home/pipeline_user/microbiome16S_pipeline/data/Silva132.201908/ - -### unite201902 -COPY ./data/unite201902/DADA2_DB_amp_taxonomy_* /home/pipeline_user/microbiome16S_pipeline/data/unite201902/ -COPY ./data/unite201902/DB_amp* /home/pipeline_user/microbiome16S_pipeline/data/unite201902/ -COPY ./data/unite201902/Decipher_DB_amp_taxonomy_decipher_trained_tax.rds /home/pipeline_user/microbiome16S_pipeline/data/unite201902/ -COPY ./data/unite201902/Readme.Md /home/pipeline_user/microbiome16S_pipeline/data/unite201902/ - -COPY ./data/validation_datasets /home/pipeline_user/microbiome16S_pipeline/data/validation_datasets -COPY ./envs /home/pipeline_user/microbiome16S_pipeline/envs/ -COPY ./ressources /home/pipeline_user/microbiome16S_pipeline/ressources/ -COPY ./rules /home/pipeline_user/microbiome16S_pipeline/rules/ -COPY ./README* /home/pipeline_user/microbiome16S_pipeline/ -COPY ./Snakefile* /home/pipeline_user/microbiome16S_pipeline/ - - -## Clone the pipeline files and assembly_finder, developped by @idfarbanecha -RUN git clone --single-branch --branch v0.1.1-alpha https://$GITHUB_AT@github.com/metagenlab/assembly_finder.git $assembly_finder_folder - - -## Get in the validation directory -WORKDIR ${pipeline_folder}/data/validation_datasets - -#################### Build environements of the pipeline ##################### -## Here, with "--create-envs-only", we only build the environements -RUN snakemake --snakefile ${pipeline_folder}/Insilico_taxa_assign.Snakefile --use-conda --conda-prefix /opt/conda/ --create-envs-only --configfile 16S_config_in_silico.yml insilico_validation -RUN snakemake --snakefile ${pipeline_folder}/Snakefile --use-conda --conda-prefix /opt/conda/ --create-envs-only --configfile 16S_config.yml all PICRUSt2_output -RUN conda clean -a - -## Run the insilico validation pipeline. It allows to create compiled versions of reference databases with root privileges -ARG TEST_CPU -RUN snakemake --snakefile ${pipeline_folder}/Insilico_taxa_assign.Snakefile --cores $TEST_CPU --resources ncbi_requests=2 --use-conda --conda-prefix /opt/conda/ --configfile 16S_config_in_silico.yml insilico_validation - - -#################### Set final access rights, variables and work dir ##################### -RUN chown pipeline_user:pipeline_user ${main} -USER pipeline_user -RUN mkdir -p ${main}/data/analysis \ - ${main}/.config/biopython/Bio/Entrez/DTDs \ - ${main}/.config/biopython/Bio/Entrez/XSDs -ENV HOME ${main} -RUN conda init bash -WORKDIR ${main}/data/analysis/ -ENTRYPOINT ["/bin/bash"] diff --git a/ressources/old/Dockerfile.validation b/ressources/old/Dockerfile.validation deleted file mode 100644 index 89b8e377..00000000 --- a/ressources/old/Dockerfile.validation +++ /dev/null @@ -1,15 +0,0 @@ - -ARG VERSION -ARG TEST_CPU - -FROM metagenlab/amplicon_pipeline:$VERSION - -#################### Run the pipeline to test it ##################### -## Recover input files -RUN cp ${pipeline_folder}/data/validation_datasets/16S* ./ && \ - cp ${pipeline_folder}/data/validation_datasets/ITS* ./ - -## Run the pipeline to test it -RUN snakemake --snakefile ${pipeline_folder}/Snakefile --cores $TEST_CPU --resources max_copy=4 --use-conda --conda-prefix /opt/conda/ --configfile 16S_config.yml all - -RUN snakemake --snakefile ${pipeline_folder}/Snakefile --cores $TEST_CPU --resources max_copy=4 --use-conda --conda-prefix /opt/conda/ --configfile ITS_config.yml all diff --git a/ressources/old/barplots.yml b/ressources/old/barplots.yml deleted file mode 100644 index ea06f1fa..00000000 --- a/ressources/old/barplots.yml +++ /dev/null @@ -1,24 +0,0 @@ -name: barplots -channels: - - conda-forge - - bioconda - - biocore - - defaults -dependencies: - - r-base==3.5.1 - - r-devtools = 1.13.6 - - r-ggplot2 = 3.2.0 - - r-dplyr = 0.8.3 - - r-RColorBrewer = 1.1.2 - - r-data.table = 1.11.4 - - r-forcats = 0.3.0 - - r-ggpubr = 0.1.8 - - r-rlang - - r-cowplot = 0.9.3 - - r-cluster - - r-rtsne - - libv8 - - r-randomcolor - - - diff --git a/ressources/old/build_n_validate_docker.sh b/ressources/old/build_n_validate_docker.sh deleted file mode 100644 index 43d4fc0d..00000000 --- a/ressources/old/build_n_validate_docker.sh +++ /dev/null @@ -1,22 +0,0 @@ - -## To build and push Docker image: bash build_n_validate_docker.sh {version_tag_or_hash} {cores} {Github_access_token} -#!/bin/bash - -VERSION=$1 -echo $1 -CPU=$2 -echo $2 -GITHUBAT=$3 -echo $3 - -docker build . \ - -t metagenlab/amplicon_pipeline:$VERSION \ - -f ./Dockerfile \ - --build-arg GITHUB_AT=$GITHUBAT \ - --no-cache \ - --build-arg TEST_CPU=$CPU && \ -docker build . \ - -f ./Dockerfile.validation \ - --build-arg VERSION=$VERSION \ - --build-arg TEST_CPU=$CPU && \ -docker push metagenlab/amplicon_pipeline:$VERSION diff --git a/ressources/pipeline_visualization/DADA2_dag.pdf b/ressources/pipeline_visualization/DADA2_dag.pdf deleted file mode 100644 index a4faa2cf3b97c89338fd68e10c9130441ca11d28..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 35624 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 diff --git a/ressources/pipeline_visualization/DADA2_rule_graph.pdf b/ressources/pipeline_visualization/DADA2_rule_graph.pdf deleted file mode 100644 index 7784fdbdaa7982b31801d6c340c85b36b544db59..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 16681 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

=>*r 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 Gq5nT8#N;6W diff --git a/ressources/pipeline_visualization/both_rule_graph.pdf b/ressources/pipeline_visualization/both_rule_graph.pdf deleted file mode 100644 index 331dd42248c321a92c57dfb71252ad8c53078c9e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 18585 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 xFv#XXK`oOU=CFwFn} diff --git a/ressources/pipeline_visualization/vsearch_dag.pdf b/ressources/pipeline_visualization/vsearch_dag.pdf deleted file mode 100644 index 95455627e923fbffeaa1270b1fd64d92f8b123ab..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 34846 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 diff --git a/ressources/pipeline_visualization/vsearch_rule_graph.pdf b/ressources/pipeline_visualization/vsearch_rule_graph.pdf deleted file mode 100644 index ce6d6aaa0311fc5fd1d083f29b776c6f646c0945..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 16429 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 diff --git a/ressources/r_scripts/importation/create_phyloseq_from_metadata.R b/ressources/r_scripts/importation/create_phyloseq_from_metadata.R deleted file mode 100755 index 28ad3602..00000000 --- a/ressources/r_scripts/importation/create_phyloseq_from_metadata.R +++ /dev/null @@ -1,42 +0,0 @@ - - -# Generate a phyloseq object from what have been loaded into the R environment. -## More on phyloseq is available here: (https://joey711.github.io/phyloseq/import-data.html) -## https://github.com/joey711/phyloseq/issues/901 - - -### Generate a function to create a phyloseq object -create_phyloseq_fct <- function(physeq_name = "physeq", melted_df_name = "physeq_df", otu_table, phy_tree = NULL, tax_table, Metadata_table){ - - ### Create the phyloseq object in cases where no phylogenic tree are given - if (is.null(phy_tree)){ - physeq_obj<-phyloseq( - otu_table(otu_table$data, taxa_are_rows = T), - tax_table(as.data.frame(tax_table) %>% select(-Confidence) %>% column_to_rownames("Feature.ID") %>% as.matrix()), #moving the taxonomy to the way phyloseq wants it - sample_data(Metadata_table %>% as.data.frame() %>% column_to_rownames("Sample"))) - } - else{ - ### Create the phyloseq object with a phylogenic tree - physeq_obj<-phyloseq( - otu_table(otu_table$data, taxa_are_rows = T), - phy_tree(phy_tree$data), - tax_table(as.data.frame(tax_table) %>% select(-Confidence) %>% column_to_rownames("Feature.ID") %>% as.matrix()), #moving the taxonomy to the way phyloseq wants it - sample_data(Metadata_table %>% as.data.frame() %>% column_to_rownames("Sample"))) - } - - ### Assign the object into the environnement - assign(value = physeq_obj, x = physeq_name, envir = .GlobalEnv) - - ### Melt the physeq objet into a dataframe with one row per feature.id and per sample, needed later - physeq_df <- psmelt(physeq_obj) - - ### Assign the object into the environnement - assign(value = physeq_df, x = melted_df_name, envir = .GlobalEnv) - - ### Write the phyloseq object in a file, so it doesn't have to be recalculated each time - save(physeq_obj, file = "physeq_object") - - ### Write this table in a tsv file since it is ground for coming analysis and slow to compute - write.table(x = physeq_df, file = "physeq_df.tsv", append = F, sep = "\t", eol = "\n", row.names = F, col.names = T ) - -} \ No newline at end of file diff --git a/ressources/r_scripts/importation/dada2_to_R.R b/ressources/r_scripts/importation/dada2_to_R.R deleted file mode 100755 index 50ab87a6..00000000 --- a/ressources/r_scripts/importation/dada2_to_R.R +++ /dev/null @@ -1,134 +0,0 @@ -# Load objects into R -# Here, we load: -# - taxonomy table -# - Metadata table -# - features (either sequences variants or OTU) counts table -# - taxonomic tree -# -# Regarding the "taxonomy table": -# The loaded table should have NO headers and in column 1. the features ID and in column 2. seven taxonomic ranks sepatrated by ";". We have here the option to replace or not the NA by the previous taxonomic rank and a spaceholder such as "_sp" for unassigned species -# -# Regarding the "Metadata table", it should follow the template described in doc (to be written), including beeing in the .tsv format -# -# Regarding the objects comming from Qiime2 pipeline which are in .qza format : -# They are loaded using the package available here: (https://github.com/jbisanz/qiime2R) - - -### Create the function - -load_objects_fct <- function(taxonomy_class_table, replace_empty_tax = TRUE, features_counts_table , Metadata_table, tree) { - - ## Taxonomy table - - ### Load into R a table with all Features ID and their taxonomic assignemnt. - taxonomy_class_table<-read_tsv(file.path(taxonomy_class_table), col_names = FALSE) - - ### Convert the table into a tabular split version - taxonomy_class_table<-taxonomy_class_table %>% as.tibble() %>% separate(X2, sep=";", c("Kingdom","Phylum","Class","Order","Family","Genus","Species")) - - ### Replace the not properly named columns headers into proper ones - colnames(taxonomy_class_table)[colnames(taxonomy_class_table)=="X1"] <- "Feature.ID" - colnames(taxonomy_class_table)[colnames(taxonomy_class_table)=="X3"] <- "Confidence" - - ### Convert taxonomic levels as character (needed for the next steps) - taxonomy_class_table$Kingdom<-as.character(taxonomy_class_table$Kingdom) - taxonomy_class_table$Phylum<-as.character(taxonomy_class_table$Phylum) - taxonomy_class_table$Class<-as.character(taxonomy_class_table$Class) - taxonomy_class_table$Order<-as.character(taxonomy_class_table$Order) - taxonomy_class_table$Family<-as.character(taxonomy_class_table$Family) - taxonomy_class_table$Genus<-as.character(taxonomy_class_table$Genus) - taxonomy_class_table$Species<-as.character(taxonomy_class_table$Species) - - if(replace_empty_tax == TRUE) { - ### Replace NA by the previous order + a space_holder for each taxonomic level - taxonomy_class_table$Kingdom[is.na(taxonomy_class_table$Kingdom)] <- (("Unkown_Kingdom")[is.na(taxonomy_class_table$Kingdom)]) - taxonomy_class_table$Phylum[is.na(taxonomy_class_table$Phylum)] <- ((paste(taxonomy_class_table$Kingdom,"_phy",sep=""))[is.na(taxonomy_class_table$Phylum)]) - taxonomy_class_table$Class[is.na(taxonomy_class_table$Class)] <- ((paste(taxonomy_class_table$Phylum,"_clas",sep=""))[is.na(taxonomy_class_table$Class)]) - taxonomy_class_table$Order[is.na(taxonomy_class_table$Order)] <- ((paste(taxonomy_class_table$Class,"_ord",sep=""))[is.na(taxonomy_class_table$Order)]) - taxonomy_class_table$Family[is.na(taxonomy_class_table$Family)] <- ((paste(taxonomy_class_table$Order,"_fam",sep=""))[is.na(taxonomy_class_table$Family)]) - taxonomy_class_table$Genus[is.na(taxonomy_class_table$Genus)] <- ((paste(taxonomy_class_table$Family,"_gen",sep=""))[is.na(taxonomy_class_table$Genus)]) - taxonomy_class_table$Species[is.na(taxonomy_class_table$Species)] <- ((paste(taxonomy_class_table$Genus,"_sp",sep=""))[is.na(taxonomy_class_table$Species)]) - - print("table NA remplaced by spaceholders") - - }else{ - - print("table NA NOT remplaced by spaceholders") - } - - ### Convert taxonomic levels back as factor (Not sure if really needed?) - taxonomy_class_table$Kingdom<-as.factor(taxonomy_class_table$Kingdom) - taxonomy_class_table$Phylum<-as.factor(taxonomy_class_table$Phylum) - taxonomy_class_table$Class<-as.factor(taxonomy_class_table$Class) - taxonomy_class_table$Order<-as.factor(taxonomy_class_table$Order) - taxonomy_class_table$Family<-as.factor(taxonomy_class_table$Family) - taxonomy_class_table$Genus<-as.factor(taxonomy_class_table$Genus) - taxonomy_class_table$Species<-as.factor(taxonomy_class_table$Species) - - ### Save this table in global environnement - taxonomy_class_table <<- taxonomy_class_table - - - ## .tsv Metadata table - - ### Load Metadata table into R environment - Metadata<-read_tsv(file.path(Metadata_table), col_names = TRUE) - - ### Save this table in global environnement - Metadata <<- Metadata - - - ## Qiime2 .qza features counts table - - ### Load Qiime2 .qza counts table into R environment - features_counts_table<-read_qza(file.path(features_counts_table)) - - ### Shows the unique identifier of the file - print("features counts table") - print(features_counts_table$uuid) - - ### Print information about the object - names(features_counts_table) - - ### Show the first 5 samples and features - print(features_counts_table$data[1:5,1:5]) - - ### Save this table in global environnement - features_counts_table <<- features_counts_table - - - - ## Qiime2 .qza tree. - - - ### In case there IS a tree - if (!is.null(tree)){ - - ### Load into environment Qiime2 .qza tree - tree<-read_qza(file.path(tree)) - - ### Save this table in global environnement - tree <<- tree - - ### Shows the unique identifier of the file - print("Tree") - print(tree$uuid) - - ### Shows data - print(tree$data) - - ### Shows data - print(tree$data$Vectors[1:5, 1:15]) - - ### Save this table in global environnement - tree <<- tree - - ### In case there is NO tree - - }else if (is.null(tree)){ - print("No tree loaded, tree is NULL") - } - - -} - diff --git a/ressources/r_scripts/importation/merge_at_given_taxa_rank.R b/ressources/r_scripts/importation/merge_at_given_taxa_rank.R deleted file mode 100755 index f4b03366..00000000 --- a/ressources/r_scripts/importation/merge_at_given_taxa_rank.R +++ /dev/null @@ -1,18 +0,0 @@ - -merge_variants_df_fct <- function(melted_dataframe, rank_sequences_merging){ - -to_melt_df <- melted_dataframe - -rank_colum <- rlang::sym(rank_sequences_merging) -merged_melted_df <- to_melt_df %>% - dplyr::group_by(Sample, !!(rank_colum)) %>% - dplyr::mutate(Abundance = sum(Abundance)) %>% - dplyr::ungroup() %>% - dplyr::distinct(Sample, !!(rank_colum), .keep_all = TRUE) - -name <- paste0(rank_sequences_merging, "_merged_df") - -assign(value = merged_melted_df, x = name, envir = .GlobalEnv) - - -} \ No newline at end of file diff --git a/ressources/r_scripts/importation/reads_counts_df_fct.R b/ressources/r_scripts/importation/reads_counts_df_fct.R deleted file mode 100755 index a3108805..00000000 --- a/ressources/r_scripts/importation/reads_counts_df_fct.R +++ /dev/null @@ -1,17 +0,0 @@ -## Function to create a Metadata table containing the number of reads per Sample - -reads_counts_df_fct <- function(Metadata_table = Metadata , out_put_name = "reads_counts_df", melted_dataframe = physeq_df ){ - - print('"Metadata_table" column names must match the "melted_dataframe" ones') - - - reads_counts_df <- melted_dataframe %>% group_by(Sample) %>% - mutate(TotalReads = sum(Abundance)) %>% - ungroup() %>% - distinct(Sample, .keep_all = TRUE) %>% - select(c(colnames(Metadata_table), TotalReads)) - - ### Assign the object into the environnement - assign(x = out_put_name, value = reads_counts_df, envir = .GlobalEnv) - -} diff --git a/ressources/r_scripts/importation/reorder_metadata_levels.R b/ressources/r_scripts/importation/reorder_metadata_levels.R deleted file mode 100755 index 4b7a4b23..00000000 --- a/ressources/r_scripts/importation/reorder_metadata_levels.R +++ /dev/null @@ -1,66 +0,0 @@ - -############################################################################################################ - -## Define levels for Metadata columns -# Define manually or automatically an order for the filling column, e.g. the sample_source column and the grouping column, e.g. the patientID column (sorting them in a way compatible with ggplot2). This way it will be shown in this given order in plots. Should be adapted depending of the desired output of plots. -# -#The level is set for two columns: -# - the grouping column, which will be used to group samples together on individual graphs -# - the filling column, which will be used to fill barplots of reads in different colors -# - the sample_label, which will be used as label in plots. It should be unique as Sample but more descriptive or duplicated (e.g.), for instance if there is one per grouping_column. -# By default, it is ordered by the order of other columns but can be set manually -# - the facetting_column, if it is intendend to create facets for a specific factors, creating one common graph but with multiple pannels - - -## Set the levels for our columns of interest - -### create a function to define levels -reoder_columns_levels_fct <- function(grouping_column, grouping_col_order_vector = NULL, filling_column, filling_col_order_vector = NULL, facetting_column = NULL, facetting_col_order_vector = NULL, sample_label, x_axis_col_order_vector = NULL, Metadata_table){ - - ### Order the grouping_column - ### In absence of specific order vector, order by numeric order - if (is.null(grouping_col_order_vector)){ - Metadata_table[[grouping_column]] <- factor(Metadata_table[[grouping_column]], levels = sort(unique(Metadata_table[[grouping_column]])), ordered = TRUE) - }else{L - ### In option, we could set an order manually - Metadata_table[[grouping_column]] <- factor(Metadata_table[[grouping_column]], levels = grouping_col_order_vector, ordered = TRUE) - } - Metadata <<- Metadata_table - - - ### Order the filling_column - ### In absence of specific order vector, order by numeric order - if (is.null(filling_col_order_vector)){ - Metadata_table[[filling_column]] <- factor(Metadata_table[[filling_column]], levels = sort(unique(Metadata_table[[filling_column]])), ordered = TRUE) - }else{L - ### In option, we could set an order manually - Metadata_table[[filling_column]] <- factor(Metadata_table[[filling_column]], levels = filling_col_order_vector, ordered = TRUE) - } - Metadata <<- Metadata_table - - - - ### Order the facetting_column - ### In absence of specific order vector, order by numeric order - if (is.null(facetting_col_order_vector)){ - Metadata_table[[facetting_column]] <- factor(Metadata_table[[facetting_column]], levels = sort(unique(Metadata_table[[facetting_column]])), ordered = TRUE) - }else{ - ### In option, we could set an order manually - Metadata_table[[facetting_column]] <- factor(Metadata_table[[facetting_column]], levels = facetting_col_order_vector, ordered = TRUE) - } - Metadata <<- Metadata_table - - - - ### By default, set the levels for the sample_label by the other - if (is.null(x_axis_col_order_vector)){ - Metadata_table[[sample_label]] <- fct_reorder(Metadata_table[[sample_label]], as.numeric(Metadata_table[[grouping_column]])) - Metadata_table[[sample_label]] <- fct_reorder(Metadata_table[[sample_label]], as.numeric(Metadata_table[[filling_column]])) - Metadata_table[[sample_label]] <- fct_reorder(Metadata_table[[sample_label]], as.numeric(Metadata_table[[facetting_column]])) - }else{ - ### In option, we could set an order manually - Metadata_table[[sample_label]] <- factor(Metadata_table[[sample_label]], levels = x_axis_col_order_vector, ordered = TRUE) - } - Metadata <<- Metadata_table - -} diff --git a/ressources/r_scripts/visualization/1_Reads/dna_quant_barplots_fct.R b/ressources/r_scripts/visualization/1_Reads/dna_quant_barplots_fct.R deleted file mode 100755 index c50dc726..00000000 --- a/ressources/r_scripts/visualization/1_Reads/dna_quant_barplots_fct.R +++ /dev/null @@ -1,118 +0,0 @@ -### Create the function -dna_quant_barplots_fct <- function(count_table_df, figures_save_dir, grouping_column, filling_column, sample_label, distinct_colors = FALSE, facet_plot = FALSE, facetting_column = NULL, dna_quant_column, patient_ID){ - - - ### Record data on the distribution of number of reads (useful later to scale plots axis) - smin <- min(count_table_df$TotalReads) - print(smin) - smean <- mean(count_table_df$TotalReads) - print(smean) - smax <- max(count_table_df$TotalReads) - print(smax) - - - - ### Create an overall barplot - - ### Set colors - - #### BrewerColors - if (distinct_colors == FALSE) { - getPalette = colorRampPalette(brewer.pal(n=11, "Paired")) - ColList = unique(count_table_df[[filling_column]]) - ColPalette = getPalette(length(ColList)) - names(ColPalette) = ColList - colors_palette <- ColPalette - } - if (distinct_colors == TRUE) { - - ### Distinct colors - set.seed(2) - ColList <- unique(count_table_df[[filling_column]]) - ColPalette <- distinctColorPalette(altCol = FALSE, k = length(unique(count_table_df[[filling_column]]))) - names(ColPalette) = ColList - colors_palette <- ColPalette - } - - - -for (i in patient_ID){ - - count_table_df_i <- count_table_df %>% filter(grepl(i, .[[grouping_column]])) - - - ### Create the barplot for DNA - overall_dna_barplot <- ggplot(count_table_df_i, aes(x = get(sample_label), y = get(dna_quant_column), fill = get(filling_column))) + - geom_col() + - theme_bw() + - scale_fill_manual(values = colors_palette) + - labs(x= grouping_column, y ="Quantification [nM]") + - ggtitle(paste("DNA quantity")) + - scale_x_discrete(drop = TRUE) + # Keep all groups, included the ones with values. Alternative : (drop = FALSE) - scale_y_continuous(labels = comma) + - theme(axis.text.x = element_text(angle = 90, hjust = 1), axis.title.x=element_blank(), axis.title.y=element_blank()) + - guides(fill=guide_legend(title=filling_column)) + - theme(axis.title.x=element_blank(), axis.text.x=element_blank(), axis.ticks.x=element_blank()) - - ### Create facet view - if (isTRUE(facet_plot)){ - overall_dna_barplot <- overall_dna_barplot + facet_grid(.~get(facetting_column), scales = "free") - - } - - - - ### Zoom on a on lower y - zoomed_overall_dna_barplot <- overall_dna_barplot + coord_cartesian(ylim=c(0,50)) + - theme(axis.title.x=element_blank(), axis.text.x=element_blank(), axis.ticks.x=element_blank()) + - ggtitle(paste("DNA quantity - low values")) - - ### Create facet view - if (isTRUE(facet_plot)){ - zoomed_overall_dna_barplot <- zoomed_overall_dna_barplot + facet_grid(.~get(facetting_column), scales = "free") - } - - ### Create the barplot for reads - overall_reads_barplot <- ggplot(count_table_df_i, aes(x = get(sample_label), y = TotalReads, fill = get(filling_column))) + - theme_bw() + - geom_col() + - scale_fill_manual(values = colors_palette) + - labs(x= grouping_column, y ="Reads") + - ggtitle(paste("Reads counts overall")) + - scale_x_discrete(drop = TRUE) + # Keep all groups, included the ones with values. Alternative : (drop = FALSE) - scale_y_continuous(labels = comma, limits = c(0,smax)) + - theme(axis.text.x = element_text(angle = 90, hjust = 1), axis.title.x=element_blank(), axis.title.y=element_blank()) + - guides(fill=guide_legend(title=filling_column)) - - - ### Create facet view - if (isTRUE(facet_plot)){ - overall_reads_barplot <- overall_reads_barplot + facet_grid(.~get(facetting_column), scales = "free") - - } - - ### Zoom on a on lower y - zoomed_overall_reads_barplot <- overall_reads_barplot + coord_cartesian(ylim=c(0,300000)) + - theme(axis.title.x=element_blank(), axis.text.x=element_blank(), axis.ticks.x=element_blank()) + - ggtitle(paste("Reads counts overal - low values")) - - - - ### Create facet view - if (isTRUE(facet_plot)){ - zoomed_overall_reads_barplot <- zoomed_overall_reads_barplot + facet_grid(.~get(facetting_column), scales = "free") - }else{ - - zoomed_overall_reads_barplot <- zoomed_overall_reads_barplot - } - - - - - - png(paste(figures_save_dir, "/reads/barplot/DNA_Quant_", filling_column, "_", grouping_column,"_",i,"_reads_barplot.png",sep=""), width = 400, height = 900, units = "px") - grid.draw(rbind(ggplotGrob(zoomed_overall_dna_barplot), ggplotGrob(overall_dna_barplot), ggplotGrob(zoomed_overall_reads_barplot), ggplotGrob(overall_reads_barplot), size = "last")) - dev.off() - -} -} diff --git a/ressources/r_scripts/visualization/1_Reads/qPCR_dna_quant_barplots_fct.R b/ressources/r_scripts/visualization/1_Reads/qPCR_dna_quant_barplots_fct.R deleted file mode 100755 index 760d1309..00000000 --- a/ressources/r_scripts/visualization/1_Reads/qPCR_dna_quant_barplots_fct.R +++ /dev/null @@ -1,111 +0,0 @@ - -## qPCR reads plots - -https://gist.github.com/tomhopper/faa24797bb44addeba79 - - -```{r} - -### Create the function -qPCR_dna_quant_barplots_fct <- function(count_table_df, figures_save_dir, grouping_column, filling_column, sample_label, distinct_colors = FALSE, facet_plot = FALSE, facetting_column = NULL){ - - - ### Record data on the distribution of number of reads (useful later to scale plots axis) - smin <- min(count_table_df$TotalReads) - print(smin) - smean <- mean(count_table_df$TotalReads) - print(smean) - smax <- max(count_table_df$TotalReads) - print(smax) - - - - ### Create an overall barplot - - ### Set colors - - #### BrewerColors - if (distinct_colors == FALSE) { - getPalette = colorRampPalette(brewer.pal(n=11, "Paired")) - ColList = unique(count_table_df[[filling_column]]) - ColPalette = getPalette(length(ColList)) - names(ColPalette) = ColList - colors_palette <- ColPalette - } - if (distinct_colors == TRUE) { - - ### Distinct colors - set.seed(2) - ColList <- unique(count_table_df[[filling_column]]) - ColPalette <- distinctColorPalette(altCol = FALSE, k = length(unique(count_table_df[[filling_column]]))) - names(ColPalette) = ColList - colors_palette <- ColPalette - } - - - ### Create the barplot for qPCR actin - qPCR_16s_barplot <- ggplot(count_table_df, aes(x = get(sample_label), y = Quantity_actin, fill = get(filling_column))) + - geom_col() + - scale_fill_manual(values = colors_palette) + - labs(x= grouping_column, y ="Quantification actin qPCR [copies/ml]") + - ggtitle(paste("Actin quantity qPCR")) + - scale_x_discrete(drop = TRUE) + # Keep all groups, included the ones with values. Alternative : (drop = FALSE) - scale_y_continuous(labels = comma) + - theme(axis.text.x = element_text(angle = 90, hjust = 1), axis.title.x=element_blank(), axis.title.y=element_blank()) + - guides(fill=guide_legend(title=filling_column)) + - theme(axis.title.x=element_blank(), axis.text.x=element_blank(), axis.ticks.x=element_blank()) - - - ### Create the barplot for qPCR 16S - qPCR_actin_barplot <- ggplot(count_table_df, aes(x = get(sample_label), y = Quantity_16S, fill = get(filling_column))) + - geom_col() + - scale_fill_manual(values = colors_palette) + - labs(x= grouping_column, y ="Quantification 16S qPCR [copies/ml]") + - ggtitle(paste("16S quantity qPCR")) + - scale_x_discrete(drop = TRUE) + # Keep all groups, included the ones with values. Alternative : (drop = FALSE) - scale_y_continuous() + - theme(axis.text.x = element_text(angle = 90, hjust = 1), axis.title.x=element_blank(), axis.title.y=element_blank()) + - guides(fill=guide_legend(title=filling_column)) + - theme(axis.title.x=element_blank(), axis.text.x=element_blank(), axis.ticks.x=element_blank()) - - - ## Create the barplot for bact to actin ratio - bact_to_actin_ratio_barplot <- ggplot(count_table_df, aes(x = get(sample_label), y = bact_to_human_ratio, fill = get(filling_column))) + - geom_col() + - scale_fill_manual(values = colors_palette) + - labs(x= grouping_column, y ="Quantitative DNA ratio between bacteria and actin") + - ggtitle(paste("Quantitative ratio between bacterial and human DNA")) + - scale_x_discrete(drop = TRUE) + # Keep all groups, included the ones with values. Alternative : (drop = FALSE) - scale_y_continuous(labels = comma) + - theme(axis.text.x = element_text(angle = 90, hjust = 1), axis.title.x=element_blank(), axis.title.y=element_blank()) + - guides(fill=guide_legend(title=filling_column)) + - theme(axis.title.x=element_blank(), axis.text.x=element_blank(), axis.ticks.x=element_blank()) - - - ### Create the barplot for reads - overall_reads_barplot <- ggplot(count_table_df, aes(x = get(sample_label), y = TotalReads, fill = get(filling_column))) + - geom_col() + - scale_fill_manual(values = colors_palette) + - labs(x= sample_label, y ="Reads") + - ggtitle(paste("Reads counts overall")) + - scale_x_discrete(drop = TRUE) + # Keep all groups, included the ones with values. Alternative : (drop = FALSE) - scale_y_continuous(labels = comma, limits = c(0,smax)) + - theme(axis.text.x = element_text(angle = 90, hjust = 1), axis.title.x=element_blank(), axis.title.y=element_blank()) + - guides(fill=guide_legend(title=filling_column)) - - png(paste(figures_save_dir, "/reads/barplot/DNA_Quant_", filling_column, "_", grouping_column, "_reads_barplot2.png",sep=""), width = 800, height = 800, units = "px") - grid.draw(rbind(ggplotGrob(qPCR_16s_barplot), ggplotGrob(qPCR_actin_barplot), ggplotGrob(bact_to_actin_ratio_barplot), ggplotGrob(overall_reads_barplot), size = "last")) - dev.off() - - -} - -dna_quant_barplots_fct(count_table_df = reads_counts_df, figures_save_dir = "r_figures", grouping_column = "extraction_kit", filling_column = "sample_source", sample_label = "sample_label", distinct_colors = TRUE, facet_plot = FALSE) - - - - -``` - - - diff --git a/ressources/r_scripts/visualization/1_Reads/reads_counts_barplot_fct.R b/ressources/r_scripts/visualization/1_Reads/reads_counts_barplot_fct.R deleted file mode 100755 index 68ce7886..00000000 --- a/ressources/r_scripts/visualization/1_Reads/reads_counts_barplot_fct.R +++ /dev/null @@ -1,98 +0,0 @@ -### Create the function -reads_barplots_fct <- function(count_table_df, figures_save_dir, grouping_column, filling_column, sample_label, distinct_colors = FALSE, facet_plot = FALSE, facetting_column = NULL, zoome){ - - - ### Record data on the distribution of number of reads (useful later to scale plots axis) - smin <- min(count_table_df$TotalReads) - print(smin) - smean <- mean(count_table_df$TotalReads) - print(smean) - smax <- max(count_table_df$TotalReads) - print(smax) - - - - ### Create an overall barplot - - ### Set colors - - #### BrewerColors - if (distinct_colors == FALSE) { - getPalette = colorRampPalette(brewer.pal(n=11, "Paired")) - ColList = unique(count_table_df[[filling_column]]) - ColPalette = getPalette(length(ColList)) - names(ColPalette) = ColList - colors_palette <- ColPalette - } - if (distinct_colors == TRUE) { - - ### Distinct colors - set.seed(2) - ColList <- unique(count_table_df[[filling_column]]) - ColPalette <- distinctColorPalette(altCol = TRUE, k = length(unique(count_table_df[[filling_column]]))) - names(ColPalette) = ColList - colors_palette <- ColPalette - } - - ### Create the barplot - overall_reads_barplot <- ggplot(count_table_df, aes(x = get(sample_label), y = TotalReads, fill = get(filling_column))) + - theme_bw() + - geom_col() + - scale_fill_manual(values = colors_palette) + - labs(x= grouping_column, y ="Reads") + - ggtitle(paste("Reads counts overall")) + - scale_x_discrete(drop = TRUE) + # Keep all groups, included the ones with values. Alternative : (drop = FALSE) - scale_y_continuous(labels = comma, limits = c(0,smax)) + - theme(axis.text.x = element_text(angle = 90, hjust = 1)) + - guides(fill=guide_legend(title=filling_column)) - - - ### Save it - ggsave(overall_reads_barplot, filename = paste(figures_save_dir, "/reads/barplot/overall", filling_column, "_", grouping_column, "_reads_barplot.png",sep=""), width = 10, height = 5) - - - ### Create facet view - if (isTRUE(facet_plot)){ - facet_reads_barplot <- overall_reads_barplot + facet_grid(.~get(facetting_column), scales = "free", space = "free") - - facet_reads_barplot <<- facet_reads_barplot - - ### Save it - ggsave(facet_reads_barplot, filename = paste(figures_save_dir, "/reads/barplot/overall_facet",facetting_column,"_", filling_column, "_", grouping_column, "_reads_barplot.png",sep=""), width = 10, height = 5) - } - - - - ### Create a separate plot for each of the grouping column value - - - ### Run a loop for each on the values in a column - for (i in unique(reads_counts_df[[grouping_column]])) { - - filtered_count_table_df <- filter(count_table_df, count_table_df[[grouping_column]] == i ) - - smax_g <- max(filtered_count_table_df$TotalReads) - print(smax_g) - - ### Create the barplot - reads_barplot <- ggplot(filtered_count_table_df, aes(x = get(sample_label), y = TotalReads, fill = get(filling_column))) + - theme_bw() + - geom_col() + - scale_fill_manual(values = colors_palette) + - labs(x= grouping_column, y ="Reads") + - ggtitle(paste("Reads counts",grouping_column, i)) + - scale_x_discrete(drop = TRUE) + # Keep all groups, included the ones with values. Alternative : (drop = FALSE) - scale_y_continuous(labels = comma, limits = c(0,smax_g)) + - theme(axis.text.x = element_text(angle = 90, hjust = 1)) + - guides(fill=guide_legend(title=filling_column)) - - - ### Create facet view - if (isTRUE(facet_plot)){ - reads_barplot <- reads_barplot + facet_grid(.~get(facetting_column), scales = "free", space = "free") - - } - - ### Save it - ggsave(reads_barplot, filename = paste(figures_save_dir, "/reads/barplot/", filling_column, "_", grouping_column, i, "_reads_barplot.png",sep=""), width = 10, height = 5) - }} diff --git a/ressources/r_scripts/visualization/2_Barplots/adapted_KRONA_fct.R b/ressources/r_scripts/visualization/2_Barplots/adapted_KRONA_fct.R deleted file mode 100755 index a195d9f6..00000000 --- a/ressources/r_scripts/visualization/2_Barplots/adapted_KRONA_fct.R +++ /dev/null @@ -1,30 +0,0 @@ - -## Adapter function https://rdrr.io/github/cpauvert/psadd/man/plot_krona.html. -## This adapted version goes form the melted df instead of the phyloseq object in the original version of the function - - -adapted_KRONA_fct <- function(melted_dataframe, grouping_column, r_figures, sample_label){ - - df0 <- melted_dataframe - -for (i in (unique(df0[[grouping_column]]))){ - - df <- filter(df0, df0[[grouping_column]] == i) - - - df <- df[, c("Abundance", sample_label, "Kingdom", "Phylum", "Class", "Order", "Family", "Genus", "Species", "OTU")] - df <- as.data.frame(unclass(df)) - df[, 2] <- gsub(" |\\(|\\)", "", df[, 2]) - df[, 2] <- as.factor(df[, 2]) - dir.create(file.path(r_figures,"/",i)) - for (lvl in levels(df[, 2])) { - write.table(unique(df[which(df[, 2] == lvl), -2]), file = paste0(r_figures,"/",i, "/", lvl, "taxonomy.txt"), sep = "\t", row.names = F, col.names = F, na = "", quote = F) - } - - krona_args <- paste(r_figures,"/", i, "/", levels(df[, 2]), "taxonomy.txt,", levels(df[, 2]), sep = "", collapse = " ") - output <- paste(r_figures,"/",i,".html", sep = "") - system(paste("ktImportText", krona_args, "-o", output, sep = " ")) - -}} - - diff --git a/ressources/r_scripts/visualization/2_Barplots/barplots_fct.R b/ressources/r_scripts/visualization/2_Barplots/barplots_fct.R deleted file mode 100755 index cf625889..00000000 --- a/ressources/r_scripts/visualization/2_Barplots/barplots_fct.R +++ /dev/null @@ -1,408 +0,0 @@ - -### Create a function -barplots_fct <- function(melted_dataframe, sample_label, grouping_column, grouping_column_filtering = c(FALSE, TRUE), grouping_column_filtering_value, t_neg_PCR_sample_on_plots, t_neg_PCR_sample_grp_filter_column_value, taxonomic_filtering = c(TRUE, FALSE), taxonomic_filtering_rank = "Kingdom" , taxonomic_filtering_value = "Bacteria" , quantity_filtering_type = c("relative", "absolute", "rank", "nofiltering", "absolute_and_rank"), absolute_quantity_filtering_value, relative_quantity_filtering_value, rank_quantity_filtering_value, plotting_value = c("relative", "absolute"), plotting_tax_ranks = "all", figures_save_dir, distinct_colors = TRUE, horizontal_plot = FALSE, facet_plot = FALSE, facetting_column = NULL, order_by_abundance = TRUE, separated_legend){ - - ### In option, filter for a given grouping_columns. In both cases keep only lines with Abundance not equal to 0 to reduce the dataframe size - - ### No filter for the grouping column - if (isFALSE(grouping_column_filtering)){ - melted_dataframe_filtered <- melted_dataframe - grouping_column_filtering_value <- "no_group_column_filtering" - - print("No filtering of the grouping_column") - } - - ### Filter for the grouping column - else if (isTRUE(grouping_column_filtering)){ - melted_dataframe_filtered <- melted_dataframe %>% - filter(get(grouping_column) == grouping_column_filtering_value) - - print(paste("Data filtered for", grouping_column, "is", grouping_column_filtering_value)) - - }else{ stop("grouping_column_filtering must be TRUE or FALSE")} - - - ### In option, filter at a given taxonomic rank and for a given taxonomic identifier - - ### No filter - if (isFALSE(taxonomic_filtering)) { - physeq_subset_df <- melted_dataframe_filtered - physeq_subet_norm_df <- physeq_subset_df %>% - ungroup %>% - dplyr::group_by(Sample) %>% - dplyr::mutate(per=as.numeric(100*Abundance/sum(Abundance))) %>% - dplyr::ungroup() %>% - dplyr::select(-Abundance) %>% - dplyr::rename(Abundance = per) - taxonomic_filtering_value <- "no_tax_rank_filtering" - - print("No filtering of any taxonomic rank") - } - - ### A specific filter - else if (isTRUE(taxonomic_filtering)){ - physeq_subset_df <- melted_dataframe_filtered %>% - filter(get(taxonomic_filtering_rank) == taxonomic_filtering_value) - physeq_subet_norm_df <- physeq_subset_df %>% - group_by(Sample) %>% - mutate(per=paste0((100*Abundance/sum(Abundance)))) %>% - ungroup %>% - select(-Abundance) %>% - dplyr::rename(Abundance = per) - physeq_subet_norm_df$Abundance <- as.numeric(physeq_subet_norm_df$Abundance) - - print(paste("Data filtered for", taxonomic_filtering_rank, "is", taxonomic_filtering_value)) - - }else{ stop('"taxonomic_filtering" must be TRUE or FALSE') - } - - - ### Choose between relative value or absolute value dataframe for plotting value - - ### Relative value plotting - if (plotting_value == "relative"){ - plotting <- "Relative" - print("Relative value plotting") - plotted_df <- physeq_subet_norm_df - } - - # Absolute value plotting - else if (plotting_value == "absolute"){ - plotting <- "Absolute" - print("Absolute value plotting") - plotted_df <- physeq_subset_df - - }else{ stop('"relative_value_plotting" must be "relative" or "absolute"') - } - - - ### Filter values at a relative or absolute quantity threshold or at a given rank in a group ( x highest OTU in the group) - - ### No filtering - if (quantity_filtering_type == "nofiltering"){ - filtering <- "Nofiltering" - quantity_filtering_value <- "0" - print("No filtering based on quantity") - physeq_subset_df_filtered <- physeq_subet_norm_df - } - - - ### Relative value filtering - else if (quantity_filtering_type == "relative"){ - filtering <- "Relative" - quantity_filtering_value <- relative_quantity_filtering_value - print("Relative value based filtering") - physeq_subset_df_filtered <- physeq_subet_norm_df %>% filter(Abundance > quantity_filtering_value) - } - - ### Absolute value filtering - else if (quantity_filtering_type == "absolute"){ - filtering <- "Absolute" - quantity_filtering_value <- absolute_quantity_filtering_value - print("Absolute value based filtering") - physeq_subset_df_filtered <- physeq_subset_df %>% filter(Abundance > quantity_filtering_value) - } - - ### Abundance rank of the taxa in the group filtering - else if (quantity_filtering_type == "rank"){ - filtering <- "Rank" - print("Rank based filtering") - quantity_filtering_value <- rank_quantity_filtering_value - ### Define the variables as dplyr wants them - g_column <- rlang::sym(grouping_column) - x_column<- rlang::sym(sample_label) - ### Generate the filtered dataframe - physeq_subset_df_filtered <- physeq_subset_df %>% - group_by(!! g_column) %>% ## group the dataframe by the grouping column - mutate(per=as.numeric(paste0((100*Abundance/sum(Abundance))))) %>% ## calculate a relative abundance of the taxa in the group - ungroup %>% - group_by((!! g_column), OTU) %>% ## now group the OTU in each grouping columns - mutate(sumper = as.numeric(paste0(sum(per)))) %>% ## Sum the relative abundance of each OTU in each group - ungroup %>% - group_by(!!! list(g_column, x_column)) %>% ## Group the dataframe for each by group and filling column - top_n(n = quantity_filtering_value*n_distinct(sample_label), wt = sumper) %>% ## Keep the x number of rows for each - ungroup - } - - - ### Combined absolute and rank of the taxa in the group filtering - else if (quantity_filtering_type == "absolute_and_rank"){ - filtering <- "Absolute_and_Rank" - print("Absolute_and_Rank based filtering") - ### Define the variables as dplyr wants them - g_column <- rlang::sym(grouping_column) - x_column<- rlang::sym(sample_label) - ### Generate the filtered dataframe - ### Relative reads filtering - physeq_subset_df_filtered <- physeq_subset_df %>% - group_by(!! g_column) %>% ## group the dataframe by the grouping column - mutate(per=as.numeric(paste0((100*Abundance/sum(Abundance))))) %>% ## calculate a relative abundance of the taxa in the group - ungroup %>% - group_by((!! g_column), OTU) %>% ## now group the OTU in each grouping columns - mutate(sumper = as.numeric(paste0(sum(per)))) %>% ## Sum the relative abundance of each OTU in each group - ungroup %>% - group_by(!!! list(g_column, x_column)) %>% ## Group the dataframe for each by group and filling column - top_n(n = rank_quantity_filtering_value*n_distinct(sample_label), wt = sumper) %>% ## Keep the x number of rows for each - ungroup - - - ### Absolute reads filtering - physeq_subset_df_filtered <- physeq_subset_df_filtered %>% filter(Abundance > absolute_quantity_filtering_value) - - ### Write "quantity_filtering_value" for ggsave later - quantity_filtering_value <- paste0("rank_", rank_quantity_filtering_value, "_absolute_", absolute_quantity_filtering_value) - - }else{ stop('quantity_filtering_type must be "nofiltering", "relative", "absolute", "rank" or "absolute_and_rank"') - } - - - ### Now we have a dataframe ("plotted_df") containing all values, with Abundance expressed in absolute reads number or relative quanity in %, and a dataframe containing the rows that have to be kept while the rest will be merged by tagging them with the same name - - - ### In a dataframe, keep only the rows matching the filtered rowmanes - above_threshold_df <- semi_join(plotted_df, physeq_subset_df_filtered, by = c("OTU", "Sample")) - - - ### In another dataframe, keep only the lines NOT matching the filtered rowmanes - under_threshold_df <- anti_join(plotted_df, physeq_subset_df_filtered, by =c ("OTU", "Sample")) - - - ### To follow and control the process, write external .tsv tables - ### Write the filtration table, without Abundance = 0 rows to reduce its size. - physeq_subset_df_filtered %>% - filter(Abundance>0) %>% - write.table(file = paste0(figures_save_dir,"/quantitative_barplots/", plotting, "/",filtering,"/Table/", taxonomic_filtering_rank, "_",taxonomic_filtering_value,"_",filtering, "u", quantity_filtering_value, "_all_", sample_label, "_filtration_table.tsv"), append = FALSE, sep = "\t", eol = "\n", na = "NA", dec = ".", col.names = TRUE, row.names = FALSE) - - ### Write the plotted table , without Abundance = 0 rows to reduce its size. - plotted_df %>% - filter(Abundance>0) %>% - write.table(file = paste0(figures_save_dir,"/quantitative_barplots/", plotting, "/",filtering,"/Table/", taxonomic_filtering_rank, "_",taxonomic_filtering_value,"_",filtering, "u", quantity_filtering_value, "_all_", sample_label, "_plotted_table.tsv"), append = FALSE, sep = "\t", eol = "\n", na = "NA", dec = ".", col.names = TRUE, row.names = FALSE) - - ### In another dataframe, keep the join of the under and above tables, before masking of the filtered taxa and without Abundance = 0 rows to reduce its size. - merged_filtered_abs <- full_join(under_threshold_df,above_threshold_df) %>% - filter(Abundance>0) - write.table(merged_filtered_abs, file = paste0(figures_save_dir,"/quantitative_barplots/", plotting, "/",filtering,"/Table/", taxonomic_filtering_rank, "_",taxonomic_filtering_value,"_",filtering, "u", quantity_filtering_value, "_all_", sample_label, "_merged_without_masking.tsv"), append = FALSE, sep = "\t", eol = "\n", na = "NA", dec = ".", col.names = TRUE, row.names = FALSE) - - under_threshold_df <- under_threshold_df - - ### Convert taxonomic all levels as character (needed for the next steps) - above_threshold_df$Kingdom<-as.character(above_threshold_df$Kingdom) - above_threshold_df$Phylum<-as.character(above_threshold_df$Phylum) - above_threshold_df$Class<-as.character(above_threshold_df$Class) - above_threshold_df$Order<-as.character(above_threshold_df$Order) - above_threshold_df$Family<-as.character(above_threshold_df$Family) - above_threshold_df$Genus<-as.character(above_threshold_df$Genus) - above_threshold_df$Species<-as.character(above_threshold_df$Species) - - under_threshold_df$Kingdom<-as.character(under_threshold_df$Kingdom) - under_threshold_df$Phylum<-as.character(under_threshold_df$Phylum) - under_threshold_df$Class<-as.character(under_threshold_df$Class) - under_threshold_df$Order<-as.character(under_threshold_df$Order) - under_threshold_df$Family<-as.character(under_threshold_df$Family) - under_threshold_df$Genus<-as.character(under_threshold_df$Genus) - under_threshold_df$Species<-as.character(under_threshold_df$Species) - - - ### Rename taxa with < percent/reads abundance, defending of the filtering applied - ### Define the filtering tag depending of the applied filtering - if (quantity_filtering_type == "relative"){ - filtering_tag <-paste("<_", quantity_filtering_value,"%_abund") - } - ### Absolute filtering - else if (quantity_filtering_type == "absolute"){ - filtering_tag <-paste("<_", quantity_filtering_value,"reads") - } - ### Abundance rank of the taxa in the group filtering - else if (quantity_filtering_type == "rank"){ - filtering_tag <-paste("<_", quantity_filtering_value,"reads") - } - ### Absolute abundance AND rank of the taxa in the group filtering - else if (quantity_filtering_type == "absolute_and_rank"){ - filtering_tag <-paste("<_", quantity_filtering_value) -} - ### Applied the filtering tag - under_threshold_df$Kingdom <-filtering_tag - under_threshold_df$Phylum <-filtering_tag - under_threshold_df$Class <-filtering_tag - under_threshold_df$Order <-filtering_tag - under_threshold_df$Family <-filtering_tag - under_threshold_df$Genus <-filtering_tag - under_threshold_df$Species <-filtering_tag - - - ### Join the two dataframes, to put back all rows together, the ones above threshold keeping their original taxonomic identifier while the others are now grouped together - threshod_filtered_abs <- full_join(under_threshold_df,above_threshold_df) - - ### convert taxonomic levels back as factor (needed?) - threshod_filtered_abs$Kingdom<-as.factor(threshod_filtered_abs$Kingdom) - threshod_filtered_abs$Phylum<-as.factor(threshod_filtered_abs$Phylum) - threshod_filtered_abs$Class<-as.factor(threshod_filtered_abs$Class) - threshod_filtered_abs$Order<-as.factor(threshod_filtered_abs$Order) - threshod_filtered_abs$Family<-as.factor(threshod_filtered_abs$Family) - threshod_filtered_abs$Genus<-as.factor(threshod_filtered_abs$Genus) - threshod_filtered_abs$Species<-as.factor(threshod_filtered_abs$Species) - - - ### Remove the (now but needed before) Abundance = 0 rows - threshod_filtered_abs_no_zero <- filter(threshod_filtered_abs, threshod_filtered_abs$Abundance>0) - - - - ### Reorder the facet factor if later used for plotting - if (isTRUE(facet_plot)){ - threshod_filtered_abs_no_zero[[facetting_column]] <- fct_reorder(threshod_filtered_abs_no_zero[[facetting_column]], as.numeric(threshod_filtered_abs_no_zero[[sample_label]])) - } - else if (isFALSE(facet_plot)){ - print("No faceting") - } - else { - stop('"facet_plot" must be TRUE or FALSE') - } - - ### Reoder the filling column for later if using horizontal barplot - if (isTRUE(horizontal_plot)){ - threshod_filtered_abs_no_zero[[sample_label]] <- fct_rev(threshod_filtered_abs_no_zero[[sample_label]]) - } - else if (isFALSE(horizontal_plot)){ - print("Vertical plotting") - } - else { - stop('"horizontal_plot" must be TRUE or FALSE') - } - - - ### Create a loop for all taxonomic ranks to be plotted - - if (plotting_tax_ranks == "all"){ - tax_ranks <- c("Kingdom", "Phylum", "Class", "Order", "Family", "Genus", "Species", "OTU") - - }else{ - tax_ranks <- plotting_tax_ranks - print('Plotting at a specific taxonomic rank. Check spelling one or several of ("Kingdom", "Phylum", "Class", "Order", "Family", "Genus", "Species", "OTU"). This error does NOT always mean that there was an error in spelling') - } - - for (t in tax_ranks){ - - - ### Set colors palette - ### Brewer colors - if (distinct_colors == FALSE) { - getPalette <- colorRampPalette(brewer.pal(n=9, "Set1")) - ColList <- unique(threshod_filtered_abs_no_zero[[t]]) - ColPalette = getPalette(length(ColList)) - names(ColPalette) = ColList - colors_palette <- ColPalette - } - - ### Distinct colors - else if (distinct_colors == TRUE) { - set.seed(1) - ColList <- unique(threshod_filtered_abs_no_zero[[t]]) - ColPalette <- distinctColorPalette(altCol = FALSE, k = length(unique(threshod_filtered_abs_no_zero[[t]]))) - names(ColPalette) = ColList - colors_palette <- ColPalette - - }else{ print("distinct_colors must be TRUE or FALSE") - } - - - ### Loop for unique value in grouping_column - for (i in unique(threshod_filtered_abs_no_zero[[grouping_column]])) { - print(paste("Start plotting", grouping_column, i)) - - ### filter the table for this value of the grouping columns. Depending of the used arguments, the t_neg_PCR values are kept or not on the barplots - ### Keep t_neg_PCR rows - if (isTRUE(t_neg_PCR_sample_on_plots) & !is.null(t_neg_PCR_sample_grp_filter_column_value)){ - filtered_df_abs_i <- filter(threshod_filtered_abs_no_zero, threshod_filtered_abs_no_zero[[grouping_column]] == i - | threshod_filtered_abs_no_zero[[grouping_column]] == t_neg_PCR_sample_grp_filter_column_value) - print('Keeping t_neg_PCR values for the graphs. The "t_neg_PCR_sample_grp_filter_column_value" must match the one in the grouping_column for this sample') - } - else if (isTRUE(t_neg_PCR_sample_on_plots) & is.null(t_neg_PCR_sample_grp_filter_column_value)){ - stop('If "t_neg_PCR_sample_on_plots" is "TRUE, a "t_neg_PCR_sample_grp_filter_column_value" indicating the value of the T neg PCR sample in the grouping column must be indicated') - } - - ### Do not keep t_neg_PCR rows - else if (isFALSE(t_neg_PCR_sample_on_plots)){ - filtered_df_abs_i <- filter(threshod_filtered_abs_no_zero, threshod_filtered_abs_no_zero[[grouping_column]] == i) - } - - else{ stop('"t_neg_PCR_sample_on_plots" must be TRUE or FALSE') - } - - ### Reorder by abundance - if (isTRUE(order_by_abundance)){ - print("Ordered by abundance") - filtered_df_abs_i[[t]] <- reorder(filtered_df_abs_i[[t]], filtered_df_abs_i$Abundance) - } - else if (isFALSE(order_by_abundance)){ - print("NOT ordered by abundance") - } - else { - stop('"order_by_abundance" must be TRUE or FALSE') - } - - - ### Write this table in a external file - write.table(filtered_df_abs_i, file = paste0(figures_save_dir,"/quantitative_barplots/", plotting, "/",filtering,"/Table/", taxonomic_filtering_rank, "_",taxonomic_filtering_value,"_",filtering, "u", quantity_filtering_value, "_",grouping_column, "_", i, "_", sample_label, "_abundancy_table.tsv"), append = FALSE, sep = "\t", eol = "\n", na = "NA", dec = ".", col.names = TRUE, row.names = FALSE) - - - ### Reset the order of filtered taxa as the last to have it on top of graphs - filtered_df_abs_i[[t]] <- fct_relevel(filtered_df_abs_i[[t]], filtering_tag, after = 0) - - - ### Renames the values of the vector used for labeling - #x_labels <- as(filtered_df_abs_i[[sample_label]], "character") - #names(x_labels) <- filtered_df_abs_i[["Sample"]] - - - - ### Create the barplot - taxrank_barplot <- filtered_df_abs_i %>% - ggplot(aes(x = get(sample_label), y = Abundance, fill = get(t))) + - theme_bw() + - geom_col() + - #scale_x_discrete(labels = x_labels, drop = TRUE) + # Set false to keep empty bars - theme(axis.text.x = element_text(angle = 90, hjust = 0, vjust = 0.5), plot.title = element_text(hjust = 1)) + # axis and title settings - guides(fill = guide_legend(title = paste(grouping_column, i, sep= " "),reverse = FALSE, keywidth = 1, keyheight = 1, ncol = 1)) + # settings of the legend - labs(x=sample_label, y = paste(plotting, "abundance"), title = paste(t, "composition",grouping_column , i, sep= " ")) + # axis and graph title - scale_fill_manual(values = colors_palette) # colors as set previously - - - ### Turn barplot horizontally - if (isTRUE(horizontal_plot)){ - ### Reverse the order of the samples - taxrank_barplot <- taxrank_barplot + coord_flip() - } - - ### Create facet view - if (isTRUE(facet_plot)){ - taxrank_barplot <- taxrank_barplot + facet_grid(get(facetting_column) ~., scales = "free", space = "free") - } - - if (isTRUE(separated_legend)){ - ### Keep the barplot without the legend - taxrank_barplot_no_leg <- (taxrank_barplot + guides(fill = FALSE)) - } - else { - taxrank_barplot_no_leg <- taxrank_barplot - } - - ### Save it - ### Set the filename - filename_base <- (paste0(figures_save_dir,"/quantitative_barplots/", plotting, "/",filtering,"/", taxonomic_filtering_rank, "_",taxonomic_filtering_value,"_",filtering, "u", quantity_filtering_value, "_",grouping_column, "_", i, "_", sample_label, "_",facetting_column,"_" ,t)) - ### Print the filename to follow progress - print(filename_base) - ###Save the figure - ggsave(taxrank_barplot_no_leg, filename = paste0(filename_base, "_barplot.png"), width = 7, height = 7) - - - ### Extract the legend and save it - ### Extract the legend - leg <- get_legend(taxrank_barplot) - - ## Save the legend - ggsave(leg, filename = paste0(filename_base, "_barplot_leg.png"), width = 5, height = 10) - - - - }}} diff --git a/ressources/r_scripts/visualization/2_Barplots/barplots_fct_filtration_at_tax_levels.R b/ressources/r_scripts/visualization/2_Barplots/barplots_fct_filtration_at_tax_levels.R deleted file mode 100644 index e68a5c9b..00000000 --- a/ressources/r_scripts/visualization/2_Barplots/barplots_fct_filtration_at_tax_levels.R +++ /dev/null @@ -1,328 +0,0 @@ - -### Create a function -barplots_fct <- function(melted_dataframe, sample_label, grouping_column, grouping_column_filtering = c(FALSE, TRUE), grouping_column_filtering_value, t_neg_PCR_sample_on_plots, t_neg_PCR_sample_grp_filter_column_value, taxonomic_filtering = c(TRUE, FALSE), taxonomic_filtering_rank = "Kingdom" , taxonomic_filtering_value = "Bacteria" , quantity_filtering_type = c("relative", "absolute", "nofiltering"), absolute_quantity_filtering_value, relative_quantity_filtering_value, plotting_value = c("relative", "absolute"), plotting_tax_ranks = "all", figures_save_dir, distinct_colors = TRUE, horizontal_plot = FALSE, facet_plot = FALSE, facetting_column = NULL, order_by_abundance = TRUE, separated_legend){ - -# Transform values in dplyr format - g_column <- rlang::sym(grouping_column) - x_column<- rlang::sym(sample_label) - - -# In option, filter for a given grouping_columns. - ## No filter for the grouping column - if (isFALSE(grouping_column_filtering)){ - melted_dataframe_filtered <- melted_dataframe - print("No filtering of the grouping_column") - } - - ## Filter for the grouping column - else if (isTRUE(grouping_column_filtering)){ - melted_dataframe_filtered <- melted_dataframe %>% filter(get(grouping_column) == grouping_column_filtering_value) - print(paste("Data filtered for", grouping_column, "is", grouping_column_filtering_value)) - - }else{ stop("grouping_column_filtering must be TRUE or FALSE") - } - - -# In option, filter at a given taxonomic rank and for a given taxonomic ID - ## No filter - if (isFALSE(taxonomic_filtering)) { - physeq_subset_df <- melted_dataframe_filtered - - physeq_subset_norm_df <- physeq_subset_df %>% # calculate % normalized Abundance - ungroup %>% - dplyr::group_by(Sample) %>% - dplyr::mutate(per=as.numeric(100*Abundance/sum(Abundance))) %>% - ungroup() %>% - dplyr::select(-Abundance) %>% - dplyr::rename(Abundance = per) - - taxonomic_filtering_value <- "no_tax_rank_filtering" - print("No filtering of any taxonomic rank") - } - - ## A specific filter - else if (isTRUE(taxonomic_filtering)){ - physeq_subset_df <- melted_dataframe_filtered %>% - dplyr::filter(get(taxonomic_filtering_rank) == taxonomic_filtering_value) - - physeq_subset_norm_df <- physeq_subset_df %>% - dplyr::group_by(Sample) %>% - dplyr::mutate(per=paste0((100*Abundance/sum(Abundance)))) %>% - ungroup %>% - dplyr::select(-Abundance) %>% - dplyr::rename(Abundance = per) - - physeq_subset_norm_df$Abundance <- as.numeric(physeq_subset_norm_df$Abundance) - print(paste("Data filtered for", taxonomic_filtering_rank, "is", taxonomic_filtering_value)) - - }else{stop('"taxonomic_filtering" must be TRUE or FALSE') - } - -# Choose between relative value or absolute value dataframe for plotting value - ## Relative value plotting - if (plotting_value == "relative"){ - plotting <- "Relative" - print("Relative value plotting") - plotted_df <- physeq_subset_norm_df %>% - group_by(!! x_column) %>% - mutate(per=paste0((100*Abundance/sum(Abundance)))) %>% - ungroup %>% - select(-Abundance) %>% - dplyr::rename(Abundance = per) - plotted_df$Abundance <- as.numeric(plotted_df$Abundance) - } - ## Absolute value plotting - else if (plotting_value == "absolute"){ - plotting <- "Absolute" - print("Absolute value plotting") - plotted_df <- physeq_subset_df - }else{ stop('"relative_value_plotting" must be "relative" or "absolute"') - } - -# From here on, loop for all taxonomic levels chosen for plotting, this is what changes with the alternative plotting script where this is done at the end. - ## Define the taxa ranks whill will be plotted - if (plotting_tax_ranks == "all"){ - tax_ranks <- c("Kingdom", "Phylum", "Class", "Order", "Family", "Genus", "Species", "OTU") - }else{ - tax_ranks <- plotting_tax_ranks - print('Plotting at a specific taxonomic rank. Check spelling one or several of ("Kingdom", "Phylum", "Class", "Order", "Family", "Genus", "Species", "OTU"). This error does NOT always mean that there was an error in spelling') - } - - ## Run the loop - for (t in tax_ranks){ - t_column <- rlang::sym(t) # set the taxa in format compatible with dplyr - print(t_column) - - ### Filter values at a relative or absolute quantity - #### No filtering - if (quantity_filtering_type == "nofiltering"){ - filtering <- "Nofiltering" - quantity_filtering_value <- "0" - print("No filtering based on quantity") - physeq_subset_df_filtered <- physeq_subset_norm_df - } - - #### Relative value filtering - else if (quantity_filtering_type == "relative"){ - filtering <- "Relative" - quantity_filtering_value <- relative_quantity_filtering_value - print("Relative value based filtering") - physeq_subset_df_filtered <- physeq_subset_norm_df %>% - dplyr::group_by(Sample, !!t_column) %>% # group the dataframe by Sample and taxa - dplyr::mutate(sumper=as.numeric(sum(Abundance))) %>% # calculate the cumulative relative abundance of the taxa in the sample - #ungroup %>% - #dplyr::group_by(Sample, !!t_column) %>% # group the dataframe by Sample and taxa - dplyr::filter(sumper >= quantity_filtering_value) %>%# keep only the taxa above threshold. From the applied grouping, the taxa remain in all amples if over the filtering value in one sample? - ungroup - } - - #### Absolute value filtering - else if (quantity_filtering_type == "absolute"){ - filtering <- "Absolute" - quantity_filtering_value <- absolute_quantity_filtering_value - print("Absolute value based filtering") - physeq_subset_df_filtered <- physeq_subset_df %>% - dplyr::group_by(Sample, !!t_column) %>% # group the dataframe by Sample and taxa - dplyr::mutate(sumper=as.numeric(sum(Abundance))) %>% # calculate the cumulative relative abundance of the taxa in the sample - #ungroup %>% - #dplyr::group_by(Sample, !!t_column) %>% # group the dataframe by Sample and taxa - dplyr::filter(sumper >= quantity_filtering_value) %>%# keep only the taxa above threshold. From the applied grouping, the taxa remain in all amples if over the filtering value in one sample? - ungroup - } - - #### Write the table after table choice for relative of absolute value plotting , without Abundance = 0 rows to reduce its size. - plotted_df %>% - filter(Abundance>0) %>% - write.table(file = paste0(figures_save_dir,"/quantitative_barplots/", plotting, "/",filtering,"/Table/", taxonomic_filtering_rank, "_",taxonomic_filtering_value,"_", "u", "_all_", sample_label, "_plotted_table.tsv"), append = FALSE, sep = "\t", eol = "\n", na = "NA", dec = ".", col.names = TRUE, row.names = FALSE) - - - #### Write the table with values that passed the quantity filtering. - physeq_subset_df_filtered %>% ### To follow and control the process, write external .tsv tables - filter(Abundance>0) %>% - write.table(file = paste0(figures_save_dir,"/quantitative_barplots/", plotting, "/",filtering,"/Table/", taxonomic_filtering_rank, "_",taxonomic_filtering_value,"_",filtering, "u", quantity_filtering_value, "_all_", sample_label,"_",t ,"_filtration_table.tsv"), append = FALSE, sep = "\t", eol = "\n", na = "NA", dec = ".", col.names = TRUE, row.names = FALSE) - - - ### Now we have a dataframe ("plotted_df") containing all values, with Abundance expressed in absolute reads number or relative quanity in %, and a dataframe containing the rows that have to be kept while the rest will be merged by tagging them with the same name - above_threshold_df <- semi_join(plotted_df, physeq_subset_df_filtered, by = c("OTU", "Sample")) ### In a dataframe, keep only the rows matching the filtered rowmanes - under_threshold_df <- anti_join(plotted_df, physeq_subset_df_filtered, by =c ("OTU", "Sample")) ### In another dataframe, keep only the lines NOT matching the filtered rowmanes - - - ### In another dataframe, keep the join of the under and above tables, before masking of the filtered taxa and without Abundance = 0 rows to reduce its size. - merged_filtered_abs <- full_join(under_threshold_df,above_threshold_df) %>% - filter(Abundance>0) - write.table(merged_filtered_abs, file = paste0(figures_save_dir,"/quantitative_barplots/", plotting, "/",filtering,"/Table/", taxonomic_filtering_rank, "_",taxonomic_filtering_value,"_",filtering, "u", quantity_filtering_value, "_all_", sample_label, "_merged_without_masking.tsv"), append = FALSE, sep = "\t", eol = "\n", na = "NA", dec = ".", col.names = TRUE, row.names = FALSE) - - ### Rename taxa with < percent/reads abundance, defending of the filtering applied - #### Define the filtering tag depending of the applied filtering - ##### Relative - if (quantity_filtering_type == "relative"){ - filtering_tag <-paste("Relative abund. <", quantity_filtering_value , "%") - } - ##### Absolute filtering - else if (quantity_filtering_type == "absolute"){ - filtering_tag <-paste("Absolute abund. <", quantity_filtering_value, "reads") - } - - if (quantity_filtering_type != "nofiltering"){ - #### Apply the filtering tag - under_threshold_df$Kingdom <-filtering_tag - under_threshold_df$Phylum <-filtering_tag - under_threshold_df$Class <-filtering_tag - under_threshold_df$Order <-filtering_tag - under_threshold_df$Family <-filtering_tag - under_threshold_df$Genus <-filtering_tag - under_threshold_df$Species <-filtering_tag - under_threshold_df$OTU <-filtering_tag - } - - - ### Join the two dataframes, to put back all rows together, the ones above threshold keeping their original taxonomic identifier while the others are now grouped together - threshod_filtered_abs <- full_join(under_threshold_df,above_threshold_df) - - ### Remove the (now but needed before) Abundance = 0 rows - threshod_filtered_abs_no_zero <- filter(threshod_filtered_abs, threshod_filtered_abs$Abundance>0) - - - ### Reorder the facet factor if later used for plotting - if (isTRUE(facet_plot)){ - threshod_filtered_abs_no_zero[[facetting_column]] <- fct_reorder(threshod_filtered_abs_no_zero[[facetting_column]], as.numeric(threshod_filtered_abs_no_zero[[sample_label]])) - - }else if (isFALSE(facet_plot)){print("No faceting") - - }else {stop('"facet_plot" must be TRUE or FALSE') - } - - ### Reverse the sample_label column for later if using horizontal barplot - if (isTRUE(horizontal_plot)){ - threshod_filtered_abs_no_zero[[sample_label]] <- fct_rev(threshod_filtered_abs_no_zero[[sample_label]]) - - } else if (isFALSE(horizontal_plot)){print("Vertical plotting") - - } else {stop('"horizontal_plot" must be TRUE or FALSE') - } - - - ### Set colors palette - #### Brewer colors - if (distinct_colors == FALSE) { - getPalette <- colorRampPalette(brewer.pal(n=9, "Set1")) - ColList <- unique(threshod_filtered_abs_no_zero[[t]]) - ColPalette = getPalette(length(ColList)) - names(ColPalette) = ColList - colors_palette <- ColPalette - colors_palette[filtering_tag] <- "#d3d3d3" # Set the filtered in balck - } - - #### Distinct colors - else if (distinct_colors == TRUE) { - set.seed(8) - ColList <- unique(threshod_filtered_abs_no_zero[[t]]) - ColPalette <- distinctColorPalette(altCol = FALSE, k = length(unique(threshod_filtered_abs_no_zero[[t]]))) - names(ColPalette) = ColList - colors_palette <- ColPalette - colors_palette[filtering_tag] <- "#d3d3d3" # Set the filtered in balck - - }else{ print("distinct_colors must be TRUE or FALSE") - } - - ### Loop for unique value in grouping_column - for (i in unique(threshod_filtered_abs_no_zero[[grouping_column]])) { - print(paste("Start plotting", grouping_column, i)) - - #### filter the table for this value of the grouping columns. Depending of the used arguments, the t_neg_PCR values are kept or not on the barplots - ##### Keep t_neg_PCR rows - if (isTRUE(t_neg_PCR_sample_on_plots) & !is.null(t_neg_PCR_sample_grp_filter_column_value)){ - filtered_df_abs_i <- filter(threshod_filtered_abs_no_zero, threshod_filtered_abs_no_zero[[grouping_column]] == i | threshod_filtered_abs_no_zero[[grouping_column]] == t_neg_PCR_sample_grp_filter_column_value) - - print('Keeping t_neg_PCR values for the graphs. The "t_neg_PCR_sample_grp_filter_column_value" must match the one in the grouping_column for this sample') - } - else if (isTRUE(t_neg_PCR_sample_on_plots) & is.null(t_neg_PCR_sample_grp_filter_column_value)){ - stop('If "t_neg_PCR_sample_on_plots" is "TRUE, a "t_neg_PCR_sample_grp_filter_column_value" indicating the value of the T neg PCR sample in the grouping column must be indicated') - } - - ##### Do not keep t_neg_PCR rows - else if (isFALSE(t_neg_PCR_sample_on_plots)){ - filtered_df_abs_i <- filter(threshod_filtered_abs_no_zero, threshod_filtered_abs_no_zero[[grouping_column]] == i) - } - - else{ stop('"t_neg_PCR_sample_on_plots" must be TRUE or FALSE') - } - - #### Reorder by abundance - if (isTRUE(order_by_abundance)){ - print("Ordered by abundance") - filtered_df_abs_i <- filtered_df_abs_i %>% - group_by(!! t_column) %>% - mutate(tot = sum(Abundance)) %>% - ungroup() %>% - mutate(!! t_column := fct_reorder(!! t_column, tot)) %>% - arrange(desc(tot)) - } - else if (isFALSE(order_by_abundance)){ print("NOT ordered by abundance") - } - else { stop('"order_by_abundance" must be TRUE or FALSE') - } - - #### Set the filtering_tabl at the top of the plot - filtered_df_abs_i[[t]] <- fct_relevel(filtered_df_abs_i[[t]], filtering_tag, after = 0) - - - #### Write the content of the plot table in a external file - write.table(filtered_df_abs_i, file = paste0(figures_save_dir,"/quantitative_barplots/", plotting, "/",filtering,"/Table/", taxonomic_filtering_rank, "_",taxonomic_filtering_value,"_",filtering, "u", quantity_filtering_value, "_",grouping_column, "_", i, "_", t,"_",sample_label, "_abundancy_table.tsv"), append = FALSE, sep = "\t", eol = "\n", na = "NA", dec = ".", col.names = TRUE, row.names = FALSE) - - - #### Renames the values of the vector used for labeling - #x_labels <- as(filtered_df_abs_i[[sample_label]], "character") - #names(x_labels) <- filtered_df_abs_i[["Sample"]] - - - #### Create the barplot - taxrank_barplot <- filtered_df_abs_i %>% - ggplot(aes(x = get(sample_label), y = Abundance, fill = get(t))) + - theme_bw() + - geom_col() + - #scale_x_discrete(labels = x_labels, drop = TRUE) + # Set false to keep empty bars - theme(axis.text.x = element_text(angle = 90, hjust = 0, vjust = 0.5), plot.title = element_text(hjust = 0.5)) + # axis and title settings - guides(fill = guide_legend(title = paste0(t),reverse = FALSE, keywidth = 1, keyheight = 1, ncol = 1)) + # settings of the legend - labs(x="Sample", y = paste(plotting, "abundance"), title = paste("Taxonomic composition", t,"level" )) + # axis and graph title - scale_fill_manual(values = colors_palette) # colors as set previously - - - #### In option, turn barplot horizontally - if (isTRUE(horizontal_plot)){ - taxrank_barplot <- taxrank_barplot + coord_flip() ### Reverse the order of the samples - - #### In option, create facet view - if (isTRUE(facet_plot)){ - taxrank_barplot <- taxrank_barplot + facet_grid(get(facetting_column) ~., scales = "free", space = "free") - } - } else { - #### In option, create facet view - if (isTRUE(facet_plot)){ - taxrank_barplot <- taxrank_barplot + facet_grid(~ get(facetting_column), scales = "free", space = "free") - } - } - - - - #### In option, keep the barplot without the legend - if (isTRUE(separated_legend)){ - taxrank_barplot_no_leg <- (taxrank_barplot + guides(fill = FALSE)) - }else { - taxrank_barplot_no_leg <- taxrank_barplot - } - - #### Save it - ##### Set the filename - filename_base <- (paste0(figures_save_dir,"/quantitative_barplots/", plotting, "/",filtering,"/", taxonomic_filtering_rank, "_",taxonomic_filtering_value,"_",filtering, "u", quantity_filtering_value, "_",grouping_column, "_", i, "_", sample_label, "_",facetting_column,"_" ,t)) - print(filename_base) #Print the filename to follow progress - ##### Finally, save the figure - ggsave(taxrank_barplot_no_leg, filename = paste0(filename_base, "_barplot.svg"), width = 7, height = 4) - - #### Extract the legend and save it - leg <- get_legend(taxrank_barplot) - ggsave(leg, filename = paste0(filename_base, "_barplot_leg.svg"), width = 5, height = 10) - - # The loop restart to create blots for all taxa - - }}} diff --git a/ressources/r_scripts/visualization/3_Heatmaps/201901_WIP_heatmap.R b/ressources/r_scripts/visualization/3_Heatmaps/201901_WIP_heatmap.R deleted file mode 100644 index 6ec013c5..00000000 --- a/ressources/r_scripts/visualization/3_Heatmaps/201901_WIP_heatmap.R +++ /dev/null @@ -1,368 +0,0 @@ - -### Create a function -Variants_heatmap_fct <- function(melted_dataframe, sample_label, grouping_column, grouping_column_filtering = c(FALSE, TRUE), grouping_column_filtering_value, t_neg_PCR_sample_on_plots, t_neg_PCR_sample_grp_filter_column_value, taxonomic_filtering = c(TRUE, FALSE), taxonomic_filtering_rank = "Kingdom" , taxonomic_filtering_value = "Bacteria" , quantity_filtering_type = c("relative", "absolute", "rank", "nofiltering", "absolute_and_rank"), absolute_quantity_filtering_value, relative_quantity_filtering_value, rank_quantity_filtering_value, plotting_value = c("relative", "absolute"), plotting_tax_ranks = "all", figures_save_dir, horizontal_plot = FALSE, facet_plot = FALSE, facetting_column, order_by = TRUE, comparison, patient_ID){ - - # Transform values in dplyr format - g_column <- rlang::sym(grouping_column) - x_column <- rlang::sym(sample_label) - - - # In option, filter for a given grouping_columns. - ## No filter for the grouping column - if (isFALSE(grouping_column_filtering)){ - melted_dataframe_filtered <- melted_dataframe - print("No filtering of the grouping_column") - } - - ## Filter for the grouping column - else if (isTRUE(grouping_column_filtering)){ - melted_dataframe_filtered <- melted_dataframe %>% filter(get(grouping_column) == grouping_column_filtering_value) - print(paste("Data filtered for", grouping_column, "is", grouping_column_filtering_value)) - - }else{ stop("grouping_column_filtering must be TRUE or FALSE") - } - - - # In option, filter at a given taxonomic rank and for a given taxonomic ID - ## No filter - if (isFALSE(taxonomic_filtering)) { - physeq_subset_df <- melted_dataframe_filtered - - physeq_subset_norm_df <- physeq_subset_df %>% # calculate % normalized Abundance - ungroup %>% - dplyr::group_by(Sample) %>% - dplyr::mutate(per=as.numeric(100*Abundance/sum(Abundance))) %>% - ungroup() %>% - dplyr::select(-Abundance) %>% - dplyr::rename(Abundance = per) - - taxonomic_filtering_value <- "no_tax_rank_filtering" - print("No filtering of any taxonomic rank") - } - - ## A specific filter - else if (isTRUE(taxonomic_filtering)){ - physeq_subset_df <- melted_dataframe_filtered %>% - dplyr::filter(get(taxonomic_filtering_rank) == taxonomic_filtering_value) - - physeq_subset_norm_df <- physeq_subset_df %>% - dplyr::group_by(Sample) %>% - dplyr::mutate(per=paste0((100*Abundance/sum(Abundance)))) %>% - ungroup %>% - dplyr::select(-Abundance) %>% - dplyr::rename(Abundance = per) - - physeq_subset_norm_df$Abundance <- as.numeric(physeq_subset_norm_df$Abundance) - print(paste("Data filtered for", taxonomic_filtering_rank, "is", taxonomic_filtering_value)) - - }else{stop('"taxonomic_filtering" must be TRUE or FALSE') - } - - # Choose between relative value or absolute value dataframe for plotting value - ## Relative value plotting - if (plotting_value == "relative"){ - plotting <- "Relative" - print("Relative value plotting") - plotted_df <- physeq_subset_norm_df - } - ## Absolute value plotting - else if (plotting_value == "absolute"){ - plotting <- "Absolute" - print("Absolute value plotting") - plotted_df <- physeq_subset_df - }else{ stop('"relative_value_plotting" must be "relative" or "absolute"') - } - - # From here on, loop for all taxonomic levels chosen for plotting, this is what changes with the alternative plotting script where this is done at the end. - ## Define the taxa ranks whill will be plotted - if (plotting_tax_ranks == "all"){ - tax_ranks <- c("Kingdom", "Phylum", "Class", "Order", "Family", "Genus", "Species", "OTU") - }else{ - tax_ranks <- plotting_tax_ranks - print('Plotting at a specific taxonomic rank. Check spelling one or several of ("Kingdom", "Phylum", "Class", "Order", "Family", "Genus", "Species", "OTU"). This error does NOT always mean that there was an error in spelling') - } - - ## Run the loop - for (t in tax_ranks){ - t_column <- rlang::sym(t) # set the taxa in format compatible with dplyr - print(t_column) - - ### Filter values at a relative or absolute quantity - #### No filtering - if (quantity_filtering_type == "nofiltering"){ - filtering <- "Nofiltering" - filtering_tag <- 0 - quantity_filtering_value <- "0" - print("No filtering based on quantity") - physeq_subset_df_filtered <- physeq_subset_norm_df - } - - #### Relative value filtering - else if (quantity_filtering_type == "relative"){ - filtering <- "Relative" - quantity_filtering_value <- relative_quantity_filtering_value - print("Relative value based filtering") - physeq_subset_df_filtered <- physeq_subset_norm_df %>% - dplyr::group_by(Sample, Genus, Species) %>% # group the dataframe by Sample and taxa - dplyr::mutate(sumper=as.numeric(sum(Abundance))) %>% # calculate the cumulative relative abundance of the taxa in the sample - ungroup %>% - dplyr::group_by(!!g_column, Genus, Species) %>% # group the dataframe by Sample and taxa - dplyr::filter(sumper >= quantity_filtering_value) %>%# keep only the taxa above threshold. From the applied grouping, the taxa remain in all amples if over the filtering value in one sample? - ungroup - } - - #### Absolute value filtering - else if (quantity_filtering_type == "absolute"){ - filtering <- "Absolute" - quantity_filtering_value <- absolute_quantity_filtering_value - print("Absolute value based filtering") - physeq_subset_df_filtered <- physeq_subset_df %>% - dplyr::group_by(Sample, !!t_column) %>% # group the dataframe by Sample and taxa - dplyr::mutate(sumper=as.numeric(sum(Abundance))) %>% # calculate the cumulative relative abundance of the taxa in the sample - ungroup %>% - dplyr::group_by(!!g_column, !!t_column) %>% # group the dataframe by Sample and taxa - dplyr::filter(sumper >= quantity_filtering_value) %>%# keep only the taxa above threshold. From the applied grouping, the taxa remain in all amples if over the filtering value in one sample? - ungroup - } - - #### Write the table after table choice for relative of absolute value plotting , without Abundance = 0 rows to reduce its size. - plotted_df %>% - filter(Abundance>0) %>% - write.table(file = paste0(figures_save_dir,"/heatmaps/", plotting, "/",filtering,"/Table/", taxonomic_filtering_rank, "_",taxonomic_filtering_value,"_", "u", "_all_", sample_label, "_plotted_table.tsv"), append = FALSE, sep = "\t", eol = "\n", na = "NA", dec = ".", col.names = TRUE, row.names = FALSE) - - - #### Write the table with values that passed the quantity filtering. - physeq_subset_df_filtered %>% ### To follow and control the process, write external .tsv tables - filter(Abundance>0) %>% - write.table(file = paste0(figures_save_dir,"/heatmaps/", plotting, "/",filtering,"/Table/", taxonomic_filtering_rank, "_",taxonomic_filtering_value,"_",filtering, "u", quantity_filtering_value, "_all_", sample_label,"_",t ,"_filtration_table.tsv"), append = FALSE, sep = "\t", eol = "\n", na = "NA", dec = ".", col.names = TRUE, row.names = FALSE) - - - ### Now we have a dataframe ("plotted_df") containing all values, with Abundance expressed in absolute reads number or relative quanity in %, and a dataframe containing the rows that have to be kept while the rest will be merged by tagging them with the same name - above_threshold_df <- semi_join(plotted_df, physeq_subset_df_filtered, by = c("OTU", "Sample")) ### In a dataframe, keep only the rows matching the filtered rowmanes - under_threshold_df <- anti_join(plotted_df, physeq_subset_df_filtered, by =c ("OTU", "Sample")) ### In another dataframe, keep only the lines NOT matching the filtered rowmanes - - - ### In another dataframe, keep the join of the under and above tables, before masking of the filtered taxa and without Abundance = 0 rows to reduce its size. - merged_filtered_abs <- full_join(under_threshold_df,above_threshold_df) %>% - filter(Abundance>0) - write.table(merged_filtered_abs, file = paste0(figures_save_dir,"/heatmaps/", plotting, "/",filtering,"/Table/", taxonomic_filtering_rank, "_",taxonomic_filtering_value,"_",filtering, "u", quantity_filtering_value, "_all_", sample_label, "_merged_without_masking.tsv"), append = FALSE, sep = "\t", eol = "\n", na = "NA", dec = ".", col.names = TRUE, row.names = FALSE) - - ### Rename taxa with < percent/reads abundance, defending of the filtering applied - #### Define the filtering tag depending of the applied filtering - ##### Relative - if (quantity_filtering_type == "relative"){ - filtering_tag <-paste0("Relative abund.<", quantity_filtering_value , "%") - } - ##### Absolute filtering - else if (quantity_filtering_type == "absolute"){ - filtering_tag <-paste0("Absolute abund.<", quantity_filtering_value, "reads") - } - - - if (quantity_filtering_type != "nofiltering"){ - #### Apply the filtering tag - under_threshold_df$Kingdom <-filtering_tag - under_threshold_df$Phylum <-filtering_tag - under_threshold_df$Class <-filtering_tag - under_threshold_df$Order <-filtering_tag - under_threshold_df$Family <-filtering_tag - under_threshold_df$Genus <-filtering_tag - under_threshold_df$Species <-filtering_tag - under_threshold_df$OTU <-filtering_tag - } - - - ### Join the two dataframes, to put back all rows together, the ones above threshold keeping their original taxonomic identifier while the others are now grouped together - threshod_filtered_abs <- full_join(under_threshold_df,above_threshold_df) - - - - ### convert taxonomic levels back as factor - threshod_filtered_abs$Kingdom<-as.factor(threshod_filtered_abs$Kingdom) - threshod_filtered_abs$Phylum<-as.factor(threshod_filtered_abs$Phylum) - threshod_filtered_abs$Class<-as.factor(threshod_filtered_abs$Class) - threshod_filtered_abs$Order<-as.factor(threshod_filtered_abs$Order) - threshod_filtered_abs$Family<-as.factor(threshod_filtered_abs$Family) - threshod_filtered_abs$Genus<-as.factor(threshod_filtered_abs$Genus) - threshod_filtered_abs$Species<-as.factor(threshod_filtered_abs$Species) - threshod_filtered_abs$OTU<-as.factor(threshod_filtered_abs$OTU) - - ### Remove the (now but needed before) Abundance = 0 rows - threshod_filtered_abs_no_zero <- filter(threshod_filtered_abs, threshod_filtered_abs$Abundance!=0) - - - ### Reorder the facet factor if later used for plotting - if (isTRUE(facet_plot)){ - threshod_filtered_abs_no_zero[[facetting_column]] <- fct_reorder(threshod_filtered_abs_no_zero[[facetting_column]], as.numeric(threshod_filtered_abs_no_zero[[sample_label]])) - - }else if (isFALSE(facet_plot)){print("No faceting") - - }else {stop('"facet_plot" must be TRUE or FALSE') - } - - ### Reverse the sample_label column for later if using horizontal barplot - if (isTRUE(horizontal_plot)){ - threshod_filtered_abs_no_zero[[sample_label]] <- fct_rev(threshod_filtered_abs_no_zero[[sample_label]]) - - } else if (isFALSE(horizontal_plot)){print("Vertical plotting") - - } else {stop('"horizontal_plot" must be TRUE or FALSE') - } - - - ####################################################################### barplot_fct_filtration_at_tax_level.R modified from here - - ### Loop for unique value in grouping_column - for (i in unique(threshod_filtered_abs_no_zero[[grouping_column]])) { - print(paste("Start plotting", grouping_column, i)) - - - ### filter the table for this value of the grouping columns. Depending of the used arguments, the t_neg_PCR values are kept or not on the barplots - ### Keep t_neg_PCR rows - if (isTRUE(t_neg_PCR_sample_on_plots) & !is_null(t_neg_PCR_sample_grp_filter_column_value)){ - filtered_df_abs_i <- threshod_filtered_abs_no_zero %>% filter(grepl(i, .[[grouping_column]]) | grepl(t_neg_PCR_sample_grp_filter_column_value, .[[grouping_column]])) - print('Keeping t_neg_PCR values for the graphs. The "t_neg_PCR_sample_grp_filter_column_value" must match the one in the grouping_column for this sample') - - } - else if (isTRUE(t_neg_PCR_sample_on_plots) & is_null(t_neg_PCR_sample_grp_filter_column_value)){ - stop('If "t_neg_PCR_sample_on_plots" is "TRUE, a "t_neg_PCR_sample_grp_filter_column_value" indicating the value of the T neg PCR sample in the grouping column must be indicated') - } - - ### Do not keep t_neg_PCR rows - else if (isFALSE(t_neg_PCR_sample_on_plots)){ - filtered_df_abs_i <- filter(threshod_filtered_abs_no_zero, get(grouping_column) == i) - } - - else{ stop('"t_neg_PCR_sample_on_plots" must be TRUE or FALSE') - } - - ### Keep only the OTU which passed filters - select_filtered_df_abs_i <- filtered_df_abs_i - - ############################################################################ Modified for clustering based on OTU abundance - - g_column <- rlang::sym(grouping_column) - x_column<- rlang::sym(sample_label) - f_column <- rlang::sym(facetting_column) - taxa_column <- rlang::sym(t) - select_filtered_df_abs_i <- select_filtered_df_abs_i %>% - dplyr::group_by(Sample, !!(x_column), !!(g_column), !!(f_column), Genus, Species) %>% - dplyr::mutate(Abundance = sum(Abundance)) %>% - dplyr::ungroup() %>% - dplyr::distinct(Sample, Genus, Species, .keep_all = TRUE) - - - #### Reorder by abundancy - if (order_by == "abundance"){ - print("Ordered by abundance") - select_filtered_df_abs_i_reord <- select_filtered_df_abs_i %>% - group_by(!! t_column) %>% - mutate(tot = sum(Abundance))%>% - ungroup() %>% - arrange(desc(tot)) %>% - mutate(OTU = factor(OTU, levels = unique(OTU), ordered = TRUE)) - order_by_ <- "abund" - select_filtered_df_abs_i_reord <<- select_filtered_df_abs_i_reord - - }else if (order_by =="alphabet_class"){ - print("Ordered alphabetically considering the Class") - #select_filtered_df_abs_i <- select_filtered_df_abs_i - select_filtered_df_abs_i_reord <- select_filtered_df_abs_i #%>% - #arrange(desc(Class, Genus,Species)) %>% - #dplyr::mutate(OTU = factor(OTU, levels = unique(OTU), ordered = TRUE)) - select_filtered_df_abs_i_reord$OTU = factor(select_filtered_df_abs_i_reord$OTU, levels=unique(select_filtered_df_abs_i_reord$OTU[order(select_filtered_df_abs_i_reord$Genus, decreasing = T)]), ordered=TRUE) - order_by_ <- "alphclass" - - - }else if (order_by =="cluster"){ - selected_col <- select_filtered_df_abs_i %>% select(c("Sample","OTU", "Abundance")) - selected_col <<- selected_col - selected_col_wide <- reshape2::dcast(selected_col, Sample ~ OTU) - selected_col_wide[is.na(selected_col_wide)] <- 0 - rownames(selected_col_wide) <- selected_col_wide[,1] - selected_col_wide[["Sample"]] <- NULL - # sample_source_order = c("EC_Q", "liver", "spleen", "lungs", "water_Q", "EC_MN", "PCR_neg") - row.order <- hclust(dist(selected_col_wide))$order # clustering - col.order <- hclust(dist(t(selected_col_wide)))$order - dat_new <- selected_col_wide[row.order , col.order] # re-order matrix accoring to clustering - df_molten_dat <- melt(as.matrix(dat_new)) - names(df_molten_dat)[c(1:3)] <- c("Sample", "OTU","Abundance") - select_filtered_df_abs_i_reord <- df_molten_dat %>% filter(Abundance != 0) - select_filtered_df_abs_i_reord <- left_join(select_filtered_df_abs_i_reord, select_filtered_df_abs_i, by = c("Sample", "OTU", "Abundance")) - order_by_ <- "clu" - select_filtered_df_abs_i_reord <<- select_filtered_df_abs_i_reord - - - }else{ - stop('order_by_abundance must be "abundancy", "alphabet_class" or "cluster"') - } - - - if (quantity_filtering_type != "nofiltering"){ - #### Set the filtering_tabl at the top of the plot - print("t1") - select_filtered_df_abs_i_reord[[t]] <- fct_relevel(select_filtered_df_abs_i_reord[[t]], filtering_tag, after = 0) - print("t2") - } - - - select_filtered_df_abs_i_reord[["OTU"]] <- fct_relevel(select_filtered_df_abs_i_reord[["OTU"]], filtering_tag, after = 0) - print("t3") - - #### Write the content of the plot table in a external file - write.table(select_filtered_df_abs_i_reord, file = paste0(figures_save_dir,"/heatmaps/", plotting, "/",filtering,"/Table/", taxonomic_filtering_rank, "_",taxonomic_filtering_value,"_",filtering, "u", quantity_filtering_value, "_",grouping_column, "_", i, "_", t,"_",sample_label, "_abundancy_table.tsv"), append = FALSE, sep = "\t", eol = "\n", na = "NA", dec = ".", col.names = TRUE, row.names = FALSE) - - - - - ### Renames the values in the vector of plotted used taxrank with their related OTU name to keep them matching. Without this step, the labels do NOT match the rows - taxalabel <- as.character(paste0(toupper(substr(select_filtered_df_abs_i_reord[["Phylum"]],1 ,9)) , ";", select_filtered_df_abs_i_reord[[t]])) - names(taxalabel) <- select_filtered_df_abs_i[["OTU"]] - - - select_filtered_df_abs_i_reord <<- select_filtered_df_abs_i_reord - ### Generate heatmap - heatmap <- ggplot(select_filtered_df_abs_i_reord, aes(x = get(sample_label), y = OTU, fill = Abundance)) + - theme_bw() + - geom_tile(aes(fill=Abundance), show.legend=TRUE) + - scale_fill_gradient(na.value = "white", low="#000033", high="#CCFF66") + - geom_text(aes(label = round(Abundance, digits = 1), color = Abundance > max(Abundance)/2), size = 2 ) + - scale_color_manual(guide = FALSE, values = c("white", "black")) + - theme(axis.text.x = element_text(angle = -90, vjust = 0.5, hjust = 0)) + - scale_y_discrete(labels = taxalabel, drop = TRUE) + - scale_x_discrete(drop = TRUE) + - labs(x= sample_label, y = t) - - - ### Turn heatmap horizontally - if (isTRUE(horizontal_plot)){ - ### Reverse the order of the samples - heatmap <- heatmap + coord_flip() - } - - ### Create facet view - if (isTRUE(facet_plot)){ - heatmap <- heatmap + facet_grid(scales = "free", space = "free", cols = vars(get(facetting_column)), drop = TRUE) - } - - - ### Save it - ### Set the filename - filename_base <- (paste0(figures_save_dir,"/heatmaps/", plotting, "/",filtering,"/", taxonomic_filtering_rank, "_",filtering, "u", quantity_filtering_value, "_",grouping_column, "_", i, "_", sample_label, "_",comparison,"_",facetting_column,"_" ,t, "_",order_by_)) - ### Print the filename to follow progress - print(filename_base) - ###Save the figure - height_value <- 2 + 0.15*n_distinct(select_filtered_df_abs_i_reord[["OTU"]]) - ggsave(heatmap, filename = paste0(filename_base, "_heat.png"), width = 7, height = height_value) - - - - - }}} - - - - - - - diff --git a/ressources/r_scripts/visualization/3_Heatmaps/comparative_heatmap_fct.R b/ressources/r_scripts/visualization/3_Heatmaps/comparative_heatmap_fct.R deleted file mode 100755 index 052903ae..00000000 --- a/ressources/r_scripts/visualization/3_Heatmaps/comparative_heatmap_fct.R +++ /dev/null @@ -1,359 +0,0 @@ - -### Create a function -comparative_variants_heatmap_fct <- function(melted_dataframe, sample_label, grouping_column, grouping_column_filtering = c(FALSE, TRUE), grouping_column_filtering_value, t_neg_PCR_sample_on_plots, t_neg_PCR_sample_grp_filter_column_value, taxonomic_filtering = c(TRUE, FALSE), taxonomic_filtering_rank = "Kingdom" , taxonomic_filtering_value = "Bacteria" , quantity_filtering_type = c("relative", "absolute", "rank", "nofiltering", "absolute_and_rank"), absolute_quantity_filtering_value, relative_quantity_filtering_value, rank_quantity_filtering_value, plotting_value = c("relative", "absolute"), plotting_tax_ranks = "all", figures_save_dir, horizontal_plot = FALSE, facet_plot = FALSE, facetting_column, order_by = TRUE, comparison, patient_ID){ - - # Transform values in dplyr format - g_column <- rlang::sym(grouping_column) - x_column <- rlang::sym(sample_label) - - - # In option, filter for a given grouping_columns. - ## No filter for the grouping column - if (isFALSE(grouping_column_filtering)){ - melted_dataframe_filtered <- melted_dataframe - print("No filtering of the grouping_column") - } - - ## Filter for the grouping column - else if (isTRUE(grouping_column_filtering)){ - melted_dataframe_filtered <- melted_dataframe %>% filter(get(grouping_column) == grouping_column_filtering_value) - print(paste("Data filtered for", grouping_column, "is", grouping_column_filtering_value)) - - }else{ stop("grouping_column_filtering must be TRUE or FALSE") - } - - - # In option, filter at a given taxonomic rank and for a given taxonomic ID - ## No filter - if (isFALSE(taxonomic_filtering)) { - physeq_subset_df <- melted_dataframe_filtered - - physeq_subset_norm_df <- physeq_subset_df %>% # calculate % normalized Abundance - ungroup %>% - dplyr::group_by(Sample) %>% - dplyr::mutate(per=as.numeric(100*Abundance/sum(Abundance))) %>% - ungroup() %>% - dplyr::select(-Abundance) %>% - dplyr::rename(Abundance = per) - - taxonomic_filtering_value <- "no_tax_rank_filtering" - print("No filtering of any taxonomic rank") - } - - ## A specific filter - else if (isTRUE(taxonomic_filtering)){ - physeq_subset_df <- melted_dataframe_filtered %>% - dplyr::filter(get(taxonomic_filtering_rank) == taxonomic_filtering_value) - - physeq_subset_norm_df <- physeq_subset_df %>% - dplyr::group_by(Sample) %>% - dplyr::mutate(per=paste0((100*Abundance/sum(Abundance)))) %>% - ungroup %>% - dplyr::select(-Abundance) %>% - dplyr::rename(Abundance = per) - - physeq_subset_norm_df$Abundance <- as.numeric(physeq_subset_norm_df$Abundance) - print(paste("Data filtered for", taxonomic_filtering_rank, "is", taxonomic_filtering_value)) - - }else{stop('"taxonomic_filtering" must be TRUE or FALSE') - } - - # Choose between relative value or absolute value dataframe for plotting value - ## Relative value plotting - if (plotting_value == "relative"){ - plotting <- "Relative" - print("Relative value plotting") - plotted_df <- physeq_subset_norm_df - } - ## Absolute value plotting - else if (plotting_value == "absolute"){ - plotting <- "Absolute" - print("Absolute value plotting") - plotted_df <- physeq_subset_df - }else{ stop('"relative_value_plotting" must be "relative" or "absolute"') - } - - # From here on, loop for all taxonomic levels chosen for plotting, this is what changes with the alternative plotting script where this is done at the end. - ## Define the taxa ranks whill will be plotted - if (plotting_tax_ranks == "all"){ - tax_ranks <- c("Kingdom", "Phylum", "Class", "Order", "Family", "Genus", "Species", "OTU") - }else{ - tax_ranks <- plotting_tax_ranks - print('Plotting at a specific taxonomic rank. Check spelling one or several of ("Kingdom", "Phylum", "Class", "Order", "Family", "Genus", "Species", "OTU"). This error does NOT always mean that there was an error in spelling') - } - - ## Run the loop - for (t in tax_ranks){ - t_column <- rlang::sym(t) # set the taxa in format compatible with dplyr - print(t_column) - - ### Filter values at a relative or absolute quantity - #### No filtering - if (quantity_filtering_type == "nofiltering"){ - filtering <- "Nofiltering" - filtering_tag <- 0 - quantity_filtering_value <- "0" - print("No filtering based on quantity") - physeq_subset_df_filtered <- physeq_subset_norm_df - } - - #### Relative value filtering - else if (quantity_filtering_type == "relative"){ - filtering <- "Relative" - quantity_filtering_value <- relative_quantity_filtering_value - print("Relative value based filtering") - physeq_subset_df_filtered <- physeq_subset_norm_df %>% - dplyr::group_by(Sample, !!t_column) %>% # group the dataframe by Sample and taxa - dplyr::mutate(sumper=as.numeric(sum(Abundance))) %>% # calculate the cumulative relative abundance of the taxa in the sample - ungroup %>% - dplyr::group_by(!!g_column, !!t_column) %>% # group the dataframe by Sample and taxa - dplyr::filter(sumper >= quantity_filtering_value) %>%# keep only the taxa above threshold. From the applied grouping, the taxa remain in all amples if over the filtering value in one sample? - ungroup - } - - #### Absolute value filtering - else if (quantity_filtering_type == "absolute"){ - filtering <- "Absolute" - quantity_filtering_value <- absolute_quantity_filtering_value - print("Absolute value based filtering") - physeq_subset_df_filtered <- physeq_subset_df %>% - dplyr::group_by(Sample, !!t_column) %>% # group the dataframe by Sample and taxa - dplyr::mutate(sumper=as.numeric(sum(Abundance))) %>% # calculate the cumulative relative abundance of the taxa in the sample - ungroup %>% - dplyr::group_by(!!g_column, !!t_column) %>% # group the dataframe by Sample and taxa - dplyr::filter(sumper >= quantity_filtering_value) %>%# keep only the taxa above threshold. From the applied grouping, the taxa remain in all amples if over the filtering value in one sample? - ungroup - } - - #### Write the table after table choice for relative of absolute value plotting , without Abundance = 0 rows to reduce its size. - plotted_df %>% - filter(Abundance>0) %>% - write.table(file = paste0(figures_save_dir,"/Comparative_heatmaps/", plotting, "/",filtering,"/Table/", taxonomic_filtering_rank, "_",taxonomic_filtering_value,"_", "u", "_all_", sample_label, "_plotted_table.tsv"), append = FALSE, sep = "\t", eol = "\n", na = "NA", dec = ".", col.names = TRUE, row.names = FALSE) - - - #### Write the table with values that passed the quantity filtering. - physeq_subset_df_filtered %>% ### To follow and control the process, write external .tsv tables - filter(Abundance>0) %>% - write.table(file = paste0(figures_save_dir,"/Comparative_heatmaps/", plotting, "/",filtering,"/Table/", taxonomic_filtering_rank, "_",taxonomic_filtering_value,"_",filtering, "u", quantity_filtering_value, "_all_", sample_label,"_",t ,"_filtration_table.tsv"), append = FALSE, sep = "\t", eol = "\n", na = "NA", dec = ".", col.names = TRUE, row.names = FALSE) - - - ### Now we have a dataframe ("plotted_df") containing all values, with Abundance expressed in absolute reads number or relative quanity in %, and a dataframe containing the rows that have to be kept while the rest will be merged by tagging them with the same name - above_threshold_df <- semi_join(plotted_df, physeq_subset_df_filtered, by = c("OTU", "Sample")) ### In a dataframe, keep only the rows matching the filtered rowmanes - under_threshold_df <- anti_join(plotted_df, physeq_subset_df_filtered, by =c ("OTU", "Sample")) ### In another dataframe, keep only the lines NOT matching the filtered rowmanes - - - ### In another dataframe, keep the join of the under and above tables, before masking of the filtered taxa and without Abundance = 0 rows to reduce its size. - merged_filtered_abs <- full_join(under_threshold_df,above_threshold_df) %>% - filter(Abundance>0) - write.table(merged_filtered_abs, file = paste0(figures_save_dir,"/Comparative_heatmaps/", plotting, "/",filtering,"/Table/", taxonomic_filtering_rank, "_",taxonomic_filtering_value,"_",filtering, "u", quantity_filtering_value, "_all_", sample_label, "_merged_without_masking.tsv"), append = FALSE, sep = "\t", eol = "\n", na = "NA", dec = ".", col.names = TRUE, row.names = FALSE) - - ### Rename taxa with < percent/reads abundance, defending of the filtering applied - #### Define the filtering tag depending of the applied filtering - ##### Relative - if (quantity_filtering_type == "relative"){ - filtering_tag <-paste("Relative abund. <", quantity_filtering_value , "%") - } - ##### Absolute filtering - else if (quantity_filtering_type == "absolute"){ - filtering_tag <-paste("Absolute abund. <", quantity_filtering_value, "reads") - } - - - if (quantity_filtering_type != "nofiltering"){ - #### Apply the filtering tag - under_threshold_df$Kingdom <-filtering_tag - under_threshold_df$Phylum <-filtering_tag - under_threshold_df$Class <-filtering_tag - under_threshold_df$Order <-filtering_tag - under_threshold_df$Family <-filtering_tag - under_threshold_df$Genus <-filtering_tag - under_threshold_df$Species <-filtering_tag - under_threshold_df$OTU <-filtering_tag - } - - - ### Join the two dataframes, to put back all rows together, the ones above threshold keeping their original taxonomic identifier while the others are now grouped together - threshod_filtered_abs <- full_join(under_threshold_df,above_threshold_df) - - ### Remove the (now but needed before) Abundance = 0 rows - threshod_filtered_abs_no_zero <- filter(threshod_filtered_abs, threshod_filtered_abs$Abundance!=0) - - - ### Reorder the facet factor if later used for plotting - if (isTRUE(facet_plot)){ - threshod_filtered_abs_no_zero[[facetting_column]] <- fct_reorder(threshod_filtered_abs_no_zero[[facetting_column]], as.numeric(threshod_filtered_abs_no_zero[[sample_label]])) - - }else if (isFALSE(facet_plot)){print("No faceting") - - }else {stop('"facet_plot" must be TRUE or FALSE') - } - - ### Reverse the sample_label column for later if using horizontal barplot - if (isTRUE(horizontal_plot)){ - threshod_filtered_abs_no_zero[[sample_label]] <- fct_rev(threshod_filtered_abs_no_zero[[sample_label]]) - - } else if (isFALSE(horizontal_plot)){print("Vertical plotting") - - } else {stop('"horizontal_plot" must be TRUE or FALSE') - } - - - ####################################################################### barplot_fct_filtration_at_tax_level.R modified from here - - ### Loop for unique value in grouping_column - for (i in patient_ID) { - print(paste("Start plotting", grouping_column, i)) - - - ### filter the table for this value of the grouping columns. Depending of the used arguments, the t_neg_PCR values are kept or not on the barplots - ### Keep t_neg_PCR rows - if (isTRUE(t_neg_PCR_sample_on_plots) & !is_null(t_neg_PCR_sample_grp_filter_column_value)){ - filtered_df_abs_i <- threshod_filtered_abs_no_zero %>% filter(grepl(i, .[[grouping_column]]) | grepl(t_neg_PCR_sample_grp_filter_column_value, .[[grouping_column]])) - print('Keeping t_neg_PCR values for the graphs. The "t_neg_PCR_sample_grp_filter_column_value" must match the one in the grouping_column for this sample') - - } - else if (isTRUE(t_neg_PCR_sample_on_plots) & is_null(t_neg_PCR_sample_grp_filter_column_value)){ - stop('If "t_neg_PCR_sample_on_plots" is "TRUE, a "t_neg_PCR_sample_grp_filter_column_value" indicating the value of the T neg PCR sample in the grouping column must be indicated') - } - - ### Do not keep t_neg_PCR rows - else if (isFALSE(t_neg_PCR_sample_on_plots)){ - filtered_df_abs_i <- filter(threshod_filtered_abs_no_zero, get(grouping_column) == i) - } - - else{ stop('"t_neg_PCR_sample_on_plots" must be TRUE or FALSE') - } - - ### Write this table in a external file - write.table(filtered_df_abs_i, file = paste0(figures_save_dir,"/Comparative_heatmaps/", plotting, "/",filtering,"/Table/", taxonomic_filtering_rank, "_",taxonomic_filtering_value,"_",filtering, "u", quantity_filtering_value, "_",grouping_column, "_", i, "_", sample_label, "_abundancy_table.tsv"), append = FALSE, sep = "\t", eol = "\n", na = "NA", dec = ".", col.names = TRUE, row.names = FALSE) - - ### Filter the table for specific conditions comparing for presence of absence of taxa in given samples - ### Simple filtering, comparing samples of interest (e.g spleen and liver) with related controls - ### Filter the table for specific conditions comparing for presence of absence of taxa in given samples - ### Simple filtering, comparing samples of interest (e.g spleen and liver) with related controls - if (comparison == "1Rule"){ - compare_df_i <- dcast(filtered_df_abs_i, OTU + Class + Genus + Species ~ sample_source, value.var = "Abundance",fun.aggregate = sum) - selected_by_comparison_df_i <- compare_df_i %>% - subset(spleen > 5 & spleen > 5*EC_Q & spleen > 5* PCR_neg | liver > 5 & liver > 5*EC_Q & liver > 5* PCR_neg ) - - } - ### Composite comparison, with two rules - else if (comparison == "2Rules") { - compare_df_i <- dcast(filtered_df_abs_i, OTU + Class + Genus + Species ~ sample_source, value.var = "Abundance",fun.aggregate = sum) - selected_by_comparison_df_i <- compare_df_i %>% - filter(spleen > 5 & spleen > 5*EC_Q & spleen > 5* PCR_neg | liver > 5 & liver > 5*EC_Q & liver > 5* PCR_neg ) %>% - filter(water_Q > 5 & water_Q > 5*EC_Q & water_Q > 5* PCR_neg | water_MN > 5 & water_MN > 5*EC_MN & water_MN > 5* PCR_neg | lungs > 5 & lungs > 5*EC_Q & lungs > 5 * PCR_neg) - } - - else if(comparison == "0Rule"){ - selected_by_comparison_df_i <- filtered_df_abs_i - } - - else { - stop('comparison must be "1Rule", "2Rules" or "0Rule" ') - } - - write.table(selected_by_comparison_df_i, file = paste0(figures_save_dir,"/Comparative_heatmaps/", plotting, "/",filtering,"/Table/", taxonomic_filtering_rank, "_",taxonomic_filtering_value,"_",filtering, "u", quantity_filtering_value, "_",grouping_column, "_", i, "_", sample_label, "_Rules_filtered_table.tsv"), append = FALSE, sep = "\t", eol = "\n", na = "NA", dec = ".", col.names = TRUE, row.names = FALSE) - - ### Keep only the OTU which passed filters - select_filtered_df_abs_i <- semi_join(filtered_df_abs_i, selected_by_comparison_df_i, by ="OTU") - - ############################################################################ Modified for clustering based on OTU abundance - - #### Reorder by abundancy - if (order_by == "abundancy"){ - print("Ordered by abundance") - select_filtered_df_abs_i_reord <- select_filtered_df_abs_i %>% - group_by(!! t_column) %>% - mutate(tot = sum(Abundance))%>% - ungroup() %>% - arrange(desc(tot)) %>% - mutate(OTU = factor(OTU, levels = unique(OTU), ordered = TRUE)) - order_by_ <- "abund" - - }else if (order_by =="alphabet_class"){ - print("Ordered alphabetically considering the Class") - select_filtered_df_abs_i_reord <- select_filtered_df_abs_i %>% - arrange(desc(Class, Genus, Species)) %>% - mutate(OTU = factor(OTU, levels = unique(OTU), ordered = TRUE)) - order_by_ <- "alphclass" - - - }else if (order_by =="cluster"){ - selected_col <- select_filtered_df_abs_i %>% select(c("sample_source","OTU", "Abundance")) - selected_col_wide <- reshape2::dcast(selected_col, sample_source ~ OTU) - selected_col_wide[is.na(selected_col_wide)] <- 0 - rownames(selected_col_wide) <- selected_col_wide[,1] - selected_col_wide[[sample_label]] <- NULL - # sample_source_order = c("EC_Q", "liver", "spleen", "lungs", "water_Q", "EC_MN", "PCR_neg") - row.order <- hclust(dist(selected_col_wide))$order # clustering - col.order <- hclust(dist(t(selected_col_wide)))$order - dat_new <- selected_col_wide[row.order , col.order] # re-order matrix accoring to clustering - df_molten_dat <- melt(as.matrix(dat_new)) - names(df_molten_dat)[c(1:3)] <- c("sample_source", "OTU","Abundance") - select_filtered_df_abs_i_reord <- df_molten_dat %>% filter(Abundance != 0) - select_filtered_df_abs_i_reord$OTU <- fct_rev(select_filtered_df_abs_i_reord$OTU) - order_by_ <- "clu" - }else{ - stop('order_by_abundance must be "abundancy", "alphabet_class" or "cluster"') - } - - - if (quantity_filtering_type != "nofiltering"){ - #### Set the filtering_tabl at the top of the plot - select_filtered_df_abs_i_reord[[t]] <- fct_relevel(select_filtered_df_abs_i_reord[[t]], filtering_tag, after = 0) - } - - #### Write the content of the plot table in a external file - write.table(select_filtered_df_abs_i_reord, file = paste0(figures_save_dir,"/Comparative_heatmaps/", plotting, "/",filtering,"/Table/", taxonomic_filtering_rank, "_",taxonomic_filtering_value,"_",filtering, "u", quantity_filtering_value, "_",grouping_column, "_", i, "_", t,"_",sample_label, "_abundancy_table.tsv"), append = FALSE, sep = "\t", eol = "\n", na = "NA", dec = ".", col.names = TRUE, row.names = FALSE) - - - ### Renames the values in the vector of plotted used taxrank with their related OTU name to keep them matching. Without this step, the labels do NOT match the rows - taxalabel <- as.character(paste0(toupper(substr(select_filtered_df_abs_i[["Class"]],1 ,9)) , ";", select_filtered_df_abs_i[[t]])) - names(taxalabel) <- select_filtered_df_abs_i[["OTU"]] - - ### Generate heatmap - heatmap <- ggplot(select_filtered_df_abs_i_reord, aes(x = get(sample_label), y = OTU, fill = Abundance)) + - theme_bw() + - geom_tile(aes(fill=Abundance), show.legend=TRUE) + - scale_fill_gradient(na.value = "white", low="#000033", high="#CCFF66") + - geom_text(aes(label = round(Abundance, digits = 1), color = Abundance > max(Abundance)/2), size = 2 ) + - scale_color_manual(guide = FALSE, values = c("white", "black")) + - theme(axis.text.x = element_text(angle = -90, vjust = 0.5, hjust = 0)) + - scale_y_discrete(labels = taxalabel, drop = TRUE) + - scale_x_discrete(drop = FALSE) + - labs(x= sample_label, y = t) - - - ### Turn heatmap horizontally - if (isTRUE(horizontal_plot)){ - ### Reverse the order of the samples - heatmap <- heatmap + coord_flip() - } - - ### Create facet view - if (isTRUE(facet_plot)){ - heatmap <- heatmap + facet_grid(scales = "free", space = "free", cols = vars(get(facetting_column))) - } - - - ### Save it - ### Set the filename - filename_base <- (paste0(figures_save_dir,"/Comparative_heatmaps/", plotting, "/",filtering,"/", taxonomic_filtering_rank, "_",filtering, "u", quantity_filtering_value, "_",grouping_column, "_", i, "_", sample_label, "_",comparison,"_",facetting_column,"_" ,t, "_",order_by_)) - ### Print the filename to follow progress - print(filename_base) - ###Save the figure - height_value <- 2 + 0.15*n_distinct(select_filtered_df_abs_i_reord[["OTU"]]) - ggsave(heatmap, filename = paste0(filename_base, "_heat.png"), width = 7, height = height_value) - - - - - }}} - - - - - - - diff --git a/ressources/r_scripts/visualization/3_Heatmaps/comparative_heatmap_fct_serie1_2019_WIP.R b/ressources/r_scripts/visualization/3_Heatmaps/comparative_heatmap_fct_serie1_2019_WIP.R deleted file mode 100755 index 97164246..00000000 --- a/ressources/r_scripts/visualization/3_Heatmaps/comparative_heatmap_fct_serie1_2019_WIP.R +++ /dev/null @@ -1,359 +0,0 @@ - -### Create a function -comparative_variants_heatmap_fct <- function(melted_dataframe, sample_label, grouping_column, grouping_column_filtering = c(FALSE, TRUE), grouping_column_filtering_value, t_neg_PCR_sample_on_plots, t_neg_PCR_sample_grp_filter_column_value, taxonomic_filtering = c(TRUE, FALSE), taxonomic_filtering_rank = "Kingdom" , taxonomic_filtering_value = "Bacteria" , quantity_filtering_type = c("relative", "absolute", "rank", "nofiltering", "absolute_and_rank"), absolute_quantity_filtering_value, relative_quantity_filtering_value, rank_quantity_filtering_value, plotting_value = c("relative", "absolute"), plotting_tax_ranks = "all", figures_save_dir, horizontal_plot = FALSE, facet_plot = FALSE, facetting_column, order_by = TRUE, comparison, patient_ID){ - - # Transform values in dplyr format - g_column <- rlang::sym(grouping_column) - x_column <- rlang::sym(sample_label) - - - # In option, filter for a given grouping_columns. - ## No filter for the grouping column - if (isFALSE(grouping_column_filtering)){ - melted_dataframe_filtered <- melted_dataframe - print("No filtering of the grouping_column") - } - - ## Filter for the grouping column - else if (isTRUE(grouping_column_filtering)){ - melted_dataframe_filtered <- melted_dataframe %>% filter(get(grouping_column) == grouping_column_filtering_value) - print(paste("Data filtered for", grouping_column, "is", grouping_column_filtering_value)) - - }else{ stop("grouping_column_filtering must be TRUE or FALSE") - } - - - # In option, filter at a given taxonomic rank and for a given taxonomic ID - ## No filter - if (isFALSE(taxonomic_filtering)) { - physeq_subset_df <- melted_dataframe_filtered - - physeq_subset_norm_df <- physeq_subset_df %>% # calculate % normalized Abundance - ungroup %>% - dplyr::group_by(Sample) %>% - dplyr::mutate(per=as.numeric(100*Abundance/sum(Abundance))) %>% - ungroup() %>% - dplyr::select(-Abundance) %>% - dplyr::rename(Abundance = per) - - taxonomic_filtering_value <- "no_tax_rank_filtering" - print("No filtering of any taxonomic rank") - } - - ## A specific filter - else if (isTRUE(taxonomic_filtering)){ - physeq_subset_df <- melted_dataframe_filtered %>% - dplyr::filter(get(taxonomic_filtering_rank) == taxonomic_filtering_value) - - physeq_subset_norm_df <- physeq_subset_df %>% - dplyr::group_by(Sample) %>% - dplyr::mutate(per=paste0((100*Abundance/sum(Abundance)))) %>% - ungroup %>% - dplyr::select(-Abundance) %>% - dplyr::rename(Abundance = per) - - physeq_subset_norm_df$Abundance <- as.numeric(physeq_subset_norm_df$Abundance) - print(paste("Data filtered for", taxonomic_filtering_rank, "is", taxonomic_filtering_value)) - - }else{stop('"taxonomic_filtering" must be TRUE or FALSE') - } - - # Choose between relative value or absolute value dataframe for plotting value - ## Relative value plotting - if (plotting_value == "relative"){ - plotting <- "Relative" - print("Relative value plotting") - plotted_df <- physeq_subset_norm_df - } - ## Absolute value plotting - else if (plotting_value == "absolute"){ - plotting <- "Absolute" - print("Absolute value plotting") - plotted_df <- physeq_subset_df - }else{ stop('"relative_value_plotting" must be "relative" or "absolute"') - } - - # From here on, loop for all taxonomic levels chosen for plotting, this is what changes with the alternative plotting script where this is done at the end. - ## Define the taxa ranks whill will be plotted - if (plotting_tax_ranks == "all"){ - tax_ranks <- c("Kingdom", "Phylum", "Class", "Order", "Family", "Genus", "Species", "OTU") - }else{ - tax_ranks <- plotting_tax_ranks - print('Plotting at a specific taxonomic rank. Check spelling one or several of ("Kingdom", "Phylum", "Class", "Order", "Family", "Genus", "Species", "OTU"). This error does NOT always mean that there was an error in spelling') - } - - ## Run the loop - for (t in tax_ranks){ - t_column <- rlang::sym(t) # set the taxa in format compatible with dplyr - print(t_column) - - ### Filter values at a relative or absolute quantity - #### No filtering - if (quantity_filtering_type == "nofiltering"){ - filtering <- "Nofiltering" - filtering_tag <- 0 - quantity_filtering_value <- "0" - print("No filtering based on quantity") - physeq_subset_df_filtered <- physeq_subset_norm_df - } - - #### Relative value filtering - else if (quantity_filtering_type == "relative"){ - filtering <- "Relative" - quantity_filtering_value <- relative_quantity_filtering_value - print("Relative value based filtering") - physeq_subset_df_filtered <- physeq_subset_norm_df %>% - dplyr::group_by(Sample, !!t_column) %>% # group the dataframe by Sample and taxa - dplyr::mutate(sumper=as.numeric(sum(Abundance))) %>% # calculate the cumulative relative abundance of the taxa in the sample - ungroup %>% - dplyr::group_by(!!g_column, !!t_column) %>% # group the dataframe by Sample and taxa - dplyr::filter(sumper >= quantity_filtering_value) %>%# keep only the taxa above threshold. From the applied grouping, the taxa remain in all amples if over the filtering value in one sample? - ungroup - } - - #### Absolute value filtering - else if (quantity_filtering_type == "absolute"){ - filtering <- "Absolute" - quantity_filtering_value <- absolute_quantity_filtering_value - print("Absolute value based filtering") - physeq_subset_df_filtered <- physeq_subset_df %>% - dplyr::group_by(Sample, !!t_column) %>% # group the dataframe by Sample and taxa - dplyr::mutate(sumper=as.numeric(sum(Abundance))) %>% # calculate the cumulative relative abundance of the taxa in the sample - ungroup %>% - dplyr::group_by(!!g_column, !!t_column) %>% # group the dataframe by Sample and taxa - dplyr::filter(sumper >= quantity_filtering_value) %>%# keep only the taxa above threshold. From the applied grouping, the taxa remain in all amples if over the filtering value in one sample? - ungroup - } - - #### Write the table after table choice for relative of absolute value plotting , without Abundance = 0 rows to reduce its size. - plotted_df %>% - filter(Abundance>0) %>% - write.table(file = paste0(figures_save_dir,"/Comparative_heatmaps/", plotting, "/",filtering,"/Table/", taxonomic_filtering_rank, "_",taxonomic_filtering_value,"_", "u", "_all_", sample_label, "_plotted_table.tsv"), append = FALSE, sep = "\t", eol = "\n", na = "NA", dec = ".", col.names = TRUE, row.names = FALSE) - - - #### Write the table with values that passed the quantity filtering. - physeq_subset_df_filtered %>% ### To follow and control the process, write external .tsv tables - filter(Abundance>0) %>% - write.table(file = paste0(figures_save_dir,"/Comparative_heatmaps/", plotting, "/",filtering,"/Table/", taxonomic_filtering_rank, "_",taxonomic_filtering_value,"_",filtering, "u", quantity_filtering_value, "_all_", sample_label,"_",t ,"_filtration_table.tsv"), append = FALSE, sep = "\t", eol = "\n", na = "NA", dec = ".", col.names = TRUE, row.names = FALSE) - - - ### Now we have a dataframe ("plotted_df") containing all values, with Abundance expressed in absolute reads number or relative quanity in %, and a dataframe containing the rows that have to be kept while the rest will be merged by tagging them with the same name - above_threshold_df <- semi_join(plotted_df, physeq_subset_df_filtered, by = c("OTU", "Sample")) ### In a dataframe, keep only the rows matching the filtered rowmanes - under_threshold_df <- anti_join(plotted_df, physeq_subset_df_filtered, by =c ("OTU", "Sample")) ### In another dataframe, keep only the lines NOT matching the filtered rowmanes - - - ### In another dataframe, keep the join of the under and above tables, before masking of the filtered taxa and without Abundance = 0 rows to reduce its size. - merged_filtered_abs <- full_join(under_threshold_df,above_threshold_df) %>% - filter(Abundance>0) - write.table(merged_filtered_abs, file = paste0(figures_save_dir,"/Comparative_heatmaps/", plotting, "/",filtering,"/Table/", taxonomic_filtering_rank, "_",taxonomic_filtering_value,"_",filtering, "u", quantity_filtering_value, "_all_", sample_label, "_merged_without_masking.tsv"), append = FALSE, sep = "\t", eol = "\n", na = "NA", dec = ".", col.names = TRUE, row.names = FALSE) - - ### Rename taxa with < percent/reads abundance, defending of the filtering applied - #### Define the filtering tag depending of the applied filtering - ##### Relative - if (quantity_filtering_type == "relative"){ - filtering_tag <-paste("Relative abund. <", quantity_filtering_value , "%") - } - ##### Absolute filtering - else if (quantity_filtering_type == "absolute"){ - filtering_tag <-paste("Absolute abund. <", quantity_filtering_value, "reads") - } - - - if (quantity_filtering_type != "nofiltering"){ - #### Apply the filtering tag - under_threshold_df$Kingdom <-filtering_tag - under_threshold_df$Phylum <-filtering_tag - under_threshold_df$Class <-filtering_tag - under_threshold_df$Order <-filtering_tag - under_threshold_df$Family <-filtering_tag - under_threshold_df$Genus <-filtering_tag - under_threshold_df$Species <-filtering_tag - under_threshold_df$OTU <-filtering_tag - } - - - ### Join the two dataframes, to put back all rows together, the ones above threshold keeping their original taxonomic identifier while the others are now grouped together - threshod_filtered_abs <- full_join(under_threshold_df,above_threshold_df) - - ### Remove the (now but needed before) Abundance = 0 rows - threshod_filtered_abs_no_zero <- filter(threshod_filtered_abs, threshod_filtered_abs$Abundance!=0) - - - ### Reorder the facet factor if later used for plotting - if (isTRUE(facet_plot)){ - threshod_filtered_abs_no_zero[[facetting_column]] <- fct_reorder(threshod_filtered_abs_no_zero[[facetting_column]], as.numeric(threshod_filtered_abs_no_zero[[sample_label]])) - - }else if (isFALSE(facet_plot)){print("No faceting") - - }else {stop('"facet_plot" must be TRUE or FALSE') - } - - ### Reverse the sample_label column for later if using horizontal barplot - if (isTRUE(horizontal_plot)){ - threshod_filtered_abs_no_zero[[sample_label]] <- fct_rev(threshod_filtered_abs_no_zero[[sample_label]]) - - } else if (isFALSE(horizontal_plot)){print("Vertical plotting") - - } else {stop('"horizontal_plot" must be TRUE or FALSE') - } - - - ####################################################################### barplot_fct_filtration_at_tax_level.R modified from here - - ### Loop for unique value in grouping_column - for (i in patient_ID) { - print(paste("Start plotting", grouping_column, i)) - - - ### filter the table for this value of the grouping columns. Depending of the used arguments, the t_neg_PCR values are kept or not on the barplots - ### Keep t_neg_PCR rows - if (isTRUE(t_neg_PCR_sample_on_plots) & !is_null(t_neg_PCR_sample_grp_filter_column_value)){ - filtered_df_abs_i <- threshod_filtered_abs_no_zero %>% filter(grepl(i, .[[grouping_column]]) | grepl(t_neg_PCR_sample_grp_filter_column_value, .[[grouping_column]])) - print('Keeping t_neg_PCR values for the graphs. The "t_neg_PCR_sample_grp_filter_column_value" must match the one in the grouping_column for this sample') - - } - else if (isTRUE(t_neg_PCR_sample_on_plots) & is_null(t_neg_PCR_sample_grp_filter_column_value)){ - stop('If "t_neg_PCR_sample_on_plots" is "TRUE, a "t_neg_PCR_sample_grp_filter_column_value" indicating the value of the T neg PCR sample in the grouping column must be indicated') - } - - ### Do not keep t_neg_PCR rows - else if (isFALSE(t_neg_PCR_sample_on_plots)){ - filtered_df_abs_i <- filter(threshod_filtered_abs_no_zero, get(grouping_column) == i) - } - - else{ stop('"t_neg_PCR_sample_on_plots" must be TRUE or FALSE') - } - - ### Write this table in a external file - write.table(filtered_df_abs_i, file = paste0(figures_save_dir,"/Comparative_heatmaps/", plotting, "/",filtering,"/Table/", taxonomic_filtering_rank, "_",taxonomic_filtering_value,"_",filtering, "u", quantity_filtering_value, "_",grouping_column, "_", i, "_", sample_label, "_abundancy_table.tsv"), append = FALSE, sep = "\t", eol = "\n", na = "NA", dec = ".", col.names = TRUE, row.names = FALSE) - - ### Filter the table for specific conditions comparing for presence of absence of taxa in given samples - ### Simple filtering, comparing samples of interest (e.g spleen and liver) with related controls - ### Filter the table for specific conditions comparing for presence of absence of taxa in given samples - ### Simple filtering, comparing samples of interest (e.g spleen and liver) with related controls - if (comparison == "1Rule"){ - compare_df_i <- dcast(filtered_df_abs_i, OTU + Class + Genus + Species ~ sample_source, value.var = "Abundance",fun.aggregate = sum) - selected_by_comparison_df_i <- compare_df_i %>% - subset(spleen > 5 & spleen > 5*EC_Q & spleen > 5* PCR_neg | liver > 5 & liver > 5*EC_Q & liver > 5* PCR_neg ) - - } - ### Composite comparison, with two rules - else if (comparison == "2Rules") { - compare_df_i <- dcast(filtered_df_abs_i, OTU + Class + Genus + Species ~ sample_source, value.var = "Abundance",fun.aggregate = sum) - selected_by_comparison_df_i <- compare_df_i %>% - filter(spleen > 5 & spleen > 5*EC_Q & spleen > 5* PCR_neg | liver > 5 & liver > 5*EC_Q & liver > 5* PCR_neg ) %>% - filter(water_MN > 5 & water_MN > 5*EC_MN & water_MN > 5* PCR_neg | lungs > 5 & lungs > 5*EC_Q & lungs > 5 * PCR_neg) - } - - else if(comparison == "0Rule"){ - selected_by_comparison_df_i <- filtered_df_abs_i - } - - else { - stop('comparison must be "1Rule", "2Rules" or "0Rule" ') - } - - write.table(selected_by_comparison_df_i, file = paste0(figures_save_dir,"/Comparative_heatmaps/", plotting, "/",filtering,"/Table/", taxonomic_filtering_rank, "_",taxonomic_filtering_value,"_",filtering, "u", quantity_filtering_value, "_",grouping_column, "_", i, "_", sample_label, "_Rules_filtered_table.tsv"), append = FALSE, sep = "\t", eol = "\n", na = "NA", dec = ".", col.names = TRUE, row.names = FALSE) - - ### Keep only the OTU which passed filters - select_filtered_df_abs_i <- semi_join(filtered_df_abs_i, selected_by_comparison_df_i, by ="OTU") - - ############################################################################ Modified for clustering based on OTU abundance - - #### Reorder by abundancy - if (order_by == "abundancy"){ - print("Ordered by abundance") - select_filtered_df_abs_i_reord <- select_filtered_df_abs_i %>% - group_by(!! t_column) %>% - mutate(tot = sum(Abundance))%>% - ungroup() %>% - arrange(desc(tot)) %>% - mutate(OTU = factor(OTU, levels = unique(OTU), ordered = TRUE)) - order_by_ <- "abund" - - }else if (order_by =="alphabet_class"){ - print("Ordered alphabetically considering the Class") - select_filtered_df_abs_i_reord <- select_filtered_df_abs_i %>% - arrange(desc(Class, Genus, Species)) %>% - mutate(OTU = factor(OTU, levels = unique(OTU), ordered = TRUE)) - order_by_ <- "alphclass" - - - }else if (order_by =="cluster"){ - selected_col <- select_filtered_df_abs_i %>% select(c("sample_source","OTU", "Abundance")) - selected_col_wide <- reshape2::dcast(selected_col, sample_source ~ OTU) - selected_col_wide[is.na(selected_col_wide)] <- 0 - rownames(selected_col_wide) <- selected_col_wide[,1] - selected_col_wide[[sample_label]] <- NULL - # sample_source_order = c("EC_Q", "liver", "spleen", "lungs", "water_Q", "EC_MN", "PCR_neg") - row.order <- hclust(dist(selected_col_wide))$order # clustering - col.order <- hclust(dist(t(selected_col_wide)))$order - dat_new <- selected_col_wide[row.order , col.order] # re-order matrix accoring to clustering - df_molten_dat <- melt(as.matrix(dat_new)) - names(df_molten_dat)[c(1:3)] <- c("sample_source", "OTU","Abundance") - select_filtered_df_abs_i_reord <- df_molten_dat %>% filter(Abundance != 0) - select_filtered_df_abs_i_reord$OTU <- fct_rev(select_filtered_df_abs_i_reord$OTU) - order_by_ <- "clu" - }else{ - stop('order_by_abundance must be "abundancy", "alphabet_class" or "cluster"') - } - - - if (quantity_filtering_type != "nofiltering"){ - #### Set the filtering_tabl at the top of the plot - select_filtered_df_abs_i_reord[[t]] <- fct_relevel(select_filtered_df_abs_i_reord[[t]], filtering_tag, after = 0) - } - - #### Write the content of the plot table in a external file - write.table(select_filtered_df_abs_i_reord, file = paste0(figures_save_dir,"/Comparative_heatmaps/", plotting, "/",filtering,"/Table/", taxonomic_filtering_rank, "_",taxonomic_filtering_value,"_",filtering, "u", quantity_filtering_value, "_",grouping_column, "_", i, "_", t,"_",sample_label, "_abundancy_table.tsv"), append = FALSE, sep = "\t", eol = "\n", na = "NA", dec = ".", col.names = TRUE, row.names = FALSE) - - - ### Renames the values in the vector of plotted used taxrank with their related OTU name to keep them matching. Without this step, the labels do NOT match the rows - taxalabel <- as.character(paste0(toupper(substr(select_filtered_df_abs_i[["Class"]],1 ,9)) , ";", select_filtered_df_abs_i[[t]])) - names(taxalabel) <- select_filtered_df_abs_i[["OTU"]] - - ### Generate heatmap - heatmap <- ggplot(select_filtered_df_abs_i_reord, aes(x = get(sample_label), y = OTU, fill = Abundance)) + - theme_bw() + - geom_tile(aes(fill=Abundance), show.legend=TRUE) + - scale_fill_gradient(na.value = "white", low="#000033", high="#CCFF66") + - geom_text(aes(label = round(Abundance, digits = 1), color = Abundance > max(Abundance)/2), size = 2 ) + - scale_color_manual(guide = FALSE, values = c("white", "black")) + - theme(axis.text.x = element_text(angle = -90, vjust = 0.5, hjust = 0)) + - scale_y_discrete(labels = taxalabel, drop = TRUE) + - scale_x_discrete(drop = FALSE) + - labs(x= sample_label, y = t) - - - ### Turn heatmap horizontally - if (isTRUE(horizontal_plot)){ - ### Reverse the order of the samples - heatmap <- heatmap + coord_flip() - } - - ### Create facet view - if (isTRUE(facet_plot)){ - heatmap <- heatmap + facet_grid(scales = "free", space = "free", cols = vars(get(facetting_column))) - } - - - ### Save it - ### Set the filename - filename_base <- (paste0(figures_save_dir,"/Comparative_heatmaps/", plotting, "/",filtering,"/", taxonomic_filtering_rank, "_",filtering, "u", quantity_filtering_value, "_",grouping_column, "_", i, "_", sample_label, "_",comparison,"_",facetting_column,"_" ,t, "_",order_by_)) - ### Print the filename to follow progress - print(filename_base) - ###Save the figure - height_value <- 2 + 0.15*n_distinct(select_filtered_df_abs_i_reord[["OTU"]]) - ggsave(heatmap, filename = paste0(filename_base, "_heat.png"), width = 7, height = height_value) - - - - - }}} - - - - - - - diff --git a/ressources/r_scripts/visualization/3_Heatmaps/merge_variants_heatmap_fct.R b/ressources/r_scripts/visualization/3_Heatmaps/merge_variants_heatmap_fct.R deleted file mode 100755 index 15103191..00000000 --- a/ressources/r_scripts/visualization/3_Heatmaps/merge_variants_heatmap_fct.R +++ /dev/null @@ -1,435 +0,0 @@ -### Create a function -merge_variants_heatmap_fct <- function(melted_dataframe, sample_label, grouping_column, grouping_column_filtering = c(FALSE, TRUE), grouping_column_filtering_value, t_neg_PCR_sample_on_plots, t_neg_PCR_sample_grp_filter_column_value, taxonomic_filtering = c(TRUE, FALSE), taxonomic_filtering_rank = "Kingdom" , taxonomic_filtering_value = "Bacteria" , quantity_filtering_type = c("relative", "absolute", "rank", "nofiltering", "absolute_and_rank"), absolute_quantity_filtering_value, relative_quantity_filtering_value, rank_quantity_filtering_value, plotting_value = c("relative", "absolute"), plotting_tax_ranks = "all", figures_save_dir, horizontal_plot = FALSE, facet_plot = FALSE, facetting_column, order_by_abundance = TRUE){ - - - x_column <- rlang::sym(sample_label) - - ### In option, filter for a given grouping_columns. In both cases keep only lines with Abundance not equal to 0 to reduce the dataframe size - - ### No filter for the grouping column - if (isFALSE(grouping_column_filtering)){ - melted_dataframe_filtered <- melted_dataframe - grouping_column_filtering_value <- "no_group_column_filtering" - - print("No filtering of the grouping_column") - } - - ### Filter for the grouping column - else if (isTRUE(grouping_column_filtering)){ - melted_dataframe_filtered <- melted_dataframe %>% - filter(get(grouping_column) == grouping_column_filtering_value) - - print(paste("Data filtered for", grouping_column, "is", grouping_column_filtering_value)) - - }else{ stop("grouping_column_filtering must be TRUE or FALSE")} - - - ### In option, filter at a given taxonomic rank and for a given taxonomic identifier - - ### No filter - if (isFALSE(taxonomic_filtering)) { - physeq_subset_df <- melted_dataframe_filtered - physeq_subet_norm_df <- physeq_subset_df %>% - ungroup %>% - dplyr::group_by(Sample) %>% - dplyr::mutate(per=as.numeric(100*Abundance/sum(Abundance))) %>% - dplyr::ungroup() %>% - dplyr::select(-Abundance) %>% - dplyr::rename(Abundance = per) - taxonomic_filtering_value <- "no_tax_rank_filtering" - - print("No filtering of any taxonomic rank") - } - - ### A specific filter - else if (isTRUE(taxonomic_filtering)){ - physeq_subset_df <- melted_dataframe_filtered %>% - filter(get(taxonomic_filtering_rank) == taxonomic_filtering_value) - physeq_subet_norm_df <- physeq_subset_df %>% - group_by(Sample) %>% - mutate(per=paste0((100*Abundance/sum(Abundance)))) %>% - ungroup %>% - select(-Abundance) %>% - dplyr::rename(Abundance = per) - physeq_subet_norm_df$Abundance <- as.numeric(physeq_subet_norm_df$Abundance) - - print(paste("Data filtered for", taxonomic_filtering_rank, "is", taxonomic_filtering_value)) - - }else{ stop('"taxonomic_filtering" must be TRUE or FALSE') - } - - - ### Choose between relative value or absolute value dataframe for plotting value - - ### Relative value plotting - if (plotting_value == "relative"){ - plotting <- "Relative" - print("Relative value plotting") - plotted_df <- physeq_subet_norm_df %>% - group_by(!! x_column) %>% - mutate(per=paste0((100*Abundance/sum(Abundance)))) %>% - ungroup %>% - select(-Abundance) %>% - dplyr::rename(Abundance = per) - plotted_df$Abundance <- as.numeric(plotted_df$Abundance) - } - - # Absolute value plotting - else if (plotting_value == "absolute"){ - plotting <- "Absolute" - print("Absolute value plotting") - plotted_df <- physeq_subset_df - - }else{ stop('"relative_value_plotting" must be "relative" or "absolute"') - } - - - ### Filter values at a relative or absolute quantity threshold or at a given rank in a group ( x highest OTU in the group) - - ### No filtering - if (quantity_filtering_type == "nofiltering"){ - filtering <- "Nofiltering" - quantity_filtering_value <- "0" - print("No filtering based on quantity") - physeq_subset_df_filtered <- physeq_subet_norm_df - } - - - ### Relative value filtering - else if (quantity_filtering_type == "relative"){ - filtering <- "Relative" - quantity_filtering_value <- relative_quantity_filtering_value - print("Relative value based filtering") - physeq_subset_df_filtered <- physeq_subet_norm_df %>% filter(Abundance > quantity_filtering_value) - } - - ### Absolute value filtering - else if (quantity_filtering_type == "absolute"){ - filtering <- "Absolute" - quantity_filtering_value <- absolute_quantity_filtering_value - print("Absolute value based filtering") - physeq_subset_df_filtered <- physeq_subset_df %>% filter(Abundance > quantity_filtering_value) - } - - ### Abundance rank of the taxa in the group filtering - else if (quantity_filtering_type == "rank"){ - filtering <- "Rank" - print("Rank based filtering") - quantity_filtering_value <- rank_quantity_filtering_value - ### Define the variables as dplyr wants them - g_column <- rlang::sym(grouping_column) - x_column <- rlang::sym(sample_label) - ### Generate the filtered dataframe - physeq_subset_df_filtered <- physeq_subset_df %>% - group_by(!! g_column) %>% ## group the dataframe by the grouping column - mutate(per=as.numeric(paste0((100*Abundance/sum(Abundance))))) %>% ## calculate a relative abundance of the taxa in the group - ungroup %>% - group_by((!! g_column), OTU) %>% ## now group the OTU in each grouping columns - mutate(sumper = as.numeric(paste0(sum(per)))) %>% ## Sum the relative abundance of each OTU in each group - ungroup %>% - group_by(!!! list(g_column, x_column)) %>% ## Group the dataframe for each by group and filling column - top_n(n = quantity_filtering_value*n_distinct(sample_label), wt = sumper) %>% ## Keep the x number of rows for each - ungroup - } - - - ### Combined absolute and rank of the taxa in the group filtering - else if (quantity_filtering_type == "absolute_and_rank"){ - filtering <- "Absolute_and_Rank" - print("Absolute_and_Rank based filtering") - ### Define the variables as dplyr wants them - g_column <- rlang::sym(grouping_column) - x_column<- rlang::sym(sample_label) - ### Generate the filtered dataframe - ### Relative reads filtering - physeq_subset_df_filtered <- physeq_subset_df %>% - group_by(!! g_column) %>% ## group the dataframe by the grouping column - mutate(per=as.numeric(paste0((100*Abundance/sum(Abundance))))) %>% ## calculate a relative abundance of the taxa in the group - ungroup %>% - group_by((!! g_column), OTU) %>% ## now group the OTU in each grouping columns - mutate(sumper = as.numeric(paste0(sum(per)))) %>% ## Sum the relative abundance of each OTU in each group - ungroup %>% - group_by(!!! list(g_column, x_column)) %>% ## Group the dataframe for each by group and filling column - top_n(n = rank_quantity_filtering_value*n_distinct(sample_label), wt = sumper) %>% ## Keep the x number of rows for each - ungroup - - - ### Absolute reads filtering - physeq_subset_df_filtered <- physeq_subset_df_filtered %>% filter(Abundance > absolute_quantity_filtering_value) - - ### Write "quantity_filtering_value" for ggsave later - quantity_filtering_value <- paste0("rank_", rank_quantity_filtering_value, "_absolute_", absolute_quantity_filtering_value) - - }else{ stop('quantity_filtering_type must be "nofiltering", "relative", "absolute", "rank" or "absolute_and_rank"') - } - - - ### Now we have a dataframe ("plotted_df") containing all values, with Abundance expressed in absolute reads number or relative quanity in %, and a dataframe containing the rows that have to be kept while the rest will be merged by tagging them with the same name - - - ### In a dataframe, keep only the rows matching the filtered rowmanes - above_threshold_df <- semi_join(plotted_df, physeq_subset_df_filtered, by = c("OTU", "Sample")) - - - ### In another dataframe, keep only the lines NOT matching the filtered rowmanes - under_threshold_df <- anti_join(plotted_df, physeq_subset_df_filtered, by =c ("OTU", "Sample")) - - - ### To follow and control the process, write external .tsv tables - ### Write the filtration table, without Abundance = 0 rows to reduce its size. - physeq_subset_df_filtered %>% - filter(Abundance>0) %>% - write.table(file = paste0(figures_save_dir,"/heatmaps/", plotting, "/",filtering,"/Table/", taxonomic_filtering_rank, "_",taxonomic_filtering_value,"_",filtering, "u", quantity_filtering_value, "_all_", sample_label, "_filtration_table.tsv"), append = FALSE, sep = "\t", eol = "\n", na = "NA", dec = ".", col.names = TRUE, row.names = FALSE) - - ### Write the plotted table , without Abundance = 0 rows to reduce its size. - plotted_df %>% - filter(Abundance>0) %>% - write.table(file = paste0(figures_save_dir,"/heatmaps/", plotting, "/",filtering,"/Table/", taxonomic_filtering_rank, "_",taxonomic_filtering_value,"_",filtering, "u", quantity_filtering_value, "_all_", sample_label, "_plotted_table.tsv"), append = FALSE, sep = "\t", eol = "\n", na = "NA", dec = ".", col.names = TRUE, row.names = FALSE) - - ### In another dataframe, keep the join of the under and above tables, before masking of the filtered taxa and without Abundance = 0 rows to reduce its size. - merged_filtered_abs <- full_join(under_threshold_df,above_threshold_df) %>% - filter(Abundance>0) - write.table(merged_filtered_abs, file = paste0(figures_save_dir,"/heatmaps/", plotting, "/",filtering,"/Table/", taxonomic_filtering_rank, "_",taxonomic_filtering_value,"_",filtering, "u", quantity_filtering_value, "_all_", sample_label, "_merged_without_masking.tsv"), append = FALSE, sep = "\t", eol = "\n", na = "NA", dec = ".", col.names = TRUE, row.names = FALSE) - - - - ### Convert taxonomic all levels as character (needed for the next steps) - above_threshold_df$Kingdom<-as.character(above_threshold_df$Kingdom) - above_threshold_df$Phylum<-as.character(above_threshold_df$Phylum) - above_threshold_df$Class<-as.character(above_threshold_df$Class) - above_threshold_df$Order<-as.character(above_threshold_df$Order) - above_threshold_df$Family<-as.character(above_threshold_df$Family) - above_threshold_df$Genus<-as.character(above_threshold_df$Genus) - above_threshold_df$Species<-as.character(above_threshold_df$Species) - above_threshold_df$OTU<-as.character(above_threshold_df$OTU) - - under_threshold_df$Kingdom<-as.character(under_threshold_df$Kingdom) - under_threshold_df$Phylum<-as.character(under_threshold_df$Phylum) - under_threshold_df$Class<-as.character(under_threshold_df$Class) - under_threshold_df$Order<-as.character(under_threshold_df$Order) - under_threshold_df$Family<-as.character(under_threshold_df$Family) - under_threshold_df$Genus<-as.character(under_threshold_df$Genus) - under_threshold_df$Species<-as.character(under_threshold_df$Species) - under_threshold_df$OTU<-as.character(under_threshold_df$OTU) - - ### Rename taxa with < percent/reads abundance, defending of the filtering applied - ### Rename taxa with < percent/reads abundance, defending of the filtering applied - #### Define the filtering tag depending of the applied filtering - ##### Relative - if (relative_or_absolute_filtering == "relative"){ - filtering_tag <-paste("Relative abund. <", filtering_value , "%") - } - ##### Absolute filtering - else if (relative_or_absolute_filtering == "absolute"){ - filtering_tag <-paste("Absolute abund. <", filtering_value, "reads") - } - - ##### No filtering - else if (relative_or_absolute_filtering == "nofiltering"){ - filtering_tag <-paste("nofiltering") - } - - - if (relative_or_absolute_filtering != "nofiltering"){ - #### Apply the filtering tag - under_threshold_df$Kingdom <-filtering_tag - under_threshold_df$Phylum <-filtering_tag - under_threshold_df$Class <-filtering_tag - under_threshold_df$Order <-filtering_tag - under_threshold_df$Family <-filtering_tag - under_threshold_df$Genus <-filtering_tag - under_threshold_df$Species <-filtering_tag - under_threshold_df$OTU <-filtering_tag - } - - - ### Join the two dataframes, to put back all rows together, the ones above threshold keeping their original taxonomic identifier while the others are now grouped together - threshod_filtered_abs <- full_join(under_threshold_df,above_threshold_df) - - ### Remove the (now but needed before) Abundance = 0 rows - threshod_filtered_abs_no_zero <- filter(threshod_filtered_abs, Abundance>0) - - ### Reorder the facet factor if later used for plotting - if (isTRUE(facet_plot)){ - threshod_filtered_abs_no_zero[[facetting_column]] <- fct_reorder(threshod_filtered_abs_no_zero[[facetting_column]], as.numeric(threshod_filtered_abs_no_zero[[sample_label]])) - } - else if (isFALSE(facet_plot)){ - print("No faceting") - } - else { - stop('"facet_plot" must be TRUE or FALSE') - } - - ### Reoder the x_label_column for later if using horizontal barplot - if (isTRUE(horizontal_plot)){ - threshod_filtered_abs_no_zero[[sample_label]] <- fct_rev(threshod_filtered_abs_no_zero[[sample_label]]) - } - else if (isFALSE(horizontal_plot)){ - print("Vertical plotting") - } - else { - stop('"horizontal_plot" must be TRUE or FALSE') - } - - threshod_filtered_abs_no_zero <- threshod_filtered_abs_no_zero - - ### Loop for unique value in grouping_column - for (i in unique(threshod_filtered_abs_no_zero[[grouping_column]])) { - print(paste("Start plotting", grouping_column, i)) - - ### filter the table for this value of the grouping columns. Depending of the used arguments, the t_neg_PCR values are kept or not on the barplots - ### Keep t_neg_PCR rows - if (isTRUE(t_neg_PCR_sample_on_plots) & !is_null(t_neg_PCR_sample_grp_filter_column_value)){ - filtered_df_abs_i <- filter(threshod_filtered_abs_no_zero, get(grouping_column) == i - | get(grouping_column) == t_neg_PCR_sample_grp_filter_column_value) - print('Keeping t_neg_PCR values for the graphs. The "t_neg_PCR_sample_grp_filter_column_value" must match the one in the grouping_column for this sample') - } - else if (isTRUE(t_neg_PCR_sample_on_plots) & is_null(t_neg_PCR_sample_grp_filter_column_value)){ - stop('If "t_neg_PCR_sample_on_plots" is "TRUE, a "t_neg_PCR_sample_grp_filter_column_value" indicating the value of the T neg PCR sample in the grouping column must be indicated') - } - - ### Do not keep t_neg_PCR rows - else if (isFALSE(t_neg_PCR_sample_on_plots)){ - filtered_df_abs_i <- filter(threshod_filtered_abs_no_zero, get(grouping_column) == i) - } - - else{ stop('"t_neg_PCR_sample_on_plots" must be TRUE or FALSE') - } - - - ### Write this table in a external file - write.table(filtered_df_abs_i, file = paste0(figures_save_dir,"/heatmaps/", plotting, "/",filtering,"/Table/", taxonomic_filtering_rank, "_",taxonomic_filtering_value,"_",filtering, "u", quantity_filtering_value, "_",grouping_column, "_", i, "_", sample_label, "_abundancy_table.tsv"), append = FALSE, sep = "\t", eol = "\n", na = "NA", dec = ".", col.names = TRUE, row.names = FALSE) - - ### No filtering - if (quantity_filtering_type != "nofiltering"){ - - ### Merged rows that were filtered in previous step so that they are only on one line on the heatmaps - g_column <- rlang::sym(grouping_column) - x_column<- rlang::sym(sample_label) - f_column <- rlang::sym(facetting_column) - t_rank <- rlang::sym(facetting_column) - filtered_OTU <- filtered_df_abs_i %>% - dplyr::filter(grepl(filtering_tag, Species)) %>% - group_by(!!(x_column), !!(g_column), !!(f_column)) %>% - summarise(Abundance = sum(Abundance)) %>% - filter(Abundance > 0) - - ### Rename "filtered" those rows representing filtered rows - filtered_OTU$Kingdom <- "Filtered" - filtered_OTU$Phylum <- "Filtered" - filtered_OTU$Class <- "Filtered" - filtered_OTU$Order <- "Filtered" - filtered_OTU$Family <- "Filtered" - filtered_OTU$Genus <- "Filtered" - filtered_OTU$Species <- "Filtered" - filtered_OTU$OTU <- "Filtered" - - ### Keep only the NOT filtered rows - individual_OTU <- filtered_df_abs_i %>% - dplyr::filter(!grepl("<", Species)) - - ### Bind the filtered and individual rows - merged_filtered_OTU <- bind_rows(filtered_OTU, individual_OTU) - - } - - if (quantity_filtering_type == "nofiltering"){ - merged_filtered_OTU <- filtered_df_abs_i - } - - ### Reorder by abundance - if (isTRUE(order_by_abundance)){ - print("Ordered by abundance") - merged_filtered_OTU$Kingdom <- factor(merged_filtered_OTU$Kingdom, levels = unique(merged_filtered_OTU$Kingdom[order(as.numeric(merged_filtered_OTU$Abundance), decreasing = TRUE)])) - merged_filtered_OTU$Phylum <- factor(merged_filtered_OTU$Phylum, levels = unique(merged_filtered_OTU$Phylum[order(as.numeric(merged_filtered_OTU$Abundance), decreasing = TRUE)])) - merged_filtered_OTU$Class <- factor(merged_filtered_OTU$Class, levels = unique(merged_filtered_OTU$Class[order(as.numeric(merged_filtered_OTU$Abundance), decreasing = TRUE)])) - merged_filtered_OTU$Order <- factor(merged_filtered_OTU$Order, levels = unique(merged_filtered_OTU$Order[order(as.numeric(merged_filtered_OTU$Abundance), decreasing = TRUE)])) - merged_filtered_OTU$Family <- factor(merged_filtered_OTU$Family, levels = unique(merged_filtered_OTU$Family[order(as.numeric(merged_filtered_OTU$Abundance), decreasing = TRUE)])) - merged_filtered_OTU$Genus <- factor(merged_filtered_OTU$Genus, levels = unique(merged_filtered_OTU$Genus[order(as.numeric(merged_filtered_OTU$Abundance), decreasing = TRUE)])) - merged_filtered_OTU$Species <- factor(merged_filtered_OTU$Species, levels = unique(merged_filtered_OTU$Species[order(as.numeric(merged_filtered_OTU$Abundance), decreasing = TRUE)])) - merged_filtered_OTU$OTU <- factor(merged_filtered_OTU$OTU, levels = unique(merged_filtered_OTU$OTU[order(as.numeric(merged_filtered_OTU$Abundance), decreasing = TRUE)])) - } - - else if (isFALSE(order_by_abundance)){ - ### Set filtered at the top on the heatmap and the rest on order - - merged_filtered_OTU$Kingdom <- fct_rev(fct_relevel(merged_filtered_OTU$Kingdom, "Filtered", after = 0)) - merged_filtered_OTU$Phylum <- fct_rev(fct_relevel(merged_filtered_OTU$Phylum, "Filtered", after = 0)) - merged_filtered_OTU$Class <- fct_rev(fct_relevel(merged_filtered_OTU$Class, "Filtered", after = 0)) - merged_filtered_OTU$Order <- fct_rev(fct_relevel(merged_filtered_OTU$Order, "Filtered", after = 0)) - merged_filtered_OTU$Family <- fct_rev(fct_relevel(merged_filtered_OTU$Family, "Filtered", after = 0)) - merged_filtered_OTU$Genus <- fct_rev(fct_relevel(merged_filtered_OTU$Genus, "Filtered", after = 0)) - merged_filtered_OTU$Species <- fct_rev(fct_relevel(merged_filtered_OTU$Species, "Filtered", after = 0)) - merged_filtered_OTU$OTU <- fct_rev(fct_relevel(merged_filtered_OTU$OTU, "Filtered", after = 0)) - - } - - - ### Create a loop for all taxonomic ranks to be plotted - - if (plotting_tax_ranks == "all"){ - tax_ranks <- c("Kingdom", "Phylum", "Class", "Order", "Family", "Genus", "Species", "OTU") - - }else{ - tax_ranks <- plotting_tax_ranks - print('Plotting at a specific taxonomic rank. Check spelling one or several of ("Kingdom", "Phylum", "Class", "Order", "Family", "Genus", "Species", "OTU"). This error does NOT always mean that there was an error in spelling') - } - - for (t in tax_ranks){ - - to_melt_df <- merged_filtered_OTU - - g_column <- rlang::sym(grouping_column) - x_column<- rlang::sym(sample_label) - f_column <- rlang::sym(facetting_column) - taxa_column <- rlang::sym(t) - merged_taxa_df <- to_melt_df %>% - dplyr::group_by(!!(x_column), !!(g_column), !!(f_column), !!(taxa_column)) %>% - dplyr::mutate(Abundance = sum(Abundance)) %>% - dplyr::ungroup() %>% - dplyr::distinct(!!(x_column), !!(taxa_column), .keep_all = TRUE) - - - ### Renames the values in the vector of plotted used taxrank with their related OTU name to keep them matching. Without this step, the labels do NOT match the rows - taxalabel <- as(merged_taxa_df[[t]], "character") - names(taxalabel) <- merged_taxa_df[[t]] - - ### Generate heatmap - heatmap <- ggplot(merged_taxa_df, aes(x = get(sample_label), y = get(t), fill = Abundance)) + - theme_bw() + - geom_tile(aes(fill=Abundance), show.legend=TRUE) + - scale_fill_gradient(na.value = "white", low="#000033", high="#CCFF66") + - geom_text(aes(label = round(Abundance, digits = 1), color = Abundance > 15), size = 2 ) + - scale_color_manual(guide = FALSE, values = c("white", "black")) + - theme(axis.text.x = element_text(angle = -90, vjust = 0.5, hjust = 0)) + - scale_y_discrete(labels = taxalabel) + - labs(x= sample_label, y = t) - - - - ### Turn heatmap horizontally - if (isTRUE(horizontal_plot)){ - ### Reverse the order of the samples - heatmap <- heatmap + coord_flip() - } - - ### Create facet view - if (isTRUE(facet_plot)){ - heatmap <- heatmap + facet_grid(scales = "free", space = "free", cols = vars(get(facetting_column))) - } - - - - ### Save it - ### Set the filename - filename_base <- (paste0(figures_save_dir,"/heatmaps/", plotting, "/",filtering,"/", taxonomic_filtering_rank, "_",taxonomic_filtering_value,"_",filtering, "u", quantity_filtering_value, "_",grouping_column, "_", i, "_", sample_label, "_",facetting_column,"_" ,t)) - ### Print the filename to follow progress - print(filename_base) - ###Save the figure - ggsave(heatmap, filename = paste0(filename_base, "_heatmap.svg"), width = 4.5, height = 7) - - - - - }}} - diff --git a/ressources/r_scripts/visualization/3_Heatmaps/sequence_variants_heatmap_fct.R b/ressources/r_scripts/visualization/3_Heatmaps/sequence_variants_heatmap_fct.R deleted file mode 100755 index 247b99d9..00000000 --- a/ressources/r_scripts/visualization/3_Heatmaps/sequence_variants_heatmap_fct.R +++ /dev/null @@ -1,436 +0,0 @@ -### Create a function -sequence_variants_heatmap_fct <- function(melted_dataframe, sample_label, grouping_column, grouping_column_filtering = c(FALSE, TRUE), grouping_column_filtering_value, t_neg_PCR_sample_on_plots, t_neg_PCR_sample_grp_filter_column_value, taxonomic_filtering = c(TRUE, FALSE), taxonomic_filtering_rank = "Kingdom" , taxonomic_filtering_value = "Bacteria" , quantity_filtering_type = c("relative", "absolute", "rank", "nofiltering", "absolute_and_rank"), absolute_quantity_filtering_value, relative_quantity_filtering_value, rank_quantity_filtering_value, plotting_value = c("relative", "absolute"), plotting_tax_ranks = "all", figures_save_dir, horizontal_plot = FALSE, facet_plot = FALSE, facetting_column, order_by_abundance = TRUE){ - - - ### In option, filter for a given grouping_columns. In both cases keep only lines with Abundance not equal to 0 to reduce the dataframe size - - ### No filter for the grouping column - if (isFALSE(grouping_column_filtering)){ - melted_dataframe_filtered <- melted_dataframe - grouping_column_filtering_value <- "no_group_column_filtering" - - print("No filtering of the grouping_column") - } - - ### Filter for the grouping column - else if (isTRUE(grouping_column_filtering)){ - melted_dataframe_filtered <- melted_dataframe %>% - filter(get(grouping_column) == grouping_column_filtering_value) - - print(paste("Data filtered for", grouping_column, "is", grouping_column_filtering_value)) - - }else{ stop("grouping_column_filtering must be TRUE or FALSE")} - - - ### In option, filter at a given taxonomic rank and for a given taxonomic identifier - - ### No filter - if (isFALSE(taxonomic_filtering)) { - physeq_subset_df <- melted_dataframe_filtered - physeq_subet_norm_df <- physeq_subset_df %>% - ungroup %>% - dplyr::group_by(Sample) %>% - dplyr::mutate(per=as.numeric(100*Abundance/sum(Abundance))) %>% - dplyr::ungroup() %>% - dplyr::select(-Abundance) %>% - dplyr::rename(Abundance = per) - taxonomic_filtering_value <- "no_tax_rank_filtering" - - print("No filtering of any taxonomic rank") - } - - ### A specific filter - else if (isTRUE(taxonomic_filtering)){ - physeq_subset_df <- melted_dataframe_filtered %>% - filter(get(taxonomic_filtering_rank) == taxonomic_filtering_value) - physeq_subet_norm_df <- physeq_subset_df %>% - group_by(Sample) %>% - mutate(per=paste0((100*Abundance/sum(Abundance)))) %>% - ungroup %>% - select(-Abundance) %>% - dplyr::rename(Abundance = per) - physeq_subet_norm_df$Abundance <- as.numeric(physeq_subet_norm_df$Abundance) - - print(paste("Data filtered for", taxonomic_filtering_rank, "is", taxonomic_filtering_value)) - - }else{ stop('"taxonomic_filtering" must be TRUE or FALSE') - } - - - ### Choose between relative value or absolute value dataframe for plotting value - - ### Relative value plotting - if (plotting_value == "relative"){ - plotting <- "Relative" - print("Relative value plotting") - plotted_df <- physeq_subet_norm_df - } - - # Absolute value plotting - else if (plotting_value == "absolute"){ - plotting <- "Absolute" - print("Absolute value plotting") - plotted_df <- physeq_subset_df - - }else{ stop('"relative_value_plotting" must be "relative" or "absolute"') - } - - - ### Filter values at a relative or absolute quantity threshold or at a given rank in a group ( x highest OTU in the group) - - ### No filtering - if (quantity_filtering_type == "nofiltering"){ - filtering <- "Nofiltering" - quantity_filtering_value <- "0" - print("No filtering based on quantity") - physeq_subset_df_filtered <- physeq_subet_norm_df - } - - - ### Relative value filtering - else if (quantity_filtering_type == "relative"){ - filtering <- "Relative" - quantity_filtering_value <- relative_quantity_filtering_value - print("Relative value based filtering") - physeq_subset_df_filtered <- physeq_subet_norm_df %>% filter(Abundance > quantity_filtering_value) - } - - ### Absolute value filtering - else if (quantity_filtering_type == "absolute"){ - filtering <- "Absolute" - quantity_filtering_value <- absolute_quantity_filtering_value - print("Absolute value based filtering") - physeq_subset_df_filtered <- physeq_subset_df %>% filter(Abundance > quantity_filtering_value) - } - - ### Abundance rank of the taxa in the group filtering - else if (quantity_filtering_type == "rank"){ - filtering <- "Rank" - print("Rank based filtering") - quantity_filtering_value <- rank_quantity_filtering_value - ### Define the variables as dplyr wants them - g_column <- rlang::sym(grouping_column) - x_column <- rlang::sym(sample_label) - ### Generate the filtered dataframe - physeq_subset_df_filtered <- physeq_subset_df %>% - group_by(!! g_column) %>% ## group the dataframe by the grouping column - mutate(per=as.numeric(paste0((100*Abundance/sum(Abundance))))) %>% ## calculate a relative abundance of the taxa in the group - ungroup %>% - group_by((!! g_column), OTU) %>% ## now group the OTU in each grouping columns - mutate(sumper = as.numeric(paste0(sum(per)))) %>% ## Sum the relative abundance of each OTU in each group - ungroup %>% - group_by(!!! list(g_column, x_column)) %>% ## Group the dataframe for each by group and filling column - top_n(n = quantity_filtering_value*n_distinct(sample_label), wt = sumper) %>% ## Keep the x number of rows for each - ungroup - } - - - ### Combined absolute and rank of the taxa in the group filtering - else if (quantity_filtering_type == "absolute_and_rank"){ - filtering <- "Absolute_and_Rank" - print("Absolute_and_Rank based filtering") - ### Define the variables as dplyr wants them - g_column <- rlang::sym(grouping_column) - x_column<- rlang::sym(sample_label) - ### Generate the filtered dataframe - ### Relative reads filtering - physeq_subset_df_filtered <- physeq_subset_df %>% - group_by(!! g_column) %>% ## group the dataframe by the grouping column - mutate(per=as.numeric(paste0((100*Abundance/sum(Abundance))))) %>% ## calculate a relative abundance of the taxa in the group - ungroup %>% - group_by((!! g_column), OTU) %>% ## now group the OTU in each grouping columns - mutate(sumper = as.numeric(paste0(sum(per)))) %>% ## Sum the relative abundance of each OTU in each group - ungroup %>% - group_by(!!! list(g_column, x_column)) %>% ## Group the dataframe for each by group and filling column - top_n(n = rank_quantity_filtering_value*n_distinct(sample_label), wt = sumper) %>% ## Keep the x number of rows for each - ungroup - - - ### Absolute reads filtering - physeq_subset_df_filtered <- physeq_subset_df_filtered %>% filter(Abundance > absolute_quantity_filtering_value) - - ### Write "quantity_filtering_value" for ggsave later - quantity_filtering_value <- paste0("rank_", rank_quantity_filtering_value, "_absolute_", absolute_quantity_filtering_value) - - }else{ stop('quantity_filtering_type must be "nofiltering", "relative", "absolute", "rank" or "absolute_and_rank"') - } - - - ### Now we have a dataframe ("plotted_df") containing all values, with Abundance expressed in absolute reads number or relative quanity in %, and a dataframe containing the rows that have to be kept while the rest will be merged by tagging them with the same name - - - ### In a dataframe, keep only the rows matching the filtered rowmanes - above_threshold_df <- semi_join(plotted_df, physeq_subset_df_filtered, by = c("OTU", "Sample")) - - - ### In another dataframe, keep only the lines NOT matching the filtered rowmanes - under_threshold_df <- anti_join(plotted_df, physeq_subset_df_filtered, by =c ("OTU", "Sample")) - - - ### To follow and control the process, write external .tsv tables - ### Write the filtration table, without Abundance = 0 rows to reduce its size. - physeq_subset_df_filtered %>% - filter(Abundance>0) %>% - write.table(file = paste0(figures_save_dir,"/heatmaps/", plotting, "/",filtering,"/Table/", taxonomic_filtering_rank, "_",taxonomic_filtering_value,"_",filtering, "u", quantity_filtering_value, "_all_", sample_label, "_filtration_table.tsv"), append = FALSE, sep = "\t", eol = "\n", na = "NA", dec = ".", col.names = TRUE, row.names = FALSE) - - ### Write the plotted table , without Abundance = 0 rows to reduce its size. - plotted_df %>% - filter(Abundance>0) %>% - write.table(file = paste0(figures_save_dir,"/heatmaps/", plotting, "/",filtering,"/Table/", taxonomic_filtering_rank, "_",taxonomic_filtering_value,"_",filtering, "u", quantity_filtering_value, "_all_", sample_label, "_plotted_table.tsv"), append = FALSE, sep = "\t", eol = "\n", na = "NA", dec = ".", col.names = TRUE, row.names = FALSE) - - ### In another dataframe, keep the join of the under and above tables, before masking of the filtered taxa and without Abundance = 0 rows to reduce its size. - merged_filtered_abs <- full_join(under_threshold_df,above_threshold_df) %>% - filter(Abundance>0) - write.table(merged_filtered_abs, file = paste0(figures_save_dir,"/heatmaps/", plotting, "/",filtering,"/Table/", taxonomic_filtering_rank, "_",taxonomic_filtering_value,"_",filtering, "u", quantity_filtering_value, "_all_", sample_label, "_merged_without_masking.tsv"), append = FALSE, sep = "\t", eol = "\n", na = "NA", dec = ".", col.names = TRUE, row.names = FALSE) - - - - ### Convert taxonomic all levels as character (needed for the next steps) - above_threshold_df$Kingdom<-as.character(above_threshold_df$Kingdom) - above_threshold_df$Phylum<-as.character(above_threshold_df$Phylum) - above_threshold_df$Class<-as.character(above_threshold_df$Class) - above_threshold_df$Order<-as.character(above_threshold_df$Order) - above_threshold_df$Family<-as.character(above_threshold_df$Family) - above_threshold_df$Genus<-as.character(above_threshold_df$Genus) - above_threshold_df$Species<-as.character(above_threshold_df$Species) - above_threshold_df$OTU<-as.character(above_threshold_df$OTU) - - under_threshold_df$Kingdom<-as.character(under_threshold_df$Kingdom) - under_threshold_df$Phylum<-as.character(under_threshold_df$Phylum) - under_threshold_df$Class<-as.character(under_threshold_df$Class) - under_threshold_df$Order<-as.character(under_threshold_df$Order) - under_threshold_df$Family<-as.character(under_threshold_df$Family) - under_threshold_df$Genus<-as.character(under_threshold_df$Genus) - under_threshold_df$Species<-as.character(under_threshold_df$Species) - under_threshold_df$OTU<-as.character(under_threshold_df$OTU) - - ### Rename taxa with < percent/reads abundance, defending of the filtering applied - ### Relative filtering - if (quantity_filtering_type == "relative"){ - under_threshold_df$Kingdom <-paste("<_", quantity_filtering_value,"%_abund") - under_threshold_df$Phylum <-paste("<_", quantity_filtering_value,"%_abund") - under_threshold_df$Class <-paste("<_", quantity_filtering_value,"%_abund") - under_threshold_df$Order <-paste("<_", quantity_filtering_value,"%_abund") - under_threshold_df$Family <-paste("<_", quantity_filtering_value,"%_abund") - under_threshold_df$Genus <-paste("<_", quantity_filtering_value,"%_abund") - under_threshold_df$Species <-paste("<_", quantity_filtering_value,"%_abund") - } - - ### Absolute filtering - else if (quantity_filtering_type == "absolute"){ - under_threshold_df$Kingdom <-paste("<_", quantity_filtering_value,"reads") - under_threshold_df$Phylum <-paste("<_", quantity_filtering_value,"reads") - under_threshold_df$Class <-paste("<_", quantity_filtering_value,"reads") - under_threshold_df$Order <-paste("<_", quantity_filtering_value,"reads") - under_threshold_df$Family <-paste("<_", quantity_filtering_value,"reads") - under_threshold_df$Genus <-paste("<_", quantity_filtering_value,"reads") - under_threshold_df$Species <-paste("<_", quantity_filtering_value,"reads") - } - - ### Abundance rank of the taxa in the group filtering - else if (quantity_filtering_type == "rank"){ - under_threshold_df$Kingdom <-paste("<_", quantity_filtering_value,"rank") - under_threshold_df$Phylum <-paste("<_", quantity_filtering_value,"rank") - under_threshold_df$Class <-paste("<_", quantity_filtering_value,"rank") - under_threshold_df$Order <-paste("<_", quantity_filtering_value,"rank") - under_threshold_df$Family <-paste("<_", quantity_filtering_value,"rank") - under_threshold_df$Genus <-paste("<_", quantity_filtering_value,"rank") - under_threshold_df$Species <-paste("<_", quantity_filtering_value,"rank") - } - - ### Absolute abundance AND rank of the taxa in the group filtering - else if (quantity_filtering_type == "absolute_and_rank"){ - under_threshold_df$Kingdom <-paste("<_", quantity_filtering_value) - under_threshold_df$Phylum <-paste("<_", quantity_filtering_value) - under_threshold_df$Class <-paste("<_", quantity_filtering_value) - under_threshold_df$Order <-paste("<_", quantity_filtering_value) - under_threshold_df$Family <-paste("<_", quantity_filtering_value) - under_threshold_df$Genus <-paste("<_", quantity_filtering_value) - under_threshold_df$Species <-paste("<_", quantity_filtering_value) } - - - ### Join the two dataframes, to put back all rows together, the ones above threshold keeping their original taxonomic identifier while the others are now grouped together - threshod_filtered_abs <- full_join(under_threshold_df,above_threshold_df) - - ### Remove the (now but needed before) Abundance = 0 rows - threshod_filtered_abs_no_zero <- filter(threshod_filtered_abs, Abundance>0) - - ### Reorder the facet factor if later used for plotting - if (isTRUE(facet_plot)){ - threshod_filtered_abs_no_zero[[facetting_column]] <- fct_reorder(threshod_filtered_abs_no_zero[[facetting_column]], as.numeric(threshod_filtered_abs_no_zero[[sample_label]])) - } - else if (isFALSE(facet_plot)){ - print("No faceting") - } - else { - stop('"facet_plot" must be TRUE or FALSE') - } - - ### Reoder the x_label_column for later if using horizontal barplot - if (isTRUE(horizontal_plot)){ - threshod_filtered_abs_no_zero[[sample_label]] <- fct_rev(threshod_filtered_abs_no_zero[[sample_label]]) - } - else if (isFALSE(horizontal_plot)){ - print("Vertical plotting") - } - else { - stop('"horizontal_plot" must be TRUE or FALSE') - } - - threshod_filtered_abs_no_zero <- threshod_filtered_abs_no_zero - - ### Loop for unique value in grouping_column - for (i in unique(threshod_filtered_abs_no_zero[[grouping_column]])) { - print(paste("Start plotting", grouping_column, i)) - - ### filter the table for this value of the grouping columns. Depending of the used arguments, the t_neg_PCR values are kept or not on the barplots - ### Keep t_neg_PCR rows - if (isTRUE(t_neg_PCR_sample_on_plots) & !is_null(t_neg_PCR_sample_grp_filter_column_value)){ - filtered_df_abs_i <- filter(threshod_filtered_abs_no_zero, get(grouping_column) == i - | get(grouping_column) == t_neg_PCR_sample_grp_filter_column_value) - print('Keeping t_neg_PCR values for the graphs. The "t_neg_PCR_sample_grp_filter_column_value" must match the one in the grouping_column for this sample') - } - else if (isTRUE(t_neg_PCR_sample_on_plots) & is_null(t_neg_PCR_sample_grp_filter_column_value)){ - stop('If "t_neg_PCR_sample_on_plots" is "TRUE, a "t_neg_PCR_sample_grp_filter_column_value" indicating the value of the T neg PCR sample in the grouping column must be indicated') - } - - ### Do not keep t_neg_PCR rows - else if (isFALSE(t_neg_PCR_sample_on_plots)){ - filtered_df_abs_i <- filter(threshod_filtered_abs_no_zero, get(grouping_column) == i) - } - - else{ stop('"t_neg_PCR_sample_on_plots" must be TRUE or FALSE') - } - - - filtered_df_abs_i <-filtered_df_abs_i - - ### Write this table in a external file - write.table(filtered_df_abs_i, file = paste0(figures_save_dir,"/heatmaps/", plotting, "/",filtering,"/Table/", taxonomic_filtering_rank, "_",taxonomic_filtering_value,"_",filtering, "u", quantity_filtering_value, "_",grouping_column, "_", i, "_", sample_label, "_abundancy_table.tsv"), append = FALSE, sep = "\t", eol = "\n", na = "NA", dec = ".", col.names = TRUE, row.names = FALSE) - - ### No filtering - if (quantity_filtering_type != "nofiltering"){ - - ### Merged rows that were filtered in previous step so that they are only on one line on the heatmaps - g_column <- rlang::sym(grouping_column) - x_column<- rlang::sym(sample_label) - f_column <- rlang::sym(facetting_column) - filtered_OTU <- filtered_df_abs_i %>% - dplyr::filter(grepl("<", Species)) %>% - group_by(Sample, !!(x_column), !!(g_column), !!(f_column), OTU) %>% - summarise(Abundance = sum(Abundance)) %>% - filter(Abundance > 0) - - ### Rename "filtered" those rows representing filtered rows - filtered_OTU$Kingdom <- "Filtered" - filtered_OTU$Phylum <- "Filtered" - filtered_OTU$Class <- "Filtered" - filtered_OTU$Order <- "Filtered" - filtered_OTU$Family <- "Filtered" - filtered_OTU$Genus <- "Filtered" - filtered_OTU$Species <- "Filtered" - filtered_OTU$OTU <- "Filtered" - - ### Keep only the NOT filtered rows - individual_OTU <- filtered_df_abs_i %>% - dplyr::filter(!grepl("<", Species)) - - ### Bind the filtered and individual rows - merged_filtered_OTU <- bind_rows(filtered_OTU, individual_OTU) - - } - - - if (quantity_filtering_type == "nofiltering"){ - merged_filtered_OTU <- filtered_df_abs_i - - } - - ### Reorder by abundance - if (isTRUE(order_by_abundance)){ - print("Ordered by abundance") - merged_filtered_OTU$Kingdom <- factor(merged_filtered_OTU$Kingdom, levels = unique(merged_filtered_OTU$Kingdom[order(as.numeric(merged_filtered_OTU$Abundance), decreasing = TRUE)])) - merged_filtered_OTU$Phylum <- factor(merged_filtered_OTU$Phylum, levels = unique(merged_filtered_OTU$Phylum[order(as.numeric(merged_filtered_OTU$Abundance), decreasing = TRUE)])) - merged_filtered_OTU$Class <- factor(merged_filtered_OTU$Class, levels = unique(merged_filtered_OTU$Class[order(as.numeric(merged_filtered_OTU$Abundance), decreasing = TRUE)])) - merged_filtered_OTU$Order <- factor(merged_filtered_OTU$Order, levels = unique(merged_filtered_OTU$Order[order(as.numeric(merged_filtered_OTU$Abundance), decreasing = TRUE)])) - merged_filtered_OTU$Family <- factor(merged_filtered_OTU$Family, levels = unique(merged_filtered_OTU$Family[order(as.numeric(merged_filtered_OTU$Abundance), decreasing = TRUE)])) - merged_filtered_OTU$Genus <- factor(merged_filtered_OTU$Genus, levels = unique(merged_filtered_OTU$Genus[order(as.numeric(merged_filtered_OTU$Abundance), decreasing = TRUE)])) - merged_filtered_OTU$Species <- factor(merged_filtered_OTU$Species, levels = unique(merged_filtered_OTU$Species[order(as.numeric(merged_filtered_OTU$Abundance), decreasing = TRUE)])) - merged_filtered_OTU$OTU <- factor(merged_filtered_OTU$OTU, levels = unique(merged_filtered_OTU$OTU[order(as.numeric(merged_filtered_OTU$Abundance), decreasing = TRUE)])) - } - - else if (isFALSE(order_by_abundance)){ - ### Set filtered at the top on the heatmap and the rest on order - - merged_filtered_OTU$Kingdom <- fct_rev(fct_relevel(merged_filtered_OTU$Kingdom, "Filtered", after = 0)) - merged_filtered_OTU$Phylum <- fct_rev(fct_relevel(merged_filtered_OTU$Phylum, "Filtered", after = 0)) - merged_filtered_OTU$Class <- fct_rev(fct_relevel(merged_filtered_OTU$Class, "Filtered", after = 0)) - merged_filtered_OTU$Order <- fct_rev(fct_relevel(merged_filtered_OTU$Order, "Filtered", after = 0)) - merged_filtered_OTU$Family <- fct_rev(fct_relevel(merged_filtered_OTU$Family, "Filtered", after = 0)) - merged_filtered_OTU$Genus <- fct_rev(fct_relevel(merged_filtered_OTU$Genus, "Filtered", after = 0)) - merged_filtered_OTU$Species <- fct_rev(fct_relevel(merged_filtered_OTU$Species, "Filtered", after = 0)) - merged_filtered_OTU$OTU <- fct_rev(fct_relevel(merged_filtered_OTU$OTU, "Filtered", after = 0)) - - } - - - - ### Create a loop for all taxonomic ranks to be plotted - - if (plotting_tax_ranks == "all"){ - tax_ranks <- c("Kingdom", "Phylum", "Class", "Order", "Family", "Genus", "Species", "OTU") - - }else{ - tax_ranks <- plotting_tax_ranks - print('Plotting at a specific taxonomic rank. Check spelling one or several of ("Kingdom", "Phylum", "Class", "Order", "Family", "Genus", "Species", "OTU"). This error does NOT always mean that there was an error in spelling') - } - - for (t in tax_ranks){ - - merged_taxa_df <- merged_filtered_OTU - - - ### Renames the values in the vector of plotted used taxrank with their related OTU name to keep them matching. Without this step, the labels do NOT match the rows - taxalabel <- as(merged_taxa_df[[t]], "character") - names(taxalabel) <- merged_taxa_df[["OTU"]] - - ### Generate heatmap - heatmap <- ggplot(merged_taxa_df, aes(x = get(sample_label), y = OTU, fill = Abundance)) + - theme_bw() + - geom_tile(aes(fill=Abundance), show.legend=TRUE) + - scale_fill_gradient(na.value = "white", low="#000033", high="#CCFF66") + - geom_text(aes(label = round(Abundance, digits = 1), color = Abundance > mean(Abundance)), size = 2 ) + - scale_color_manual(guide = FALSE, values = c("white", "black")) + - theme(axis.text.x = element_text(size = 5, angle = -90, vjust = 0.5, hjust = 0)) + - theme(axis.text.y = element_text(size = 5)) + - scale_y_discrete(labels = taxalabel) + - labs(x= sample_label, y = t) - - - - ### Turn heatmap horizontally - if (isTRUE(horizontal_plot)){ - ### Reverse the order of the samples - heatmap <- heatmap + coord_flip() - } - - ### Create facet view - if (isTRUE(facet_plot)){ - heatmap <- heatmap + facet_grid(scales = "free", space = "free", cols = vars(get(facetting_column))) - } - - - - ### Save it - ### Set the filename - filename_base <- (paste0(figures_save_dir,"/heatmaps/", plotting, "/",filtering,"/", taxonomic_filtering_rank, "_",taxonomic_filtering_value,"_",filtering, "u", quantity_filtering_value, "_",grouping_column, "_", i, "_", sample_label, "_",facetting_column,"_" ,t)) - ### Print the filename to follow progress - print(filename_base) - ###Save the figure - ggsave(heatmap, filename = paste0(filename_base, "_heatmap.png"), width = 10, height = 7) - - - - - }}} - diff --git a/ressources/r_scripts/visualization/4_Diversity/modif_plot_richness_fct.R b/ressources/r_scripts/visualization/4_Diversity/modif_plot_richness_fct.R deleted file mode 100755 index a243a27f..00000000 --- a/ressources/r_scripts/visualization/4_Diversity/modif_plot_richness_fct.R +++ /dev/null @@ -1,115 +0,0 @@ - -### Create the function, from "plot_richness" of phyloseq but with modifications to allows facetting by values but also for a factor in data -modif_plot_richness_fct <- function (physeq, x = "samples", color = NULL, shape = NULL, - title = NULL, scales = "free_y", nrow = NULL, shsi = NULL, measures = NULL, - sortby = NULL, facetting_column = NULL, r_figures , boxplot = TRUE, jitter = TRUE) -{ - erDF = estimate_richness(physeq, split = TRUE, measures = measures) - measures = colnames(erDF) - ses = colnames(erDF)[grep("^se\\.", colnames(erDF))] - measures = measures[!measures %in% ses] - if (!is.null(sample_data(physeq, errorIfNULL = FALSE))) { - DF <- data.frame(erDF, sample_data(physeq)) - } - else { - DF <- data.frame(erDF) - } - if (!"samples" %in% colnames(DF)) { - DF$samples <- sample_names(physeq) - } - if (!is.null(x)) { - if (x %in% c("sample", "samples", "sample_names", "sample.names")) { - x <- "samples" - } - } - else { - x <- "samples" - } - mdf = reshape2::melt(DF, measure.vars = measures) - mdf$se <- NA_integer_ - if (length(ses) > 0) { - selabs = ses - names(selabs) <- substr(selabs, 4, 100) - substr(names(selabs), 1, 1) <- toupper(substr(names(selabs), - 1, 1)) - mdf$wse <- sapply(as.character(mdf$variable), function(i, - selabs) { - selabs[i] - }, selabs) - for (i in 1:nrow(mdf)) { - if (!is.na(mdf[i, "wse"])) { - mdf[i, "se"] <- mdf[i, (mdf[i, "wse"])] - } - } - mdf <- mdf[, -which(colnames(mdf) %in% c(selabs, "wse"))] - } - if (!is.null(measures)) { - if (any(measures %in% as.character(mdf$variable))) { - mdf <- mdf[as.character(mdf$variable) %in% measures, - ] - } - else { - warning("Argument to `measures` not supported. All alpha-diversity measures (should be) included in plot.") - } - } - if (!is.null(shsi)) { - warning("shsi no longer supported option in plot_richness. Please use `measures` instead") - } - if (!is.null(sortby)) { - if (!all(sortby %in% levels(mdf$variable))) { - warning("`sortby` argument not among `measures`. Ignored.") - } - if (!is.discrete(mdf[, x])) { - warning("`sortby` argument provided, but `x` not a discrete variable. `sortby` is ignored.") - } - if (all(sortby %in% levels(mdf$variable)) & is.discrete(mdf[, - x])) { - wh.sortby = which(mdf$variable %in% sortby) - mdf[, x] <- factor(mdf[, x], levels = names(sort(tapply(X = mdf[wh.sortby, - "value"], INDEX = mdf[wh.sortby, x], mean, na.rm = TRUE, - simplify = TRUE)))) - } - } - richness_map <<- aes_string(x = x, y = "value", colour = color, - shape = shape) - mdf <<- mdf - - p <- ggplot(mdf, richness_map) + geom_point(na.rm = TRUE) - if (any(!is.na(mdf[, "se"]))) { - p = p + geom_errorbar(aes(ymax = value + se, ymin = value - - se), width = 0.1) - } - - if (isTRUE(boxplot)) { - p = p + geom_boxplot() - } - if (isTRUE(jitter)) { - p = p + geom_boxplot() + geom_jitter(width = 0.1) - } - - - p = p + theme(axis.text.x = element_text(angle = -90, vjust = 0.5, - hjust = 0)) - p = p + ylab("Alpha Diversity Measure") - - if (!is.null(facetting_column)){ - p = p + facet_grid(cols = vars(get(facetting_column)), rows = (vars(variable)), scales = scales) - } - else if (is.null(facetting_column)){ - p = p + facet_grid(rows = (vars(variable)), scales = scales) - } - - if (!is.null(title)) { - p = p + ggtitle(title) - - - } - ggsave(p, filename = paste0(r_figures ,"/Diversity/overall_richness.png"), width = 5, height = 10) - return(p) - -} - - - - - diff --git a/ressources/r_scripts/visualization/app.R b/ressources/r_scripts/visualization/app.R deleted file mode 100644 index e125aa69..00000000 --- a/ressources/r_scripts/visualization/app.R +++ /dev/null @@ -1,1125 +0,0 @@ -# Load packages -## Shiny packages -library(shiny) -library(shinythemes) -library(colourpicker) - -## Specific packages -library(tidyr); packageVersion("tidyr") -library(dplyr); packageVersion("dplyr") -library(pheatmap); packageVersion("pheatmap") -library(ggplot2); packageVersion("ggplot2") -library(RColorBrewer); packageVersion("RColorBrewer") -library(randomcoloR); packageVersion("randomcoloR") -library(data.table); packageVersion("data.table") -library(forcats); packageVersion("forcats") -library(rlang); packageVersion("rlang") -library(ggpubr); packageVersion("ggpubr") -library(cowplot); packageVersion("cowplot") -library(vegan); packageVersion("vegan") -library(readxl); packageVersion("readxl") - -# Create sets of functions -## Filter metadata to exclude samples -filter_metadata_fct <- function(metadata_table, filter_out, filtering_column, filter_out_value){ - - - if (isTRUE(filter_out)){ - metadata_table_filtered <- metadata_table %>% dplyr::filter(!(!! rlang::sym(filtering_column) %in% filter_out_value)) - } else { - metadata_table_filtered <- metadata_table - } - - metadata_table_filtered <- droplevels(metadata_table_filtered) - - return(metadata_table_filtered) - -} - -## Add colors to metadata for alpha diversity and NMDS -add_colors_fct <- function(metadata_table, alp_NMDS_color, alp_NMDS_palette){ - - - #### Add Brewer colors for each value in the color columns to prepare for alpha-diversity and NMDS plots - getPalette <- colorRampPalette(brewer.pal(n=12, alp_NMDS_palette)) # Create a function to generate a palette of colors - colors_palette <- getPalette(length(unique(metadata_table[[alp_NMDS_color]]))) # Call this function for each unique value of the column of interest - names(colors_palette) <- unique(metadata_table[[alp_NMDS_color]]) # Name colors by the value in the column - m <- match(metadata_table[[alp_NMDS_color]], names(colors_palette)) # match the names of the colors with the values - metadata_table$NMDS_alpha_colors <- colors_palette[m] # Fill the table with the colors for each value - - return(metadata_table) - -} - -## Prepare a melted long table from OTU, taxonomy and metadata table -filter_OTU_count_fct <- function(count_table, metadata_table, taxonomy_table, grouping_column, x_axis_column, sort_column, facetting_column, relative_or_absolute_filtering = c("relative", "absolute", "nofiltering"), filter_value, plotting_tax_ranks, distinct_colors, abundance_filtre_level = c("Sample", "Group")){ - - - ## Reformat and join count table, metadata and taxonomy - ### Transform count table to long format - count_table[["ASV"]] <- row.names(count_table) - count_table_long <- tidyr::gather(count_table, key = "Sample", value = "Count", -one_of("ASV")) - - ### Reformat taxonomy - taxonomy_table_split <- taxonomy_table %>% - tidyr::separate(V2, sep=";", c("Kingdom","Phylum","Class","Order","Family","Genus","Species", "Confidence")) - colnames(taxonomy_table_split)[1] <- "ASV" - - ### Add taxonomy to long count table - count_table_long_tax <- dplyr::left_join(count_table_long, taxonomy_table_split, by = "ASV") - - ## Filter taxa based on quantities - ### Set a list with all ranks from Kingdom to the plotted rank. Used to then be able to concatenate names - all_rank <- c("Kingdom","Phylum","Class","Order","Family","Genus","Species", "ASV") - ranks <- all_rank[1:match(plotting_tax_ranks, all_rank)] - - ### Absolute value filtering - if (relative_or_absolute_filtering == "absolute"){ - ## Define a tag to fill the taxonomy of low-abundance taxa - filtering_tag <- paste0("<", filter_value, "reads") - - count_table_long_tax_sum <- count_table_long_tax %>% - dplyr::group_by_at(c("Sample", ranks)) %>% - dplyr::summarise(Count = sum(Count)) %>% # Regroup counts for identical taxa within each sample - dplyr::mutate_at(.vars = vars(ranks), .funs = list(newtax = ~ dplyr::if_else(condition = (filter_value < Count), true = unique(.), false = filtering_tag))) %>% # Each taxa represented less counts that the cut-off are replated by the tag - dplyr::ungroup() %>% - dplyr::select(-one_of(ranks)) %>% # remove the orginal taxonomic columns - dplyr::rename_at(.vars = vars(ends_with("_newtax")), - .funs = ~sub(x = ., pattern = "_newtax$", replacement = "")) %>% # Rename the columns just created by the mutate function to the original names - dplyr::group_by_at(c("Sample", ranks)) %>% - dplyr::summarise(Count = sum(Count)) %>% # Regroup the identical taxa per sample (the filtered ones) - dplyr::ungroup() - - print("filtered") - - ### Relative value filtering - } else if (relative_or_absolute_filtering == "relative"){ - ## Define a tag to fill the taxonomy of low-abundance taxa - filtering_tag <- paste0("<", filter_value, "%_of_reads") - - count_table_long_tax_sum <- count_table_long_tax %>% - dplyr::group_by_at(c("Sample", ranks)) %>% - dplyr::summarise(Count = sum(Count)) %>% # Regroup counts for identical taxa within each sample - dplyr::ungroup() %>% - dplyr::group_by_at("Sample") %>% - dplyr::mutate(S_sum = sum(Count)) %>% # Create a column containing the sum of counts for each samples - dplyr::ungroup() %>% - dplyr::group_by_at(c("Sample", ranks)) %>% - dplyr::mutate(Count = (100*Count)/S_sum) %>% # Compute the % for each taxa within each sample - dplyr::mutate_at(.vars = vars(ranks), .funs = list(newtax = ~ dplyr::if_else(condition = (filter_value < Count), true = unique(.), false = filtering_tag))) %>% # Each taxa represented less counts that the cut-off are replated by the tag - dplyr::ungroup() %>% - dplyr::select(-one_of(ranks)) %>% # remove the orginal taxonomic columns - dplyr::rename_at(.vars = vars(ends_with("_newtax")), - .funs = ~sub(x = ., pattern = "_newtax$", replacement = "")) %>% # Rename the columns just created by the mutate function to the original names - dplyr::select(-S_sum) %>% # Remove the sum of counts per sample column - dplyr::group_by_at(c("Sample", ranks)) %>% - dplyr::summarise(Count = sum(Count)) %>% # Regroup the identical taxa per sample (the filtered ones) - dplyr::ungroup() - - - - ### No filtering - } else if (relative_or_absolute_filtering == "nofiltering"){ - - count_table_long_tax_sum <- count_table_long_tax - - } - - - ## Add metadata to this long form at count table with taxonomy - ### Keep only the used columns to keep the table small - metadata_table_select <- select(metadata_table, c(Sample, x_axis_column, grouping_column, facetting_column, sort_column)) - - ### Join tables - count_table_long_tax_meta <- dplyr::right_join(count_table_long_tax_sum, metadata_table_select, by = "Sample") - ### Order the x_axis_column based on the column order - count_table_long_tax_meta[[x_axis_column]] <- reorder(count_table_long_tax_meta[[x_axis_column]], count_table_long_tax_meta[[sort_column]]) - - ## Add a color palette for the taxonomy - #### Brewer colors - if (distinct_colors == FALSE){ - getPalette <- colorRampPalette(brewer.pal(n=9, "Set1")) - ColList <- unique(count_table_long_tax_meta[[plotting_tax_ranks]]) - colors_palette <- getPalette(length(ColList)) - names(colors_palette) <- ColList - colors_palette[filtering_tag] <- "#d3d3d3" # Set the filtered in balck - m <- match(count_table_long_tax_meta[[plotting_tax_ranks]], names(colors_palette)) - count_table_long_tax_meta$colors <- colors_palette[m] - - - #### Randomcolors distinct colors - }else if (distinct_colors == TRUE){ - set.seed(4) - ColList <- unique(count_table_long_tax_meta[[plotting_tax_ranks]]) - colors_palette <- distinctColorPalette(altCol = FALSE, k = length(unique(count_table_long_tax_meta[[plotting_tax_ranks]]))) - names(colors_palette) <- ColList - colors_palette[filtering_tag] <- "#d3d3d3" # Set the filtered in balck - m <- match(count_table_long_tax_meta[[plotting_tax_ranks]], names(colors_palette)) - count_table_long_tax_meta$colors <- colors_palette[m] - names(count_table_long_tax_meta$colors) <- count_table_long_tax_meta[[plotting_tax_ranks]] - } - - - ## Return filtered table - return(count_table_long_tax_meta) - -} - -## Create barplot -barplots_fct <- function(long_count_table, grouping_column, grouping_column_value, x_axis_column, t_neg_PCR_sample_on_plots, t_neg_PCR_group_column_value, relative_or_absolute_plot = c("relative", "absolute"), plotting_tax_ranks, horizontal_plot, facet_plot, facetting_column, order_by_abundance){ - - ## Select Tax ranks needed for labelling - if(plotting_tax_ranks == "Kingdom"){ - label_ranks <- c("Kingdom", "") - } else if(plotting_tax_ranks == "Phylum"){ - label_ranks <- c("Kingdom", "Phylum") - - } else if(plotting_tax_ranks == "Class"){ - label_ranks <- c("Phylum", "Class") - - } else if(plotting_tax_ranks == "Order"){ - label_ranks <- c("Class", "Order") - - } else if(plotting_tax_ranks == "Family"){ - label_ranks <- c("Order", "Family") - - } else if(plotting_tax_ranks == "Genus"){ - label_ranks <- c("Family", "Genus") - - } else if(plotting_tax_ranks == "Species"){ - label_ranks <- c("Phylum", "Species") - - } else if(plotting_tax_ranks == "ASV"){ - label_ranks <- c("Species", "ASV") - } - - - ## Keep the sample matching a specified value in the groupe_column variable. In option, we integrate negative controls too. - if(isTRUE(t_neg_PCR_sample_on_plots)){ - filtered_count_table_f <- long_count_table[long_count_table[[grouping_column]] == grouping_column_value | long_count_table[[grouping_column]] == t_neg_PCR_group_column_value,] - } else if (isFALSE(t_neg_PCR_sample_on_plots)){ - filtered_count_table_f <- long_count_table[long_count_table[[grouping_column]] == grouping_column_value,] - } - - ## Normalize in % the table or plot absolute abundance - filtered_count_table_f_norm <- filtered_count_table_f %>% # calculate % normalized Abundance - ungroup %>% - dplyr::group_by(Sample) %>% - dplyr::mutate(Count=as.numeric(100*Count/sum(Count))) %>% - ungroup() - - ## Reorder by abundance - ### Unquote a factors - t_column <- rlang::sym(plotting_tax_ranks) - ### Reorder - if (isTRUE(order_by_abundance)){ - filtered_count_table_ord <- filtered_count_table_f_norm %>% - group_by(!! t_column) %>% - mutate(tot = sum(Count)) %>% - ungroup() %>% - mutate(!! t_column := fct_reorder(!! t_column, tot)) %>% - arrange(desc(tot)) - }else if (isFALSE(order_by_abundance)){ print("NOT ordered by abundance") - filtered_count_table_ord <- filtered_count_table_f_norm - } - - ### Set the filtering_tag at the top of the plot - filtered_count_table_ord[[plotting_tax_ranks]] <- fct_relevel(filtered_count_table_ord[[plotting_tax_ranks]], unique(grep(filtered_count_table_ord[[plotting_tax_ranks]], value = TRUE, pattern = "<")), after = 0) - - ## Generate taxonomic and color labels - ### Renames the values in the vector of plotted used taxrank with their related OTU name to keep them matching. Without this step, the labels do NOT match the rows - taxalabel <- as.character(paste(toupper(substr(filtered_count_table_ord[[label_ranks[1]]],1 ,6)) , filtered_count_table_ord[[label_ranks[2]]], sep = ";")) - names(taxalabel) <- filtered_count_table_ord[[plotting_tax_ranks]] - - ### Colors are recovered from an column and renamed to match their taxonomic assignment - col <- as.character(filtered_count_table_ord$colors) - names(col) <- as.character(filtered_count_table_ord[[plotting_tax_ranks]]) - - - ### Create the barplot - taxrank_barplot <- filtered_count_table_ord %>% - ggplot(aes_string(x = x_axis_column, y = "Count", fill = plotting_tax_ranks)) + - theme_bw() + - geom_bar(stat = "identity") + - theme(axis.text.x = element_text(angle = 90, hjust = 0, vjust = 0.5), legend.text=element_text(size=10), plot.title = element_text(hjust = 0.5)) + # axis and title settings - guides(fill = guide_legend(title = paste0(plotting_tax_ranks),reverse = FALSE, keywidth = 1, keyheight = 1, ncol = 1)) + # settings of the legend - labs(x = x_axis_column, y = paste("Counts"), title = paste(plotting_tax_ranks,"level", grouping_column)) + # axis and graph title - scale_fill_manual(values = col, labels = taxalabel) # colors and taxa labels as set previously - - - #### In option, turn barplot horizontally and create facet view - if (isTRUE(horizontal_plot)){ - taxrank_barplot <- taxrank_barplot + coord_flip() ### Reverse the order of the samples - if (isTRUE(facet_plot)){ - taxrank_barplot <- taxrank_barplot + facet_grid(get(facetting_column) ~., scales = "free", space = "free") - } - - }else if(!isTRUE(horizontal_plot)){ - if (isTRUE(facet_plot)){ - taxrank_barplot <- taxrank_barplot + facet_grid(~ get(facetting_column), scales = "free", space = "free") - } - } - - return(taxrank_barplot) -} - -## Heatmaps -heatmaps_fct <- function(long_count_table, metadata_table, grouping_column, grouping_column_value, x_axis_column, t_neg_PCR_sample_on_plots, t_neg_PCR_group_column_value, relative_or_absolute_plot = c("relative", "absolute"), plotting_tax_ranks, horizontal_plot, facet_plot ,facetting_column, high_color, low_color, log_transform){ - - ## Select Tax ranks needed for labelling - if(plotting_tax_ranks == "Kingdom"){ - label_ranks <- c("Kingdom", "") - } else if(plotting_tax_ranks == "Phylum"){ - label_ranks <- c("Kingdom", "Phylum") - - } else if(plotting_tax_ranks == "Class"){ - label_ranks <- c("Phylum", "Class") - - } else if(plotting_tax_ranks == "Order"){ - label_ranks <- c("Class", "Order") - - } else if(plotting_tax_ranks == "Family"){ - label_ranks <- c("Order", "Family") - - } else if(plotting_tax_ranks == "Genus"){ - label_ranks <- c("Family", "Genus") - - } else if(plotting_tax_ranks == "Species"){ - label_ranks <- c("Phylum", "Species") - - } else if(plotting_tax_ranks == "ASV"){ - label_ranks <- c("Species", "ASV") - } - - - ## Keep the sample of matching a specified value in the groupe_column variable - if(isTRUE(t_neg_PCR_sample_on_plots)){ - filtered_count_table_f <- long_count_table[long_count_table[[grouping_column]] == grouping_column_value | long_count_table[[grouping_column]] == t_neg_PCR_group_column_value,] - } else if (isFALSE(t_neg_PCR_sample_on_plots)){ - filtered_count_table_f <- long_count_table[long_count_table[[grouping_column]] == grouping_column_value,] - } - - ## Keep the sample of matching a specified value in the groupe_column variable - if(isTRUE(t_neg_PCR_sample_on_plots)){ - metadata_table_f <- metadata_table[metadata_table[[grouping_column]] == grouping_column_value | metadata_table[[grouping_column]] == t_neg_PCR_group_column_value,] - } else if (isFALSE(t_neg_PCR_sample_on_plots)){ - metadata_table_f <- metadata_table[metadata_table[[grouping_column]] == grouping_column_value,] - } - - ## Normalize counts in % - if (relative_or_absolute_plot == "relative"){ - ## Normalize in % the table or plot absolute abundance - filtered_count_table_f_norm <- filtered_count_table_f %>% # calculate % normalized Abundance - ungroup %>% - dplyr::group_by(Sample) %>% - dplyr::mutate(Count=as.numeric(100*Count/sum(Count))) %>% - ungroup() - }else if (relative_or_absolute_plot == "absolute"){ - filtered_count_table_f_norm <- filtered_count_table_f - } - - ## Generate taxa labels - filtered_count_table_f_norm$taxalabel <- as.character(paste(toupper(substr(filtered_count_table_f_norm[[label_ranks[1]]],1 ,6)) , filtered_count_table_f_norm[[label_ranks[2]]], sep = ";")) - ### Renames the values in the vector of plotted used taxrank with their related OTU name to keep them matching. Without this step, the labels do NOT match the rows - names(filtered_count_table_f_norm$taxalabel) <- filtered_count_table_f_norm[[plotting_tax_ranks]] - - - ## Cluster taxa to order the heatmap - ### Generate an OTU wide table from the long format - filtered_df_abs_i_wide <- filtered_count_table_f_norm %>% - select(Sample, taxalabel, Count) %>% - spread(taxalabel, value = Count) - - #### Set the Sample ID as a rowname - row.names(filtered_df_abs_i_wide) <- filtered_df_abs_i_wide[["Sample"]] - filtered_df_abs_i_wide[["Sample"]] <- NULL - - ### Format the OTU table for clustering with vegdist - #### Transpose the table - filtered_df_abs_i_wide <- t(filtered_df_abs_i_wide) - - - #### Set the most abundant taxa at the top - filtered_df_abs_i_wide <- filtered_df_abs_i_wide[order(rowSums(filtered_df_abs_i_wide, na.rm = TRUE), decreasing=T),] - #### Shape it into a matrix - filtered_df_abs_i_wide <- data.matrix(filtered_df_abs_i_wide) - - ### Cluster samples based on Bray-Curits distance, remplacing Na by 0 for clustering only - data.dist.g <- vegdist(t(filtered_df_abs_i_wide), na.rm = TRUE, method = "bray") - data.dist.g[is.na(data.dist.g)] <- 0 - col.clus <- hclust(data.dist.g, method = "ward.D2") - - ### Prepare annotation of the heatmap - #### x_axis_column - metadata_table_f <- data.frame(metadata_table_f, stringsAsFactors = FALSE, check.rows = F, check.names = F) - rownames(metadata_table_f) <- metadata_table_f[["Sample"]] - l_rows <- metadata_table_f[[x_axis_column]][match(colnames(filtered_df_abs_i_wide), rownames(metadata_table_f))] - - #### in option, transform the counts - ##### Get rid of 0 for tansformation - - - if(isTRUE(facet_plot)){ - - #### Color annotation - anno <- data.frame(facetting_column = metadata_table_f[[facetting_column]]) - colnames(anno)[1] <- facetting_column - rownames(anno) <- metadata_table_f[["Sample"]] - - ### Plot vertically - if (isFALSE(horizontal_plot)){ - heat <- pheatmap(mat = filtered_df_abs_i_wide, labels_col = l_rows, annotation_col = anno, cluster_rows = FALSE , cluster_cols = col.clus, cellwidth = 11, cellheight = 11, color = colorRampPalette(c(low_color, high_color))(100), fontsize = 10) - - } - - ### Plot horizontally - else if (isTRUE(horizontal_plot)){ - heat <- pheatmap(mat = t(filtered_df_abs_i_wide), labels_row = l_rows, annotation_row = anno, cluster_rows = col.clus , cluster_cols = FALSE, cellwidth = 11, cellheight = 11, color = colorRampPalette(c(low_color, high_color))(100), fontsize = 8, angle_col = 45) - } - - }else{ - - ### Plot vertically - if (isFALSE(horizontal_plot)){ - heat <- pheatmap(mat = filtered_df_abs_i_wide, labels_col = l_rows, cluster_rows = FALSE , cluster_cols = FALSE, cellwidth = 11, cellheight = 11, color = colorRampPalette(c(low_color, high_color))(100), fontsize = 10) - } - - ### Plot horizontally - else if (isTRUE(horizontal_plot)){ - heat <- pheatmap(mat = t(filtered_df_abs_i_wide), labels_row = l_rows, cluster_rows = col.clus , cluster_cols = FALSE, cellwidth = 11, cellheight = 11, color = colorRampPalette(c(low_color, high_color))(100), fontsize = 8, angle_col = 45) - } - - - } - - return(heat) - - -} - -## NMDS -NMDS_fct <- function(count_table, metadata_table, grouping_column, grouping_column_value, t_neg_PCR_sample_on_plots, t_neg_PCR_group_column_value, plotting_tax_ranks, distance, color_column, shape_column, color_palette){ - - ## Keep the sample of matching a specified value in the groupe_column variable - if(isTRUE(t_neg_PCR_sample_on_plots)){ - metadata_table_f <- metadata_table[metadata_table[[grouping_column]] == grouping_column_value | metadata_table[[grouping_column]] == t_neg_PCR_group_column_value,] - } else if (isFALSE(t_neg_PCR_sample_on_plots)){ - metadata_table_f <- metadata_table[metadata_table[[grouping_column]] == grouping_column_value,] - } - - ## Filter the columns of the count_table to keep only those - count_table_col_f <- count_table[which(names(count_table) %in% metadata_table_f[["Sample"]])] - count_table_f <- count_table_col_f[which(rowSums(count_table_col_f) != 0),] - - ## Compute the distance matrix - if (distance == "jaccard"){ - beta_dist <- vegdist(t(count_table_f),method = distance, binary = TRUE) # By default, jaccard is NOT computed after presence/absence transformation, when it should - }else{ - beta_dist <- vegdist(t(count_table_f), method = distance, binary = FALSE) - } - - ## Generate the NMDS clustering - mds <- metaMDS(beta_dist, try = 50) - - ## Re-add the metadata to the points - mds_data <- as.data.frame(mds$points) - mds_data$Sample <- rownames(mds_data) - mds_data <- dplyr::left_join(mds_data, metadata_table_f, by = "Sample") - - mds_data <<- mds_data - - ## Generate the clustering - plot <- ggplot(mds_data, aes_string(x = "MDS1", y = "MDS2", color = "NMDS_alpha_colors", shape = shape_column)) + - geom_point(size=4) + - theme_bw() + - coord_equal() + - #scale_color_brewer(palette=color_palette) + - scale_colour_identity(guide = "legend", labels = mds_data[[color_column]], breaks = mds_data[["NMDS_alpha_colors"]]) - stat_ellipse(aes(group = NMDS_alpha_colors, color = NMDS_alpha_colors),linetype = 2, type = "t") - - ## Create stress plot, from https://stackoverflow.com/questions/47124238/annotate-in-ggplot2-does-not-honor-newline-is-a-pasted-and-parsed-command - ### This plot indicates the concordance between the real distance between the samples and the distances in the plots - #### Format the table - tib <- tibble(mds$diss, mds$dist, mds$dhat) - colnames(tib) <- c("diss", "dist", "dhat") - stress <- mds$stress - coord_x <- min(tib$diss) - coord_y <- max(tib$dist) - nonmetric_r2 <- round(1 - stress * stress, digits = 3) - linear_r2 <- round(summary(lm(mds$dist~mds$dhat))$adj.r.squared, 3) - - ### Generate the plot - #### Generate a label - nonmetric_label = c(paste0("Non-metric~fit~italic(R)^2 ==", nonmetric_r2), - paste0("Linear~fit~italic(R)^2 ==", linear_r2)) - #### Plot - stress <- ggplot(tib, - aes(x = diss, y = dist)) + - geom_point(color = "blue") + - geom_step(aes(x = diss, y = dhat), color = "red") + - annotate( - geom = "text", - x = coord_x, - y = c(coord_y, 0.95*coord_y), - hjust = 0, - #vjust = 1, - label = nonmetric_label, parse = TRUE) + - labs(x = "Observed Dissimilarity", - y = "Ordination Distance") + - theme_classic() + - coord_equal() + - labs(caption=paste0("stress:", round(mds$stress, digits = 2))) + - theme(plot.caption=element_text(size=12, hjust=0, margin=margin(15,0,0,0))) - - ## Return both plots in a list, that will be splitted then - return(list(plot, stress)) - -} - -## Alphy diversity -alpha_fct <- function(metadata_table, x_axis_column, grouping_column, grouping_column_value, t_neg_PCR_sample_on_plots, t_neg_PCR_group_column_value,facet_plot = FALSE, facetting_column = NULL, index, color_column){ - - ## Keep the sample of matching a specified value in the groupe_column variable - if(isTRUE(t_neg_PCR_sample_on_plots)){ - metadata_table_f <- metadata_table[metadata_table[[grouping_column]] == grouping_column_value | metadata_table[[grouping_column]] == t_neg_PCR_group_column_value,] - } else if (isFALSE(t_neg_PCR_sample_on_plots)){ - metadata_table_f <- metadata_table[metadata_table[[grouping_column]] == grouping_column_value,] - } - - ## Plot alpha diversity - ### Here, we only recover the pre-computed values. We could eventually re-compute these values on the fly - alpha <- ggplot(metadata_table_f, aes_string(x=x_axis_column, y=index, color = "NMDS_alpha_colors")) + - geom_boxplot() + - theme_bw() + - theme(axis.text.x = element_text(angle = 90, hjust = 1, vjust = 0.5)) + - xlab(label = x_axis_column) + - ylab(label = index) + - labs(color = color_column) + - ggtitle(paste(grouping_column_value, "alpha-diversity")) + - scale_colour_identity(guide = "legend", labels = metadata_table_f[[color_column]], breaks = metadata_table_f[["NMDS_alpha_colors"]]) - - ### In option, generate facets - if (isTRUE(facet_plot)){ - alpha <- alpha + facet_grid(.~get(facetting_column) , scales = "free", space = "free") - - } - - return(alpha) - -} - - -# Define UI -ui <- fluidPage(theme = shinytheme("lumen"), - - titlePanel("Amplicon metagenomics visualization"), - - - fluidRow( - column(3, h3("Import data"), - - ## Load data - ### Metadata - fileInput("input_metadata_table", "Load metadata", - placeholder = "metadata_table.tsv", - multiple = FALSE, - accept = c("text/csv", - "text/comma-separated-values,text/plain", - ".tsv", - ".xls", - ".xlsx")), - - ### Taxonomy - fileInput("input_taxonomy_table", "Load taxonomy", - placeholder = "dna-sequences_tax_assignments.txt", - multiple = FALSE, - accept = c("text/csv", - "text/comma-separated-values,text/plain", - ".tsv")), - - ### Count table - fileInput("input_count_table", "Load count table", - placeholder = "count_table.tsv", - multiple = FALSE, - accept = c("text/csv", - "text/comma-separated-values,text/plain", - ".tsv")), - - - ## Set filtering out of certain samples - ### Yes/no of exclsuion - checkboxInput(inputId = "filter_out", label = "Exclude samples", value = FALSE), - - ### If yes, which columns is used to select samples to exclude - conditionalPanel(condition = "input.filter_out == true", - selectInput(inputId = "filtering_column", label = "Column for filtering column",choice = NULL)), - - ### If yes, which values in this columns - conditionalPanel(condition = "input.filter_out == true", - selectInput(inputId = "filter_out_value", label = "Value in filtering column",choice = NULL, multiple = TRUE)), - - ## Select in which format to generate the plots - selectInput(inputId = "fformat", "Download plot format", choices=c("png","svg","jpeg","pdf"), selected = "png", multiple = FALSE, selectize = TRUE) - - ), - - column(3, h3("Select input columns"), - ## Select columm of interest - ### grouping column (a column for which an individual plot will be generated) - selectInput(inputId = "grouping_column", label = "Grouping column", choices = NULL), - - ### grouping column value (for which value in the grouping column do we want a plot) - selectInput(inputId ="grouping_column_value", label = "Grouping column value", choices = NULL), - - ### QC on plots (do we add the QC on each plots) - checkboxInput(inputId = "t_neg_PCR_sample_on_plots", label = "QC on each plot", value = FALSE), - - ### QC value in grouping column (which values have QC in the grouping column) - conditionalPanel(condition = "input.t_neg_PCR_sample_on_plots == true", - selectInput(inputId ="t_neg_PCR_group_column_value", label = "QC value in grouping column",choices = NULL)), - - ### Facet plot - checkboxInput(inputId = "facet_plot", label = "Facet plot", value = FALSE), - - ### Column value for facetting - conditionalPanel(condition = "input.facet_plot == true", - selectInput(inputId ="facetting_column", label = "Column for facets", choices = NULL)), - - ### x axis - selectInput(inputId = "x_axis_column", label = "X axis column", choices = NULL), - - ### Sort column - ### x axis - selectInput(inputId = "sort_column", label = "Sorting column", choices = NULL) - - ), - - column(3, h3("Barplot and Heatmap settings"), - - h4("Both"), - - ### Relative or absolute filtering - selectInput(inputId = "relative_or_absolute_filtering",label = "Filtering type", c("relative", "absolute", "nofiltering")), - - ### Filter cumulated at the sample or group scale - #selectInput(inputId = "abundance_filtre_level",label = "Filtering scale",c("Sample", "Group")), - - ### Filtering value - numericInput(inputId = "filtering_value", label = "Filtering value", value = 1, min = 0), - - ### Plotting value - selectInput(inputId = "relative_or_absolute_plot", label = "Plotting value", c("relative", "absolute")), - - ### Plotting tax rank - selectInput(inputId = "plotting_tax_ranks",label = "Plotting taxonomic rank", choices = c("Kingdom", "Phylum", "Class", "Order", "Family", "Genus", "Species", "ASV"), selected = "Genus"), - - ### Horizontal plot - checkboxInput(inputId = "horizontal_plot", label = "Horizontal plot", value = FALSE), - - - h4("Barplots only"), - - ### Order by abundance - checkboxInput(inputId = "order_by_abundance", label = "Order by abundance", value = FALSE), - - ### Distinct color - checkboxInput(inputId = "distinct_colors", label = "Random distinct color", value = TRUE), - - ### Seprated legends - checkboxInput(inputId = "separated_legend", label = "Separated legend", value = TRUE), - - h4("Heatmap only"), - - colourInput(inputId = "low_color",label = "Low abundance color", "#000033"), - - colourInput(inputId = "high_color",label = "High abundance color", "#CCFF66"), - - selectInput(inputId = "log_transform",label = "Log transformation", choices = c("None", 2, 10), selected = FALSE) - - - ), - - column(3, h3("Alpha-diversity and NMDS settings"), - - h4("Both"), - ### Color column - selectInput(inputId ="alp_NMDS_color", label = "Colors column", choices = NULL), - - ### Color palette - selectInput(inputId ="alp_NMDS_palette", label = "Color palette", choices = c("Set1", "Set2", "Set3", "Pastel1", "Pastel2","Paired", "Dark2", "Accent", "Spectral"), selected = "Dark2"), - - h4("Alpha-diversity"), - ### Index - selectInput(inputId ="alpha_index", label = "Alpha-diversity index", choices = c("Observed", "Chao1", "ACE", "Shannon", "Simpson","InvSimpson", "Observed_min_1"), selected = "Chao1"), - - h4("NMDS of beta-diversity"), - ### Shapes column - selectInput(inputId ="NMDS_shape", label = "NMDS shapes", choices = NULL), - - ### Distances - selectInput(inputId ="NMDS_distance", label = "NMDS distance", choices = c("bray", "jaccard", "manhattan", "euclidean", "canberra","gower", "altGower", "morisita", "horn", "mountford", "raup" , "binomial", "chao", "cao", "mahalanobis"), selected = "bray") - - - ) - - ), - - tabsetPanel( - # Barplots plannel - tabPanel(title = "Baplots", - - downloadButton('Download_barplot', label = "Barplot"), - downloadButton('Download_barplot_legend', label = "Legend"), - splitLayout(cellWidths = c("75%", "25%"), plotOutput("b_plot"), plotOutput("legend")) - - ), - - - # Heatmaps - tabPanel(title = "Heatmaps", - - downloadButton('Download_heatmap', label = "Heatmap"), - plotOutput("heat_plot") - ), - - - ## Alpha diversity - tabPanel(title = "Alpha", - - downloadButton('Download_alpha', label = "Alpha-diversity"), - plotOutput("alpha_plot") - - ), - - # NMDS - tabPanel(title = "NMDS", - - downloadButton('Download_NMDS',label = "NMDS"), - splitLayout(cellWidths = c("50%", "50%"), plotOutput("NMDS", height = "400px", width = "500px"), plotOutput("NMDS_stress")) - - ) - - ) - - -) - -# Define server function -server <- function(input, output, session) { - - ## Increase default max size - options(shiny.maxRequestSize=300*1024^2) - - ## Load data - ### Metadata table - metadata_table <- reactive({ - req(input$input_metadata_table) - - if(any(grepl(x = input$input_metadata_table$datapath, pattern = ".tsv"), grepl(x = input$input_metadata_table$datapath, pattern = ".txt"))){ - m <- read.table(input$input_metadata_table$datapath, header = TRUE, sep = "\t", check.names = FALSE) - } else if(any(grepl(x = input$input_metadata_table$datapath, pattern = ".xlsx"),grepl(x = input$input_metadata_table$datapath, pattern = ".xls"))){ - m <- read_excel(path = input$input_metadata_table$datapath, sheet = 1) - } - - return(m) - }) - - ### Count table - count_table <- reactive({ - req(input$input_count_table) - c <- read.table(input$input_count_table$datapath, header = TRUE, sep = "\t", check.names = FALSE) - return(c) - }) - - ### Taxonomy - taxonomy_table <- reactive({ - req(input$input_taxonomy_table) - t <- read.table(input$input_taxonomy_table$datapath, header = FALSE, sep = "\t", check.names = FALSE) - return(t) - }) - - - ## In option, filter out some samples from the metadata - ### Columns used for filtering - observeEvent({metadata_table()},{ - updateSelectInput(session = session, 'filtering_column', - choices=colnames(metadata_table())[!colnames(metadata_table()) %in% c("Observed" ,"Chao1", "se.chao1", "ACE", "se.ACE", "Shannon", "Simpson", "InvSimpson", "Observed_min_1")]) - }) - - ### Values filtered out in this column - observeEvent({metadata_table() - input$filtering_column},{ - updateSelectInput(session = session, 'filter_out_value', - choices=unique(metadata_table()[[input$filtering_column]])) - }) - - ### Filter out the columns - metadata_table_filtered <- reactive({ - req(metadata_table()) - metadata_table_filtered <- filter_metadata_fct(metadata_table = metadata_table(), - filter_out = input$filter_out, - filtering_column = input$filtering_column, - filter_out_value = input$filter_out_value) - - return(metadata_table_filtered) - - }) - - - - ## Add colors to the filtered metadata for later plotting of NMDS and alpha diversity - metadata_table_filtered_col <- reactive({ - req(metadata_table_filtered()) - metadata_colored <- add_colors_fct(metadata_table = metadata_table_filtered(), - alp_NMDS_color = input$alp_NMDS_color, - alp_NMDS_palette = input$alp_NMDS_palette) - - return(metadata_colored) - - }) - - ## Update parameters based on the content of the filtered metadata - #### Grouping_column - observeEvent({metadata_table_filtered()},{ - updateSelectInput(session = session, 'grouping_column', - choices=colnames(metadata_table_filtered())[!colnames(metadata_table_filtered()) %in% c("Observed" ,"Chao1", "se.chao1", "ACE", "se.ACE", "Shannon", "Simpson", "InvSimpson", "Observed_min_1")]) - }) - - #### Grouping column value - observeEvent({metadata_table_filtered() - input$grouping_column - metadata_table_filtered()},{ - updateSelectInput(session = session, 'grouping_column_value', - choices=unique(metadata_table_filtered()[[input$grouping_column]])) - }) - - #### Grouping column value for QC - observeEvent({metadata_table_filtered() - input$t_neg_PCR_sample_on_plots - input$grouping_column},{ - updateSelectInput(session = session,'t_neg_PCR_group_column_value', - choices=unique(metadata_table_filtered()[[input$grouping_column]])) - }) - - #### x_axis_column - observeEvent({metadata_table_filtered()},{ - updateSelectInput(session = session, 'x_axis_column', - choices=colnames(metadata_table_filtered())[!colnames(metadata_table_filtered()) %in% c("Observed" ,"Chao1", "se.chao1", "ACE", "se.ACE", "Shannon", "Simpson", "InvSimpson", "Observed_min_1")]) - }) - - #### Column for sorting of x_axis_column - observeEvent({metadata_table_filtered() - input$x_axis_column},{ - updateSelectInput(session = session, 'sort_column', - choices=colnames(metadata_table_filtered())[!colnames(metadata_table_filtered()) %in% c("Observed" ,"Chao1", "se.chao1", "ACE", "se.ACE", "Shannon", "Simpson", "InvSimpson", "Observed_min_1")]) - }) - - #### Facetting column - observeEvent({metadata_table_filtered() - input$facet_plot},{ - updateSelectInput(session = session, 'facetting_column', - choices=colnames(metadata_table_filtered())[!colnames(metadata_table_filtered()) %in% c("Observed" ,"Chao1", "se.chao1", "ACE", "se.ACE", "Shannon", "Simpson", "InvSimpson", "Observed_min_1")]) - }) - - - #### Column for which we want alpha/NMDS colors - observeEvent({metadata_table_filtered()},{ - updateSelectInput(session = session, 'alp_NMDS_color', - choices=colnames(metadata_table_filtered())[!colnames(metadata_table_filtered()) %in% c("Observed" ,"Chao1", "se.chao1", "ACE", "se.ACE", "Shannon", "Simpson", "InvSimpson", "Observed_min_1")]) - }) - - ### NMDS shapes - observeEvent({metadata_table_filtered()},{ - updateSelectInput(session = session, 'NMDS_shape', - choices=colnames(metadata_table_filtered())[!colnames(metadata_table_filtered()) %in% c("Observed" ,"Chao1", "se.chao1", "ACE", "se.ACE", "Shannon", "Simpson", "InvSimpson", "Observed_min_1")]) - }) - - - ## Generate a long table for barplot, heatmap and NMDS - long_table <- reactive({ - req(metadata_table_filtered()) - req(count_table()) - req(taxonomy_table()) - - long_table <- filter_OTU_count_fct( - count_table = count_table(), - metadata_table = metadata_table_filtered(), - taxonomy_table = taxonomy_table(), - grouping_column = input$grouping_column, - x_axis_column = input$x_axis_column, - sort_column = input$sort_column, - facetting_column = input$facetting_column, - relative_or_absolute_filtering = input$relative_or_absolute_filtering, - filter_value = input$filtering_value, - plotting_tax_ranks = input$plotting_tax_ranks, - distinct_colors = input$distinct_colors, - abundance_filtre_level = c("Sample", "Group")) - - return(long_table) - - }) - - - ## Generate plots - ### Barplots - #### Generate the barplot - b_plot <- reactive({ - req(long_table()) - - barplots_fct(long_count_table = long_table(), - x_axis_column = input$x_axis_column, - grouping_column = input$grouping_column, - grouping_column_value = input$grouping_column_value, - t_neg_PCR_sample_on_plots = input$t_neg_PCR_sample_on_plots , - t_neg_PCR_group_column_value = input$t_neg_PCR_group_column_value, - relative_or_absolute_plot = input$relative_or_absolute_plot, - plotting_tax_ranks = input$plotting_tax_ranks, - horizontal_plot = input$horizontal_plot, - facet_plot = input$facet_plot, - facetting_column = input$facetting_column, - order_by_abundance = input$order_by_abundance) - }) - - #### Compute the widht of the barplot based on the number of values in the x_axis_column - b_size <- reactive({ - req(long_table()) - - #### Filter the long table for the value of the grouping column - table_i <- long_table()[long_table()[[input$grouping_column]] == input$grouping_column_value,] - - #### basic width - w <- 50 + 30*(length(unique(table_i[[input$x_axis_column]]))) - - ##### Add more if legend with plot - if(isFALSE(input$separated_legend)){ - w <- w + 200 - } - - ### basic height - h <- 400 - - ### Revert widht and height if horizontal plot - if(isTRUE(input$horizontal_plot)){ - return(list(w,h)) - }else{ - return(list(h,w)) - } - }) - - - #### Remove the legend and render the plot - output$b_plot <- renderPlot({ - req(b_plot()) - req(b_size()) - - if (isTRUE(input$separated_legend)){ - p <- b_plot() + guides(fill = FALSE) - - } else if(isFALSE(input$separated_legend)){ - p <- b_plot() - } - - ggsave(paste0("barplot.",input$fformat), p) - - return(p) - - }, height = function(){b_size()[[1]]}, width = function(){b_size()[[2]]}) # Recover the pre-computed length - - output$legend <- renderPlot({ - req(b_plot()) - - leg <- as_ggplot(get_legend(b_plot())) - - ggsave(paste0("legend.",input$fformat), leg) - - return(leg) - - }) - - ### Heatmap - #### Compute the widht of the barplot based on the number of values in the x_axis_column - h_size <- reactive({ - req(long_table()) - - #### Filter the long table for the value of the grouping column - table_i <- long_table()[long_table()[[input$grouping_column]] == input$grouping_column_value,] - - #### basic width - w <- 300 + 11*(length(unique(table_i[[input$x_axis_column]]))) - - ##### Add more if legend with plot - if(isTRUE(input$facet_plot)){ - w <- w + 200 - } - - ### basic height - h <- 400 + 11 *(length(unique(table_i[[input$plotting_tax_ranks]]))) - - ### Revert widht and height if horizontal plot - if(isTRUE(input$horizontal_plot)){ - return(list(w,h)) - }else{ - return(list(h,w)) - } - }) - - - #### Generate plot - output$heat_plot <- renderPlot({ - req(long_table()) - req(h_size()) - - heat <- heatmaps_fct(long_count_table = long_table(), - metadata_table = metadata_table_filtered(), - x_axis_column = input$x_axis_column, - grouping_column = input$grouping_column, - grouping_column_value = input$grouping_column_value, - t_neg_PCR_sample_on_plots = input$t_neg_PCR_sample_on_plots , - t_neg_PCR_group_column_value = input$t_neg_PCR_group_column_value, - relative_or_absolute_plot = input$relative_or_absolute_plot, - plotting_tax_ranks = input$plotting_tax_ranks, - horizontal_plot = input$horizontal_plot, - facet_plot = input$facet_plot, - facetting_column = input$facetting_column, - high_color = input$high_color, - low_color = input$low_color, - log_transform = input$log_transform) - - ggsave(paste0("heatmap.",input$fformat), plot = heat) - - return(heat) - }, height = function(){h_size()[[1]]}, width = function(){h_size()[[2]]}) # Recover the pre-computed length - - - ### NMDS - #### Generate the plot - NMDS_biplot <- reactive({ - req(metadata_table_filtered_col()) - plot <- NMDS_fct(count_table = count_table() , - metadata_table = metadata_table_filtered_col(), - grouping_column = input$grouping_column, - grouping_column_value = input$grouping_column_value, - t_neg_PCR_sample_on_plots = input$t_neg_PCR_sample_on_plots, - t_neg_PCR_group_column_value = input$t_neg_PCR_group_column_value, - plotting_tax_ranks = input$plotting_tax_ranks, - distance = input$NMDS_distance, - color_column = input$alp_NMDS_color, - shape_column = input$NMDS_shape, - color_palette = input$alp_NMDS_palette) - - print(paste0("NMDS.",input$fformat)) - - ggsave(filename = paste0("NMDS.",input$fformat), plot = plot[[1]]) - - return(plot) - }) - - #### Render the NMDS itself - output$NMDS <- renderPlot({ - req(NMDS_biplot()) - return(NMDS_biplot()[[1]]) - }) - - #### Render the stress plot - output$NMDS_stress <- renderPlot({ - req(NMDS_biplot()) - return(NMDS_biplot()[[2]]) - }) - - ### Alpha-diversity - #### Compute the width of the table based on the number of values in the x_axis_column - alp_width <- reactive({ - req(long_table()) - - table_i <- long_table()[long_table()[[input$grouping_column]] == input$grouping_column_value,] - - w <- 200 + 30*(length(unique(table_i[[input$x_axis_column]]))) - - if(isTRUE(input$facet_plot)){ - w <- w + 100 - - } - - return(w) - - }) - - #### Generate alpha diversity plot - output$alpha_plot <- renderPlot({ - req(metadata_table_filtered_col()) - alpha_plot <- alpha_fct(metadata_table = metadata_table_filtered_col(), - grouping_column = input$grouping_column, - grouping_column_value = input$grouping_column_value, - t_neg_PCR_sample_on_plots = input$t_neg_PCR_sample_on_plots, - t_neg_PCR_group_column_value = input$t_neg_PCR_group_column_value, - x_axis_column = input$x_axis_column, - facet_plot = input$facet_plot, - facetting_column = input$facetting_column, - index = input$alpha_index, - color_column = input$alp_NMDS_color) - - ggsave(filename = paste0("alpha.",input$fformat), plot = alpha_plot) - - return(alpha_plot) - - }, height = 400, width = function(){alp_width()}) # Recover the pre-computed length - - - - ## Download the pre-computed plots. Here they were actually pre-saved and were here copied. - ### Barplot - output$Download_barplot <- downloadHandler( - filename = function() { - paste0("barplot.",input$fformat) - }, - content = function(file) { - file.copy(paste0("barplot.",input$fformat), file, overwrite=TRUE) - } - ) - - ### Legend of the barplot - output$Download_barplot_legend <- downloadHandler( - filename = function() { - paste0("legend.",input$fformat) - }, - content = function(file) { - file.copy(paste0("legend.",input$fformat), file, overwrite=TRUE) - } - ) - - ### Heatmap - output$Download_heatmap<- downloadHandler( - filename = function() { - paste0("heatmap.",input$fformat) - - }, - content = function(file) { - file.copy(paste0("heatmap.",input$fformat), file, overwrite=TRUE) - } - ) - - ### NMDS - output$Download_NMDS<- downloadHandler( - filename = function() { - paste0("NMDS.",input$fformat) - }, - content = function(file) { - file.copy(paste0("NMDS.",input$fformat), file, overwrite=TRUE) - } - ) - - ### Alpha-diversity - output$Download_alpha<- downloadHandler( - filename = function() { - paste0("alpha.",input$fformat) - }, - content = function(file) { - file.copy(paste0("alpha.",input$fformat), file, overwrite=TRUE) - } - ) -} - - -# Create Shiny object -shinyApp(ui = ui, server = server) - diff --git a/ressources/template_files/16S_input_table_insilico.tsv b/ressources/template_files/16S_input_table_insilico.tsv deleted file mode 100644 index 70fc1280..00000000 --- a/ressources/template_files/16S_input_table_insilico.tsv +++ /dev/null @@ -1,20 +0,0 @@ -GCA_000008005.1 -GCA_000010425.1 -GCA_000016965.1 -GCA_020546685.1 -GCA_000172575.2 -GCA_000005845.2 -GCA_000014425.1 -GCA_003324715.1 -GCA_000007645.1 -GCA_000007465.2 -GCA_013372085.1 -GCA_031191545.1 -GCA_000012825.1 -GCA_000307795.1 -GCA_000008805.1 -GCA_000010505.1 -GCA_000231215.1 -GCA_000017205.1 -GCA_000013425.1 -GCA_000007265.1 diff --git a/ressources/template_files/16S_validation_set.tsv b/ressources/template_files/16S_validation_set.tsv deleted file mode 100644 index 921bb885..00000000 --- a/ressources/template_files/16S_validation_set.tsv +++ /dev/null @@ -1,7 +0,0 @@ -Sample sample_label sample_group sample_source seq_run study_name LibraryLayout -SRR9067116 Vaginal-16s-V3V4-Library42 Genital_tract Genital_tract UNIVERSITY_OF_CAPE_TOWN pipeline_validation paired -SRR9067115 Vaginal-16s-V3V4-Library41 Genital_tract Genital_tract UNIVERSITY_OF_CAPE_TOWN pipeline_validation paired -SRR9067114 Vaginal-16s-V3V4-Library48 Genital_tract Genital_tract UNIVERSITY_OF_CAPE_TOWN pipeline_validation paired -SRR7225909 NE14 human_biliary_tract human_biliary_tract MEDICAL_RESEARCH_INSTITUTE_FOR_NANJING_MILITARY_COMMAND pipeline_validation paired -SRR7225908 A3D12 human_biliary_tract human_biliary_tract MEDICAL_RESEARCH_INSTITUTE_FOR_NANJING_MILITARY_COMMAND pipeline_validation paired -SRR7225907 NN15 human_biliary_tract human_biliary_tract MEDICAL_RESEARCH_INSTITUTE_FOR_NANJING_MILITARY_COMMAND pipeline_validation paired diff --git a/ressources/template_files/DB_snakemake_bash_command.sh b/ressources/template_files/DB_snakemake_bash_command.sh deleted file mode 100644 index dca442f7..00000000 --- a/ressources/template_files/DB_snakemake_bash_command.sh +++ /dev/null @@ -1,6 +0,0 @@ -snakemake \ - --snakefile /microbiome16S_pipeline>/DBprocess.Snakefile \ - --use-singularity --singularity-prefix \ - #or --use-conda --conda-prefix \ - --cores \ - --configfile config_DB.yaml \ No newline at end of file diff --git a/ressources/template_files/ITS_output_example.tsv b/ressources/template_files/ITS_output_example.tsv deleted file mode 100644 index 7c1a02ac..00000000 --- a/ressources/template_files/ITS_output_example.tsv +++ /dev/null @@ -1,9 +0,0 @@ -UserInputNames AssemblyID AssemblyNames AssemblyStatus Refseq_category Contig_count Assembly_length Release_date_Genbank FtpPath_Refseq FtpPath_Genbank Taxid superkingdom_taxid species genus family order phylum superkingdom phylum_taxid order_taxid family_taxid genus_taxid species_taxid in_DB Number_of_variants Sum_of_copies Genus_agreement Species_agreement Matching_amplicons Discrepant_amplicons Amplicon_1 Count_1 Tax_1 Amplicon_2 Count_2 Tax_2 Amplicon_3 Count_3 Tax_3 Amplicon_4 Count_4 Tax_4 Amplicon_5 Count_5 Tax_5 Amplicon_6 Count_6 Tax_6 Amplicon_7 Count_7 Tax_7 Amplicon_8 Count_8 Tax_8 Amplicon_9 Count_9 Tax_9 Amplicon_10 Count_10 Tax_10 -1069201 9274911 GCA_016861625.1_AkawachiiIFO4308_assembly01 Complete Genome representative genome 9 37287723 2021/02/10 00:00 ftp://ftp.ncbi.nlm.nih.gov/genomes/all/GCF/016/861/625/GCF_016861625.1_AkawachiiIFO4308_assembly01 ftp://ftp.ncbi.nlm.nih.gov/genomes/all/GCA/016/861/625/GCA_016861625.1_AkawachiiIFO4308_assembly01 1069201 2759 Aspergillus luchuensis Aspergillus Aspergillaceae Eurotiales Ascomycota Eukaryota 4890 5042 1131492 5052 1069201 FALSE 1 5 Species_not_in_your_DB Species_not_in_your_DB 0 1 Seq_5 5 Fungi;Ascomycota;Eurotiomycetes;Eurotiales;Aspergillaceae;Aspergillus;Aspergillus_piperis NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA -182096 9274961 GCA_016861735.1_AchevalieriM1_assembly01 Complete Genome representative genome 8 29697343 2021/02/10 00:00 ftp://ftp.ncbi.nlm.nih.gov/genomes/all/GCF/016/861/735/GCF_016861735.1_AchevalieriM1_assembly01 ftp://ftp.ncbi.nlm.nih.gov/genomes/all/GCA/016/861/735/GCA_016861735.1_AchevalieriM1_assembly01 182096 2759 Aspergillus chevalieri Aspergillus Aspergillaceae Eurotiales Ascomycota Eukaryota 4890 5042 1131492 5052 182096 TRUE 1 9 Matching Discrepant 0 1 Seq_2 9 Fungi;Ascomycota;Eurotiomycetes;Eurotiales;Aspergillaceae;Aspergillus;Aspergillus_sp NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA -41058 4513691 GCA_008274985.1_ASM827498v1 Complete Genome representative genome 8 40106795 2019/09/05 00:00 ftp://ftp.ncbi.nlm.nih.gov/genomes/all/GCA/008/274/985/GCA_008274985.1_ASM827498v1 41058 2759 Aspergillus sojae Aspergillus Aspergillaceae Eurotiales Ascomycota Eukaryota 4890 5042 1131492 5052 41058 FALSE 0 0 No_PCR_amplification No_PCR_amplification NA NA No_amp 0 No_amplification_products NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA -1220207 9275021 GCA_016861865.1_ApuulaauensisMK2_assembly01 Complete Genome representative genome 8 34318862 2021/02/10 00:00 ftp://ftp.ncbi.nlm.nih.gov/genomes/all/GCF/016/861/865/GCF_016861865.1_ApuulaauensisMK2_assembly01 ftp://ftp.ncbi.nlm.nih.gov/genomes/all/GCA/016/861/865/GCA_016861865.1_ApuulaauensisMK2_assembly01 1220207 2759 Aspergillus puulaauensis Aspergillus Aspergillaceae Eurotiales Ascomycota Eukaryota 4890 5042 1131492 5052 1220207 FALSE 1 4 Species_not_in_your_DB Species_not_in_your_DB 0 1 Seq_6 4 Fungi;Ascomycota;Eurotiomycetes;Eurotiales;Aspergillaceae;Aspergillus;Aspergillus_subversicolor NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA -1287682 9014201 GCA_016413765.1_ASM1641376v1 Complete Genome representative genome 8 33030366 2020/12/28 00:00 ftp://ftp.ncbi.nlm.nih.gov/genomes/all/GCA/016/413/765/GCA_016413765.1_ASM1641376v1 1287682 2759 Aspergillus felis Aspergillus Aspergillaceae Eurotiales Ascomycota Eukaryota 4890 5042 1131492 5052 1287682 TRUE 2 2 Matching Discrepant 0 2 Seq_15 1 Fungi;Ascomycota;Eurotiomycetes;Eurotiales;Aspergillaceae;Aspergillus;Aspergillus_sp Seq_9 1 Fungi;Ascomycota;Eurotiomycetes;Eurotiales;Aspergillaceae;Aspergillus;Aspergillus_unilateralis NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA -746128 22854281 GCA_040126065.1_UCR_Afum_AF100-9B_1.0 Complete Genome na 9 28371457 2024/06/11 00:00 ftp://ftp.ncbi.nlm.nih.gov/genomes/all/GCA/040/126/065/GCA_040126065.1_UCR_Afum_AF100-9B_1.0 746128 2759 Aspergillus fumigatus Aspergillus Aspergillaceae Eurotiales Ascomycota Eukaryota 4890 5042 1131492 5052 746128 TRUE 1 6 Matching Discrepant 0 1 Seq_4 6 Fungi;Ascomycota;Eurotiomycetes;Eurotiales;Aspergillaceae;Aspergillus;Aspergillus_sp NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA -5059 17700491 GCA_030515275.1_ASM3051527v1 Complete Genome na 8 37684129 2023/07/21 00:00 ftp://ftp.ncbi.nlm.nih.gov/genomes/all/GCA/030/515/275/GCA_030515275.1_ASM3051527v1 5059 2759 Aspergillus flavus Aspergillus Aspergillaceae Eurotiales Ascomycota Eukaryota 4890 5042 1131492 5052 5059 TRUE 10 19 Matching Partial_match 9 1 Seq_11 1 Fungi;Ascomycota;Eurotiomycetes;Eurotiales;Aspergillaceae;Aspergillus;Aspergillus_flavus Seq_12 1 Fungi;Ascomycota;Eurotiomycetes;Eurotiales;Aspergillaceae;Aspergillus;Aspergillus_flavus Seq_13 1 Fungi;Ascomycota;Eurotiomycetes;Eurotiales;Aspergillaceae;Aspergillus;Aspergillus_flavus Seq_14 1 Fungi;Ascomycota;Eurotiomycetes;Eurotiales;Aspergillaceae;Aspergillus;Aspergillus_pseudonomiae Seq_16 1 Fungi;Ascomycota;Eurotiomycetes;Eurotiales;Aspergillaceae;Aspergillus;Aspergillus_flavus Seq_17 1 Fungi;Ascomycota;Eurotiomycetes;Eurotiales;Aspergillaceae;Aspergillus;Aspergillus_flavus Seq_18 1 Fungi;Ascomycota;Eurotiomycetes;Eurotiales;Aspergillaceae;Aspergillus;Aspergillus_flavus Seq_3 7 Fungi;Ascomycota;Eurotiomycetes;Eurotiales;Aspergillaceae;Aspergillus;Aspergillus_flavus Seq_7 3 Fungi;Ascomycota;Eurotiomycetes;Eurotiales;Aspergillaceae;Aspergillus;Aspergillus_flavus Seq_8 2 Fungi;Ascomycota;Eurotiomycetes;Eurotiales;Aspergillaceae;Aspergillus;Aspergillus_flavus -5062 4373151 GCA_008032255.1_ASM803225v1 Complete Genome na 8 38484998 2019/08/20 00:00 ftp://ftp.ncbi.nlm.nih.gov/genomes/all/GCA/008/032/255/GCA_008032255.1_ASM803225v1 5062 2759 Aspergillus oryzae Aspergillus Aspergillaceae Eurotiales Ascomycota Eukaryota 4890 5042 1131492 5052 5062 FALSE 2 40 Species_not_in_your_DB Species_not_in_your_DB 0 2 Seq_1 39 Fungi;Ascomycota;Eurotiomycetes;Eurotiales;Aspergillaceae;Aspergillus;Aspergillus_flavus Seq_10 1 Fungi;Ascomycota;Eurotiomycetes;Eurotiales;Aspergillaceae;Aspergillus;Aspergillus_flavus NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA diff --git a/ressources/template_files/ITS_validation_set.tsv b/ressources/template_files/ITS_validation_set.tsv deleted file mode 100644 index d81fa9ca..00000000 --- a/ressources/template_files/ITS_validation_set.tsv +++ /dev/null @@ -1,9 +0,0 @@ -Sample Age_x Assay_Type AvgSpotLen Biomaterial_provider BioProject BioSample BioSampleModel Center_Name Consent DATASTORE_filetype DATASTORE_provider DATASTORE_region disease Ethnicity Experiment Instrument isolate Library_Name LibraryLayout LibrarySelection LibrarySource MBases MBytes Organism Platform ReleaseDate sample_acc Sample_Name sex SRA_Study tissue Collect_Date sample_name Sequencing_platform strategy length Nreads raw_reads clean_reads clean_data_raw_data duplication Clean_data_Mbp_ FMT_STD_number baseline_comparasion Sample_collection sample_number Randomization_arm time_point_post_treatment__FMTSTD,_week Age_y Sex donor_relationship_to_patient Household_ID_family_ID outcome Donor patient_group -SRR6308449 33 OTHER 595 Hong_Kong_(Prince_of_Wales_Hospital) PRJNA419104 SAMN08043085 Human THE_CHINESE_UNIVERSITY_OF_HONG_KONG public sra ncbi,s3,gs gs.US,s3.us-east-1,ncbi.public Control Chinese SRX3408940 Illumina_MiSeq not_applicable FCB3HKH_L1_NEWFECngaMBaA-17-54 PAIRED PCR METAGENOMIC 45 23 Homo_sapiens ILLUMINA 23.01.2018_01:00 SRS2701657 A119 male SRP125301 stool_fungi 22.03.2016 Control14 Illumina_Miseq PE300 300 0.13 205292 159234 77.56 0 47.37 Donor14 Control cross-sectional 119 FMT None 33 M Son N None Donor14 FMT_non_responders -SRR6308450 90 OTHER 593 Hong_Kong_(Prince_of_Wales_Hospital) PRJNA419104 SAMN08043086 Human THE_CHINESE_UNIVERSITY_OF_HONG_KONG public sra ncbi,s3,gs gs.US,s3.us-east-1,ncbi.public CDI Chinese SRX3408939 Illumina_MiSeq not_applicable FCB3HKH_L1_NEWFECngaMBaA-17-56 PAIRED PCR METAGENOMIC 84 43 Homo_sapiens ILLUMINA 23.01.2018_01:00 SRS2701656 A131 female SRP125301 stool_fungi 14.09.2016 F15W0 Illumina_Miseq PE300 300 0.14 629728 298766 47.44 0 88.58 FMT15 CDI longitudinal 131 FMT 0 90 F None O non_responders Donor15 FMT_non_responders -SRR6308451 81 OTHER 591 Hong_Kong_(Prince_of_Wales_Hospital) PRJNA419104 SAMN08043081 Human THE_CHINESE_UNIVERSITY_OF_HONG_KONG public sra gs,s3,ncbi ncbi.public,gs.US,s3.us-east-1 None Chinese SRX3408938 Illumina_MiSeq not_applicable FCB3HKH_L1_NEWFECngaMBaA-12-55 PAIRED PCR METAGENOMIC 23 12 Homo_sapiens ILLUMINA 23.01.2018_01:00 SRS2701655 A103 male SRP125301 stool_fungi 28.04.2016 F13W13 Illumina_Miseq PE300 297 0.14 196280 83390 42.49 0 24.64 FMT13 NA longitudinal 103 FMT 13 None None None M non_responders Donor13 FMT_non_responders -SRR6308452 43 OTHER 600 Hong_Kong_(Prince_of_Wales_Hospital) PRJNA419104 SAMN08043082 Human THE_CHINESE_UNIVERSITY_OF_HONG_KONG public sra ncbi,s3,gs gs.US,s3.us-east-1,ncbi.public None Chinese SRX3408937 Illumina_MiSeq not_applicable FCB3HKD_L1_wHAXPI052385-61 PAIRED PCR METAGENOMIC 35 19 Homo_sapiens ILLUMINA 23.01.2018_01:00 SRS2701653 A80 female SRP125301 stool_fungi 27.01.2016 Control13 Illumina_Miseq PE300 300 0.058 306382 124604 40.67 0 37.38 Donor13 NA longitudinal 80 Donor None 43 F Daughter M None Donor13 Donor_non_responders -SRR6308453 65 OTHER 594 Hong_Kong_(Prince_of_Wales_Hospital) PRJNA419104 SAMN08043083 Human THE_CHINESE_UNIVERSITY_OF_HONG_KONG public sra gs,s3,ncbi ncbi.public,gs.US,s3.us-east-1 CDI Chinese SRX3408936 Illumina_MiSeq not_applicable FCB3HKH_L1_NEWFECngaMBaA-19-53 PAIRED PCR METAGENOMIC 38 20 Homo_sapiens ILLUMINA 23.01.2018_01:00 SRS2701652 A113 male SRP125301 stool_fungi 21.03.2016 F14W0 Illumina_Miseq PE300 298 0.15 183402 137498 74.97 0 40.84 FMT14 CDI longitudinal 113 FMT 0 65 M None N non_responders Donor14 FMT_non_responders -SRR6308454 65 OTHER 592 Hong_Kong_(Prince_of_Wales_Hospital) PRJNA419104 SAMN08043084 Human THE_CHINESE_UNIVERSITY_OF_HONG_KONG public sra s3,gs,ncbi ncbi.public,gs.US,s3.us-east-1 None Chinese SRX3408935 Illumina_MiSeq not_applicable FCB3HKH_L1_NEWFECngaMBaA-12-54 PAIRED PCR METAGENOMIC 39 20 Homo_sapiens ILLUMINA 23.01.2018_01:00 SRS2701651 A115 male SRP125301 stool_fungi 29.04.2016 F14W4 Illumina_Miseq PE300 297 0.12 298982 138884 46.45 0 41.11 FMT14 NA longitudinal 115 FMT 4 None None None N non_responders Donor14 FMT_non_responders -SRR6308455 38 OTHER 592 Hong_Kong_(Prince_of_Wales_Hospital) PRJNA419104 SAMN08043077 Human THE_CHINESE_UNIVERSITY_OF_HONG_KONG public sra gs,ncbi,s3 s3.us-east-1,ncbi.public,gs.US None Chinese SRX3408934 Illumina_MiSeq not_applicable FCB3HKH_L1_DGNFECngaMFaA-8-42 PAIRED PCR METAGENOMIC 26 13 Homo_sapiens ILLUMINA 23.01.2018_01:00 SRS2701649 A68 female SRP125301 stool_fungi 05.11.2015 F12W4 Illumina_Miseq PE300 293 0.13 239996 94998 39.58 0 28.12 FMT12 NA longitudinal 68 FMT 4 None None None L non_responders Donor11 FMT_non_responders -SRR6308456 38 OTHER 587 Hong_Kong_(Prince_of_Wales_Hospital) PRJNA419104 SAMN08043078 Human THE_CHINESE_UNIVERSITY_OF_HONG_KONG public sra gs,ncbi,s3 gs.US,s3.us-east-1,ncbi.public None Chinese SRX3408933 Illumina_MiSeq not_applicable FCB3HKH_L1_NEWFECngaMBaA-15-48 PAIRED PCR METAGENOMIC 50 25 Homo_sapiens ILLUMINA 23.01.2018_01:00 SRS2701650 A69 female SRP125301 stool_fungi 28.12.2015 F12W10 Illumina_Miseq PE300 294 0.13 367404 180104 49.02 0 52.86 FMT12 NA longitudinal 69 FMT 10 None None None L non_responders Donor11 FMT_non_responders diff --git a/ressources/template_files/basic_snakemake_bash_command copy.sh b/ressources/template_files/basic_snakemake_bash_command copy.sh deleted file mode 100644 index eb01bfbb..00000000 --- a/ressources/template_files/basic_snakemake_bash_command copy.sh +++ /dev/null @@ -1,8 +0,0 @@ -snakemake \ - --snakefile /microbiome16S_pipeline>/Snakefile \ - --use-singularity --singularity-prefix \ - #or --use-conda --conda-prefix \ - --cores \ - --configfile config.yaml \ - --resources max_copy= mem_mb=\ - all #create_filtered_reads_multiqc_report PICRUSt2_output \ No newline at end of file diff --git a/ressources/template_files/cluster.json b/ressources/template_files/cluster.json deleted file mode 100644 index 867e4538..00000000 --- a/ressources/template_files/cluster.json +++ /dev/null @@ -1,82 +0,0 @@ -{ - "__default__" : - { - "queue" : "normal", - "n_cores" : "1", - "memory" : "4000", - "name" : "JOBNAME.{rule}.{wildcards}", - "output" : "logs/{rule}.{wildcards}.out", - "error" : "logs/{rule}.{wildcards}.err", - "hosts" : "span[hosts=1]", - "n_cores" : 1 - }, - - - "DADA2_learn_errors" : - { - "memory" : "8000", - "n_cores" : 4 - }, - - - "DADA2_infer_ASV" : - { - "memory" : "8000", - "n_cores" : 2 - }, - - "vsearch_derepicate_all" : - { - "memory" : "16000", - "n_cores" : 1 - }, - - "vsearch_cluster" : - { - "memory" : "16000", - "n_cores" : 1 - - }, - - "QIIME1_assign_taxonomy" : - { - "memory" : "40000", - "n_cores" : 1 - }, - - "melt_Phyloseq_object" : - { - "memory" : "8000", - "n_cores" : 1 - }, - - "rarefaction_curve" : - { - "memory" : "8000", - "n_cores" : 1 - }, - - "ordination_distance_based" : - { - "memory" : "8000", - "n_cores" : 1 - }, - - "ordination_unconstrained" : - { - "memory" : "8000", - "n_cores" : 1 - }, - - "ordination_constrained" : - { - "memory" : "8000", - "n_cores" : 1 - }, - - "picrust2_custom_tree" : - { - "memory" : "24000", - "n_cores" : 4 - }, -} diff --git a/ressources/template_files/config.yaml b/ressources/template_files/config.yaml deleted file mode 100644 index 67c18f8d..00000000 --- a/ressources/template_files/config.yaml +++ /dev/null @@ -1,65 +0,0 @@ -## Dataset ############################################################################################################ -### Set input samples list. (Mandatory, one of them or both) -link_directory: links # links by default. -sra_samples: example_sra_samples.tsv ## To access to Sequences Read Archive -local_samples: example_local_samples.tsv ## Local analysis - -### Sample processing metadata definition -run_column: seq_run # Column identifying the sequencing run (sequencing error are learned for each run with DADA2) - -### Choose the denoising approach, either the "vsearch" to generate classical 97% identity OTUs or the "DADA2" for errors corrected 100% ASVs -denoiser: ["DADA2", "vsearch"] # "vsearch", "DADA2" or both. For ITS (see below) only DADA2 has been tested. - -### PCR primers trimming sequences (with PANDAseq for vsearch approach and Cutadapt for DADA2) -Trim_primers: True # False to skip primers trimming (usefull if unkown primers or already trimmed in the input reads, for instance for SRA deposited reads) -ITS_or_16S: 16S # ITS or 16S, affects the primers trimming. With ITS, reverse occurence of the primer is allowed in the opposed read. -min_overlap: 20 # Min overlap of the reads for paired-end reads merging -forward_primer: CCTACGGGNGGCWGCAG # CCTACGGGNGGCWGCAG for Illumina V3V4 -reverse_primer: GACTACHVGGGTATCTAATCC # GACTACHVGGGTATCTAATCC for Illumina V3V4 - -### Merged sequences length-based filtering -merged_min_length: 390 # from 390 to 400 for V3V4 -merged_max_length: 480 # from 450 to 500 for V3V4 - -### DADA2 specific settings ########################################################################################### -#### Trim reads based on length. Reads shorter than this length are trimmed. -DADA2_F_reads_length_trim: 280 # 280 recommended to remove low quality ends of R1. Must be < than read_length - trimmed_primer_length -DADA2_R_reads_length_trim: 255 # 255 recommended to remove low quality ends of R2. Must be < than read_length - trimmed_primer_length - -### Filter reads based on number of expected errors after trimming -F_extected_error: 6 # 8 recommended, increase if too much reads are filtered out. Apply to DADA2 approach only -R_extected_error: 6 # 8 recommended, increase if too much reads are filtered out. Apply to DADA2 approach only - -### Taxonomic assignment ############################################################################################## -### Reference database -tax_DB_path: "/data/databases/amplicon_based_metagenomics/16S/" # The path to the reference databases -tax_DB_name: ["ezbiocloud201805.201909", "ezbiocloud201805.202005"] # The names of the databases. Must be the "tax_DB_name" provided when preprocessing the reference database. - -### Taxonimc classifier. -classifier: ["RDP", "qiimerdp", "dada2rdp","decipher"] # one or more from :"qiimerdp", "dada2rdp","decipher" - - -## Reads post-processing ############################################################################################## -rarefaction_value: [20000] # value for rarefaction. 20000 is generally enough reads but must be assessed by rarefaction curve. -min_prevalence: [0,10] # proporition (in %) of samples in which the feature has to be found to be kept -min_abundance: [0,100] # minimal count to be kept -normalization: ["CLR", "CSS","TMM", "NONE"] # value for counts normalization. Using script form https://github.com/biobakery/Maaslin2 -viz_replace_empty_tax: TRUE # "TRUE" is recommended to replace empty taxonomic levels by "_sp" for species, "_g" for the genus etc.... Otherwise they are left empty -filter_tax_rank: ["Kingdom"] # At which taxonomic rank we filter in. (Usually "Kingdom") -filter_lineage: ["Bacteria"] # What value of filter_tax_rank we keep. (Usually "Bacteria") -filter_out_tax_rank: ["Phylum"] # At which taxonomic rank we filter out. (Usually "Phylum") -filter_out_lineage: ["Bacteria_phy"] # Which value of filter_out_tax_rank we filter out. (Usually "Bacteria_phy" to remove suspicious sequences assigned as Bacteria but which are probably not. - -## Visualization ###################################################################################################### -grouping_column: ["sample_group"] # Column for which a plot will be generated for each value of. Rarefaction curve will be only generated with colors from the first value in the list -sample_label: sample_label # Column in "local_samples" used to label samples in output. Cannot be the first column and must be unique. (KRONA, heatmaps, barplots) - - -## Special outputs ############################################################################################################## -### Phyloseq output for external plitting and analysis -melted_phyloseq: False # Generate melted phyloseq table (voluminous) -phyloseq_tax_ranks: ["OTU"] # Which collapse level for phyloseq output -transposed_tables: False # True to have transposed count table - -### Qiime2 Visualization information -Qiime2_basic_output_visualization: True # or False diff --git a/ressources/template_files/config_DB.yaml b/ressources/template_files/config_DB.yaml deleted file mode 100644 index 00b2ba6b..00000000 --- a/ressources/template_files/config_DB.yaml +++ /dev/null @@ -1,21 +0,0 @@ -## Path to the input database files -DBpath_seq: "path/to/your/referenceDB/sequences.fasta" # reference genomic sequence in .fasta format -DBpath_tax: path/to/your/referenceDB/taxonomy.txt" # reference taxonomy of these sequences. - -## Output -tax_DB_path: "path/to/where/you/store/databases/" # path where to write the output of the database processing pipeline. -tax_DB_name: "Name_of_your_database" # desired name of processed database (name of the directory which will contain the output) - -## Amplicons extraction -extract_and_merge: True # False to skip the extraction of gerne region and merging of taxa with identical sequence on this region. -forward_primer: CCTACGGGNGGCWGCAG # the forward PCR primer used to extract the amplicon -reverse_primer: GACTACHVGGGTATCTAATCC # the reverse PCR primer used to extract the amplicon -amplicon_min_length: 300 # the min length of the amplicon -amplicon_max_length: 500 # the max length of the amplicon -excepted_errors: 4 # the accepted number of mistmach per primer -amplicon_min_coverage: 0.9 # the proportion of the primers that must be found in the reference sequences. - -## Taxa collapsing -numbers_species: 4 # the max number of species names to be pasted together. Over this number, taxonomic names will be replaced by a space holder. -numbers_genus: 2 # the max number of genus names to be pasted together. Over this number, taxonomic names will be replaced by a space holder. - diff --git a/ressources/template_files/config_in_silico_validation.yaml b/ressources/template_files/config_in_silico_validation.yaml deleted file mode 100644 index 35453e50..00000000 --- a/ressources/template_files/config_in_silico_validation.yaml +++ /dev/null @@ -1,48 +0,0 @@ -################ Validation config ################ - -### Input table, listing the taxonomy and number of assemblies to test from -input_table_path: '16S_input_table_insilico.tsv' - -### In silico PCR parameters, used by Simulate_PCR -forward_primer: CCTACGGGNGGCWGCAG # CCTACGGGNGGCWGCAG for Illumina V3V4 -reverse_primer: GACTACHVGGGTATCTAATCC # GACTACHVGGGTATCTAATCC for Illumina V3V4 -mismatch: 3 #0 - 3 mismatch -threeprime: 2 # Number of match at the 3' end for a hit to be considered - -### Amplicon triming parameters, used by cutadapt -excepted_errors: 0.1 ## Proportion of mismatch tolerated allowed in the primers to be trimmed -amplicon_min_coverage: 0.8 ## Covered proporition of the primer to be trimmed -merged_min_length: 390 # from 390 to 400 for V3V4 -merged_max_length: 500 # from 450 to 500 for V3V4 - -### Database used for taxonomic assignment -tax_DB_path: "/data/databases/amplicon_based_metagenomics/16S/" -tax_DB_name: [ezbiocloud201805.202005] # must be the name of the folder containing files named "DB_amp.fasta" and "DB_amp_taxonomy.txt" in /data/. Can be multiple. -classifier: ["qiimerdp"] - -### Processing for comparison of taxonomic assignment -viz_replace_empty_tax: TRUE - -########### Assembly finder config ################ -NCBI_key: '6dce38824889f62e188a25ae35c52a083c08' -NCBI_email: 'valentin.scherz@chuv.ch' - -##Parameters for search_assemblies function -#This set of parameters is to search all possible assemblies -complete_assemblies: True ## Keep only complete assemblies -reference_assemblies: False ## Keep only reference assemblies -representative_assemblies: False ## Keep only representative assemblies -exclude_from_metagenomes: True ## Excluse from metagenome -Genbank_assemblies: True ## Take from Genbank -Refseq_assemblies: True ## Take from Refseq - - -##Parameters for the filtering function -Rank_to_filter_by: 'None' -#None: Assemblies are ranked by their assembly status (complete or not) -#and Refseq category (reference, representative ...) -#If you want to filter by species, set this parameter to 'species'. The filtering function will list all unique species -#and rank their assemblies according to assembly status and Refseq category. - - - diff --git a/ressources/template_files/example_local_samples.tsv b/ressources/template_files/example_local_samples.tsv deleted file mode 100644 index 1f4bde6f..00000000 --- a/ressources/template_files/example_local_samples.tsv +++ /dev/null @@ -1,41 +0,0 @@ -# Minimal columns description. Should be removed before use ! -# Table must be 'tsv' format, meaning tablute separated values. -# -# Columns description. All but the first one 'Sample' can be renamed. -# -## Sample: -### Samples identifier. Must contain only uniques values, that are NOT only numerical and -### which do not contains '-' or '.'. '_' are OK. -### -### Shoud match what is before '_L001_R*_001.fastq.gz' in the reads. If not, an 'OldSampleName' column matching the read -### names can be added in addition. An error will be thrown if not exactly one pair of reads in 'links/' matches each -### value contained in this columns. -### A'OldSampleName' column can be used to bypass this limitation. Then the content of this column will be used for -### matching, and the samples will be be renamed by the content of 'Sample' column. -# -# -## sample_label -### Samples description. Should be unique but can be adapted to best describe the samples in plots, tables etc... -# -# -## sample_group -### Describe group of samples which should be group togheter in generated output. For instance, will create on KRONA -### plot by value of this column (e.g. Patients IDs). We can also use this column to filter out unwanted values for -### plotting (e.g. QC samples). -# -# -## color_factor -### Description of the type of samples, for instance the source of the sample. -# -# -## seq_run -### Should be not numerique only. Describe the sequencing run. Used to generate run specific MultiQC reports. Used as -### well by DADA2 which generate an error profile for each run. -# -#Example: -Sample sample_label sample_group seq_run -S1111 sample_1 patient1 run1 -MIX9B sample_2 patient1 run1 -MIX7B sample_3 patient2 run1 -MIX10B sample_4 patient2 run1 -MIX7A QC_1 QC run1 diff --git a/ressources/template_files/example_sra_samples.tsv b/ressources/template_files/example_sra_samples.tsv deleted file mode 100644 index 32a6fa99..00000000 --- a/ressources/template_files/example_sra_samples.tsv +++ /dev/null @@ -1,5 +0,0 @@ -Run SampleName LibraryLayout Replicate Group1 -SRR2081062 MIX9A paired 1 1 -SRR2081062 MIX9B paired 1 1 -SRR2081037 MIX7B paired 2 2 -SRR2081037 MIX7A paired 2 2 diff --git a/ressources/template_files/insilico_ITS_input_taxID.tsv b/ressources/template_files/insilico_ITS_input_taxID.tsv deleted file mode 100644 index 564e69a9..00000000 --- a/ressources/template_files/insilico_ITS_input_taxID.tsv +++ /dev/null @@ -1,9 +0,0 @@ -taxon -1069201 -182096 -41058 -1220207 -1287682 -746128 -5059 -5062 From 6cd50e4605f611fb927c68fdda63c1d57355eaca Mon Sep 17 00:00:00 2001 From: farchaab Date: Mon, 9 Dec 2024 12:04:18 +0100 Subject: [PATCH 2/4] remove outdated DBprocess --- DBprocess.Md | 20 -------------------- 1 file changed, 20 deletions(-) delete mode 100644 DBprocess.Md diff --git a/DBprocess.Md b/DBprocess.Md deleted file mode 100644 index a8670e26..00000000 --- a/DBprocess.Md +++ /dev/null @@ -1,20 +0,0 @@ -## DB processing : - -Preparation of reference databases for our amplicon-based metagenomic snakemake pipeline is here embedded as an ad-hoc snakemake pipeline. It takes as input a reference database containing a fasta file (sequences) and a taxonomy file (corresponding taxonomy) in the Qiime format. - -**This ad-hoc reference database preparation pipeline relies on :** - - A dedicated snakefile (Snakefile_DB) to run this ad-hoc pipeline. - - Configuration file (config_DB.yaml) to point at the input and output paths and defines parameter for amplicons extraction and taxonomy formatting. - -In brief, through the few rules in "EzBioCloud.rules", we will import the taxonomy and sequences in Qiime2 ".qza" format, and extract the region targeted by the PCR primers defined in the config file using Qiime2 "qiime feature-classifier extract-reads" function. Then, the sequences will be dereplicated (to keep only unique sequences) using vsearch. Following dereplication, taxonomy description will be merged to describe the different taxa that could correspond to a given sequences. A parameter in config allows to define up to how many Genus/Species names will merged. If there are more taxa matching a sequence, then a spaceholder (".sp") will be used. The number of taxa matching a specific taxonomic ID will also be written. - - -**To compile a new database :** -- Create a new directory in "/data" using "{dbname}{version}.{preparationyearmonth} -- In this directory, include a Readme.Md file describing the origin of the reference database. -- Locate the fasta, taxonomy files and the desired output path (just created directory) in configfile. -- Run the following command from the just created directory. -``` -snakemake --snakefile $pipeline_folder/DBprocess.Snakefile --use-conda --conda-prefix $condaprefix --cores 1 --configfile config_DB.yaml -``` -- Add to git the output fasta and taxonomy as well as the configfile, the generated logs and the Readme describing the origin of the database to enable traceability. From 4984301fc922b412b912a9b7cc059e26bb38c931 Mon Sep 17 00:00:00 2001 From: farchaab Date: Mon, 9 Dec 2024 12:08:28 +0100 Subject: [PATCH 3/4] constrain snakemake version --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index b5d06bc1..a34a5017 100644 --- a/setup.py +++ b/setup.py @@ -53,7 +53,7 @@ def get_data_files(): data_files=get_data_files(), py_modules=["zamp"], install_requires=[ - "snakemake>=8.10.6", + "snakemake>=8.0.0,<=8.24.1", "Click>=8.1.3", "attrmap>=0.0.7", "snaketool-utils>=0.0.5", From 1e8b6f5b48b1ed83f9b819b57fbc728386c71571 Mon Sep 17 00:00:00 2001 From: farchaab Date: Mon, 9 Dec 2024 14:48:56 +0100 Subject: [PATCH 4/4] update PR template --- .github/pull_request_template.md | 19 +++++-------------- 1 file changed, 5 insertions(+), 14 deletions(-) diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 043f6cba..a5887771 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,17 +1,8 @@ -PR for version +## Summary +Describe the PR's purpose -# Summary +## Additions -# Details +## Changes -## `Additions` - -* [commit hash] commit summary - -## `Changes` - -* [commit hash] commit summary - -## `Fixes` - -* [commit hash] commit summary \ No newline at end of file +## Fixes \ No newline at end of file