15 Nextflow编码实践

Nextflow 是一种强大而灵活的语言,可以通过多种方式进行编码。这可能导致一些不好的编码实践。例如,这可能导致工作流只能在某些配置或执行平台下工作。下面是一些有用的编码技巧,它们可以使维护和移植工作流更加容易。

使用空格提高可读性

Nextflow 通常对代码中的空格不敏感。这允许使用缩进、垂直间距、新行和空格来提高代码的可读性。例如:

#! /usr/bin/env nextflow

// Tip: Allow spaces around assignments ( = )
nextflow.enable.dsl = 2

// Tip: Separate blocks of code into groups with common purpose
//      e.g., parameter blocks, include statements, workflow blocks, process blocks
// Tip: Align assignment operators vertically in a block
params.reads     = ''
params.gene_list = ''
params.gene_db   = 'ftp://path/to/database'

// Tip: Align braces or instruction parts vertically
include { BAR        } from 'modules/bar'
include { TAN as BAZ } from 'modules/tan'

workflow {

    // Tip: Indent process calls
    // Tip: Use spaces around process/function parameters
    FOO ( Channel.fromPath( params.reads, checkIfExists: true ) )
    BAR ( FOO.out )
    // Tip: Use vertical spacing and indentation for many parameters.
    BAZ (
        Channel.fromPath( params.gene_list, checkIfExists: true ),
        FOO.out,
        BAR.out,
        file( params.gene_db, checkIfExists: true )
    )

}

// Tip: Uppercase process names help readability
process FOO {

    // Tip: Separate process parts into distinct blocks
    input:
    path fastq

    output:
    path "*.fasta"

    script:
    prefix = fastq.baseName
    """
    tofasta $fastq > $prefix.fasta
    """
}

使用注释

注释能提高代码的可读性和可维护性。可以在以下地方添加注释:

  • 注释通道中的期望的输入数据结构。
  • 描述高阶的函数。
  • 描述是否存在(非)预期的代码。
  • 必须和可选的进程的输入。
workflow ALIGN_SEQ {

    take:
    reads        // queue channel; [ sample_id, [ file(read1), file(read2) ] ]
    reference    // file( "path/to/reference" )

    main:
    // Quality Check input reads
    READ_QC ( reads )

    // Align reads to reference
    Channel.empty()
        .set { aligned_reads_ch }
    if( params.aligner == 'hisat2' ){
        ALIGN_HISAT2 ( READ_QC.out.reads, reference )
        aligned_reads_ch.mix( ALIGN_HISAT2.out.bam )
            .set { aligned_reads_ch }
    } else if ( params.aligner == 'star' ) {
        ALIGN_STAR ( READ_QC.out.reads, reference )
        aligned_reads_ch.mix( ALIGN_STAR.out.bam )
            .set { aligned_reads_ch }
    }
    aligned_reads_ch.view()

    emit:
    bam = aligned_reads_ch   // queue channel: [ sample_id, file(bam_file) ]

}

process COUNT_KMERS {

    input:
    // Mandatory
    tuple val(sample), path(reads)  // [ 'sample_id', [ read1, read2 ] ]: Reads in which to count kmers
    // Optional
    path kmer_table                 // 'path/to/kmer_table': Table of k-mers to count

    ...
}

收集工具版本

软件打包是一个难题,对于一个包来说,报告它所拥有的所有工具的版本是很困难的。在只使用少量工具的情况下,报告包中包含的所有内容的版本也可能有些过分。这意味着我们要有效地报告我们用来提高可重现性的工具的版本。

process HISAT2_ALIGN {

    ...

    script:
    def HISAT2_VERSION = '2.2.0' // Version not available using command-line
    """
    hisat2 ... | samtools ...

    cat <<-END_VERSIONS > versions.yml
    "${task.process}":
        hisat2: $HISAT2_VERSION
        samtools: \$( samtools --version 2>&1 | sed 's/^.*samtools //; s/Using.*\$//' )
    END_VERSIONS
    """
}

命名输出channel

process和workflow的输出channel可以使用关键字emit来命名,例如:


    ...

    emit:
    alignment = HISAT2_ALIGN.out.bam

}

process HISAT2_ALIGN {

    ...

    output:
    tuple val(sample), path("*.bam"), emit: bam
    tuple val(sample), path("*.log"), emit: summary
    path "versions.yml"             , emit: versions

    ...
}

在workflow中使用parmas.xxx,而不是process块

可以从工作流中的任何地方访问 params 变量。它们可以提供各种各样的属性和决策选项。例如:

process ALIGN {

    input:
    tuple val(sample), path(reads)
    path index

    ...

    script:
    if ( params.aligner == 'hisat2' ) {
        """
        hisat2 ... | samtools ...

        ...
        """
    } else if ( params.aligner == 'star' ){
        """
        star ...

        ...
        """
    }
}

更好的做法是将其作为输入参数。

process ALIGN {

    input:
    tuple val(sample), path(reads)
    path index
    val aligner       // params.aligner is provided as a third parameter

    ...

    script:
    if ( aligner == 'hisat2' ) {
        """
        hisat2 ... | samtools ...

        ...
        """
    } else if ( aligner == 'star' ){
        """
        star ...

        ...
        """
    }
}

