Merge branch 'CW-1044' into 'dev'

demo data de

See merge request epi2melabs/workflows/wf-transcriptomes!76
This commit is contained in:
Sarah Griffiths 2022-11-08 18:07:49 +00:00
commit 248ecdb55b
18 changed files with 318 additions and 220 deletions

View File

@ -46,10 +46,10 @@ docker-run:
NF_IGNORE_PROCESSES: preprocess_reads,merge_transcriptomes
- if: $MATRIX_NAME == "differential_expression"
variables:
NF_BEFORE_SCRIPT: wget -O differential_expression.tar.gz https://ont-exd-int-s3-euwst1-epi2me-labs.s3.amazonaws.com/wf-isoforms/wf-isoforms_differential_expression.tar.gz && tar -xzvf differential_expression.tar.gz
NF_WORKFLOW_OPTS: "--fastq differential_expression_dataset/fastq \
NF_BEFORE_SCRIPT: tar -xzvf test_data/differential_expression.tar.gz
NF_WORKFLOW_OPTS: "--fastq differential_expression/differential_expression_fastq \
--de_analysis \
--ref_genome differential_expression_dataset/hg38_chr20.fa \
--ref_annotation differential_expression_dataset/gencode.v22.annotation.chr20.gtf \
--ref_genome differential_expression/hg38_chr20.fa \
--ref_annotation differential_expression/gencode.v22.annotation.chr20.gtf \
--direct_rna"
NF_IGNORE_PROCESSES: preprocess_reads,merge_transcriptomes

View File

@ -22,8 +22,13 @@ repos:
hooks:
- id: flake8
additional_dependencies:
- flake8-import-order==0.18.1
- flake8-docstrings==1.6.0
- flake8-rst-docstrings==0.2.5
- flake8-forbid-visual-indent==0.0.2
- flake8-rst-docstrings
- flake8-docstrings
- flake8-import-order
- flake8-forbid-visual-indent
- pep8-naming
- flake8-no-types
- flake8-builtins
- flake8-absolute-import
- flake8-print
entry: flake8 bin --import-order-style google --statistics

View File

@ -4,13 +4,20 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [unreleased]
### Updated
- Removed sanitize option
### Added
- Demo differential expression data in repository.
- Improved DE explanation in docs
## [v0.1.5]
### Added
- Differential transcript and gene expression subworkflow
## [v0.1.4]
### Added
- JAFFAL fusion detectoion subworkflow
- JAFFAL fusion detection subworkflow
### Changed
- Args parser for fastqingress
- Set out_dir option type to ensure output is written to correct directory on Windows

View File

