5.2 Good practice in bioinformatics¶
Theme: Tools & technology
This morning you presented your gene dossiers. Each one draws on two workflows, four Python packages and data from ENA, SRA, Zenodo and MalariaGEN. Could someone else rebuild your figures from your repository? Could you, in a year?
On Day 1 (Module 1.2) you set up a project layout, pinned environments and a git repository. This module looks back at how those habits worked during the week, and then covers what comes next: using workflow outputs as a record of what you did, sharing data and code so others can reuse them, licensing, and writing a README. It ends with a checklist to use on your own projects.
Learning objectives¶
At the end of this module you will be able to:
Audit a project against a reproducibility checklist and fix the gaps.
Use workflow outputs (configs, logs, environment files, reports) as provenance, and write a Methods paragraph from them.
Choose where to share raw reads, processed data and code, and what metadata to provide.
Choose licences for code, text and data, and write a useful README.
Setup¶
Only standard Python and pandas are used. Files are written to course-work/ next to this notebook.
import json
import platform
import subprocess
import xml.etree.ElementTree as ET
from datetime import date
from importlib import metadata
from pathlib import Path
from urllib.request import urlopen
import pandas as pd
WORKDIR = Path("course-work").resolve()
WORKDIR.mkdir(exist_ok=True)Looking back at the week¶
| Habit (Module 1.2) | Where you used it |
|---|---|
| Project layout | Your my-dossier/ folder. RNA-Seq-Pop and AmpSeeker use the standard Snakemake layout: config/, workflow/, resources/, results/ |
| Read-only raw data | The Busia and Siaya reads were downloaded from ENA/SRA and never edited. Every result was generated from them by code |
| Pinned environments | The course’s own pixi.toml and pixi.lock (and your practice pixi project in Module 1.2); pinned %pip install lines at the top of every Colab notebook; Snakemake installed with pixi in Codespaces, which then built a conda environment for each RNA-Seq-Pop and AmpSeeker rule with --use-conda |
| Version control | You committed to your fork every day |
Take a minute to look at your own history. In your codespace terminal:
git log --oneline -- my-dossier # one line per commit
git log --stat -- my-dossier | head -40 # which files changed in each commitIf there are gaps (a day with no commits, or a commit called stuff), that is normal. The point
is that the history exists, and it gets easier with practice.
A useful question to ask about any figure in your dossier: could I regenerate it from the repository alone? If the answer depends on a file on your laptop, or a cell you ran once and then deleted, fix that first.
Workflow outputs as provenance¶
Provenance is the record of how a result was produced: from which data, by which code, with which software and settings. A workflow manager records most of this for you, as long as you keep it.
| What Snakemake leaves behind | What it records | Keep it? |
|---|---|---|
config/config.yaml, config/samples.tsv | Samples, contrasts, parameters | Commit to git |
| The workflow version (git tag or commit, e.g. an RNA-Seq-Pop release) | Exactly which code ran | Record it in your README and Methods |
workflow/envs/*.yaml | Software versions for each step | Part of the workflow repo |
logs/ and benchmarks/ | Each job’s messages, run time and memory | Keep with the results |
.snakemake/log/*.log | The full log of each run | Keep the log of the final run |
snakemake --report report.html | Results, run times, code and software, in one HTML file | Share with results |
| Results books (RNA-Seq-Pop, AmpSeeker) | Figures and tables with the code that made them | Share (e.g. GitHub Pages) |
Writing Methods from provenance¶
A Methods section for a workflow-based analysis should let a reader rerun it. It should include:
Data: accession numbers (e.g. ENA PRJNA748581), and any subsetting.
Workflow: name, version (release or commit) and a link.
Configuration: the config file (in a supplement or repository), and key parameters and thresholds.
Reference data: genome and annotation versions (e.g. AgamP4 with a named VectorBase release).
Key tools and versions: read from the workflow’s environment files.
Statistics: tests, multiple-testing correction, thresholds.
For example, for the course’s worked example gene, CYP6AA1 (AGAP002862), with placeholders to fill in from your own run:
Reads were analysed with RNA-Seq-Pop vX.Y.Z (commit
abc1234; configuration in Supplementary File 1). Reads were pseudo-aligned to the AgamP4 transcriptome (VectorBase release NN) with kallisto vX, and differential expression was tested with DESeq2 vX for the BusRes vs BusSus and BusSus vs Kisumu contrasts, using a Benjamini–Hochberg adjusted p < 0.05. Downstream analyses of CYP6AA1 (AGAP002862) were run in notebooks using the environment inpixi.lock(commitdef5678).
Practical: record your own environment¶
If you work locally with pixi, pixi.lock already records every package version, and the git
commit records which pixi.lock you used. Colab notebooks keep no such record unless you ask.
This cell writes a small provenance file for your dossier: the date, Python version, versions of
the course packages that are installed, and the git commit if you are in a repository.
def provenance(packages, repo="."):
"""Return a dict describing the current software environment."""
record = {
"date": date.today().isoformat(),
"python": platform.python_version(),
"platform": platform.platform(terse=True),
"packages": {},
}
for pkg in packages:
try:
record["packages"][pkg] = metadata.version(pkg)
except metadata.PackageNotFoundError:
record["packages"][pkg] = "not installed"
git = subprocess.run(["git", "-C", repo, "rev-parse", "--short", "HEAD"],
capture_output=True, text=True)
record["git_commit"] = git.stdout.strip() if git.returncode == 0 else None
return record
record = provenance(["pandas", "malariagen_data", "anoexpress", "anoprimer", "snakemake"])
Path(WORKDIR / "provenance.json").write_text(json.dumps(record, indent=2))
print(json.dumps(record, indent=2)){
"date": "2026-09-26",
"python": "3.12.14",
"platform": "macOS-26.5.2",
"packages": {
"pandas": "2.2.3",
"malariagen_data": "not installed",
"anoexpress": "not installed",
"anoprimer": "not installed",
"snakemake": "9.27.0"
},
"git_commit": null
}
Run this at the end of each analysis notebook and commit the output alongside your figures.
Sharing data¶
Different parts of a project go to different places:
| What | Where | Why there |
|---|---|---|
| Raw sequence reads | ENA (EMBL-EBI), SRA (NCBI) or DDBJ. These three form the INSDC and mirror each other | Permanent, free, expected by journals; accession numbers let anyone download the reads |
| Processed data (count tables, VCFs, results) and code snapshots | Zenodo (or Figshare, Dryad) | A citable DOI for each version; large files allowed; GitHub releases can be archived automatically |
| Code under development | GitHub | History, issues, collaboration. Not an archive on its own: repositories can be deleted, so archive releases on Zenodo |
| Genotypes for surveillance | Community resources, e.g. MalariaGEN data releases | Combined with other studies |
INSDC archives organise submissions as a study (BioProject, e.g. PRJNA748581), samples (BioSample, e.g. SAMN...), experiments (library details) and runs (the FASTQ files, e.g. SRR...). The course mini datasets are on Zenodo so that every notebook can cite a fixed, versioned copy.
The aim is data that are FAIR: findable, accessible, interoperable and reusable [1].
Metadata¶
Reads without metadata are almost useless. Archives provide sample checklists based on the MIxS standards [2]. For mosquito resistance studies, the fields that matter most are:
species, and how it was identified (morphology, PCR, AIMs)
collection location (country, place, latitude and longitude) and date
collection method and life stage, sex and age
for colonies: origin, year established, and selection history
phenotype: insecticide, dose, assay type (e.g. WHO tube, net exposure) and outcome (dead/alive)
tissue, library preparation and sequencing platform
Practical: how good is the Busia metadata?¶
Let’s look at what ENA holds for the Busia RNA-seq samples.
url = ("https://www.ebi.ac.uk/ena/portal/api/filereport?accession=PRJNA748581"
"&result=read_run&format=tsv&fields="
"run_accession,sample_accession,sample_alias,sample_title,country,collection_date,"
"instrument_model,library_strategy")
busia = pd.read_csv(url, sep="\t").sort_values("sample_alias")
busiaNow the full set of attributes for one sample, from the ENA browser API:
sample = busia.sample_accession.iloc[0]
xml = urlopen(f"https://www.ebi.ac.uk/ena/browser/api/xml/{sample}").read()
attributes = {
a.findtext("TAG"): a.findtext("VALUE")
for a in ET.fromstring(xml).iter("SAMPLE_ATTRIBUTE")
}
pd.Series(attributes, name=sample)collection_date 2020-12
isolate BusRes1
replicate 1
INSDC secondary accession SRS9550903
NCBI submission package Invertebrate.1.0
organism Anopheles gambiae
sex female
host NA
geo_loc_name Uganda: Busia
tissue Whole
age 3-4 days
BioSampleModel Invertebrate
ENA-FIRST-PUBLIC 2022-08-31
ENA-LAST-UPDATE 2022-08-31
Name: SAMN20339596, dtype: objectLicensing¶
Work with no licence is, legally, “all rights reserved”: others cannot safely reuse it, even
if it is public on GitHub. Add a LICENSE file to every repository.
| Type of work | Common choices | Notes |
|---|---|---|
| Code | MIT or BSD-3-Clause (permissive); GPL-3.0 (copyleft: derived code must stay open) | Permissive licences are the most common for research software |
| Text, figures, teaching material | CC BY 4.0; CC BY-SA 4.0 (derived works must use the same licence) | This course is CC BY-SA 4.0, like the PAMCA course it builds on |
| Data | CC0 (no restrictions) or CC BY 4.0 (attribution) | Check any data-sharing agreements first |
https://
Data about mosquitoes collected in partner countries may also be covered by collaboration and data-sharing agreements, national permits and benefit-sharing rules. Agree how data will be shared, and how field teams will be credited, before sampling, not at the point of publication.
Writing a good README¶
The README is the first (often the only) file people read. A good one answers:
What is this? One or two sentences, and the question it addresses.
Where is the data? Accessions or DOIs, and how to download them.
How do I install the software? The environment file and the command to create it (e.g.
pixi install).How do I run it? The exact commands, in order.
What comes out? Where the main results and figures appear.
How is it organised? A short description of the folders.
How do I cite it, what is the licence, and who do I contact?
Practical: a README for your dossier¶
This writes a README template into course-work/my-dossier/. Copy it into the my-dossier/
folder of your fork, fill it in, and commit it.
dossier = WORKDIR / "my-dossier"
dossier.mkdir(exist_ok=True)%%writefile course-work/my-dossier/README.md
# Gene dossier: <GENE NAME> (<AGAP ID>), e.g. CYP6AA1 (AGAP002862)
Evidence that <GENE> contributes to pyrethroid resistance in *Anopheles gambiae* around
Lake Victoria, from RNA-seq, meta-analysis, whole genomes, selection scans, assay design and
amplicon sequencing. Produced during the malaria vector software course.
## Data
| Data | Source |
|---|---|
| Busia RNA-seq | ENA PRJNA748581; RNA-Seq-Pop results (Zenodo DOI: <...>) |
| Expression meta-analysis | AnoExpress <version> |
| Whole genomes | MalariaGEN Ag3 via malariagen_data <version> |
| Selection signals | Malaria vector selection atlas (accessed <date>) |
| Siaya amplicons | SRA PRJNA1207724; AmpSeeker <version> |
## Reproducing
- **Locally:** install [pixi](https://pixi.sh), then `pixi install` (recreates the environment from
`pixi.lock`) and `pixi run lab`. Run the notebooks in `notebooks/` in numbered order.
- **In Colab:** open each notebook in numbered order. Package versions are pinned in its first
cell and recorded in `provenance.json`.
## Layout
- `notebooks/`: analysis notebooks, numbered in run order
- `results/`: tables and figures produced by the notebooks
- `dossier.md`: the dossier itself
## Licence and contact
Text and figures: CC BY 4.0. Code: MIT. Contact: <your name, email>.Writing course-work/my-dossier/README.md
A checklist for your own projects¶
Use this at the start of a project and again before you share it. It condenses the recommendations in [3–5]; you do not need every item on day one.
Organisation
One folder and one git repository per project, with a standard layout
Relative paths only; file names without spaces; dates as
YYYY-MM-DD
Data
Raw data read-only, with source, date and checksum recorded
Metadata in a plain-text table, with the fields a stranger would need
Every result can be regenerated from raw data by code
Software
An environment file committed with the code:
pixi.toml+pixi.lock(recommended), orenvironment.yml/ pinned pipVersions of key tools and the workflow recorded with the results
Workflows
Multi-step or multi-sample analyses run through a workflow manager
Config files committed; logs and reports kept with results
Version control
Small, frequent commits with clear messages
No large data or secrets in the repository (
.gitignore)
Sharing
Raw reads in ENA/SRA with good sample metadata
Processed data and code releases on Zenodo with a DOI
A README, a LICENSE and a citation
Agreements with partners and data owners respected, and field teams credited
Practical: an automatic audit¶
The function below checks a project folder for some of the items above. It cannot check everything, but it catches the most common gaps.
def audit_project(path):
"""Check a project folder for common reproducibility gaps."""
p = Path(path)
env_files = ["pixi.toml", "environment.yml", "environment.yaml", "requirements.txt", "pyproject.toml"]
lock_files = ["pixi.lock", "conda-lock.yml", "uv.lock"]
checks = {
"README": any(p.glob("README*")),
"LICENSE": any(p.glob("LICENSE*")) or any(p.glob("LICENCE*")),
"environment file": any((p / f).exists() for f in env_files),
"lock file": any((p / f).exists() for f in lock_files),
".gitignore": (p / ".gitignore").exists(),
"git repository": (p / ".git").exists(),
"no files over 50 MB": not any(
f.is_file() and f.stat().st_size > 50e6
for f in p.rglob("*") if ".git" not in f.parts
),
"no spaces in file names": not any(
" " in f.name for f in p.rglob("*") if ".git" not in f.parts
),
}
return pd.DataFrame({"check": list(checks), "ok": list(checks.values())})
audit_project(dossier)Exercises¶
Exercise 1¶
Look at the Busia sample metadata above. The samples fall into three groups (Kisumu, Busia unselected and Busia deltamethrin-selected). Where in the metadata is the group recorded? List three other fields you would add, or make clearer, so that someone could reuse these data without reading the paper.
Show answer
The group is only recorded in the sample alias (Kis, BusSus, BusRes), which is a free-text label. There is no field for insecticide selection. Useful additions:
a structured treatment/phenotype field (e.g. “deltamethrin-selected colony”, “unselected parental colony”, “susceptible reference strain”), and the selection regime (insecticide, dose, number of generations);
colony origin: where and when the wild founders were collected, and when the colony was established. The
collection_date(2020-12) is presumably when these colony mosquitoes were sampled for RNA, not when the population was collected from the field;for Kisumu, that it is a long-established laboratory strain: the
geo_loc_name“Kenya: Kisumu” could be misread as a recent field collection;a more informative
sample_titlethan the default “Invertebrate sample from Anopheles gambiae”.
The sex, age and tissue fields are filled in, which is good.
Exercise 2¶
Run audit_project() on the dossier folder above, then fix every failing check (you can
create files from Python or with %%bash). Rerun the audit. If you are in your codespace, run
it on your fork too: audit_project("/workspaces/anopheles-omics-training").
Show answer
(dossier / "LICENSE").write_text("MIT License\n\nCopyright (c) 2026 <your name>\n...\n")
(dossier / ".gitignore").write_text("data/raw/\n")
subprocess.run(["git", "init", "-q", str(dossier)])For the environment and lock file, use pixi (install it first as in Module 1.2 if you are in Colab):
%%bash
cd course-work/my-dossier
pixi init . # adds .pixi/ to .gitignore
pixi add python=3.11 pandas=2.2.2
pixi add --pypi malariagen_data==15.9.0Then rerun audit_project(dossier).
Use the full licence text from https://pixi.toml and pixi.lock, but my-dossier/ should still have its own README.
Exercise 3¶
Write a Methods paragraph (4–6 sentences) for the Busia differential expression result for CYP6AA1 (AGAP002862) or one of your own genes. Which values can you fill in from the workflow outputs, and which do you still need to look up?
Show answer
A good paragraph names the data (PRJNA748581, the three groups and replicate numbers), the workflow and its version (RNA-Seq-Pop release or commit), the reference (AgamP4 and the VectorBase release), the quantification and DE tools with versions (kallisto, DESeq2), the contrast (e.g. BusRes vs BusSus) and the significance threshold.
Most of these come straight from the workflow: the config file (samples, contrasts, thresholds), the environment files (tool versions) and the git history (workflow version). Things usually missing, which you have to look up, are details of the biology: how and when the colony was established, the selection regime, and mosquito age and rearing conditions.
Quiz¶
Check your understanding. The quiz runs in Colab and in the course book.
%pip install -q jupyterquiz==2.9.6.4
from jupyterquiz import display_quiz
display_quiz("https://raw.githubusercontent.com/sanjaynagi/anopheles-omics-training/main/quizzes/5-2-good-practice.json")Summary¶
The habits from Day 1 (layout, environments, git) are what make your dossier reproducible. Check that every figure can be regenerated from your repository.
Keep the environment with the project:
pixi.tomlandpixi.locklocally, pinned%piplines in Colab. Workflows add per-rule conda environments on top.Workflow outputs (configs, environment files, logs, reports, the workflow version) are a ready-made provenance record. Keep them, and use them to write your Methods.
Share raw reads in ENA/SRA with good metadata, and processed data and code on Zenodo with a DOI. Aim for FAIR data.
Always add a licence and a README.
Well done!¶
You have reached the end of the course. The rest of today is flexi time: work on your own projects or data, or extend your dossier. Keep committing to your fork, and delete your codespace when you have finished with it to free your storage allowance.
References¶
Wilkinson MD et al. (2016). The FAIR Guiding Principles for scientific data management and stewardship. Scientific Data. Wilkinson et al. (2016)
Yilmaz P et al. (2011). Minimum information about a marker gene sequence (MIMARKS) and minimum information about any (x) sequence (MIxS) specifications. Nature Biotechnology. https://
doi .org /10 .1038 /nbt .1823 Wilson G et al. (2017). Good enough practices in scientific computing. PLoS Computational Biology. Wilson et al. (2017)
Sandve GK et al. (2013). Ten simple rules for reproducible computational research. PLoS Computational Biology. Sandve et al. (2013)
Buffalo V (2015). Bioinformatics Data Skills: Reproducible and Robust Research with Open Source Tools. O’Reilly Media. ISBN 978-1-4493-6737-4.
Perez-Riverol Y et al. (2016). Ten simple rules for taking advantage of Git and GitHub. PLoS Computational Biology. Perez-Riverol et al. (2016)
- Wilkinson, M. D., Dumontier, M., Aalbersberg, Ij. J., Appleton, G., Axton, M., Baak, A., Blomberg, N., Boiten, J.-W., da Silva Santos, L. B., Bourne, P. E., Bouwman, J., Brookes, A. J., Clark, T., Crosas, M., Dillo, I., Dumon, O., Edmunds, S., Evelo, C. T., Finkers, R., … Mons, B. (2016). The FAIR Guiding Principles for scientific data management and stewardship. Scientific Data, 3(1). 10.1038/sdata.2016.18
- Wilson, G., Bryan, J., Cranston, K., Kitzes, J., Nederbragt, L., & Teal, T. K. (2017). Good enough practices in scientific computing. PLOS Computational Biology, 13(6), e1005510. 10.1371/journal.pcbi.1005510
- Sandve, G. K., Nekrutenko, A., Taylor, J., & Hovig, E. (2013). Ten Simple Rules for Reproducible Computational Research. PLoS Computational Biology, 9(10), e1003285. 10.1371/journal.pcbi.1003285
- Perez-Riverol, Y., Gatto, L., Wang, R., Sachsenberg, T., Uszkoreit, J., Leprevost, F. da V., Fufezan, C., Ternent, T., Eglen, S. J., Katz, D. S., Pollard, T. J., Konovalov, A., Flight, R. M., Blin, K., & Vizcaíno, J. A. (2016). Ten Simple Rules for Taking Advantage of Git and GitHub. PLOS Computational Biology, 12(7), e1004947. 10.1371/journal.pcbi.1004947