这使得人们可以从workflow块中查看所有参数的使用情况,从而使其更容易维护。还有一种危险是,在管道执行期间可能修改params变量,从而可能导致更复杂的工作流中出现不可重复的结果。

所有的文件/目录都应该是process的输入

你执行工作流的平台,文件可能很容易通过网络访问,或下载。然而,并非所有执行平台都支持这一点。下面的示例可以在你的系统上很好地工作,但在另一个系统上失败(例如计算没有互联网连接)。

process READ_CHECK {

    input:
    tuple val(sample), path(reads)

    ...

    script:
    """
    wget ftp://path/to/database

    check_reads $reads /local/copy/database > $sample.report
    ...
    """
}

Nextflow 的一个优势是文件暂存,即在process任务中准备使用的文件。暂存文件通过提供作为process输入有几个好处。

  • 文件仅在必要时才会更新;
  • 一个文件/目录可以被共享,而不用多次下载;
  • Nextflow 支持从任何有效的 URL 中检索文件,这意味着代码行可能更少。
process READ_CHECK {

    input:
    tuple val(sample), path(reads)
    path database

    ...

    script:
    """
    check_reads $reads $database > $sample.report
    ...
    """
}

如果文件可能是一个可选的输入,这取决于其他参数。在不应该提供任何文件的情况下,可以传递一个空列表[]来代替。

workflow {

    COUNT_KMERS ( reads, [] )
}

process COUNT_KMERS {

    input:
    // Mandatory
    tuple val(sample), path(reads)  // [ 'sample_id', [ read1, read2 ] ]: Reads in which to count kmers
    // Optional
    path kmer_table                 // 'path/to/kmer_table': Table of k-mers to count

    ...
}

避免大量短时间运行的进程

如果工作流试图执行许多短时间运行的process,那么许多执行平台都是低效的。与将短流程打包成更大的流程任务相比,为每个小实例调度和请求资源可能需要更多的时间。Nextflow 提供了方便的channel操作符,例如 buffercollat​​ecollectcollectFile,这有助于将输入组合成批处理,这些批处理可以使用给定的请求资源运行更长时间。短任务本身也可以在process脚本中使用命令行工具 xargsparallel进行并行化。

workflow REFINE_DATA {

    take:
    datapoints

    main:
    BATCH_TASK ( datapoints.collate(100) )
}

process BATCH_TASK {

    input:
    val data

    script:
    """
    printf "%s\\n" $data | \
        xargs -P $task.cpus -I {} \
        short_task {}

    # Alternative:
    # parallel --jobs $task.cpus "short_task {1}" :::: $data
    """
}

包含测试profile

test profile指定一个短的运行的测试数据集,用于检查整个管道的功能。它还可以向工作流的用户演示期望的输入和输出类型。另一个好处是可以对工作流进行自动化测试,确保在添加新功能时工作流能够继续工作。例如:

profiles {
    test {
        params {
           reads = 'https://github.com/my_repo/test/test_reads.fastq.gz'
           reference = 'https://github.com/my_repo/test/test_reference.fasta.gz'
        }
    }
}

使用现有的容器编写模块

强烈建议使用容器进行软件打包,因为它们以相同的方式运行,而与运行它的操作系统无关。使用现有容器编写模块可以减少工作流的维护,并减少包冲突。一个有用资源是包管理器 conda 的 bioconda 通道,它为许多生物信息学工具提供包装。

process FASTQC {

    container "${ workflow.containerEngine == 'singularity' ?
        'https://depot.galaxyproject.org/singularity/fastqc:0.11.9--0' :
        'quay.io/biocontainers/fastqc:0.11.9--0' }"

    ...
}

尽可能使用文件压缩和临时磁盘空间

磁盘空间通常是大多数计算机系统上有用的资源。如果可能,请使用压缩文件。有几个有用的 shell 命令和操作可以很好地处理压缩数据,例如 gunzip -czgrep

process COUNT_FASTA {

    input:
    path fasta         // reference.fasta.gz

    script:
    """
    zgrep -c '^>' $fasta
    """
}

另一个好的实践是使用本地临时磁盘空间(scratch)。通常,work 目录位于网络上的共享磁盘空间上,这会减慢对磁盘进行大量读写的进程。使用暂存空间不仅可以加快磁盘 I/O,而且还可以节省 workDir 中的空间,因为只有与进程输出指令匹配的文件才会被复制回来进行缓存。进程指令 process.scratch 可以提供布尔值或用于暂存空间的路径。

process {
    scratch = '/tmp'
}

使用一致的命名约定

使用一致的命名约定极大地提高了可读性。例如,对process名称使用大写有助于将其与其他工作流组件(如通道或操作符)区分开来。

总结

  • Nextflow对空格不敏感。可以使用空格来提高可读性。
  • 使用注释和空格来对代码块进行分组。
  • 在脚本中报告工具版本。
  • 使用emit:关键字命名输出channel。
  • 避免在process中使用params.xxx。将其作为输入参数传入。
  • 输入文件应该使用输入通道传入。
  • 将短的运行的命令组织到一个更大的进程中。
  • 包含一个在小型测试数据集上运行工作流的测试配置文件。
  • 重用现有的容器/软件包来编写process。
  • 尽可能使用压缩文件和临时磁盘空间。
  • 使用一致的命名约定。
  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值