Owner: Zlatko Salko Lagumdzija, Michael Ford Verification: Expired Tags: Onboarding
Its expected that you are familiar all of the following skills/tools. If any of these are new to you, or you’ve only had light exposure in the past, now is the time to brush up by reading the relevant chapters in Bioinformatics Data Skills.
- bash command line
- File manipulation
- grep
- sed
- cut
- find
- less
- piping and redirecting
- tmux
- git
- adding, commiting
- branching, merging, rebasing/squashing
- github: push, pull, fetch
- types of sequence information
- fasta, fai index files
- fastq
- bam/sam
- bed files
- mapping sequencing data with BWA
- sam/bam manipulation with samtools and pysam
The following is tailored to python, but if you are using other languages you should read through to take away the general principles.
The Sahinalp Group uses Cookiecutter Data Science as a guide for organizing data science projects. This structure includes directories such as data, models, notebooks, references, reports, src, and others, and is intended to keep code organized and make it easy to reproduce analyses. The project also includes a Makefile with commands to automate tasks. The guiding principles for this structure are:
The guiding principles:
- Keep the project organized and well-documented
- Make it easy to reproduce the analysis
- Encourage modularity and reusability of code
- Use version control
- Automate repetitive tasks
- Use a virtual environment and package manager
We have a modified version of cookiecutter data science:
https://github.com/michael-ford/data-science-development-project-template
To use it to start a new project simply run
cookiecutter https://github.com/michael-ford/data-science-development-project-template.git
It has the following format:
├── data
│ ├── external <- Data from third party sources.
| | └── SOURCE.tsv <- Record of data sources when not using `dvc run`
│ ├── interim <- Intermediate data that has been transformed.
│ ├── raw <- The original, immutable data dump.
│ └── results <- Final results
|
├── environment.yml <- Conda environment definition for reproducing
|
├── experiments <- Experiments devided by subdirectory
│ └── 03-04-14.sample-experiment <- Sample demonstrating the structure
│ ├── data -> ../../data/ <- Softlink to data/ for code cleanliness
│ ├── notebooks <- Jupyter notebooks for experiment
| | └── template.ipynb <- Sample notebook containing useful code
│ └── results <- Final reports for communication (e.g. html
| | version of jupyter nb)
│ └── figures <- Figures for reports
|
├── LICENSE
├── README.md <- The top-level README for developers using
| this project.
├── setup.py <- Configuration for installing src/ as python package
|
└── src <- Source code for use in this project.
├── data
│ └── __init__.py
└── __init__.py <- Makes src a Python module
|
└---- tool-repo
The key principles are:
- Code goes in
src, which is loaded as a python package in develop mode in your environment. This allows you to develop your scripts and load them into jupyter notebooks usingfrom src.data.my_script import my_function. In order to install it, simply runpython setup.py develop - Experiments, which are generally running of some data and looking at results, are organized in the
experimentsdirectory. There is another cookiecutter template to create a new experiment directory, which you can run usingcd experiments cookiecutter ../new-experiment-template - Experiment notebooks should set the working directory as the root experiment directory (see
template.ipynb). This allows them to reference data files using thedatasymlink. - Data files are stored in
data- which should not be stored in git. Git works great for plain text files, since it only saves changes to text files in the form of blobs. This does not work for binary or compressed files, as small changes can change the entire file encoding, requiring git to duplicate data, which can blow up the size of your repo. Use a.gitignoreto not include files in thedatadirectory- It is important to keep your data directory organized - sub directories are your friend here. Very little if any data files should be saved directly in
raw,external,interimorresults. Instead they should be saved within a subdirectory, with a descriptive and informative name, of the relevant directory.
- It is important to keep your data directory organized - sub directories are your friend here. Very little if any data files should be saved directly in
We use Conda to manage our Python environments. If you’re familiar with pip, this is similar. Conda is a package management system that enables you to create and manage separate environments for different projects. Here are some of the top benefits of using Conda:
- Isolation: Conda allows you to create and manage isolated environments for different projects, meaning that you can have different versions of Python and different packages installed for different projects without them conflicting with one another.
- Reproducibility: By specifying the packages and their versions in your environment file, you can ensure that all team members are working with the same dependencies and versions. This makes it easy to reproduce your development and analysis environments.
- Ease of use: Conda provides a user-friendly interface for creating, managing, and sharing environments. You can easily create a new environment with the packages you need, activate it, and start working.
- Cross-platform compatibility: Conda works on Windows, macOS, and Linux, so you can easily share your environment files with others regardless of their operating system.
- Run
echo /home/$USER/data/miniconda3/to get the install location for conda. Copy the result - Download the Miniconda installer and run
- Specify your copied install location when prompted in the installer
- DO NOT say yes to option to run conda init / copy code to .bashrc
- After install, copy the following code to your
~/.bash_profile
source /home/$USER/data/miniconda3/etc/profile.d/conda.sh
export PATH="/home/$USER/data/miniconda3/bin:$PATH"
conda activate base- Start a new shell and make sure it works
- Now install the mamba solver by running
conda update -n base conda
conda install -n base conda-libmamba-solver
conda config --set solver libmambaIt is critical to keep track of what is installed in your environment so that your work is reproducible. We do this by maintaining a yml file containing all the packages that need to be installed.
Use these for:
- Prototyping code (strongly recommend also having a .py file open to transfer your code to)
- High level running of experiments
- Visualizing data / results
Beware that it is really easy to end up with a messy and convoluted notebook if you do not regularly transfer code out of the notebook into a separate .py that you import, especially if you run cells not in order.
See the ‘Setting up Jupyter Headless’ page for how to use jupyter on biowulf
It is critical that every command or pipeline you run to produce any results, whether that is fetching raw data, generating interim data, or producing figures and results. To do this, we strongly recommend using Nextflow, as it is currently the most popular and growing pipeline management tool (and a requirement for most industry bioinformatics jobs).
Note: It is important that you use the newer DSL2 format.
Organize your Nextflow code as you would your python code:
- Save modular processes in
.nffiles in thesrcdirectory - For every pipeline run, make a new
.nffile where you import the required processes fromsrcand put them together in theworkflow. Input and output files should be hardcoded in this script, rather than specific as command line arguments.
Here is an example of fetching a paired end wgs sequence, mapping using BWA and sorting and indexing using samtools:
// File: src/data/wgs_mapping_processing.nf
nextflow.enable.dsl=2
process bwa_mapping {
input:
path ref_genome
path read1
path read2
output:
path "output_mapped.bam", emit: bam
script:
"""
bwa mem $ref_genome $read1 $read2 > output_mapped.bam
"""
}
process sort_bam {
publishDir './', mode: 'rellink'
input:
path bam
output:
path "output_sorted.bam", emit: sorted_bam
script:
"""
samtools sort $bam -o output_sorted.bam
rm $bam // Removes the input BAM once the sorted BAM is created to save space
"""
}
process index_bam {
publishDir './', mode: 'rellink'
input:
path sorted_bam
output:
path "output_sorted.bam.bai", emit: bam_index
script:
"""
samtools index $sorted_bam
"""
}This is your definition of you processes in a modular format located in src/data
// File: data/raw/sample_A_wgs/fetch_map_sample_A_wgs.nf
nextflow.enable.dsl=2
include { bwa_mapping; sort_bam; index_bam } from '../../../src/data/wgs_mapping_processing.nf'
params.ref_genome = '../../external/genome-references/GRCh38.fasta'
process fetch_fastqs {
output:
path "sample_A_R1.fastq", emit: read1
path "sample_A_R2.fastq", emit: read2
script:
"""
curl -O http://example-dataset-portal/sample_A_R1.fastq
curl -O http://example-dataset-portal/sample_A_R2.fastq
"""
}
workflow {
fetch_fastqs()
bwa_mapping(params.ref_genome, fetch_fastqs.out.read1, fetch_fastqs.out.read2)
sort_bam(bwa_mapping.out.bam)
index_bam(sort_bam.out.sorted_bam)
}This is the actual script that gets run to process the data. Note the following features:
- Process
FETCH_FASTQS: This could be also written as a modular process insrc, but since it is very simple, and there are many different ways of fetching data from a database, it is sufficient to have it defined here. If you use this specific method many times for different applications, consider moving it tosrcas a modular process to reduce code duplication - The
includestatement to import the relevant processes - The
workflowstitches the processes together into a pipeline
What you end up with in data/raw/sample_A_wgs after running nextflow fetch_map_sample_A_wgs.nf
fetch_map_sample_A_wgs.nfworkdirectoryoutput_sorted.bam -> work/some/hash/output_sorted.bamoutput_sorted.bam.bai -> work/some/other_hash/output_sorted.bam.bai
By then commiting fetch_map_sample_A_wgs.nf, and the symlinks output_sorted.bam and output_sorted.bam.bai you ensure that any future reproductions of this workflow with be identical (otherwise you will notice the symlinks have changed when you run git status as they will point to new locations)
For example, the above was generated using the following prompts:
Please provide two nextflow scripts, written with DSL2, the first being "wgs_mapping_processing.nf", which contains 3 processes: 1) mapping two input 2 fastq files representing paired end reads to an input genome reference using bwa and saving to a bam file, 2) sorting the resultant bam file, 3) indexing the resultant bam file. The second script, called "fetch_map_sample_A_wgs.nf" includes a process to fetch two fastq files from "http://example-dataset-portal" using curl, and then imports the processes defined above from "../../../src/data/wgs_mapping_processing.nf' and runs the processes in the workflow using the downloaded fastqs. The output bam file should be called "sample_A_wgs.bam".
And then
You are an expert in reproducible data analysis, coding best practices and nextflow code standards. Perform a code review of the following nf scripts, and provide improved versions if needed. Note that wgs_mapping_processing.nf is intended to be a collection of modular processes, while fetch_map_sample_A_wgs.nf is intended to be a workflow that is run as single instance of a pipeline and not be modular.
Since we are a method development group, we need to produce production level code. If you have not had much experience with any of the following, it is worth the time investment to get up to speed - not only so your project will be more stable, usable and maintainable, but to give you skills that future employers want to see, guaranteed.
Object-oriented programming (OOP) powerful and the standard in production development environments. Reminder of the main features and benefits below. Ensure you are integrating the all of the following principles in your code!
- Modularity and encapsulation: OOP allows you to break down complex programs into smaller, more manageable pieces, or modules. Each module can have its own data and functionality, making it easier to work on and test in isolation. Encapsulation ensures that the internal workings of a module are hidden from other parts of the program, reducing the likelihood of bugs and making it easier to modify and update the code.
- Code reuse: OOP encourages the creation of reusable code, which can save time and effort in the long run. Objects can be created once and used multiple times, reducing the amount of code that needs to be written and maintained.
- Inheritance and polymorphism: OOP allows you to create new objects based on existing objects, through inheritance. This can save time and effort in creating new code, and also helps ensure consistency across the program. Polymorphism allows objects to take on multiple forms, depending on the context, which can make code more flexible and extensible. This allows you to easily iterate on your tool development to test different features.
- Abstraction: OOP allows you to create abstract data types that capture the essence of a concept, without getting bogged down in implementation details. This can make code more understandable and easier to reason about.
It is important for your code to be readable and maintainable. Ensure you are integrating all of the following:
- Commenting
- Use comments to explain why code is written in a certain way, not what the code is doing.
- Avoid commenting every line of code, instead focus on complex or non-obvious sections of the code.
- Use descriptive names for variables and functions to reduce the need for comments.
- Use inline comments sparingly and only when necessary.
- Use a consistent commenting style throughout the codebase.
- Typing
- Use type hints for function arguments, return values, and class attributes.
- Use Union types to indicate that a function can accept multiple types of arguments.
- Use Optional types for arguments that are not required.
- Docstrings
- Use docstrings to describe the purpose and behavior of functions and classes.
- Follow the Google style guide for writing docstrings.
- Start the docstring with a one-line summary of the function or class.
- Include information on function arguments, return values, and exceptions that can be raised.
- Include examples of how to use the function or class.
💡
Circular imports can be a problem when typing. Use the following methods to avoid it:
- Using the following type check
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from models import Book- Use
from __future__ import annotationsand put your typing in single quotes
WIP: We need it, write it.