Skill Tracer
Skills › Scientific Agent Skills › scikit-bio › api_reference.md

api_reference.md

778 lines · skills/scikit-bio/references/api_reference.md at 5f2bc03

Open on GitHub
1# scikit-bio API Reference
2
3Targets scikit-bio 0.7.4, reviewed 2026-10-01. Filename/undefined-input examples are templates; tiny native regression tests exercise the corrected API and numerical contracts. [Review evidence and limits](review.md).
4

scikit-bio API Reference

Targets scikit-bio 0.7.4, reviewed 2026-10-01. Filename/undefined-input examples are templates; tiny native regression tests exercise the corrected API and numerical contracts. Review evidence and limits.

Raw lines 1 to 4
5## Table of Contents
61. [Sequence Classes](#sequence-classes)
72. [Alignment Methods](#alignment-methods)
83. [Phylogenetic Trees](#phylogenetic-trees)
94. [Diversity Metrics](#diversity-metrics)
105. [Ordination](#ordination)
116. [Statistical Tests](#statistical-tests)
127. [Distance Matrices](#distance-matrices)
138. [File I/O](#file-io)
149. [Troubleshooting](#troubleshooting)

Table of Contents

  1. Sequence Classes
  2. Alignment Methods
  3. Phylogenetic Trees
  4. Diversity Metrics
  5. Ordination
  6. Statistical Tests
  7. Distance Matrices
  8. File I/O
  9. Troubleshooting
Raw lines 5 to 15
16## Sequence Classes

Sequence Classes

Raw lines 16 to 17
18### DNA, RNA, and Protein Classes
20```python
21from skbio import DNA, RNA, Protein, Sequence
23# Creating sequences
24dna = DNA('ATCGATCGA', metadata={'id': 'seq1', 'description': 'Example'})
25rna = RNA('AUCGAUCG')
26protein = Protein('ACDEFGHIKLMNPQRSTVWY')
28# Sequence operations
29dna_rc = dna.reverse_complement() # Reverse complement
30rna = dna.transcribe() # DNA -> RNA
31protein = rna.translate() # RNA -> Protein
33# Using genetic code tables
34protein = rna.translate(genetic_code=11) # Bacterial code
35```

DNA, RNA, and Protein Classes

from skbio import DNA, RNA, Protein, Sequence

# Creating sequences
dna = DNA('ATCGATCGA', metadata={'id': 'seq1', 'description': 'Example'})
rna = RNA('AUCGAUCG')
protein = Protein('ACDEFGHIKLMNPQRSTVWY')

# Sequence operations
dna_rc = dna.reverse_complement()  # Reverse complement
rna = dna.transcribe()  # DNA -> RNA
protein = rna.translate()  # RNA -> Protein

# Using genetic code tables
protein = rna.translate(genetic_code=11)  # Bacterial code
Raw lines 18 to 36
37### Sequence Searching and Pattern Matching
39```python
40# Find motifs using regex
41dna = DNA('ATGCGATCGATGCATCG')
42motif_locs = dna.find_with_regex('(ATG.{3})') # capture groups yield slices
44# Find all positions
45import re
46for match in re.finditer('ATG', str(dna)):
47 print(f"ATG found at position {match.start()}")
49# k-mer counting using the public API
50kmers = dna.kmer_frequencies(k=3)
51```

Sequence Searching and Pattern Matching

# Find motifs using regex
dna = DNA('ATGCGATCGATGCATCG')
motif_locs = dna.find_with_regex('(ATG.{3})')  # capture groups yield slices

# Find all positions
import re
for match in re.finditer('ATG', str(dna)):
    print(f"ATG found at position {match.start()}")

# k-mer counting using the public API
kmers = dna.kmer_frequencies(k=3)
Raw lines 37 to 52
53### Handling Sequence Metadata
55```python
56# Sequence-level metadata
57dna = DNA('ATCG', metadata={'id': 'seq1', 'source': 'E. coli'})
58print(dna.metadata['id'])
60# Positional metadata (per-base quality scores from FASTQ)
61from skbio import DNA
62seqs = DNA.read('reads.fastq', format='fastq', phred_offset=33)
63quality_scores = seqs.positional_metadata['quality']
65# Interval metadata (features/annotations)
66dna.interval_metadata.add([(0, 4)], metadata={'type': 'gene', 'name': 'geneA'})
67```

Handling Sequence Metadata

# Sequence-level metadata
dna = DNA('ATCG', metadata={'id': 'seq1', 'source': 'E. coli'})
print(dna.metadata['id'])

# Positional metadata (per-base quality scores from FASTQ)
from skbio import DNA
seqs = DNA.read('reads.fastq', format='fastq', phred_offset=33)
quality_scores = seqs.positional_metadata['quality']

# Interval metadata (features/annotations)
dna.interval_metadata.add([(0, 4)], metadata={'type': 'gene', 'name': 'geneA'})
Raw lines 53 to 68
69### Distance Calculations
71```python
72from skbio import DNA
74seq1 = DNA('ATCGATCG')
75seq2 = DNA('ATCG--CG')
77# Hamming distance (default)
78dist = seq1.distance(seq2)
80# Custom distance function
81from skbio.sequence.distance import kmer_distance
82from functools import partial
83dist = seq1.distance(seq2, metric=partial(kmer_distance, k=3))
84```

Distance Calculations

from skbio import DNA

seq1 = DNA('ATCGATCG')
seq2 = DNA('ATCG--CG')

# Hamming distance (default)
dist = seq1.distance(seq2)

# Custom distance function
from skbio.sequence.distance import kmer_distance
from functools import partial
dist = seq1.distance(seq2, metric=partial(kmer_distance, k=3))
Raw lines 69 to 85
86## Alignment Methods

Alignment Methods

Raw lines 86 to 87
88### Pairwise Alignment
90scikit-bio 0.7.0 introduced `pair_align`, a single fast engine for global, local, and semi-global alignment. The convenience wrappers `pair_align_nucl` and `pair_align_prot` ship with BLASTN/BLASTP-like scoring. The old SSW wrapper (`local_pairwise_align_ssw`, `StripedSmithWaterman`) was removed, and the pure-Python `global_pairwise_align`/`local_pairwise_align_*` functions are deprecated.
92```python
93from skbio import DNA, Protein
94from skbio.alignment import pair_align_nucl, pair_align_prot, pair_align, align_score
96# Nucleotide alignment with BLASTN-like defaults
97seq1 = DNA('ATCGATCGATCG')
98seq2 = DNA('ATCGGGGATCG')
99aln = pair_align_nucl(seq1, seq2)
101# Inspect the result (PairAlignResult: score + paths [+ matrices])
102print(f"Score: {aln.score}")
103path = aln.paths[0] # PairAlignPath; repr shows the CIGAR
104aligned_seqs = path.to_aligned((seq1, seq2)) # list of gapped strings
106# Global alignment with custom affine scoring via pair_align
107aln = pair_align(
108 seq1, seq2,
109 mode='global',
110 free_ends=False, # default True would be semi-global/overlap
111 sub_score=(2, -3), # (match, mismatch)
112 gap_cost=(5, 2), # (open, extend) -> affine; a single number -> linear
115# Use a named substitution matrix instead of match/mismatch scores
116aln = pair_align(seq1, seq2, mode='global', sub_score='NUC.4.4', gap_cost=3)
118# Protein alignment with BLASTP-like defaults (BLOSUM62)
119protein_query = Protein('ACDEFGHIKLMNPQRSTVWY')
120protein_target = Protein('ACDEFMNPQRSTVWY')
121aln = pair_align_prot(protein_query, protein_target)
123# Re-score an existing alignment with the same parameters
124score = align_score((aln.paths[0], (protein_query, protein_target)),
125 sub_score='BLOSUM62', gap_cost=(11, 1))
127# PairAlignResult also unpacks as a tuple
128score, (path,), _ = pair_align_nucl(seq1, seq2)
129```

Pairwise Alignment

scikit-bio 0.7.0 introduced pair_align, a single fast engine for global, local, and semi-global alignment. The convenience wrappers pair_align_nucl and pair_align_prot ship with BLASTN/BLASTP-like scoring. The old SSW wrapper (local_pairwise_align_ssw, StripedSmithWaterman) was removed, and the pure-Python global_pairwise_align/local_pairwise_align_* functions are deprecated.

from skbio import DNA, Protein
from skbio.alignment import pair_align_nucl, pair_align_prot, pair_align, align_score

# Nucleotide alignment with BLASTN-like defaults
seq1 = DNA('ATCGATCGATCG')
seq2 = DNA('ATCGGGGATCG')
aln = pair_align_nucl(seq1, seq2)

# Inspect the result (PairAlignResult: score + paths [+ matrices])
print(f"Score: {aln.score}")
path = aln.paths[0]                       # PairAlignPath; repr shows the CIGAR
aligned_seqs = path.to_aligned((seq1, seq2))   # list of gapped strings

# Global alignment with custom affine scoring via pair_align
aln = pair_align(
    seq1, seq2,
    mode='global',
    free_ends=False,        # default True would be semi-global/overlap
    sub_score=(2, -3),      # (match, mismatch)
    gap_cost=(5, 2),        # (open, extend) -> affine; a single number -> linear
)

# Use a named substitution matrix instead of match/mismatch scores
aln = pair_align(seq1, seq2, mode='global', sub_score='NUC.4.4', gap_cost=3)

# Protein alignment with BLASTP-like defaults (BLOSUM62)
protein_query = Protein('ACDEFGHIKLMNPQRSTVWY')
protein_target = Protein('ACDEFMNPQRSTVWY')
aln = pair_align_prot(protein_query, protein_target)

# Re-score an existing alignment with the same parameters
score = align_score((aln.paths[0], (protein_query, protein_target)),
                    sub_score='BLOSUM62', gap_cost=(11, 1))

# PairAlignResult also unpacks as a tuple
score, (path,), _ = pair_align_nucl(seq1, seq2)
Raw lines 88 to 130
131### Multiple Sequence Alignment
133```python
134from skbio.alignment import TabularMSA, pair_align_nucl
135from skbio import DNA
137# Read MSA from file
138msa = TabularMSA.read('alignment.fasta', constructor=DNA)
140# Build a TabularMSA from a pairwise alignment path + original sequences
141score, (path,), _ = pair_align_nucl(DNA('ATCGATCG'), DNA('ATCGGGGATCG'))
142msa = TabularMSA.from_path_seqs(path, (DNA('ATCGATCG'), DNA('ATCGGGGATCG')))
144# Create MSA manually
145seqs = [
146 DNA('ATCG--'),
147 DNA('ATGG--'),
148 DNA('ATCGAT')
150msa = TabularMSA(seqs)
152# MSA operations
153consensus = msa.consensus()
154# consensus() is the majority consensus; ties follow alphabet order
156# Calculate conservation
157conservation = msa.conservation()
159# Access sequences
160first_seq = msa[0]
161column = msa[:, 2] # Third column
163# Filter gaps
164keep = msa.gap_frequencies(axis='sequence', relative=True) <= 0.5
165degapped_msa = msa.iloc[:, keep]
167# Entropy in bits; this example treats gaps as a fifth symbol
168from scipy.stats import entropy
169position_entropies = [entropy(list(col.frequencies().values()), base=2)
170 for col in msa.iter_positions()]
171```

Multiple Sequence Alignment

from skbio.alignment import TabularMSA, pair_align_nucl
from skbio import DNA

# Read MSA from file
msa = TabularMSA.read('alignment.fasta', constructor=DNA)

# Build a TabularMSA from a pairwise alignment path + original sequences
score, (path,), _ = pair_align_nucl(DNA('ATCGATCG'), DNA('ATCGGGGATCG'))
msa = TabularMSA.from_path_seqs(path, (DNA('ATCGATCG'), DNA('ATCGGGGATCG')))

# Create MSA manually
seqs = [
    DNA('ATCG--'),
    DNA('ATGG--'),
    DNA('ATCGAT')
]
msa = TabularMSA(seqs)

# MSA operations
consensus = msa.consensus()
# consensus() is the majority consensus; ties follow alphabet order

# Calculate conservation
conservation = msa.conservation()

# Access sequences
first_seq = msa[0]
column = msa[:, 2]  # Third column

# Filter gaps
keep = msa.gap_frequencies(axis='sequence', relative=True) <= 0.5
degapped_msa = msa.iloc[:, keep]

# Entropy in bits; this example treats gaps as a fifth symbol
from scipy.stats import entropy
position_entropies = [entropy(list(col.frequencies().values()), base=2)
                      for col in msa.iter_positions()]
Raw lines 131 to 172
173### CIGAR Strings and Alignment Paths
175```python
176from skbio.alignment import PairAlignPath, AlignPath, pair_align_nucl
177from skbio import DNA
179# Parse a CIGAR string into a pairwise alignment path
180path = PairAlignPath.from_cigar('10M2I5M3D10M')
181print(repr(path)) # <PairAlignPath, ..., CIGAR: '10M2I5M3D10M'>
183# A path produced by pair_align already carries its CIGAR
184aln = pair_align_nucl(DNA('ATCGATCG'), DNA('ATCGGGGATCG'))
185cigar_string = aln.paths[0].to_cigar()
187# AlignPath generalizes to >2 sequences (e.g., from aligned strings)
188path3 = AlignPath.from_aligned(['CGTCGTGC', 'CA--GT-C', 'CGTCGT-T'])
190# Parse CIGAR output from external tools such as parasail
191# path = PairAlignPath.from_cigar(res.cigar.decode)
192```

CIGAR Strings and Alignment Paths

from skbio.alignment import PairAlignPath, AlignPath, pair_align_nucl
from skbio import DNA

# Parse a CIGAR string into a pairwise alignment path
path = PairAlignPath.from_cigar('10M2I5M3D10M')
print(repr(path))   # <PairAlignPath, ..., CIGAR: '10M2I5M3D10M'>

# A path produced by pair_align already carries its CIGAR
aln = pair_align_nucl(DNA('ATCGATCG'), DNA('ATCGGGGATCG'))
cigar_string = aln.paths[0].to_cigar()

# AlignPath generalizes to >2 sequences (e.g., from aligned strings)
path3 = AlignPath.from_aligned(['CGTCGTGC', 'CA--GT-C', 'CGTCGT-T'])

# Parse CIGAR output from external tools such as parasail
# path = PairAlignPath.from_cigar(res.cigar.decode)
Raw lines 173 to 193
194## Phylogenetic Trees

Phylogenetic Trees

Raw lines 194 to 195
196### Tree Construction
198```python
199from skbio import TreeNode, DistanceMatrix
200from skbio.tree import nj, upgma
202# Distance matrix
203dm = DistanceMatrix([[0, 5, 9, 9],
204 [5, 0, 10, 10],
205 [9, 10, 0, 8],
206 [9, 10, 8, 0]],
207 ids=['A', 'B', 'C', 'D'])
209# Neighbor joining
210nj_tree = nj(dm)
212# UPGMA (assumes molecular clock)
213upgma_tree = upgma(dm)
215# Balanced Minimum Evolution (scalable for large trees)
216from skbio.tree import bme
217bme_tree = bme(dm)
218```

Tree Construction

from skbio import TreeNode, DistanceMatrix
from skbio.tree import nj, upgma

# Distance matrix
dm = DistanceMatrix([[0, 5, 9, 9],
                     [5, 0, 10, 10],
                     [9, 10, 0, 8],
                     [9, 10, 8, 0]],
                    ids=['A', 'B', 'C', 'D'])

# Neighbor joining
nj_tree = nj(dm)

# UPGMA (assumes molecular clock)
upgma_tree = upgma(dm)

# Balanced Minimum Evolution (scalable for large trees)
from skbio.tree import bme
bme_tree = bme(dm)
Raw lines 196 to 219
220### Tree Manipulation
222```python
223from skbio import TreeNode
225# Read tree
226tree = TreeNode.read('tree.nwk', format='newick')
228# Traversal
229for node in tree.traverse():
230 print(node.name)
232# Preorder, postorder, levelorder
233for node in tree.preorder():
234 print(node.name)
236# Get tips only
237tips = list(tree.tips())
239# Find specific node
240node = tree.find('taxon_name')
242# Root tree at midpoint
243rooted_tree = tree.root_at_midpoint()
245# Prune tree to specific taxa
246pruned = tree.shear(['taxon1', 'taxon2', 'taxon3'])
248# Get subtree
249lca = tree.lowest_common_ancestor(['taxon1', 'taxon2'])
250subtree = lca.copy()
252# Add/remove nodes
253parent = tree.find('parent_name')
254child = TreeNode(name='new_child', length=0.5)
255parent.append(child)
257# Remove node
258node_to_remove = tree.find('taxon_to_remove')
259node_to_remove.parent.remove(node_to_remove)
260```

Tree Manipulation

from skbio import TreeNode

# Read tree
tree = TreeNode.read('tree.nwk', format='newick')

# Traversal
for node in tree.traverse():
    print(node.name)

# Preorder, postorder, levelorder
for node in tree.preorder():
    print(node.name)

# Get tips only
tips = list(tree.tips())

# Find specific node
node = tree.find('taxon_name')

# Root tree at midpoint
rooted_tree = tree.root_at_midpoint()

# Prune tree to specific taxa
pruned = tree.shear(['taxon1', 'taxon2', 'taxon3'])

# Get subtree
lca = tree.lowest_common_ancestor(['taxon1', 'taxon2'])
subtree = lca.copy()

# Add/remove nodes
parent = tree.find('parent_name')
child = TreeNode(name='new_child', length=0.5)
parent.append(child)

# Remove node
node_to_remove = tree.find('taxon_to_remove')
node_to_remove.parent.remove(node_to_remove)
Raw lines 220 to 261
262### Tree Distances and Comparisons
264```python
265# Patristic distance (branch-length distance) between two nodes
266node1 = tree.find('taxon1')
267node2 = tree.find('taxon2')
268patristic = node1.distance(node2)
270# Cophenetic (patristic) distance matrix among all tips
271# cophenet() replaces the former tip_tip_distances()
272cophenetic_dm = tree.cophenet()
274# Robinson-Foulds distance between two trees (compare_rfd, added in 0.6.3)
275rf_dist = tree.compare_rfd(other_tree) # count (float)
276rf_prop = tree.compare_rfd(other_tree, proportion=True) # normalized to [0, 1]
278# Weighted Robinson-Foulds and cophenetic-correlation comparisons
279wrf = tree.compare_wrfd(other_tree)
280coph = tree.compare_cophenet(other_tree)
282# Pairwise RF distances among many trees -> DistanceMatrix
283from skbio.tree import rf_dists
284rf_dm = rf_dists([tree, other_tree, third_tree])
285```

Tree Distances and Comparisons

# Patristic distance (branch-length distance) between two nodes
node1 = tree.find('taxon1')
node2 = tree.find('taxon2')
patristic = node1.distance(node2)

# Cophenetic (patristic) distance matrix among all tips
# cophenet() replaces the former tip_tip_distances()
cophenetic_dm = tree.cophenet()

# Robinson-Foulds distance between two trees (compare_rfd, added in 0.6.3)
rf_dist = tree.compare_rfd(other_tree)              # count (float)
rf_prop = tree.compare_rfd(other_tree, proportion=True)  # normalized to [0, 1]

# Weighted Robinson-Foulds and cophenetic-correlation comparisons
wrf = tree.compare_wrfd(other_tree)
coph = tree.compare_cophenet(other_tree)

# Pairwise RF distances among many trees -> DistanceMatrix
from skbio.tree import rf_dists
rf_dm = rf_dists([tree, other_tree, third_tree])
Raw lines 262 to 286
287### Tree Visualization
289```python
290# ASCII art visualization
291print(tree.ascii_art())
293# For advanced visualization, export to external tools
294tree.write('tree.nwk', format='newick')
296# Then use ete3, toytree, or ggtree for publication-quality figures
297```

Tree Visualization

# ASCII art visualization
print(tree.ascii_art())

# For advanced visualization, export to external tools
tree.write('tree.nwk', format='newick')

# Then use ete3, toytree, or ggtree for publication-quality figures
Raw lines 287 to 298
299## Diversity Metrics

Diversity Metrics

Raw lines 299 to 300
301### Alpha Diversity
303```python
304from skbio.diversity import alpha_diversity, get_alpha_diversity_metrics
305import numpy as np
307# Sample count data (samples x features)
308counts = np.array([
309 [10, 5, 0, 3],
310 [2, 0, 8, 4],
311 [5, 5, 5, 5]
312])
313sample_ids = ['Sample1', 'Sample2', 'Sample3']
315# List available metrics
316print(get_alpha_diversity_metrics())
318# Calculate various alpha diversity metrics
319shannon = alpha_diversity('shannon', counts, ids=sample_ids)
320simpson = alpha_diversity('simpson', counts, ids=sample_ids)
321observed = alpha_diversity('observed_features', counts, ids=sample_ids) # was 'observed_otus'
322chao1 = alpha_diversity('chao1', counts, ids=sample_ids)
323hill_q2 = alpha_diversity('hill', counts, ids=sample_ids, order=2) # effective number of species
325# Phylogenetic alpha diversity (requires tree). Note: taxa= replaces otu_ids=
326from skbio import TreeNode
328tree = TreeNode.read('tree.nwk')
329feature_ids = ['OTU1', 'OTU2', 'OTU3', 'OTU4']
331faith_pd = alpha_diversity('faith_pd', counts, ids=sample_ids,
332 tree=tree, taxa=feature_ids)
333```

Alpha Diversity

from skbio.diversity import alpha_diversity, get_alpha_diversity_metrics
import numpy as np

# Sample count data (samples x features)
counts = np.array([
    [10, 5, 0, 3],
    [2, 0, 8, 4],
    [5, 5, 5, 5]
])
sample_ids = ['Sample1', 'Sample2', 'Sample3']

# List available metrics
print(get_alpha_diversity_metrics())

# Calculate various alpha diversity metrics
shannon = alpha_diversity('shannon', counts, ids=sample_ids)
simpson = alpha_diversity('simpson', counts, ids=sample_ids)
observed = alpha_diversity('observed_features', counts, ids=sample_ids)  # was 'observed_otus'
chao1 = alpha_diversity('chao1', counts, ids=sample_ids)
hill_q2 = alpha_diversity('hill', counts, ids=sample_ids, order=2)  # effective number of species

# Phylogenetic alpha diversity (requires tree). Note: taxa= replaces otu_ids=
from skbio import TreeNode

tree = TreeNode.read('tree.nwk')
feature_ids = ['OTU1', 'OTU2', 'OTU3', 'OTU4']

faith_pd = alpha_diversity('faith_pd', counts, ids=sample_ids,
                           tree=tree, taxa=feature_ids)
Raw lines 301 to 334
335### Beta Diversity
337```python
338from skbio.diversity import beta_diversity
340# Beta diversity (all pairwise comparisons)
341bc_dm = beta_diversity('braycurtis', counts, ids=sample_ids)
343# Jaccard (presence/absence)
344jaccard_dm = beta_diversity('jaccard', counts, ids=sample_ids)
346# Phylogenetic beta diversity (taxa= replaces the deprecated otu_ids=)
347unifrac_dm = beta_diversity('unweighted_unifrac', counts,
348 ids=sample_ids,
349 tree=tree,
350 taxa=feature_ids)
352weighted_unifrac_dm = beta_diversity('weighted_unifrac', counts,
353 ids=sample_ids,
354 tree=tree,
355 taxa=feature_ids)
357# For a subset, compute a COMPLETE matrix on selected samples.
358subset_dm = beta_diversity('braycurtis', counts[:2], ids=sample_ids[:2])
359```
361Deprecated `partial_beta_diversity` leaves uncomputed pairs as zero (and does not
362resolve general SciPy metric strings such as `braycurtis`). Those zeros are not
363measured distances. Do not use it as input to ordination or permutation tests.
364For large complete calculations, `block_beta_diversity` supports a custom `map_f`;
365returning a dense matrix still costs quadratic storage. For SciPy metrics pass
366a callable, e.g. `block_beta_diversity(scipy.spatial.distance.braycurtis, counts,
367ids=sample_ids)`, since this path delegates to the same restricted resolver.

Beta Diversity

from skbio.diversity import beta_diversity

# Beta diversity (all pairwise comparisons)
bc_dm = beta_diversity('braycurtis', counts, ids=sample_ids)

# Jaccard (presence/absence)
jaccard_dm = beta_diversity('jaccard', counts, ids=sample_ids)

# Phylogenetic beta diversity (taxa= replaces the deprecated otu_ids=)
unifrac_dm = beta_diversity('unweighted_unifrac', counts,
                            ids=sample_ids,
                            tree=tree,
                            taxa=feature_ids)

weighted_unifrac_dm = beta_diversity('weighted_unifrac', counts,
                                     ids=sample_ids,
                                     tree=tree,
                                     taxa=feature_ids)

# For a subset, compute a COMPLETE matrix on selected samples.
subset_dm = beta_diversity('braycurtis', counts[:2], ids=sample_ids[:2])

Deprecated partial_beta_diversity leaves uncomputed pairs as zero (and does not resolve general SciPy metric strings such as braycurtis). Those zeros are not measured distances. Do not use it as input to ordination or permutation tests. For large complete calculations, block_beta_diversity supports a custom map_f; returning a dense matrix still costs quadratic storage. For SciPy metrics pass a callable, e.g. block_beta_diversity(scipy.spatial.distance.braycurtis, counts, ids=sample_ids), since this path delegates to the same restricted resolver.

Raw lines 335 to 368
369### Rarefaction and Subsampling
371```python
372from skbio.stats import subsample_counts
374# Rarefy to minimum depth
375min_depth = int(counts.sum(axis=1).min())
376if min_depth <= 0:
377 raise ValueError("Remove or resolve empty samples before rarefaction")
378rng = np.random.default_rng(42)
379rarefied = [subsample_counts(row, n=min_depth, seed=rng) for row in counts]
381# Repeated rarefactions quantify subsampling variability, not biological confidence
382import numpy as np
383rarefactions = []
384for i in range(100):
385 rarefied_counts = np.array([subsample_counts(row, n=min_depth, seed=rng) for row in counts])
386 shannon_rare = alpha_diversity('shannon', rarefied_counts)
387 rarefactions.append(shannon_rare)
389# Calculate mean and std
390mean_shannon = np.mean(rarefactions, axis=0)
391std_shannon = np.std(rarefactions, axis=0)
392```

Rarefaction and Subsampling

from skbio.stats import subsample_counts

# Rarefy to minimum depth
min_depth = int(counts.sum(axis=1).min())
if min_depth <= 0:
    raise ValueError("Remove or resolve empty samples before rarefaction")
rng = np.random.default_rng(42)
rarefied = [subsample_counts(row, n=min_depth, seed=rng) for row in counts]

# Repeated rarefactions quantify subsampling variability, not biological confidence
import numpy as np
rarefactions = []
for i in range(100):
    rarefied_counts = np.array([subsample_counts(row, n=min_depth, seed=rng) for row in counts])
    shannon_rare = alpha_diversity('shannon', rarefied_counts)
    rarefactions.append(shannon_rare)

# Calculate mean and std
mean_shannon = np.mean(rarefactions, axis=0)
std_shannon = np.std(rarefactions, axis=0)
Raw lines 369 to 393
394## Ordination
396### Principal Coordinate Analysis (PCoA)
398```python
399from skbio.stats.ordination import pcoa
400from skbio import DistanceMatrix
401import numpy as np
403# PCoA from distance matrix
404dm = DistanceMatrix(...)
405pcoa_results = pcoa(dm)
407# Access coordinates
408pc1 = pcoa_results.samples['PC1']
409pc2 = pcoa_results.samples['PC2']
411# Proportion explained
412prop_explained = pcoa_results.proportion_explained
414# Eigenvalues
415eigenvalues = pcoa_results.eigvals
417# Save results
418pcoa_results.write('pcoa_results.txt')
420# Plot with matplotlib
421import matplotlib.pyplot as plt
422plt.scatter(pc1, pc2)
423plt.xlabel(f'PC1 ({prop_explained.iloc[0]*100:.1f}%)')
424plt.ylabel(f'PC2 ({prop_explained.iloc[1]*100:.1f}%)')
425```

Principal Coordinate Analysis (PCoA)

from skbio.stats.ordination import pcoa
from skbio import DistanceMatrix
import numpy as np

# PCoA from distance matrix
dm = DistanceMatrix(...)
pcoa_results = pcoa(dm)

# Access coordinates
pc1 = pcoa_results.samples['PC1']
pc2 = pcoa_results.samples['PC2']

# Proportion explained
prop_explained = pcoa_results.proportion_explained

# Eigenvalues
eigenvalues = pcoa_results.eigvals

# Save results
pcoa_results.write('pcoa_results.txt')

# Plot with matplotlib
import matplotlib.pyplot as plt
plt.scatter(pc1, pc2)
plt.xlabel(f'PC1 ({prop_explained.iloc[0]*100:.1f}%)')
plt.ylabel(f'PC2 ({prop_explained.iloc[1]*100:.1f}%)')
Raw lines 396 to 426
427### Canonical Correspondence Analysis (CCA)
429```python
430from skbio.stats.ordination import cca
431import pandas as pd
432import numpy as np
434# Species abundance matrix (samples x species)
435species = np.array([
436 [10, 5, 3],
437 [2, 8, 4],
438 [5, 5, 5]
439])
441# Environmental variables (samples x variables)
442env = pd.DataFrame({
443 'pH': [6.5, 7.0, 6.8]
444})
446# CCA
447cca_results = cca(species, env,
448 sample_ids=['Site1', 'Site2', 'Site3'],
449 feature_ids=['SpeciesA', 'SpeciesB', 'SpeciesC'])
451# Access constrained axes
452cca1 = cca_results.samples['CCA1']
453# One independent constraint gives one constrained axis; later axes are residual
455# Biplot scores for environmental variables
456env_scores = cca_results.biplot_scores
457```

Canonical Correspondence Analysis (CCA)

from skbio.stats.ordination import cca
import pandas as pd
import numpy as np

# Species abundance matrix (samples x species)
species = np.array([
    [10, 5, 3],
    [2, 8, 4],
    [5, 5, 5]
])

# Environmental variables (samples x variables)
env = pd.DataFrame({
    'pH': [6.5, 7.0, 6.8]
})

# CCA
cca_results = cca(species, env,
                 sample_ids=['Site1', 'Site2', 'Site3'],
                 feature_ids=['SpeciesA', 'SpeciesB', 'SpeciesC'])

# Access constrained axes
cca1 = cca_results.samples['CCA1']
# One independent constraint gives one constrained axis; later axes are residual

# Biplot scores for environmental variables
env_scores = cca_results.biplot_scores
Raw lines 427 to 458
459### Redundancy Analysis (RDA)
461```python
462from skbio.stats.ordination import rda
464# Similar to CCA but for linear relationships
465rda_results = rda(species, env,
466 sample_ids=['Site1', 'Site2', 'Site3'],
467 feature_ids=['SpeciesA', 'SpeciesB', 'SpeciesC'])
468```

Redundancy Analysis (RDA)

from skbio.stats.ordination import rda

# Similar to CCA but for linear relationships
rda_results = rda(species, env,
                 sample_ids=['Site1', 'Site2', 'Site3'],
                 feature_ids=['SpeciesA', 'SpeciesB', 'SpeciesC'])
Raw lines 459 to 469
470## Statistical Tests

Statistical Tests

Raw lines 470 to 471
472### PERMANOVA
474```python
475from skbio.stats.distance import permanova
476from skbio import DistanceMatrix
477import numpy as np
479# Distance matrix
480dm = DistanceMatrix(...)
482# Grouping variable
483grouping = ['Group1', 'Group1', 'Group2', 'Group2', 'Group3', 'Group3']
485# Run PERMANOVA
486results = permanova(dm, grouping, permutations=999, seed=42)
488print(f"Test statistic: {results['test statistic']}")
489print(f"p-value: {results['p-value']}")
490print(f"Sample size: {results['sample size']}")
491print(f"Number of groups: {results['number of groups']}")
492```

PERMANOVA

from skbio.stats.distance import permanova
from skbio import DistanceMatrix
import numpy as np

# Distance matrix
dm = DistanceMatrix(...)

# Grouping variable
grouping = ['Group1', 'Group1', 'Group2', 'Group2', 'Group3', 'Group3']

# Run PERMANOVA
results = permanova(dm, grouping, permutations=999, seed=42)

print(f"Test statistic: {results['test statistic']}")
print(f"p-value: {results['p-value']}")
print(f"Sample size: {results['sample size']}")
print(f"Number of groups: {results['number of groups']}")
Raw lines 472 to 493
494### ANOSIM
496```python
497from skbio.stats.distance import anosim
499# ANOSIM test
500results = anosim(dm, grouping, permutations=999, seed=42)
502print(f"R statistic: {results['test statistic']}")
503print(f"p-value: {results['p-value']}")
504```

ANOSIM

from skbio.stats.distance import anosim

# ANOSIM test
results = anosim(dm, grouping, permutations=999, seed=42)

print(f"R statistic: {results['test statistic']}")
print(f"p-value: {results['p-value']}")
Raw lines 494 to 505
506### PERMDISP
508```python
509from skbio.stats.distance import permdisp
511# Test homogeneity of dispersions; all axes (default dimensions=10 fails for n<10)
512results = permdisp(dm, grouping, permutations=999, seed=42, dimensions=0)
514print(f"F statistic: {results['test statistic']}")
515print(f"p-value: {results['p-value']}")
516```

PERMDISP

from skbio.stats.distance import permdisp

# Test homogeneity of dispersions; all axes (default dimensions=10 fails for n<10)
results = permdisp(dm, grouping, permutations=999, seed=42, dimensions=0)

print(f"F statistic: {results['test statistic']}")
print(f"p-value: {results['p-value']}")
Raw lines 506 to 517
518### Mantel Test
520```python
521from skbio.stats.distance import mantel
522from skbio import DistanceMatrix
524# Two distance matrices to compare
525dm1 = DistanceMatrix(...) # e.g., genetic distance
526dm2 = DistanceMatrix(...) # e.g., geographic distance
528# Mantel test
529r, p_value, n = mantel(dm1, dm2, method='pearson', permutations=999, seed=42)
531print(f"Correlation: {r}")
532print(f"p-value: {p_value}")
533print(f"Sample size: {n}")
535# Spearman correlation
536r_spearman, p, n = mantel(dm1, dm2, method='spearman', permutations=999, seed=42)
537```

Mantel Test

from skbio.stats.distance import mantel
from skbio import DistanceMatrix

# Two distance matrices to compare
dm1 = DistanceMatrix(...)  # e.g., genetic distance
dm2 = DistanceMatrix(...)  # e.g., geographic distance

# Mantel test
r, p_value, n = mantel(dm1, dm2, method='pearson', permutations=999, seed=42)

print(f"Correlation: {r}")
print(f"p-value: {p_value}")
print(f"Sample size: {n}")

# Spearman correlation
r_spearman, p, n = mantel(dm1, dm2, method='spearman', permutations=999, seed=42)
Raw lines 518 to 538
539### Statistical boundaries
541`mantel(x, y, ...)` tests association of two distance matrices. It has no third
542matrix/partial-Mantel option; passing `alternative='two-sided'` does not adjust
543for covariates. Use a method that explicitly represents the study design when
544adjustment, blocked permutations, or repeated measures are required. The built-in
545PERMANOVA, ANOSIM, and PERMDISP examples use unrestricted label permutations.
546With 999 permutations, p-values have a minimum of 0.001; this is a resolution,
547not an assurance of robust inference. Report effect sizes, sample sizes, seeds,
548multiple-testing choices, and design limitations. PERMANOVA uses the original
549full distance matrix; PCoA is a parallel visualization, not its input reduction.

Statistical boundaries

mantel(x, y, ...) tests association of two distance matrices. It has no third matrix/partial-Mantel option; passing alternative='two-sided' does not adjust for covariates. Use a method that explicitly represents the study design when adjustment, blocked permutations, or repeated measures are required. The built-in PERMANOVA, ANOSIM, and PERMDISP examples use unrestricted label permutations. With 999 permutations, p-values have a minimum of 0.001; this is a resolution, not an assurance of robust inference. Report effect sizes, sample sizes, seeds, multiple-testing choices, and design limitations. PERMANOVA uses the original full distance matrix; PCoA is a parallel visualization, not its input reduction.

Raw lines 539 to 550
551## Distance Matrices

Distance Matrices

Raw lines 551 to 552
553### Creating and Manipulating Distance Matrices
555```python
556from skbio import DistanceMatrix
557from skbio.stats.distance import PairwiseMatrix
558import numpy as np
560# Create from array
561data = np.array([[0, 1, 2],
562 [1, 0, 3],
563 [2, 3, 0]])
564dm = DistanceMatrix(data, ids=['A', 'B', 'C'])
566# Access elements
567dist_ab = dm['A', 'B']
568row_a = dm['A']
570# Slicing
571subset_dm = dm.filter(['A', 'C'])
573# General/asymmetric matrix: PairwiseMatrix (renamed from DissimilarityMatrix,
574# which is kept as a deprecated alias)
575asym_data = np.array([[0, 1, 2],
576 [3, 0, 4],
577 [5, 6, 0]])
578pwm = PairwiseMatrix(asym_data, ids=['X', 'Y', 'Z'])
580# Read/write
581dm.write('distances.txt')
582dm2 = DistanceMatrix.read('distances.txt')
584# Convert to condensed form (for scipy)
585condensed = dm.condensed_form()
587# Convert to dataframe
588df = dm.to_data_frame()
589```

Creating and Manipulating Distance Matrices

from skbio import DistanceMatrix
from skbio.stats.distance import PairwiseMatrix
import numpy as np

# Create from array
data = np.array([[0, 1, 2],
                 [1, 0, 3],
                 [2, 3, 0]])
dm = DistanceMatrix(data, ids=['A', 'B', 'C'])

# Access elements
dist_ab = dm['A', 'B']
row_a = dm['A']

# Slicing
subset_dm = dm.filter(['A', 'C'])

# General/asymmetric matrix: PairwiseMatrix (renamed from DissimilarityMatrix,
# which is kept as a deprecated alias)
asym_data = np.array([[0, 1, 2],
                      [3, 0, 4],
                      [5, 6, 0]])
pwm = PairwiseMatrix(asym_data, ids=['X', 'Y', 'Z'])

# Read/write
dm.write('distances.txt')
dm2 = DistanceMatrix.read('distances.txt')

# Convert to condensed form (for scipy)
condensed = dm.condensed_form()

# Convert to dataframe
df = dm.to_data_frame()
Raw lines 553 to 590
591## File I/O
593### Reading Sequences
595```python
596import skbio
598# Read single sequence
599dna = skbio.DNA.read('sequence.fasta', format='fasta')
601# Read multiple sequences (generator)
602for seq in skbio.io.read('sequences.fasta', format='fasta', constructor=skbio.DNA):
603 print(seq.metadata['id'], len(seq))
605# Read into list
606sequences = list(skbio.io.read('sequences.fasta', format='fasta',
607 constructor=skbio.DNA))
609# Read FASTQ with quality scores
610for seq in skbio.io.read('reads.fastq', format='fastq', constructor=skbio.DNA, phred_offset=33):
611 quality = seq.positional_metadata['quality']
612 print(f"Mean quality: {quality.mean()}")
613```

Reading Sequences

import skbio

# Read single sequence
dna = skbio.DNA.read('sequence.fasta', format='fasta')

# Read multiple sequences (generator)
for seq in skbio.io.read('sequences.fasta', format='fasta', constructor=skbio.DNA):
    print(seq.metadata['id'], len(seq))

# Read into list
sequences = list(skbio.io.read('sequences.fasta', format='fasta',
                               constructor=skbio.DNA))

# Read FASTQ with quality scores
for seq in skbio.io.read('reads.fastq', format='fastq', constructor=skbio.DNA, phred_offset=33):
    quality = seq.positional_metadata['quality']
    print(f"Mean quality: {quality.mean()}")
Raw lines 593 to 614
615### Writing Sequences
617```python
618# Write single sequence
619dna.write('output.fasta', format='fasta')
621# Write multiple sequences: a generator is required (not a list/list iterator)
622sequences = [dna1, dna2, dna3]
623skbio.io.write((seq for seq in sequences), format='fasta', into='output.fasta')
625# Write with custom line wrapping
626dna.write('output.fasta', format='fasta', max_width=60)
627```

Writing Sequences

# Write single sequence
dna.write('output.fasta', format='fasta')

# Write multiple sequences: a generator is required (not a list/list iterator)
sequences = [dna1, dna2, dna3]
skbio.io.write((seq for seq in sequences), format='fasta', into='output.fasta')

# Write with custom line wrapping
dna.write('output.fasta', format='fasta', max_width=60)
Raw lines 615 to 628
629### BIOM Tables
631```python
632from skbio import Table
634# Native scikit-bio reader/writer handles BIOM 2.1 HDF5, format name is 'biom'
635table = Table.read('table.biom', format='biom')
636# For legacy JSON, use biom.load_table(path); do not pass format='hdf5'.
638# Access data
639sample_ids = table.ids(axis='sample')
640feature_ids = table.ids(axis='observation')
641matrix = table.matrix_data.toarray() # if sparse
643# Filter samples
644abundant_samples = table.filter(lambda row, id_, md: row.sum() > 1000,
645 axis='sample', inplace=False)
647# Filter features (OTUs/ASVs)
648prevalent_features = table.filter(lambda col, id_, md: (col > 0).sum() >= 3,
649 axis='observation', inplace=False)
651# Normalize
652relative_abundance = table.norm(axis='sample', inplace=False)
654# Write
655abundant_samples.write('filtered_table.biom', format='biom')
656```

BIOM Tables

from skbio import Table

# Native scikit-bio reader/writer handles BIOM 2.1 HDF5, format name is 'biom'
table = Table.read('table.biom', format='biom')
# For legacy JSON, use biom.load_table(path); do not pass format='hdf5'.

# Access data
sample_ids = table.ids(axis='sample')
feature_ids = table.ids(axis='observation')
matrix = table.matrix_data.toarray()  # if sparse

# Filter samples
abundant_samples = table.filter(lambda row, id_, md: row.sum() > 1000,
                                 axis='sample', inplace=False)

# Filter features (OTUs/ASVs)
prevalent_features = table.filter(lambda col, id_, md: (col > 0).sum() >= 3,
                                 axis='observation', inplace=False)

# Normalize
relative_abundance = table.norm(axis='sample', inplace=False)

# Write
abundant_samples.write('filtered_table.biom', format='biom')
Raw lines 629 to 657
658### Format Conversion
660```python
661# FASTQ to FASTA
662seqs = skbio.io.read('input.fastq', format='fastq', constructor=skbio.DNA, phred_offset=33)
663skbio.io.write(seqs, format='fasta', into='output.fasta')
665# GenBank to FASTA
666seqs = skbio.io.read('genes.gb', format='genbank', constructor=skbio.DNA)
667skbio.io.write(seqs, format='fasta', into='genes.fasta')
668```

Format Conversion

# FASTQ to FASTA
seqs = skbio.io.read('input.fastq', format='fastq', constructor=skbio.DNA, phred_offset=33)
skbio.io.write(seqs, format='fasta', into='output.fasta')

# GenBank to FASTA
seqs = skbio.io.read('genes.gb', format='genbank', constructor=skbio.DNA)
skbio.io.write(seqs, format='fasta', into='genes.fasta')
Raw lines 658 to 669
670## Troubleshooting

Troubleshooting

Raw lines 670 to 671
672### Common Issues and Solutions
674#### Issue: Duplicate IDs or missing count provenance
676Do not silently drop duplicate records. Determine whether they are technical
677replicates, distinct records with colliding labels, or true duplicates; preserve
678an explicit mapping when renaming or aggregating. A unique-ID check cannot resolve
679biological identity.
681Do not turn proportions into invented counts with `(abundance * 1000).astype(int)`.
682Obtain original counts for Chao1/ACE, rarefaction, and count-based inference. If
683only relative abundance is available, choose a compatible descriptive metric
684(such as Shannon or Bray-Curtis), disclose normalization, and do not imply that
685sampling-depth information was recovered. Validate finite/nonnegative values
686and empty samples yourself: the general diversity driver accepts floats and
687its validation does not establish count provenance or finite data.
689#### Issue: Memory error with large files
690```python
691# Problem: Loading entire file into memory
692# Solution: Use generators
693for seq in skbio.io.read('huge.fasta', format='fasta', constructor=skbio.DNA):
694 # Process one at a time
695 process(seq)
696```
698#### Issue: Tree tips don't match OTU IDs
699```python
700# Problem: Mismatch between tree tip names and feature IDs
701# Solution: Verify and align IDs
702tree_tips = {tip.name for tip in tree.tips()}
703missing_in_tree = set(feature_ids) - tree_tips
704if missing_in_tree:
705 raise ValueError(f"Features missing from tree: {sorted(missing_in_tree)}")
706# Keep feature_ids ordered exactly as table columns. Extra tree tips are allowed.
707# Optional shearing may change the root/stem; review rooting before UniFrac/PD.
708```
710#### Issue: Pairwise alignment receives already-gapped sequences
711```python
712# Pairwise inputs may have different lengths; TabularMSA rows must already align.
713# Degap only when deliberately recomputing alignment, preserving original data.
714seq1_degapped = seq1.degap()
715seq2_degapped = seq2.degap()
716alignment = pair_align_nucl(seq1_degapped, seq2_degapped)
717```

Common Issues and Solutions

Issue: Duplicate IDs or missing count provenance

Do not silently drop duplicate records. Determine whether they are technical replicates, distinct records with colliding labels, or true duplicates; preserve an explicit mapping when renaming or aggregating. A unique-ID check cannot resolve biological identity.

Do not turn proportions into invented counts with (abundance * 1000).astype(int). Obtain original counts for Chao1/ACE, rarefaction, and count-based inference. If only relative abundance is available, choose a compatible descriptive metric (such as Shannon or Bray-Curtis), disclose normalization, and do not imply that sampling-depth information was recovered. Validate finite/nonnegative values and empty samples yourself: the general diversity driver accepts floats and its validation does not establish count provenance or finite data.

Issue: Memory error with large files

# Problem: Loading entire file into memory
# Solution: Use generators
for seq in skbio.io.read('huge.fasta', format='fasta', constructor=skbio.DNA):
    # Process one at a time
    process(seq)

Issue: Tree tips don't match OTU IDs

# Problem: Mismatch between tree tip names and feature IDs
# Solution: Verify and align IDs
tree_tips = {tip.name for tip in tree.tips()}
missing_in_tree = set(feature_ids) - tree_tips
if missing_in_tree:
    raise ValueError(f"Features missing from tree: {sorted(missing_in_tree)}")
# Keep feature_ids ordered exactly as table columns. Extra tree tips are allowed.
# Optional shearing may change the root/stem; review rooting before UniFrac/PD.

Issue: Pairwise alignment receives already-gapped sequences

# Pairwise inputs may have different lengths; TabularMSA rows must already align.
# Degap only when deliberately recomputing alignment, preserving original data.
seq1_degapped = seq1.degap()
seq2_degapped = seq2.degap()
alignment = pair_align_nucl(seq1_degapped, seq2_degapped)
Raw lines 672 to 718
719### Performance Tips
7211. **Use appropriate data structures**: BIOM HDF5 for large tables, generators for large sequence files
7222. **Parallel processing**: Use `block_beta_diversity(..., map_f=...)` for a controlled mapper, or documented 0.7.4 compute engines. Incomplete pair matrices are not valid analysis inputs.
7233. **Subsample large datasets**: For exploratory analysis, work with subsampled data first
7244. **Cache results**: Save distance matrices and ordination results to avoid recomputation

Performance Tips

  1. Use appropriate data structures: BIOM HDF5 for large tables, generators for large sequence files
  2. Parallel processing: Use block_beta_diversity(..., map_f=...) for a controlled mapper, or documented 0.7.4 compute engines. Incomplete pair matrices are not valid analysis inputs.
  3. Subsample large datasets: For exploratory analysis, work with subsampled data first
  4. Cache results: Save distance matrices and ordination results to avoid recomputation
Raw lines 719 to 725
726### Integration Examples
728#### With pandas
729```python
730import pandas as pd
731from skbio import DistanceMatrix
733# Distance matrix to DataFrame
734dm = DistanceMatrix(...)
735df = dm.to_data_frame()
737# Alpha diversity to DataFrame
738alpha = alpha_diversity('shannon', counts, ids=sample_ids)
739alpha_df = pd.DataFrame({'shannon': alpha})
740```
742#### With matplotlib/seaborn
743```python
744import matplotlib.pyplot as plt
745import seaborn as sns
747# PCoA plot
748fig, ax = plt.subplots()
749codes, labels = pd.factorize(grouping)
750scatter = ax.scatter(pc1, pc2, c=codes, cmap='viridis')
751ax.set_xlabel(f'PC1 ({prop_explained.iloc[0]*100:.1f}%)')
752ax.set_ylabel(f'PC2 ({prop_explained.iloc[1]*100:.1f}%)')
753# codes are category labels, not a continuous numerical measurement
754handles, _ = scatter.legend_elements()
755ax.legend(handles, labels)
757# Heatmap of distance matrix
758sns.heatmap(dm.to_data_frame(), cmap='viridis')
759```
761#### With QIIME 2
762```python
763# scikit-bio objects are compatible with QIIME 2
764# Export from QIIME 2
765# qiime tools export --input-path table.qza --output-path exported/
767# Read in scikit-bio
768table = Table.read('exported/feature-table.biom')
770# Process with scikit-bio
771# ...
773# Import back to QIIME 2 if needed
774table.write('processed-table.biom')
775# qiime tools import --type 'FeatureTable[Frequency]' --input-path processed-table.biom --output-path processed.qza
776# Only use Frequency for nonnegative integer counts. QIIME 2 is a separate install;
777# this illustrative CLI was not executed during the scikit-bio runtime review.
778```

Integration Examples

With pandas

import pandas as pd
from skbio import DistanceMatrix

# Distance matrix to DataFrame
dm = DistanceMatrix(...)
df = dm.to_data_frame()

# Alpha diversity to DataFrame
alpha = alpha_diversity('shannon', counts, ids=sample_ids)
alpha_df = pd.DataFrame({'shannon': alpha})

With matplotlib/seaborn

import matplotlib.pyplot as plt
import seaborn as sns

# PCoA plot
fig, ax = plt.subplots()
codes, labels = pd.factorize(grouping)
scatter = ax.scatter(pc1, pc2, c=codes, cmap='viridis')
ax.set_xlabel(f'PC1 ({prop_explained.iloc[0]*100:.1f}%)')
ax.set_ylabel(f'PC2 ({prop_explained.iloc[1]*100:.1f}%)')
# codes are category labels, not a continuous numerical measurement
handles, _ = scatter.legend_elements()
ax.legend(handles, labels)

# Heatmap of distance matrix
sns.heatmap(dm.to_data_frame(), cmap='viridis')

With QIIME 2

# scikit-bio objects are compatible with QIIME 2
# Export from QIIME 2
# qiime tools export --input-path table.qza --output-path exported/

# Read in scikit-bio
table = Table.read('exported/feature-table.biom')

# Process with scikit-bio
# ...

# Import back to QIIME 2 if needed
table.write('processed-table.biom')
# qiime tools import --type 'FeatureTable[Frequency]' --input-path processed-table.biom --output-path processed.qza
# Only use Frequency for nonnegative integer counts. QIIME 2 is a separate install;
# this illustrative CLI was not executed during the scikit-bio runtime review.
Raw lines 726 to 778