Dry lab · Software
Software
Any software tools we built to make our work easier or more reproducible.
Every result on our Model page is reproducible from public data and the scripts below.
Code base
Every result on this page is reproducible from public data and the scripts below. This section walks through the pipeline in the order we ran it, with the real code for each stage. The complete scripts, the intermediate tables behind each figure, and a README are provided as a downloadable bundle with this page. This is analysis code for reproducing our results, not a released software tool.
Get the code and data
Everything on this page, every script, every results table, every figure, and a README that maps each figure to its script and table, is in one download.
Inputs
Raw 16S reads: ENA accession PRJEB38494 (939 samples, 118 children). Reference database for metabolic modeling: AGORA2. Everything else is produced by the code here.The analysis these commands produce, with the figures and the argument built on them, is on our Model page.
Stage 1: sequence processing (QIIME 2)
Import, denoise to ASVs with DADA2, and filter. This produces the feature table and representative sequences used everywhere downstream.
qiime tools import --type 'SampleData[PairedEndSequencesWithQuality]' \
--input-path manifest.tsv --output-path demux.qza \
--input-format PairedEndFastqManifestPhred33V2
qiime dada2 denoise-paired --i-demultiplexed-seqs demux.qza \
--p-trunc-len-f 220 --p-trunc-len-r 220 --p-pooling-method pseudo \
--o-table table.qza --o-representative-sequences rep-seqs.qza \
--o-denoising-stats denoising-stats.qza
# drop samples below 2,000 reads (removes 3 of 942 -> 939 samples)
qiime feature-table filter-samples --i-table table.qza \
--p-min-frequency 2000 --o-filtered-table table-filt.qza
qiime feature-table filter-features --i-table table-filt.qza \
--p-min-frequency 10 --p-min-samples 2 --o-filtered-table table-final.qzaStage 2: taxonomy and phylogeny
Assign names with a naive Bayes classifier against SILVA, then (for the metabolic step) re-classify against GTDB r207 so names match AGORA2. Build a tree for the diversity analyses.
qiime feature-classifier classify-sklearn \
--i-classifier silva-138-99-nb-classifier.qza \
--i-reads rep-seqs.qza --o-classification taxonomy.qza
# re-classify against GTDB r207 for AGORA2 name compatibility (coverage 53% -> 95%)
qiime feature-classifier classify-sklearn \
--i-classifier gtdb-r207-nb-classifier.qza \
--i-reads rep-seqs.qza --o-classification taxonomy-gtdb.qza
qiime phylogeny align-to-tree-mafft-fasttree \
--i-sequences rep-seqs.qza \
--o-rooted-tree rooted-tree.qza --o-alignment aligned.qzaStage 3: diversity and the community test
Core diversity metrics and the PERMANOVA that tests whether a grouping (arm, timepoint) separates the communities.
qiime diversity core-metrics-phylogenetic \
--i-phylogeny rooted-tree.qza --i-table table-final.qza \
--p-sampling-depth 15000 --m-metadata-file metadata.tsv \
--output-dir core-metrics
qiime diversity beta-group-significance \
--i-distance-matrix core-metrics/weighted_unifrac_distance_matrix.qza \
--m-metadata-file metadata.tsv --m-metadata-column treatment_arm \
--o-visualization permanova-arm.qzv --p-method permanova --p-pairwiseStage 4: the mixed-effects differential abundance
This is the model of Equation (1): a per-child random intercept so the eight samples per child are not double-counted. We export the tables from QIIME, then fit the model in R with MaAsLin2 at both ASV and genus level. The core is one function call.
qiime tools export --input-path table-final.qza --output-path exp-asv
biom convert -i exp-asv/feature-table.biom -o asv-table.tsv --to-tsv
qiime tools export --input-path table-genus-final.qza --output-path exp-genus
biom convert -i exp-genus/feature-table.biom -o genus-table.tsv --to-tsvlibrary(Maaslin2)
# MDCF-2 is the reference, so a negative treatment_arm coefficient = higher in MDCF-2
metadata$treatment_arm <- relevel(factor(metadata$treatment_arm), ref = "MDCF-2")
Maaslin2(
input_data = feat, # samples x features
input_metadata = meta, # arm, timepoint, participant_id
output = out_dir,
fixed_effects = c("treatment_arm", "timepoint"), # the beta1, beta2 terms
random_effects = c("participant_id"), # the u_i term: one per child
reference = c("treatment_arm,MDCF-2"),
normalization = "TSS", transform = "LOG", # compositional handling
min_prevalence = 0.1, max_significance = 0.05) # BH-FDR q < 0.05Compositional differential abundance was cross-checked with ANCOM-BC2 (Equation 1 of Section 2.3) through the QIIME q2-composition plugin, giving the same qualitative result.
Stage 5: community metabolic modeling (MICOM)
One master script builds the community models, runs every metabolic analysis, and writes every Results II figure and table. The genome-scale optimization of Equation (3) is handled inside MICOM; our script sets it up, extracts the fluxes, fits the same mixed model on predicted metabolism, and traces the cross-feeding. The key loading step rebuilds the growth result directly from the three tables inside the QIIME artifact, which is version-proof:
from micom.workflows import build, grow
from micom.workflows.results import GrowthResults
from micom import interaction as it
# build and grow the community models (tradeoff 0.3; 0.5 stalled the solver)
manifest = build(abundances, model_db="agora201_gtdb207_genus_1.qza",
out_folder="models", cutoff=1e-4, threads=8)
growth = grow(manifest, "models", medium="western_diet_gut_agora.qza",
tradeoff=0.3, threads=8)
# rebuild GrowthResults from the 3 CSVs inside growth.qza (version-proof)
res = GrowthResults(growth_rates, exchange_fluxes, annotations)
# cross-feeding: directed hand-offs among the growth-associated taxa
ints = it.interactions(res, taxa=wlz_taxa, progress=False) # provided / received
mes = it.MES(res) # which metabolites are tradedCommunity production is the export flux in the shared compartment; per-arm and per-timepoint tests reuse the mixed model of Equation (1) with Benjamini-Hochberg control (Equation 2). The full script (model build, SCFA test, 48-metabolite scan, MES, cross-feeding network, roles, activity, and every figure) is micom_master_make_figures.py in the bundle.
Stage 6: the minimal cross-feeding model
The acetate-to-butyrate competition model of Equations (4)-(5), solved to its steady state. Running it reproduces the sign of the empirical butyrate-abundance relationship.
def steady(P, F, C, qA=1.0, Va=1.0, Ka=1.5, Vc=1.3, Kc=1.5,
Yb=0.6, kB=0.9, Kb=1.0, D=0.25, iters=500, dt=0.02):
"""P acetate producers, F butyrate makers, C non-butyrogenic consumers."""
A, B = 1.0, 1.0
for _ in range(iters):
dA = qA*P - Va*F*A/(Ka+A) - Vc*C*A/(Kc+A) - D*A # Eq. (4)
dB = Yb*Va*F*A/(Ka+A) - kB*C*B/(Kb+B) - D*B # Eq. (5)
A = max(A + dt*dA, 1e-6)
B = max(B + dt*dB, 1e-6)
return A, B # as the consumer-dominated community grows, B* fallsBundle contents
The bundle (TexasiGEM_BestModel_Bundle.zip) is organized so a reader can run the pipeline end to end:
| Path | Contents |
|---|---|
| 01_qiime/ | All QIIME 2 commands as runnable shell scripts (Stages 1-3), with the exact parameters above |
| 02_diffabund/ | 1_prepare_tables.sh (export) and 2_run_maaslin2.R (the per-child mixed model, ASV and genus) |
| 03_micom/ | micom_master_make_figures.py (full metabolic pipeline and all figures), plus the standalone extraction and figure scripts |
| 04_model/ | crossfeeding_model.py (the minimal acetate-to-butyrate model) |
| tables/ | Intermediate outputs behind every figure: per-taxon exchange fluxes, the 48-metabolite scan with FDR, cross-feeding hand-offs and producer/consumer roles, growth rates |
| README.md | Run order, software versions, and which script and table produce each figure |
Software: QIIME 2 (amplicon 2026.1), R with MaAsLin2, Python with MICOM on COBRApy, statsmodels and matplotlib. Each figure maps to one script and one released table, listed in the README.