@ -41,12 +41,38 @@ Fusion gene detection is performed using [JAFFA](https://github.com/Oshlack/JAFF
with ONT long reads.
### Differential expression analysis
* Differential expression is done using the transcripts output by the workflow.
* A non redundant transcriptome is found using the merge function in [stringtie](http://ccb.jhu.edu/software/stringtie).
* The reads are then aligned to the transcriptome using minimap2 in a splice-aware manner.
* [salmon](https://github.com/COMBINE-lab/salmon) is used for transcript quantification.
* R packages [edgeR](https://bioconductor.org/packages/release/bioc/html/edgeR.html) and [stageR](https://bioconductor.org/packages/release/bioc/html/stageR.html) are used for differential expression analysis.
* [DEXSeq](https://bioconductor.org/packages/release/bioc/html/DEXSeq.html) is then used for differential transcript usage analysis.
Differential gene expression (DGE) and differential transcript usage (DTU) analyses aim to identify genes and/or transcripts that show statistically altered expression patterns in a studied biological system. The results of the differential analyses are presented in a quantitative format and therefore the degree of change (up or down regulation) between experimental conditions can be calculated for each gene identified.
These differential analyses work by taking a “snapshot” of mRNA abundance and calculating the relative levels of transcripts and isoforms. In this context, expression corresponds to the number of messenger RNAs (mRNA) measured from each gene isoform within the organism / tissue / culture being investigated. In order to determine expression levels across the whole genome, sequence data specifically targeting the mRNA molecules can be generated.
Oxford Nanopore Technologies provides a number of sequencing solutions to allow users to generate the required snapshot of gene expression. This can be achieved by both sequencing the mRNA directly, or via a complementary DNA (cDNA) proxy. In contrast to short read sequencing technologies, entire mRNA transcripts can be captured as single reads. The example data provided with this tutorial is from a study based on the PCR-cDNA kit. This is a robust choice for performing differential transcript usage studies. This kit is suitable for preparation of sequence libraries from low mRNA input quantities. The cDNA population is enriched through PCR with low bias; an important prerequisite for the subsequent statistical analysis.
[Workflow-transcriptomes](https://github.com/epi2me-labs/wf-transcriptomes) includes a subworkflow for DGE and DTU. The first step involves using either a reference alignment or _de novo_ assembly approach to create a set of mRNA sequences per sample. These are merged into a non-redundant transcriptome using [stringtie merge](http://ccb.jhu.edu/software/stringtie). The reads are then aligned to the transcriptome using minimap2 in a splice-aware manner. [Salmon](https://github.com/COMBINE-lab/salmon) is used for transcript quantification, giving per transcript counts and then the following R packages are used for analysis.
### Pre-filtering of quantitative data using DRIMSeq
DRIMSeq (Nowicka and Robinson (2016)) is used to filter the transcript count data from the salmon analysis. The filter step will be used to select for genes and transcripts that satisfy rules for the number of samples in which a gene or transcript must be observed and minimum threshold levels for the number of observed reads. The parameters used for filtering are defined in the config.yaml file. The default parameters defined for this analysis include
* min_samps_gene_expr = 3 - a transcript must be mapped to a gene in at least this minimum number of samples for the gene be included in the analysis
* min_samps_feature_expr = 1 - a transcript must be mapped to an isoform in at least this this minimum number of samples for the gene isoform to be included in the analysis
* min_gene_expr = 10 - the minimum number of total mapped sequence reads for a gene to be considered expressed
* min_feature_expr = 3 - the minimum number of total mapped sequence reads for a gene isoform to be considered
### edgeR based differential expression analysis
+A statistical analysis is first performed using edgeR (Robinson, McCarthy, and Smyth (2010), McCarthy et al. (2012)) to identify the subset of differentially expressed genes. The filtered list of gene counts is used as input. A normalisation factor is calculated for each sequence library (using the default TMM method - please see McCarthy et al. (2012) for further details). The defined experimental design is used to calculate estimates of dispersion for each of the gene features. Statistical tests are calculated using the contrasts defined in the experimental design. The differentially expressed genes are corrected for false discovery (fdr) using the method of Benjamini & Hochberg (Benjamini and Hochberg (1995))
### Differential transcript usage using DEXSeq
Differential transcript usage analysis is performed using the R DEXSeq package (Reyes et al. (2013)). Similar to the edgeR package, DEXSeq estimates the variance between the biological replicates and applies generalised linear models for the statistical testing. The key difference is that the DEXSeq method looks for differences at the exon count level. DEXSeq uses the filtered transcript count data prepared earlier in this analysis.
### StageR stage-wise analysis of DGE and DTU
The final component of this isoform analysis is a stage-wise statistical test using the R software package `stageR` (Van den Berge and Clement (2018)). stageR uses (1) the raw p-values for DTU from the DEXSeq analysis in the previous section and (2) a false-discovery corrected set of p-values from testing whether individual genes contain at least one exon showing DTU. A hierarchical two-stage statistical testing evaluates the set of genes for DTU.
## Running the workflow
For the differential expression analysis section you should have at least 3 repeats for each sample.
Your fastq data will need to be organised in to 6 directories that represent 3 repeats for each condition. You may also need to provide a condition sheet.
## Analysis
Differential gene expression is sensitive to the input data quantity and quality. There should be equivalence between samples in the number of sequence reads, mapped reads and quality scores. The sequence and alignment summary plots in the report can be used to assess these metrics. There is also a table that shows the transcript per million(TPM) calculated from the salmon counts. TPM normalizes the data for gene length and then sequencing depth, and makes it easier to compare across samples compared to counts.
### Workflow inputs
- Directory containing cDNA/direct RNA reads. Or a directory containing subdirectories each with reads from different samples
@ -178,20 +204,37 @@ Differential Expression requires at least 2 replicates of each sample to compare
**Example workflow for differential expression transcript assembly**
Download differential expression data set
#### Condition sheet
The condition sheet should be a .tsv with two columns.
- The sample column will need to match the 6 directories in the input fastq directory.
- The condition column will need to contain one of two keys to indicate the two samples being compared.
`wget -O differential_expression.tar.gz https://ont-exd-int-s3-euwst1-epi2me-labs.s3.amazonaws.com/wf-isoforms/wf-isoforms_differential_expression.tar.gz && tar -xzvf differential_expression.tar.gz`
In the default `condition_sheet.tsv` available in the test_data directory we have used the following.
Run the cmd
eg. condition_sheet.tsv
```
sample,condition
barcode01,untreated
barcode02,untreated
barcode03,untreated
barcode04,treated
barcode05,treated
barcode06,treated
```
You will also need to provide a reference genome and a reference annotation file.
Here is an example cmd to run the workflow using the test_data provided.
```
OUTPUT=~/output;
nexflow run epi2me-labs/wf-transcriptomes --fastq differential_expression_dataset/fastq --de_analysis \
--ref_genome differential_expression_dataset/hg38_chr20.fa \
--ref_annotation differential_expression_dataset/gencode.v22.annotation.chr20.gtf \
nexflow run epi2me-labs/wf-transcriptomes --fastq test_data/differential_expression_fastq \
--de_analysis \
--ref_genome test_data/hg38_chr20.fa \
--ref_annotation test_data/gencode.v22.annotation.chr20.gtf \
--direct_rna
```
## Workflow outputs
* an HTML report document detailing the primary findings of the workflow.
* for each sample:
@ -201,15 +244,25 @@ nexflow run epi2me-labs/wf-transcriptomes --fastq differential_expression_datas
* merged_transcritptome.fas - annotated, assembled transcriptome
* [jaffal](https://github.com/Oshlack/JAFFA) ooutput directories
### Fusion detection outputs
in `${out_dir}/jaffal_output_${sample_id}` you will find:
* jaffa_results.csv - the csv results summary file
* jaffa_results.fasta - fusion transcritpt sequences
### Differential Expression outputs
* dtu_plots.pdf - a pdf with differntial transcript usage plots
## Useful links
* `de_analysis/results_dge.tsv` and `de_analysis/results_dge.pdf`- results of `edgeR` differential gene expression analysis.
* `de_analysis/results_dtu_gene.tsv`, `de_analysis/results_dtu_transcript.tsv` and `de_analysis/results_dtu.pdf` - results of differential transcript usage by `DEXSeq`.
* `de_analysis/results_dtu_stageR.tsv` - results of the `stageR` analysis of the `DEXSeq` output.
* `de_analysis/dtu_plots.pdf` - DTU results plot based on the `stageR` results and filtered counts.
### References
* Benjamini, Yoav, and Yosef Hochberg. 1995. “Controlling the False Discovery Rate: A Practical and Powerful Approach to Multiple Testing.” Journal of the Royal Statistical Society. Series B (Methodological) 57 (1): 289300. http://www.jstor.org/stable/2346101.
* McCarthy, Davis J., Chen, Yunshun, Smyth, and Gordon K. 2012. “Differential Expression Analysis of Multifactor Rna-Seq Experiments with Respect to Biological Variation.” Nucleic Acids Research 40 (10): 428897.
* Nowicka, Malgorzata, and Mark D. Robinson. 2016. “DRIMSeq: A Dirichlet-Multinomial Framework for Multivariate Count Outcomes in Genomics [Version 2; Referees: 2 Approved].” F1000Research 5 (1356). https://doi.org/10.12688/f1000research.8900.2.
* Patro, Robert, Geet Duggal, Michael I Love, Rafael A Irizarry, and Carl Kingsford. 2017. “Salmon Provides Fast and Bias-Aware Quantification of Transcript Expression.” Nature Methods 14 (March). https://doi.org/10.1038/nmeth.4197.
* Robinson, Mark D, Davis J McCarthy, and Gordon K Smyth. 2010. “EdgeR: A Bioconductor Package for Differential Expression Analysis of Digital Gene Expression Data.” Bioinformatics 26 (1): 13940.
* Love, Michael I., et al. Swimming Downstream: Statistical Analysis of Differential Transcript Usage Following Salmon Quantification. 7:952, F1000Research, 14 Sept. 2018. f1000research.com, https://f1000research.com/articles/7-952## Useful links
* [nextflow](https://www.nextflow.io/)
* [docker](https://www.docker.com/products/docker-desktop)

View File

@ -9,6 +9,7 @@ import argparse
from collections import defaultdict
import math
from pathlib import Path
import sys
import matplotlib
from matplotlib import pyplot as plt
@ -61,7 +62,6 @@ def parse_true_clusters(ref_file):
# deal with supplementary alignments!!
if read.is_secondary or read.is_supplementary:
continue
# print(read.query_name, read.flag)
assert prev_read_id != read.query_name
chrom = read.reference_name
@ -89,7 +89,6 @@ def parse_true_clusters(ref_file):
# if chrom not in class_ranges:
# class_ranges[chrom] = {}
# print(chrom, read_ref_start, read_ref_end)
# for start, stop in class_ranges[chrom]:
# if start <= read_ref_start and read_ref_end <= stop:
# # entirly within
@ -112,8 +111,8 @@ def parse_true_clusters_simulated(ref_file):
return classes
def compute_V_measure(clusters, classes):
"""compute_V_measure."""
def compute_v_measure(clusters, classes):
"""compute_v_measure."""
class_list, cluster_list = [], []
# not_found_id = 1000000
clustered_but_unaligned = 0
@ -122,7 +121,6 @@ def compute_V_measure(clusters, classes):
class_list.append(classes[read])
cluster_list.append(clusters[read])
else:
# print("Read was clustered but unaligned:", read)
clustered_but_unaligned += 1
# added the unprocessed reads to the measure
@ -139,22 +137,22 @@ def compute_V_measure(clusters, classes):
homog_score = homogeneity_score(class_list, cluster_list)
ari = adjusted_rand_score(class_list, cluster_list)
print("Not included in clustering but aligned:", len(not_clustered))
print(
"V:",
sys.stdout("Not included in clustering but aligned:", len(not_clustered))
sys.stdout(
"v:",
v_score,
"Completeness:",
compl_score,
"Homogeneity:",
homog_score)
print(
sys.stdout(
"Nr reads clustered but unaligned "
"(i.e., no class and excluded from V-measure): ",
"(i.e., no class and excluded from v-measure): ",
clustered_but_unaligned)
return v_score, compl_score, homog_score, clustered_but_unaligned, ari
def compute_V_measure_non_singleton_classes(clusters, classes):
def compute_v_measure_non_singleton_classes(clusters, classes):
"""V measure for non-singleton classes."""
max_cluster_id = max(clusters.values())
new_id = max_cluster_id + 1
@ -188,19 +186,19 @@ def compute_V_measure_non_singleton_classes(clusters, classes):
homog_score = homogeneity_score(class_list, cluster_list)
nr_filtered_classes = len(
[1 for cl_id in classes_dict if len(classes_dict[cl_id]) >= 5])
print(
"NONTRIVIAL CLASSES: V:",
sys.stdout(
"NONTRIvIAL CLASSES: v:",
v_score,
"Completeness:",
compl_score,
"Homogeneity:",
homog_score)
print("NUMBER OF CLASSES (FILTERED):", len(
sys.stdout("NUMBER OF CLASSES (FILTERED):", len(
[1 for cl_id in classes_dict if len(classes_dict[cl_id]) >= 5]))
return v_score, compl_score, homog_score, nr_filtered_classes
def compute_V_measure_non_singletons(clusters, classes):
def compute_v_measure_non_singletons(clusters, classes):
"""V measure for non-singletons."""
cluster_dict = {}
for read_acc, cl_id in clusters.items():
@ -225,46 +223,46 @@ def compute_V_measure_non_singletons(clusters, classes):
class_list.append(classes[read])
cluster_list.append(clusters[read])
else:
# print("Read was clustered but unaligned:", read)
# sys.stdout("Read was clustered but unaligned:", read)
clustered_but_unaligned += 1
v_score = v_measure_score(class_list, cluster_list)
compl_score = completeness_score(class_list, cluster_list)
homog_score = homogeneity_score(class_list, cluster_list)
print(
"NONTRIVIAL CLUSTERS: V:",
sys.stdout(
"NONTRIvIAL CLUSTERS: v:",
v_score,
"Completeness:",
compl_score,
"Homogeneity:",
homog_score)
print(
"NONTRIVIAL CLUSTERS: Nr reads clustered but unaligned "
"(i.e., no class and excluded from V-veasure): ",
sys.stdout(
"NONTRIvIAL CLUSTERS: Nr reads clustered but unaligned "
"(i.e., no class and excluded from v-veasure): ",
clustered_but_unaligned)
return v_score, compl_score, homog_score, clustered_but_unaligned
def percentile(N, percent, key=lambda x: x):
def percentile(n, percent, key=lambda x: x):
"""
Find the percentile of a list of values.
@parameter N - is a list of values. Note N MUST BE already sorted.
@parameter n - is a list of values. Note N MUST BE already sorted.
@parameter percent - a float value from 0.0 to 1.0.
@parameter key - optional key function to compute value
from each element of N.
@return - the percentile of the values
"""
if not N:
if not n:
return None
k = (len(N) - 1) * percent
k = (len(n) - 1) * percent
f = math.floor(k)
c = math.ceil(k)
if f == c:
return key(N[int(k)])
d0 = key(N[int(f)]) * (c - k)
d1 = key(N[int(c)]) * (k - f)
return key(n[int(k)])
d0 = key(n[int(f)]) * (c - k)
d1 = key(n[int(c)]) * (k - f)
return d0 + d1
# end of http://code.activestate.com/recipes/511478/ }}}
@ -364,11 +362,11 @@ def get_cluster_information(clusters, classes):
else:
clustered_classes[class_id] += 1
print("UNCLUSTERED:", "Tot classes:", len(not_clustered_classes))
print("CLUSTERED:", "Tot classes:", len(clustered_classes))
print("MIXED:", "Tot classes containing both:", len(
sys.stdout("UNCLUSTERED:", "Tot classes:", len(not_clustered_classes))
sys.stdout("CLUSTERED:", "Tot classes:", len(clustered_classes))
sys.stdout("MIXED:", "Tot classes containing both:", len(
set(clustered_classes.keys()) & set(not_clustered_classes.keys())))
print("Total number of classes (unique gene ID):", total_nr_classes)
sys.stdout("Total number of classes (unique gene ID):", total_nr_classes)
return (
total_nr_classes - len(singleton_classes),
len(singleton_classes),
@ -411,7 +409,7 @@ def main(args):
classes, tot_nr_reads, unclassified = parse_true_clusters(ref_file)
v_score, compl_score, homog_score, clustered_but_unaligned, ari = \
compute_V_measure(clusters, classes)
compute_v_measure(clusters, classes)
(
nr_non_singleton_classes, singleton_classes, min_class_size,
@ -451,30 +449,31 @@ def main(args):
e_class_size,
n50_class_size))
# Reads_nontrivially_clustered_(%), Singletons_(%),
# Reads_Nontrivially_clustered_but_unaligned, V, c,h ,V_nt, c_nt,h_nt,
# reads_nontrivially_clustered_(%), Singletons_(%),
# reads_nontrivially_clustered_but_unaligned, v, c,h ,v_nt, c_nt,h_nt,
# non_singleton_clusters, min, max, median, mean
Reads_nontrivially_clustered_percent = round(
reads_nontrivially_clustered_percent = round(
100 * (float(tot_nr_reads - singleton_clusters) / tot_nr_reads), 1)
# round(1.0 - Reads_nontrivially_clustered_percent, 2)
Reads_Nontrivially_clustered_but_unaligned = \
# round(1.0 - reads_nontrivially_clustered_percent, 2)
reads_nontrivially_clustered_but_unaligned = \
unaligned_but_nontrivially_clustered
V, c, h = round(v_score, 3), round(compl_score, 3), round(homog_score, 3)
v, c, h = round(v_score, 3), round(compl_score, 3), round(homog_score, 3)
non_singleton_clusters = total_nr_clusters - singleton_clusters
print("NONTRIVIAL CLUSTERS: ", (total_nr_clusters - singleton_clusters))
sys.stdout(
"NONTRIVIAL CLUSTERS: ", (total_nr_clusters - singleton_clusters))
outfile.write("CLUSTERS\n")
outfile.write(
"{0},{1},{2},{3},{4},{5},{6},{7},{8},{9},{10},{11}\n".format(
"V",
"v",
"c",
"h",
"ARI",
"Reads_nontrivially_clustered_percent",
"Reads_Nontrivially_clustered_but_unaligned",
"reads_nontrivially_clustered_percent",
"reads_nontrivially_clustered_but_unaligned",
"non_singleton_clusters",
"singleton_clusters",
"upper_75_cluster_size",
@ -483,12 +482,12 @@ def main(args):
"n50_cluster_size"))
outfile.write(
"{0},{1},{2},{3},{4},{5},{6},{7},{8},{9},{10},{11}\n".format(
V,
v,
c,
h,
ari,
Reads_nontrivially_clustered_percent,
Reads_Nontrivially_clustered_but_unaligned,
reads_nontrivially_clustered_percent,
reads_nontrivially_clustered_but_unaligned,
non_singleton_clusters,
singleton_clusters,
upper_75_cluster_size,
@ -508,19 +507,19 @@ def main(args):
dfc = pd.DataFrame(
{
'Statistic': [
'V-measure',
'v-measure',
'ARI',
'Completeness',
'Homogeneity'],
'Value': [
V,
'value': [
v,
ari,
c,
h]}).set_index('Statistic')
dfn = pd.DataFrame(
{'Statistic': ['NonSingleton',
'Singletons'],
'Value': [non_singleton_clusters,
'value': [non_singleton_clusters,
singleton_clusters]}).set_index('Statistic')
dfs = pd.DataFrame(
{
@ -529,14 +528,14 @@ def main(args):
'Upper75ClassSize',
'MedianClsSize',
'MedianClassSize'],
'Value': [
'value': [
upper_75_cluster_size,
upper_75_class_size,
median_cluster_size,
median_class_size]}).set_index('Statistic')
dfs2 = pd.DataFrame(
{'Statistic': ['N50ClsSize', 'N50ClassSize'],
'Value': [n50_cluster_size, n50_class_size]
'value': [n50_cluster_size, n50_class_size]
}).set_index('Statistic')
rdo = Path(args.raw_data_out)
@ -648,6 +647,6 @@ if __name__ == '__main__':
help='dir to save raw data for plotting')
args = parser.parse_args()
print("------------------------------------------------------------")
sys.stdout("------------------------------------------------------------")
main(args)
print("------------------------------------------------------------")
sys.stdout("------------------------------------------------------------")

View File

@ -36,7 +36,7 @@ def number_of_alignments(df, field_name):
def create_summary_table(df):
"""Create summary table."""
all = number_of_alignments(df, "Read mappings")
all_aln = number_of_alignments(df, "Read mappings")
primary = number_of_alignments(df.loc[df['Type'] == 'Primary'], "Primary")
secondary = number_of_alignments(
df.loc[df['Type'] == 'Secondary'], "Secondary")
@ -47,7 +47,7 @@ def create_summary_table(df):
avg_mapq = df.loc[df['Type'] == 'Primary'].groupby(
'fname').agg(**{"Median MAPQ": ('MapQual', 'median'), }).transpose()
return pd.concat([
all, primary, secondary, supplementary,
all_aln, primary, secondary, supplementary,
avg_acc, avg_mapq])

View File

@ -44,14 +44,15 @@ def generate_tracking_summary(tracking_file, output_dir, annotations=None):
# write a separate table for each class
for class_code, table in tracking.groupby('class'):
if not write_empty_tsvs and table.empty:
print("Skipping: No transcripts found for: {}".format(
sys.stdout("Skipping: No transcripts found for: {}".format(
class_code))
continue
path = tracking_file + ".{}.tsv".format(class_code)
table.to_csv(path)
else:
print("Skipping classification summary as no annotation provided.")
sys.stdout(
"Skipping classification summary as no annotation provided.")
def main(args):

View File

@ -6,6 +6,7 @@ from collections import Counter, defaultdict, OrderedDict
import math
import os
from pathlib import Path
import sys
from aplanat import bars, hist
from aplanat.components import simple as scomponents
@ -289,7 +290,7 @@ def grouped_bar(df, title="", tilted_xlabs=False):
return p
def gff_compare_plots(report, gffcompare_outdirs: Path, sample_ids):
def gff_compare_plots(report, gffcompare_outdirs, sample_ids):
"""Create various sections and plots in a WfReport.
:param report: aplanat WFReport
@ -387,7 +388,6 @@ def gff_compare_plots(report, gffcompare_outdirs: Path, sample_ids):
tracking_dfs = []
print(gffcompare_outdirs)
track_files = [x / 'str_merged.tracking' for x in gffcompare_outdirs]
df_tracking = load_sample_data(
@ -423,7 +423,7 @@ def gff_compare_plots(report, gffcompare_outdirs: Path, sample_ids):
})
cols = [TableColumn(
field=Ci, title=Ci, width=100) for Ci in tracking.columns]
field=ci, title=ci, width=100) for ci in tracking.columns]
track_table = DataTable(
columns=cols, source=ColumnDataSource(tracking),
@ -473,7 +473,7 @@ def gff_compare_plots(report, gffcompare_outdirs: Path, sample_ids):
try:
tmap_files = [next(x.glob('*.tmap')) for x in gffcompare_outdirs]
except StopIteration:
print("Cannot find .tmap files in {}".format(gffcompare_outdirs))
sys.stderr("Cannot find .tmap files in {}".format(gffcompare_outdirs))
return
df_tmap = load_sample_data(tmap_files, sample_ids)
@ -621,7 +621,7 @@ def transcript_table(report, df_tmaps, max_rows):
'FPKM', 'qry_gene_id', 'major_iso_id', 'ref_match_len', 'TPM'])
if len(df) == 0:
print("No transcripts found")
sys.stderr("No transcripts found")
section.markdown("No transcripts found")
return
@ -739,7 +739,7 @@ def transcriptome_summary(report, gffs, sample_ids, denovo=False):
df_sum.columns = [' ', 'count']
cols = [TableColumn(
field=Ci, title=Ci, width=80) for Ci in df_sum.columns]
field=ci, title=ci, width=80) for ci in df_sum.columns]
data_table = DataTable(
columns=cols, source=ColumnDataSource(df_sum),
index_position=None, width=180)

View File

@ -11,14 +11,14 @@ import subprocess as sub
class Node:
"""Node."""
def __init__(self, Id, File, Left, Right, Parent, Level):
def __init__(self, node_id, file_, left, right, parent, level):
"""Set node attaributes."""
self.Id = Id
self.File = File
self.Left = Left
self.Right = Right
self.Parent = Parent
self.Level = Level
self.Id = node_id
self.File = file_
self.Left = left
self.Right = right
self.Parent = parent
self.Level = level
self.Done = False
self.RightSide = False
@ -45,35 +45,35 @@ def grouper(n, iterable, fillvalue=None):
def build_job_tree():
"""Build a job tree of nodes."""
JOB_TREE = OrderedDict()
job_tree = OrderedDict()
batches = glob("batches/isONbatch_*.cer")
batch_ids = [
int(re.search(
'batches/isONbatch_(.*)\\.cer$', x).group(1))
for x in batches]
LEVELS = OrderedDict()
LEVELS[0] = []
for Id, bf in sorted(zip(batch_ids, batches), key=lambda x: x[0]):
levels = OrderedDict()
levels[0] = []
for k, bf in sorted(zip(batch_ids, batches), key=lambda x: x[0]):
n = Node(
Id,
"clusters/isONcluster_{}.cer".format(Id),
k,
"clusters/isONcluster_{}.cer".format(k),
None,
None,
None,
0)
n.Done = True
JOB_TREE[Id] = n
LEVELS[0].append(n)
job_tree[k] = n
levels[0].append(n)
level = 0
max_id = LEVELS[0][-1].Id
while len(LEVELS[level]) != 1: # Final level will be link
max_id = levels[0][-1].k
while len(levels[level]) != 1: # Final level will be link
next_level = level + 1
LEVELS[next_level] = []
for l_, r in grouper(2, LEVELS[level]):
levels[next_level] = []
for l_, r in grouper(2, levels[level]):
if r is None: # End of a level
LEVELS[level].pop() # remove last node?
levels[level].pop() # remove last node?
l_.Level += 1 # ncrement level
LEVELS[next_level].append(l_) # Add the left to the next level
levels[next_level].append(l_) # Add the left to the next level
continue
max_id += 1
new_batch = "clusters/isONcluster_{}.cer".format(max_id)
@ -81,13 +81,13 @@ def build_job_tree():
l_.Parent = new_node
r.Parent = new_node
r.RightSide = True
LEVELS[next_level].append(new_node)
JOB_TREE[max_id] = new_node
levels[next_level].append(new_node)
job_tree[max_id] = new_node
level = next_level
ROOT = JOB_TREE[len(JOB_TREE) - 1].Id
JOB_TREE[ROOT].RightSide = True
root = job_tree[len(job_tree) - 1].k
job_tree[root].RightSide = True
return JOB_TREE, LEVELS
return job_tree, levels
def main():
@ -121,7 +121,7 @@ def main():
sub.call(cmd, shell=True)
sub.call((
"ln -s `realpath clusters/isONcluster_{}.cer` "
"isONcluster_ROOT.cer".format(n.Id)), shell=True)
"isONcluster_root.cer".format(n.Id)), shell=True)
if __name__ == '__main__':

View File

@ -33,12 +33,38 @@ Fusion gene detection is performed using [JAFFA](https://github.com/Oshlack/JAFF
with ONT long reads.
### Differential expression analysis
* Differential expression is done using the transcripts output by the workflow.
* A non redundant transcriptome is found using the merge function in [stringtie](http://ccb.jhu.edu/software/stringtie).
* The reads are then aligned to the transcriptome using minimap2 in a splice-aware manner.
* [salmon](https://github.com/COMBINE-lab/salmon) is used for transcript quantification.
* R packages [edgeR](https://bioconductor.org/packages/release/bioc/html/edgeR.html) and [stageR](https://bioconductor.org/packages/release/bioc/html/stageR.html) are used for differential expression analysis.
* [DEXSeq](https://bioconductor.org/packages/release/bioc/html/DEXSeq.html) is then used for differential transcript usage analysis.
Differential gene expression (DGE) and differential transcript usage (DTU) analyses aim to identify genes and/or transcripts that show statistically altered expression patterns in a studied biological system. The results of the differential analyses are presented in a quantitative format and therefore the degree of change (up or down regulation) between experimental conditions can be calculated for each gene identified.
These differential analyses work by taking a “snapshot” of mRNA abundance and calculating the relative levels of transcripts and isoforms. In this context, expression corresponds to the number of messenger RNAs (mRNA) measured from each gene isoform within the organism / tissue / culture being investigated. In order to determine expression levels across the whole genome, sequence data specifically targeting the mRNA molecules can be generated.
Oxford Nanopore Technologies provides a number of sequencing solutions to allow users to generate the required snapshot of gene expression. This can be achieved by both sequencing the mRNA directly, or via a complementary DNA (cDNA) proxy. In contrast to short read sequencing technologies, entire mRNA transcripts can be captured as single reads. The example data provided with this tutorial is from a study based on the PCR-cDNA kit. This is a robust choice for performing differential transcript usage studies. This kit is suitable for preparation of sequence libraries from low mRNA input quantities. The cDNA population is enriched through PCR with low bias; an important prerequisite for the subsequent statistical analysis.
[Workflow-transcriptomes](https://github.com/epi2me-labs/wf-transcriptomes) includes a subworkflow for DGE and DTU. The first step involves using either a reference alignment or _de novo_ assembly approach to create a set of mRNA sequences per sample. These are merged into a non-redundant transcriptome using [stringtie merge](http://ccb.jhu.edu/software/stringtie). The reads are then aligned to the transcriptome using minimap2 in a splice-aware manner. [Salmon](https://github.com/COMBINE-lab/salmon) is used for transcript quantification, giving per transcript counts and then the following R packages are used for analysis.
### Pre-filtering of quantitative data using DRIMSeq
DRIMSeq (Nowicka and Robinson (2016)) is used to filter the transcript count data from the salmon analysis. The filter step will be used to select for genes and transcripts that satisfy rules for the number of samples in which a gene or transcript must be observed and minimum threshold levels for the number of observed reads. The parameters used for filtering are defined in the config.yaml file. The default parameters defined for this analysis include
* min_samps_gene_expr = 3 - a transcript must be mapped to a gene in at least this minimum number of samples for the gene be included in the analysis
* min_samps_feature_expr = 1 - a transcript must be mapped to an isoform in at least this this minimum number of samples for the gene isoform to be included in the analysis
* min_gene_expr = 10 - the minimum number of total mapped sequence reads for a gene to be considered expressed
* min_feature_expr = 3 - the minimum number of total mapped sequence reads for a gene isoform to be considered
### edgeR based differential expression analysis
+A statistical analysis is first performed using edgeR (Robinson, McCarthy, and Smyth (2010), McCarthy et al. (2012)) to identify the subset of differentially expressed genes. The filtered list of gene counts is used as input. A normalisation factor is calculated for each sequence library (using the default TMM method - please see McCarthy et al. (2012) for further details). The defined experimental design is used to calculate estimates of dispersion for each of the gene features. Statistical tests are calculated using the contrasts defined in the experimental design. The differentially expressed genes are corrected for false discovery (fdr) using the method of Benjamini & Hochberg (Benjamini and Hochberg (1995))
### Differential transcript usage using DEXSeq
Differential transcript usage analysis is performed using the R DEXSeq package (Reyes et al. (2013)). Similar to the edgeR package, DEXSeq estimates the variance between the biological replicates and applies generalised linear models for the statistical testing. The key difference is that the DEXSeq method looks for differences at the exon count level. DEXSeq uses the filtered transcript count data prepared earlier in this analysis.
### StageR stage-wise analysis of DGE and DTU
The final component of this isoform analysis is a stage-wise statistical test using the R software package `stageR` (Van den Berge and Clement (2018)). stageR uses (1) the raw p-values for DTU from the DEXSeq analysis in the previous section and (2) a false-discovery corrected set of p-values from testing whether individual genes contain at least one exon showing DTU. A hierarchical two-stage statistical testing evaluates the set of genes for DTU.
## Running the workflow
For the differential expression analysis section you should have at least 3 repeats for each sample.
Your fastq data will need to be organised in to 6 directories that represent 3 repeats for each condition. You may also need to provide a condition sheet.
## Analysis
Differential gene expression is sensitive to the input data quantity and quality. There should be equivalence between samples in the number of sequence reads, mapped reads and quality scores. The sequence and alignment summary plots in the report can be used to assess these metrics. There is also a table that shows the transcript per million(TPM) calculated from the salmon counts. TPM normalizes the data for gene length and then sequencing depth, and makes it easier to compare across samples compared to counts.
### Workflow inputs
- Directory containing cDNA/direct RNA reads. Or a directory containing subdirectories each with reads from different samples

View File

@ -122,20 +122,37 @@ Differential Expression requires at least 2 replicates of each sample to compare
**Example workflow for differential expression transcript assembly**
Download differential expression data set
#### Condition sheet
The condition sheet should be a .tsv with two columns.
- The sample column will need to match the 6 directories in the input fastq directory.
- The condition column will need to contain one of two keys to indicate the two samples being compared.
`wget -O differential_expression.tar.gz https://ont-exd-int-s3-euwst1-epi2me-labs.s3.amazonaws.com/wf-isoforms/wf-isoforms_differential_expression.tar.gz && tar -xzvf differential_expression.tar.gz`
In the default `condition_sheet.tsv` available in the test_data directory we have used the following.
Run the cmd
eg. condition_sheet.tsv
```
sample,condition
barcode01,untreated
barcode02,untreated
barcode03,untreated
barcode04,treated
barcode05,treated
barcode06,treated
```
You will also need to provide a reference genome and a reference annotation file.
Here is an example cmd to run the workflow using the test_data provided.
```
OUTPUT=~/output;
nexflow run epi2me-labs/wf-transcriptomes --fastq differential_expression_dataset/fastq --de_analysis \
--ref_genome differential_expression_dataset/hg38_chr20.fa \
--ref_annotation differential_expression_dataset/gencode.v22.annotation.chr20.gtf \
nexflow run epi2me-labs/wf-transcriptomes --fastq test_data/differential_expression_fastq \
--de_analysis \
--ref_genome test_data/hg38_chr20.fa \
--ref_annotation test_data/gencode.v22.annotation.chr20.gtf \
--direct_rna
```
## Workflow outputs
* an HTML report document detailing the primary findings of the workflow.
* for each sample:
@ -145,11 +162,22 @@ nexflow run epi2me-labs/wf-transcriptomes --fastq differential_expression_datas
* merged_transcritptome.fas - annotated, assembled transcriptome
* [jaffal](https://github.com/Oshlack/JAFFA) ooutput directories
### Fusion detection outputs
in `${out_dir}/jaffal_output_${sample_id}` you will find:
* jaffa_results.csv - the csv results summary file
* jaffa_results.fasta - fusion transcritpt sequences
### Differential Expression outputs
* dtu_plots.pdf - a pdf with differntial transcript usage plots
* `de_analysis/results_dge.tsv` and `de_analysis/results_dge.pdf`- results of `edgeR` differential gene expression analysis.
* `de_analysis/results_dtu_gene.tsv`, `de_analysis/results_dtu_transcript.tsv` and `de_analysis/results_dtu.pdf` - results of differential transcript usage by `DEXSeq`.
* `de_analysis/results_dtu_stageR.tsv` - results of the `stageR` analysis of the `DEXSeq` output.
* `de_analysis/dtu_plots.pdf` - DTU results plot based on the `stageR` results and filtered counts.
### References
* Benjamini, Yoav, and Yosef Hochberg. 1995. “Controlling the False Discovery Rate: A Practical and Powerful Approach to Multiple Testing.” Journal of the Royal Statistical Society. Series B (Methodological) 57 (1): 289300. http://www.jstor.org/stable/2346101.
* McCarthy, Davis J., Chen, Yunshun, Smyth, and Gordon K. 2012. “Differential Expression Analysis of Multifactor Rna-Seq Experiments with Respect to Biological Variation.” Nucleic Acids Research 40 (10): 428897.
* Nowicka, Malgorzata, and Mark D. Robinson. 2016. “DRIMSeq: A Dirichlet-Multinomial Framework for Multivariate Count Outcomes in Genomics [Version 2; Referees: 2 Approved].” F1000Research 5 (1356). https://doi.org/10.12688/f1000research.8900.2.
* Patro, Robert, Geet Duggal, Michael I Love, Rafael A Irizarry, and Carl Kingsford. 2017. “Salmon Provides Fast and Bias-Aware Quantification of Transcript Expression.” Nature Methods 14 (March). https://doi.org/10.1038/nmeth.4197.
* Robinson, Mark D, Davis J McCarthy, and Gordon K Smyth. 2010. “EdgeR: A Bioconductor Package for Differential Expression Analysis of Digital Gene Expression Data.” Bioinformatics 26 (1): 13940.
* Love, Michael I., et al. Swimming Downstream: Statistical Analysis of Differential Transcript Usage Following Salmon Quantification. 7:952, F1000Research, 14 Sept. 2018. f1000research.com, https://f1000research.com/articles/7-952

View File

@ -6,6 +6,7 @@ channels:
- defaults
dependencies:
- python==3.8.*
- bokeh==2.4.3
- aplanat>=0.6.4
- epi2melabs
- minimap2==2.24

View File

@ -1,7 +1,15 @@
import ArgumentParser
process handleSingleFile {
// Downstream tooling assumes FASTQ files are nicely organised into directories.
// In the case where a single FASTQ file has been input and the parent directory
// contains other valid FASTQ, we will create a directory in the work area to
// hold it instead. We stage the file in with `copy` (rather than `link`)
// to ensure that when the new dir is mounted to containers downstream it does
// not contain a symlink that cannot be read.
// See CW-1154
process isolateSingleFile {
label params.process_label
stageInMode 'copy'
cpus 1
input:
file reads
@ -9,7 +17,6 @@ process handleSingleFile {
path "$reads.simpleName"
script:
def name = reads.simpleName
def reads_dir = 'reads_dir'
"""
mkdir $name
mv $reads $name
@ -51,7 +58,9 @@ def compareSampleSheetFastq(int sample_sheet_count, int valid_dir_count)
/**
* Take an input file and sample name to return a channel with
* a single named sample.
* a single named sample. If the input file is in a directory with other valid
* input files (or other directories containing valid files), a copy of it will
* be made to the working directory using the isolateSingleFile process.
*
*
* @param input_file Single fastq file
@ -61,9 +70,16 @@ def compareSampleSheetFastq(int sample_sheet_count, int valid_dir_count)
def handle_single_file(input_file, sample_name)
{
singleFile = Channel.fromPath(input_file)
sample = handleSingleFile(singleFile)
ArrayList valid_files_in_dir = find_fastq(input_file.parent, true)
if (valid_files_in_dir.size() == 1) {
// Avoid a stageInMode copy if the parent directory contains only one valid FASTQ anyway
return singleFile.map { it -> tuple(it.parent, create_metamap([sample_id:sample_name ?: it.simpleName])) }
}
else {
// Isolate the file via copy with isolateSingleFile
sample = isolateSingleFile(singleFile)
return sample.map { it -> tuple(it, create_metamap([sample_id:sample_name ?: it.simpleName])) }
}
}
@ -72,70 +88,45 @@ def handle_single_file(input_file, sample_name)
* method.
*
* @param pattern file object corresponding to top level input folder.
* @param maxdepth maximum depth to traverse
* @param search_subdirs boolean flag to search subdirectories of pattern
* @return list of files.
*/
def find_fastq(pattern, maxdepth)
def find_fastq(pattern, search_subdirs)
{
files = []
extensions = ["fastq", "fastq.gz", "fq", "fq.gz"]
ArrayList files = []
ArrayList extensions = ["fastq", "fastq.gz", "fq", "fq.gz"]
for (ext in extensions) {
files += file(pattern.resolve("*.${ext}"), type: 'file', maxdepth: maxdepth)
if (search_subdirs) {
files += file(pattern.resolve("**.${ext}"), type: 'file')
}
else {
files += file(pattern.resolve("*.${ext}"), type: 'file')
}
}
return files
}
/**
* Rework EPI2ME flattened directory structure into standard form
* files are matched on barcode\d+ and moved into corresponding
* subdirectories ready for processing.
*
* @param input_folder Top-level input directory.
* @param staging Top-level output_directory.
* @return A File object representating the staging directory created
* under output
*/
def sanitize_fastq(input_folder, staging)
{
// TODO: this fails if input_folder is an S3 path
log.info "Running sanitization."
log.info " - Moving files: ${input_folder} -> ${staging}"
staging.mkdirs()
files = find_fastq(input_folder.resolve("**"), 1)
for (fastq in files) {
fname = fastq.getFileName()
// find barcode
pattern = ~/barcode\d+/
matcher = fname =~ pattern
if (!matcher.find()) {
// not barcoded - leave alone
fastq.renameTo(staging.resolve(fname))
} else {
bc_dir = file(staging.resolve(matcher[0]))
bc_dir.mkdirs()
fastq.renameTo(staging.resolve("${matcher[0]}/${fname}"))
}
}
log.info " - Finished sanitization."
return staging
}
/**
* Take an input directory return the barcode and non barcode
* sub directories contained within.
*
*
* @param input_directory Top level input folder to locate sub directories
* @param unclassified Keep unclassified directory
*
* @return A list containing sublists of barcode and non_barcode sub directories
*/
def get_subdirectories(input_directory)
def get_subdirectories(input_directory, unclassified)
{
barcode_dirs = file(input_directory.resolve("barcode*"), type: 'dir', maxdepth: 1)
all_dirs = file(input_directory.resolve("*"), type: 'dir', maxdepth: 1)
if (!unclassified) {
all_dirs.removeIf(it -> it.SimpleName.toLowerCase() == "unclassified")
}
non_barcoded = (all_dirs + barcode_dirs) - all_dirs.intersect(barcode_dirs)
return [barcode_dirs, non_barcoded]
}
@ -183,7 +174,7 @@ def get_valid_directories(input_dirs)
invalid_files_dirs = []
for (d in input_dirs) {
valid = true
fastq = find_fastq(d, 1)
fastq = find_fastq(d, false)
all_files = file(d.resolve("*"), type: 'file', maxdepth: 1)
non_fastq = ( all_files + fastq ) - all_files.intersect(fastq)
@ -345,10 +336,9 @@ def create_metamap(Map arguments) {
* @param input Top level input file or folder to locate fastq data.
* @param sample string to name single sample data.
* @param sample_sheet Path to sample sheet CSV file.
* @param sanitize regularize inputs from EPI2ME platform.
* @param output output location, required if sanitize==true
* @param min_barcode Minimum barcode to accept.
* @param max_barcode Maximum (inclusive) barcode to accept.
* @param unclassified Keep unclassified reads.
*
* @return Channel of tuples (path, map(sample_id, type, barcode))
*/
@ -357,15 +347,12 @@ def fastq_ingress(Map arguments)
def parser = new ArgumentParser(
args:["input"],
kwargs:[
"sample":null, "sample_sheet":null, "sanitize":false, "output":null,
"min_barcode":0, "max_barcode":Integer.MAX_VALUE],
"sample":null, "sample_sheet":null,
"min_barcode":0, "max_barcode":Integer.MAX_VALUE,
"unclassified":false],
name:"fastq_ingress")
Map margs = parser.parse_args(arguments)
if (margs.sanitize && margs.output == null) {
throw new Exception("Argument 'output' required if 'sanitize' is true.")
}
log.info "Checking fastq input."
input = file(margs.input)
@ -382,14 +369,8 @@ def fastq_ingress(Map arguments)
// Handle directory input
if (input.isDirectory()) {
// EPI2ME harness
if (margs.sanitize) {
staging = file(margs.output).resolve("staging")
input = sanitize_fastq(input, staging)
}
// Get barcoded and non barcoded subdirectories
(barcoded, non_barcoded) = get_subdirectories(input)
(barcoded, non_barcoded) = get_subdirectories(input, margs.unclassified)
// Case 03: If no subdirectories, handle the single dir
if (!barcoded && !non_barcoded) {

View File

@ -475,6 +475,7 @@ workflow pipeline {
de_report = de.all_de
count_transcripts_file = de.count_transcripts
dtu_plots = de.dtu_plots
de_outputs = de.de_outputs
} else{
de_report = file("$projectDir/data/OPTIONAL_FILE")
count_transcripts_file = file("$projectDir/data/OPTIONAL_FILE")
@ -538,7 +539,7 @@ workflow pipeline {
}
if (params.de_analysis){
results = results.concat(de.dtu_plots)
results = results.concat(de.dtu_plots, de_outputs)
}
emit:
@ -617,9 +618,7 @@ workflow {
reads = fastq_ingress([
"input":params.fastq,
"sample":params.sample,
"sample_sheet":params.sample_sheet,
"sanitize": params.sanitize_fastq,
"output":params.out_dir])
"sample_sheet":params.sample_sheet])
pipeline(reads, ref_genome, ref_annotation,
jaffal_refBase, params.jaffal_genome, params.jaffal_annotation,

View File

@ -22,7 +22,6 @@ params {
out_dir = "output"
sample = null
sample_sheet = null
sanitize_fastq = false
wfversion = "v0.1.5"
aws_image_prefix = null
aws_queue = null
@ -246,3 +245,7 @@ trace {
enabled = true
file = "${params.out_dir}/execution/trace.txt"
}
env {
PYTHONNOUSERSITE = 1
}

File diff suppressed because one or more lines are too long

View File

@ -51,6 +51,7 @@ process deAnalysis {
path "merged/all_gene_counts.tsv", emit: gene_counts
path "de_analysis/results_dge.tsv", emit: dge
path "de_analysis/results_dexseq.tsv", emit: dexseq
path "de_analysis", emit: de_analysis
"""
cp $annotation annotation.gtf
@ -74,18 +75,17 @@ process plotResults {
path flt_count
path res_dtu
path condition_sheet
path de_analysis
output:
path "de_analysis/dtu_plots.pdf", emit: dtu_plots
path "condition_sheet.tsv", emit: condition_sheet_tsv
path "de_analysis", emit: stageR
"""
mkdir merged
mkdir de_analysis
mv $res_dtu de_analysis/results_dtu_stageR.tsv
mv $condition_sheet de_analysis/coldata.tsv
mv $flt_count merged/all_counts_filtered.tsv
plot_dtu_results.R
cp de_analysis/coldata.tsv condition_sheet.tsv
mv de_analysis/coldata.tsv condition_sheet.tsv
"""
}
@ -144,7 +144,7 @@ workflow differential_expression {
merged = mergeCounts(count_transcripts.out.counts.collect())
merged_TPM = mergeTPM(count_transcripts.out.counts.collect())
analysis = deAnalysis(condition_sheet, merged, ref_annotation)
plotResults(analysis.flt_counts, analysis.stageR, condition_sheet)
plotResults(analysis.flt_counts, analysis.stageR, condition_sheet, analysis.de_analysis)
de_report = analysis.flt_counts.combine(analysis.gene_counts).combine(analysis.dge).combine(analysis.dexseq).combine(
analysis.stageR).combine(plotResults.out.condition_sheet_tsv).combine(merged).combine(
ref_annotation).combine(merged_TPM)
@ -153,4 +153,5 @@ emit:
all_de = de_report
count_transcripts = count_transcripts_file
dtu_plots = plotResults.out.dtu_plots
de_outputs = plotResults.out.stageR
}

View File

@ -1,7 +1,6 @@
sample,condition,type
barcode01,untreated,single-read
barcode02,untreated,single-read
barcode03,untreated,single-read
barcode04,treated,single-read
barcode05,treated,single-read
barcode06,treated,single-read
sample,condition
barcode01,untreated
barcode02,untreated
barcode04,treated
barcode05,treated
barcode06,treated

1 sample,condition,type sample,condition
2 barcode01,untreated,single-read barcode01,untreated
3 barcode02,untreated,single-read barcode02,untreated
4 barcode03,untreated,single-read barcode04,treated
5 barcode04,treated,single-read barcode05,treated
6 barcode05,treated,single-read barcode06,treated
barcode06,treated,single-read