Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Ultra-fast RNA-seq transcript/gene quantification via quasi-mapping (no BAM). Builds a k-mer index from transcriptome FASTA, quantifies in minutes. Outputs TPM/count tables (quant.sf) with optional GC- and sequence-bias correction. Integrates with tximeta/tximport for DESeq2/edgeR. Use STAR when a genome-aligned BAM is needed.
.claude/skills/jaechang-hits-salmon-rna-quantification/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-08 | ✗→✓ | ▲ Improved | 310% | 0% |
| case-10 | ✗→✓ | ▲ Improved | 565% | 0% |
| case-17 | ✓→✗ | ▼ Worse | 140% | 0% |
| case-01 | ✓→✓ | = Same ✓ | 253% | 0% |
| case-02 | ✓→✓ | = Same ✓ | 558% | 0% |
Salmon quantifies transcript abundance from RNA-seq reads using quasi-mapping — matching reads to a k-mer index of the transcriptome without full genome alignment. This makes Salmon 20–50× faster than alignment-based tools while producing accurate TPM and estimated count values. Salmon corrects for sequence-specific bias (--seqBias), GC-content bias (--gcBias), and fragment length distribution automatically. Output quant.sf files integrate directly with tximeta (R) or pydeseq2 (Python) for differential expression analysis. For improved accuracy, decoy-aware indexing uses the full genome to identify spurious quasi-mappings.
--gcBias --seqBias--numBootstrapspandas for parsing output; pydeseq2 for differential expression> Check before installing: The tool may already be available in the current environment (e.g., inside a pixi / conda env). Run command -v salmon first and skip the install commands below if it returns a path. When running inside a pixi project, invoke the tool via pixi run salmon rather than bare salmon.
bash# Install with conda (recommended) conda install -c bioconda salmon # Verify salmon --version # salmon 1.10.3 # Or download pre-compiled binary wget https://github.com/COMBINE-lab/salmon/releases/download/v1.10.0/salmon-1.10.0_linux_x86_64.tar.gz tar xzvf salmon-1.10.0_linux_x86_64.tar.gz export PATH="$PWD/salmon-latest_linux_x86_64/bin:$PATH"
bash# 1. Build transcriptome index (~5 min) salmon index -t transcriptome.fa -i salmon_index/ -p 8 # 2. Quantify paired-end reads (~2-5 min per sample) salmon quant \ -i salmon_index/ \ -l A \ -1 sample_R1.fastq.gz \ -2 sample_R2.fastq.gz \ -p 8 \ --gcBias --validateMappings \ -o results/sample1/ # Output: results/sample1/quant.sf head results/sample1/quant.sf
Fetch a transcript FASTA from GENCODE or Ensembl (cDNA sequences only — not genome).
bash# Human transcriptome from GENCODE (recommended) wget https://ftp.ebi.ac.uk/pub/databases/gencode/Gencode_human/release_47/gencode.v47.transcripts.fa.gz gunzip gencode.v47.transcripts.fa.gz # Count transcripts grep -c "^>" gencode.v47.transcripts.fa # ~252,000 transcripts echo "Reference ready." ls -lh gencode.v47.transcripts.fa
Index the transcriptome for quasi-mapping. Add genome decoys for improved accuracy.
bash# Standard index (fast, sufficient for most analyses) salmon index \ -t gencode.v47.transcripts.fa \ -i salmon_index/ \ -p 8 echo "Standard index complete." # Decoy-aware index (recommended for accuracy — uses full genome as decoy) # Step 1: create decoy list from genome chromosome names grep "^>" GRCh38.primary_assembly.genome.fa | cut -d " " -f 1 | sed 's/>//' > decoys.txt # Step 2: concatenate transcriptome + genome cat gencode.v47.transcripts.fa GRCh38.primary_assembly.genome.fa > gentrome.fa # Step 3: build decoy-aware index salmon index \ -t gentrome.fa \ -d decoys.txt \ -i salmon_decoy_index/ \ -p 8 echo "Decoy-aware index complete."
Run Salmon on single-end FASTQ files.
bash# Single-end quantification salmon quant \ -i salmon_index/ \ -l A \ -r sample1.fastq.gz \ -p 8 \ --seqBias \ --validateMappings \ -o results/sample1/ echo "Mapping rate: $(grep 'Mapping rate' results/sample1/logs/salmon_quant.log | tail -1)" echo "Output: results/sample1/quant.sf"
Run Salmon on paired-end FASTQ files with recommended bias correction flags.
bash# Paired-end with GC bias + sequence bias correction salmon quant \ -i salmon_decoy_index/ \ -l A \ -1 sample1_R1.fastq.gz \ -2 sample1_R2.fastq.gz \ -p 8 \ --gcBias \ --seqBias \ --validateMappings \ --numBootstraps 100 \ -o results/sample1/ # quant.sf columns: Name, Length, EffectiveLength, TPM, NumReads head results/sample1/quant.sf
Parse quant.sf to build a gene-level count matrix for differential expression.
pythonimport pandas as pd from pathlib import Path # Load single-sample output quant = pd.read_csv("results/sample1/quant.sf", sep="\t") print(f"Transcripts quantified: {len(quant)}") print(f"Total estimated reads: {quant['NumReads'].sum():.0f}") print(f"Transcripts with TPM > 1: {(quant['TPM'] > 1).sum()}") print(quant.sort_values("TPM", ascending=False).head()) # Build a multi-sample TPM matrix samples = ["ctrl_1", "ctrl_2", "treat_1", "treat_2"] tpm_matrix = pd.DataFrame({ s: pd.read_csv(f"results/{s}/quant.sf", sep="\t").set_index("Name")["TPM"] for s in samples }) print(f"\nTPM matrix: {tpm_matrix.shape}") tpm_matrix.to_csv("tpm_matrix.tsv", sep="\t")
Summarize transcript-level estimates to gene level and perform differential expression.
pythonimport pandas as pd import re from pathlib import Path from pydeseq2.dds import DeseqDataSet from pydeseq2.default_inference import DefaultInference from pydeseq2.ds import DeseqStats # Aggregate transcript counts to gene level using Ensembl gene IDs # quant.sf Name format: "ENST00000456328.2|ENSG00000223972.6|..." def extract_gene_id(transcript_id): parts = transcript_id.split("|") return parts[1].split(".")[0] if len(parts) > 1 else transcript_id samples = ["ctrl_1", "ctrl_2", "treat_1", "treat_2"] count_frames = [] for s in samples: df = pd.read_csv(f"results/{s}/quant.sf", sep="\t") df["gene_id"] = df["Name"].apply(extract_gene_id) gene_counts = df.groupby("gene_id")["NumReads"].sum().round().astype(int) count_frames.append(gene_counts.rename(s)) count_matrix = pd.DataFrame(count_frames).fillna(0).astype(int) metadata = pd.DataFrame({ "condition": ["control", "control", "treated", "treated"] }, index=samples) # Run DESeq2 dds = DeseqDataSet(counts=count_matrix, metadata=metadata, design_factors="condition", inference=DefaultInference(n_cpus=4)) dds.deseq2() stat_res = DeseqStats(dds, contrast=["condition", "treated", "control"], inference=DefaultInference()) stat_res.summary() results = stat_res.results_df print(f"DE genes (padj < 0.05): {(results['padj'] < 0.05).sum()}") print(results[results['padj'] < 0.05].sort_values('log2FoldChange').head())
| Parameter | Default | Range/Options | Effect | |-----------|---------|---------------|--------| | -l / --libType | required | A (auto), SF, SR, IU, IS, MS, MR | Library strandedness; A auto-detects from first reads | | -p / --threads | 1 | 1–64 | CPU threads; 8–16 is typical | | --gcBias | off | flag | Correct for GC-content bias in fragment selection; recommended for most samples | | --seqBias | off | flag | Correct for sequence-specific bias at read starts; recommended | | --validateMappings | off | flag | Use selective alignment for improved accuracy; slight speed cost | | --numBootstraps | 0 | 0–200 | Bootstrap replicates for uncertainty estimation; enables Sleuth/Swish | | --dumpCsvCounts | off | flag | Dump raw counts to CSV alongside quant.sf | | -d / --decoys | — | file | Decoy sequence list for decoy-aware indexing | | --rangeFactorizationBins | 4 | 1–8 | Bins for range-factorization model; increases accuracy at small speed cost | | --skipQuant | off | flag | Build index and exit; useful for cluster pipelines |
bash#!/bin/bash # Quantify all paired-end samples with recommended settings INDEX="salmon_decoy_index" DATA="data" OUT="results" THREADS=12 SAMPLES=(ctrl_1 ctrl_2 treat_1 treat_2) mkdir -p "$OUT" for sample in "${SAMPLES[@]}"; do echo "Quantifying: $sample" salmon quant \ -i "$INDEX" \ -l A \ -1 "$DATA/${sample}_R1.fastq.gz" \ -2 "$DATA/${sample}_R2.fastq.gz" \ -p "$THREADS" \ --gcBias --seqBias --validateMappings \ -o "$OUT/$sample/" echo "Done: $sample — mapping $(grep 'Mapping rate' $OUT/$sample/logs/salmon_quant.log | tail -1)" done echo "All samples quantified."
python# Snakefile — Salmon quantification rule configfile: "config.yaml" SAMPLES = config["samples"] rule all: input: expand("results/{sample}/quant.sf", sample=SAMPLES) rule salmon_index: input: transcriptome = config["transcriptome_fa"] output: directory("salmon_index") threads: 8 shell: "salmon index -t {input.transcriptome} -i {output} -p {threads}" rule salmon_quant: input: index = "salmon_index", r1 = "data/{sample}_R1.fastq.gz", r2 = "data/{sample}_R2.fastq.gz" output: quant = "results/{sample}/quant.sf" params: outdir = "results/{sample}" threads: 8 shell: """ salmon quant -i {input.index} -l A \ -1 {input.r1} -2 {input.r2} \ -p {threads} --gcBias --seqBias --validateMappings \ -o {params.outdir} """
pythonimport pandas as pd import numpy as np samples = { "ctrl_1": "results/ctrl_1/quant.sf", "ctrl_2": "results/ctrl_2/quant.sf", "treat_1": "results/treat_1/quant.sf", "treat_2": "results/treat_2/quant.sf", } # Build TPM matrix tpm = pd.DataFrame({ name: pd.read_csv(path, sep="\t").set_index("Name")["TPM"] for name, path in samples.items() }) # Filter: keep transcripts with TPM > 1 in at least 2 samples expressed = (tpm > 1).sum(axis=1) >= 2 tpm_filt = tpm[expressed] print(f"Expressed transcripts: {expressed.sum()} / {len(tpm)}") # Simple log2 fold change (treat vs ctrl) ctrl_mean = tpm_filt[["ctrl_1", "ctrl_2"]].mean(axis=1) treat_mean = tpm_filt[["treat_1", "treat_2"]].mean(axis=1) lfc = np.log2(treat_mean + 0.5) - np.log2(ctrl_mean + 0.5) top_up = lfc.sort_values(ascending=False).head(10) print("Top upregulated transcripts:") print(top_up)
| Output | Format | Description | |--------|--------|-------------| | quant.sf | TSV | Transcript-level quantification: Name, Length, EffectiveLength, TPM, NumReads | | quant.genes.sf | TSV | Gene-level quantification (when --geneMap provided) | | logs/salmon_quant.log | Text | Detailed log with mapping rate, processed reads, elapsed time | | aux_info/meta_info.json | JSON | Run metadata: library type detected, mapping rate, num processed reads | | aux_info/fld.gz | Binary | Fragment length distribution (paired-end) | | bootstrap/ | Binary | Bootstrap count distributions (when --numBootstraps > 0) |
| Problem | Cause | Solution | |---------|-------|----------| | Mapping rate < 50% | Wrong transcriptome species or assembly mismatch | Verify transcriptome FASTA matches sample organism; use genome-decoy index | | Library type detection wrong | Ambiguous or mixed-strand library | Specify explicitly: -l SF (stranded fwd) or -l SR (stranded rev) | | quant.sf all zeros | Index built from wrong reference | Rebuild index with correct transcriptome FASTA | | Out of memory during indexing | Transcriptome + genome concatenation too large | Use standard index without genome decoy; or increase available RAM | | Many low-mapping transcripts | No GC/seq bias correction | Add --gcBias --seqBias --validateMappings; helps with low-complexity regions | | meta_info.json mapping rate < 30% | Reads are from a different molecule (e.g., rRNA contamination) | Check FastQC overrepresented sequences; verify library preparation | | Gene-level output missing | --geneMap or -g not provided | Re-run with -g gencode.v47.gtf to get quant.genes.sf | | Bootstrap takes too long | High --numBootstraps on slow disk | Reduce to --numBootstraps 30 for most DE tests; use SSD |
| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-01 | pass→pass | 10,730 | 10,707 | -0% | 1 | 1 | 0% | 1,634 | 5,770 | +253% | 0 | 0 | — |
case-02 | pass→pass | 5,597 | 7,123 | +27% | 1 | 1 | 0% | 805 | 5,297 | +558% | 0 | 0 | — |
case-03 | pass→pass | 9,301 | 9,047 | -3% | 1 | 1 | 0% | 1,474 | 5,640 | +283% | 0 | 0 | — |
case-04 | pass→pass | 3,414 | 2,551 | -25% | 1 | 1 | 0% | 561 | 4,546 | +710% | 0 | 0 | — |
case-05 | pass→pass | 6,099 | 3,977 | -35% | 1 | 1 | 0% | 987 | 4,868 | +393% | 0 | 0 | — |
case-06 | pass→pass | 10,023 | 9,486 | -5% | 1 | 1 | 0% | 1,555 | 4,752 | +206% | 0 | 0 | — |
case-07 | pass→pass | 9,776 | 3,050 | -69% | 1 | 1 | 0% | 1,714 | 4,682 | +173% | 0 | 0 | — |
case-08 | fail→pass | 19,944 | 18,565 | -7% | 1 | 1 | 0% | 1,292 | 5,293 | +310% | 0 | 0 | — |
case-09 | pass→pass | 4,414 | 3,578 | -19% | 1 | 1 | 0% | 816 | 4,758 | +483% | 0 | 0 | — |
case-10 | fail→pass | 4,370 | 3,170 | -27% | 1 | 1 | 0% | 711 | 4,727 | +565% | 0 | 0 | — |
case-11 | pass→pass | 4,568 | 3,156 | -31% | 1 | 1 | 0% | 876 | 4,708 | +437% | 0 | 0 | — |
case-12 | pass→pass | 5,624 | 23,612 | +320% | 1 | 1 | 0% | 1,028 | 4,956 | +382% | 0 | 0 | — |
case-13 | pass→pass | 3,957 | 3,009 | -24% | 1 | 1 | 0% | 605 | 4,701 | +677% | 0 | 0 | — |
case-14 | pass→pass | 5,999 | 3,605 | -40% | 1 | 1 | 0% | 1,063 | 4,646 | +337% | 0 | 0 | — |
case-15 | pass→pass | 7,970 | 6,523 | -18% | 1 | 1 | 0% | 1,293 | 5,375 | +316% | 0 | 0 | — |
case-16 | pass→pass | 7,675 | 4,817 | -37% | 1 | 1 | 0% | 1,193 | 4,964 | +316% | 0 | 0 | — |
case-17 | pass→fail | 24,744 | 10,297 | -58% | 1 | 1 | 0% | 2,410 | 5,777 | +140% | 0 | 0 | — |
case-18 | pass→pass | 6,444 | 5,040 | -22% | 1 | 1 | 0% | 1,115 | 5,046 | +353% | 0 | 0 | — |
case-19 | pass→pass | 4,795 | 4,306 | -10% | 1 | 1 | 0% | 806 | 4,909 | +509% | 0 | 0 | — |
case-20 | pass→pass | 4,405 | 4,463 | +1% | 1 | 1 | 0% | 753 | 4,935 | +555% | 0 | 0 | — |
case-21 | pass→pass | 6,683 | 4,248 | -36% | 1 | 1 | 0% | 1,141 | 4,852 | +325% | 0 | 0 | — |
case-22 | pass→pass | 5,203 | 5,897 | +13% | 1 | 1 | 0% | 873 | 5,162 | +491% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 22 cases were attempted, and 21 counted toward the lift figure. The other 1 produced results that are not comparable between the two arms, so they are excluded from the headline rather than averaged into it. The headline lift of -100 percentage points is the difference between those two pass rates over the 21 comparable cases. 1 case got worse with the skill loaded, and it is included in that figure.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.