# Biopython vs pandas for Bioinformatics File Parsing

Biopython parses domain-specific biological file formats such as FASTA, GenBank, FASTQ, and multiple sequence alignments, while pandas parses and manipulates rectangular [tabular data](/blog/research-skills/tabular-data-what-it-is-and-how-to-analyze-it) such as CSV, TSV, and Excel exports. The rule of thumb is simple: if the file encodes sequences, alignments, or structured biological records with their own standards, use Biopython. If the file is a table of rows and columns, use pandas. Most real pipelines in Python for bioinformatics use both, each where it fits.

This article gives you the difference between Biopython and pandas in concrete terms, a side-by-side comparison, working code for both, and worked scenarios that show when to use Biopython, when pandas is the better fit, and how to combine them.

## Why File Parsing Is the First Bottleneck in Python for Bioinformatics

Almost every bioinformatics workflow begins the same way. You download data, open the files, and turn them into structures your code can work with. Only then does analysis start. Parsing is where hours get lost, because bioinformatics files are heterogeneous by nature.

Sequence repositories and analysis tools have produced a long list of formats over the decades. Alignment and tree formats alone include FASTA, NEXUS, Newick, NeXML, and phyloXML, and each carries different assumptions about how metadata should be represented [1]. The same fragmentation exists in immunology, where repertoire analysis tools such as IgBLAST and IMGT/HighV-QUEST each emit results in their own file format and even label the same annotations differently, which motivated the creation of a dedicated standard format just to capture their outputs [2].

Two consequences follow for anyone writing Python for bioinformatics. First, a parser that treats a file as generic text will usually work but will silently drop structure, such as GenBank feature tables or per-base Phred qualities. Second, a parser that understands the format can validate, normalize, and expose that structure for you. Choosing between Biopython and pandas is really choosing how much of that structure you want handed to you, and at what memory cost.

## What Biopython Is

Biopython is a mature open source international collaboration of volunteer developers that provides Python libraries for a wide range of bioinformatics problems [3]. Its modules cover reading and writing many sequence file formats and multiple sequence alignments, working with 3D macromolecular structures, interacting with common tools such as BLAST, ClustalW, and EMBOSS, and accessing key online databases [3]. It is freely available under the Biopython license, with documentation and source code hosted at www.biopython.org [3].

The central data objects matter more than the module list. In Biopython you work with:

- **Seq**, a sequence object that knows its alphabet and supports transcription, translation, and reverse complementing.
- **SeqRecord**, a record that wraps a Seq plus an identifier, name, description, per-letter annotations such as quality scores, and free-form annotations.
- **MultipleSeqAlignment** and related alignment objects with their own accessor conventions.
- **Feature tables**, for example GenBank CDS, gene, and source features with location objects that handle joins and fuzzy boundaries.

Because the parsers populate these objects, Biopython acts as a validator as well as a reader. If a GenBank location string is malformed, Biopython fails loudly and tells you where, instead of handing you a broken string that you discover three steps later.

Install it in a clean environment:

```bash
python -m venv bioenv
source bioenv/bin/activate
pip install biopython
```

Verify the version, since some APIs changed over time:

```python
import Bio
print(Bio.__version__)
```

## What pandas Is

pandas is a general-purpose data analysis library built around the DataFrame, a labeled two-dimensional table with typed columns. It was designed for tabular data work, not for biology specifically, and it sits underneath a large ecosystem of analysis tooling. Visualization and analysis notations commonly build on libraries such as Matplotlib and pandas [4], and newer scientific tooling continues to depend on it. For example, evalci, a library for statistically rigorous comparison of model evaluations, is a pure-Python package that uses numpy, scipy, and pandas and is built around a per-item results table.

pandas does not know what a codon, a Phred score, or a GenBank feature is. What it does know is how to load, filter, join, group, pivot, and export rectangular data efficiently. Performance comparisons against spreadsheet tools on datasets of over 500,000 records and over one million records show that pandas completes loading, cleaning, aggregation, and search tasks in time intervals comparable to Excel while offering more precision and reproducibility in its procedures [5]. That combination of speed, expressiveness, and reproducibility is exactly why it became the default table layer in data-intensive fields.

Install it alongside Biopython in the same environment:

```bash
pip install pandas
```

## Side-by-Side Comparison: Biopython vs pandas for Bioinformatics File Parsing

| Criterion | Biopython | pandas |
|--|--|--|
| Core purpose | Biological formats and algorithms [3] | General tabular data analysis |
| Primary data object | Seq, SeqRecord, alignment objects | DataFrame, Series |
| File formats read natively | FASTA, FASTQ, GenBank, EMBL, Swiss-Prot, PDB, PHYLIP, Clustal, NEXUS, and many more | CSV, TSV, JSON, Excel, fixed-width, HDF5, Parquet, SQL tables |
| Understands sequence semantics | Yes, translation, complement, alphabets | No, treats sequence text as strings |
| Understands feature tables | Yes, locations, qualifiers, joins | No, you parse them yourself from tables |
| Per-record quality scores | Yes, via letter_annotations | No |
| Memory model | Iterators stream record by record | read_csv loads tables, with chunked reading available |
| Validation of domain structure | Strong, parsers fail on malformed records | None, the table is whatever the text was |
| Grouping, joining, aggregation | Minimal | Central strength |
| Online database access | Built in via Bio.Entrez and others | None |
| Dependency footprint | Biopython plus its compiled components | pandas plus numpy |
| Best fit | Sequences, alignments, records, database queries | Count matrices, annotation tables, variant and result tables |

The decisive row is the second one. Biopython hands you biological objects. pandas hands you a table. Everything else follows from that.

## What Biopython Parses Well

Biopython's sequence file support is the reason it exists. The Bio.SeqIO interface provides a single convention across formats: you call SeqIO.parse with a handle and a lowercase format string, and you get an iterator of SeqRecord objects.

### Reading FASTA

```python
from Bio import SeqIO

for record in SeqIO.parse("contigs.fasta", "fasta"):
    print(record.id, len(record.seq))
```

Each record gives you record.id, record.description, record.seq, and record.annotations. Because parse returns an iterator, a directory of 200,000 contigs streams through memory one record at a time. Convert to a list only if you truly need random access.

### Reading FASTQ with quality scores

FASTQ is FASTA plus per-base sequencing quality, usually encoded as Phred+33. Biopython decodes those characters into integers automatically:

```python
from Bio import SeqIO

for record in SeqIO.parse("reads.fastq", "fastq"):
    quals = record.letter_annotations["phred_scores"]
    mean_q = sum(quals) / len(quals)
    print(record.id, round(mean_q, 1))
```

Try writing that from a raw FASTQ with string slicing and you will eventually mishandle multi-line sequences or the Phred+64 legacy encoding. Biopython handles both encodings on read, which is a large part of the value.

### Reading GenBank with features

GenBank records are rich: they carry source organisms, references, and a feature table with CDS locations, gene qualifiers, and translation products. Biopython parses all of it into navigable objects:

```python
from Bio import SeqIO

for record in SeqIO.parse("plasmid.gb", "genbank"):
    print(record.id, record.annotations["organism"])
    for feature in record.features:
        if feature.type == "CDS":
            locus = feature.qualifiers.get("locus_tag", ["?"])
            product = feature.qualifiers.get("product", ["?"])
            print(locus, feature.location, product)
```

The location objects correctly resolve compound positions such as join operations, which is exactly the kind of format detail a hand-rolled parser gets wrong.

### Reading and writing alignments

Bio.AlignIO mirrors SeqIO for multiple sequence alignments in formats including Clustal, PHYLIP, and NEXUS. Format interoperability across many alignment and tree formats, with a common interface over their differing metadata models, is a recognized design problem in the field, and libraries built around that abstraction allow one reader and writer to support many formats at once [1]. SeqIO and AlignIO follow the same philosophy for Python.

```python
from Bio import AlignIO

alignment = AlignIO.read("tree_input.phy", "phylip")
print(alignment.get_alignment_length(), len(alignment))
```

### Querying online databases

Biopython includes programmatic access to key online databases [3]. Bio.Entrez wraps the NCBI E-utilities, so fetching a GenBank record by accession is a few lines:

```python
from Bio import Entrez, SeqIO

Entrez.email = "you@example.org"
handle = Entrez.efetch(db="nucleotide", id="NC_000913", rettype="gb", rettype2=None, retmode="text")
record = SeqIO.read(handle, "genbank")
handle.close()
print(record.description)
```

Set Entrez.email honestly and pace your requests. NCBI documents its usage policies for the E-utilities, andBiopython passes your requests through unmodified.

### Sequences as first-class objects

The payoff of domain-aware parsing appears the moment you compute something. Reverse complements, GC content, and six-frame translations are one call each:

```python
from Bio.Seq import Seq
from Bio.SeqUtils import gc_fraction

coding_dna = Seq("ATGGCCATTGTAATGGGCCGCTGAAAGGGTGCCCGATAG")
print(gc_fraction(coding_dna))
print(coding_dna.reverse_complement())
print(coding_dna.translate())
```

gc_fraction became the standard function name in Biopython 1.78 and later, replacing the older GC spelling. Pin your version in requirements files so a tutorial written years ago does not break silently.

## What pandas Parses Well

p shines wherever bioinformatics data is naturally tabular. That category is larger than people expect, and it includes the outputs of many upstream tools that you should never parse by hand.

### Count matrices and expression tables

A RNA-seq count matrix is a DataFrame by birth. Rows are genes, columns are samples, and every operation you need is a pandas idiom:

```python
import pandas as pd

counts = pd.read_csv("counts.tsv", sep="\t", index_col=0)
counts["total"] = counts.sum(axis=1)
high = counts[counts["total"] > 100]
library_sizes = counts.drop(columns="total").sum(axis=0)
cpm = counts.drop(columns="total").div(library_sizes, axis=1) * 1e6
```

### GFF3 and BED annotation files

GFF3 is a nine-column tab-delimited format with a header of comment lines and a trailing attributes column of semicolon-separated key=value pairs. BED is similar in spirit with fewer required columns. pandas reads both cleanly:

```python
import pandas as pd

gff_cols = ["seqid", "source", "type", "start", "end",
            "score", "strand", "phase", "attributes"]
gff = pd.read_csv("genes.gff3", sep="\t", comment="#",
                  names=gff_cols, header=None)

cds = gff[gff["type"] == "CDS"].copy()
cds["length"] = cds["end"] - cds["start"] + 1
per_seq = cds.groupby("seqid")["length"].sum()
print(per_seq)
```

This is a place where the difference between Biopython and pandas is practical rather than philosophical. Biopython's GFF support has historically lived outside the core package, and for bulk coordinate arithmetic the DataFrame route is shorter and faster anyway.

### BLAST tabular output

BLAST output in outfmt 6 is tab-delimited with a documented column order. Read it with explicit column names:

```python
import pandas as pd

hit_cols = ["qseqid", "sseqid", "pident", "length", "mismatch",
            "gapopen", "qstart", "qend", "sstart", "send",
            "evalue", "bitscore"]
hits = pd.read_csv("blast_hits.tsv", sep="\t", names=hit_cols)
best = hits.sort_values(["qseqid", "bitscore"],
                        ascending=[True, False]).groupby("qseqid").head(1)
```

### Variant and result tables

VCF bodies, after the header block, are tab-delimited records, and many downstream tools emit TSV summaries of variant calls, taxonomic profiles, or functional annotations. pandas ingests them all with the same read_csv call. The broader pattern is worth naming: when a field produces standardized or semi-standardized table outputs, the parsing question dissolves and the real work becomes joins and groupbys. The immune repertoire field ran into exactly this problem, where different analysis tools produced incompatible output formats and labels, and the response was a purpose-built interchange format with accompanying tools [2]. When such an interchange file lands as a table, pandas is the natural reader.

## Choosing Between Biopython or pandas: The Criteria That Decide

![Decision path asking whether the file encodes biology a flat table loses, then whether it is already flat, ending in Biopython or pandas.](/article-images/illustrations/biopython-vs-pandas-parser-choice.webp)

When people ask about biopython vs pandas, they usually want a decision rule, not a feature list. Four criteria settle almost every case.

### 1. Does the file encode biology that a flat table loses?

FASTQ quality strings, GenBank locations with joins, alignment gap columns, and structured metadata in NEXUS or NeXML all encode information that collapses if you treat the file as text. Event-based parsers exist in part because older formats carry limited metadata models while newer ones carry richer annotations, and abstracting over those models lets one reader serve many formats [1]. If the structure matters, reach for Biopython. If the file is already flat, pandas preserves everything anyway.

### 2. What is the memory profile of the file?

Biopython's SeqIO.parse streams. One 5 GB FASTQ parses in constant memory. pandas read_csv loads the whole frame, which is fast and convenient until the table exceeds your RAM. pandas mitigates this with chunksize, which returns an iterator over row blocks:

```python
import pandas as pd

total = 0
for chunk in pd.read_csv("huge_table.tsv", sep="\t", chunksize=100_000):
    total += chunk["depth"].sum()
print(total)
```

Streaming in both tools follows the same architectural logic that motivated event-based parsing in other bioinformatics libraries, where the design keeps memory bounded for large datasets [1]. Know which mode you are in before you run against production data.

### 3. What operations come next?

If the next step is translation, feature extraction, alignment trimming, or a database fetch, Biopython has the functions written and tested. If the next step is filtering rows, merging samples, computing per-group summaries, or exporting a filtered table, pandas has those operations as single method calls. A performance-focused comparison of tabular analysis workflows found pandas and spreadsheet tools finish comparable loads, cleans, aggregations, and searches in similar time on datasets beyond one million rows, while Python offered greater precision in its procedures [5]. The bottleneck is rarely raw speed and usually correctness of the transformation, which argues for the tool that expresses the transformation natively.

### 4. Who maintains the parser?

Biopython's parsers are maintained by an international volunteer collaboration with a long release history [3]. Your own regex parser is maintained by you at 2 a.m. before a deadline. That asymmetry decides more parsing questions than any benchmark.

## Worked Scenario 1: From GenBank Record to a Gene Table

A common task is producing a per-gene table from an annotated genome, then computing per-gene statistics. This is where both tools cooperate.

```python
from Bio import SeqIO
from Bio.SeqUtils import gc_fraction
import pandas as pd

rows = []
for record in SeqIO.parse("mycobacterium.gb", "genbank"):
    for feature in record.features:
        if feature.type != "CDS":
            continue
        locus = feature.qualifiers.get("locus_tag", ["NA"])
        gene = feature.qualifiers.get("gene", ["NA"])
        product = feature.qualifiers.get("product", ["NA"])
        seq = str(feature.extract(record.seq))
        rows.append({
            "locus_tag": locus,
            "gene": gene,
            "product": product,
            "length_nt": len(seq),
            "gc": round(gc_fraction(Seq(seq)), 3),
        })

genes = pd.DataFrame(rows)
genes.to_csv("gene_table.tsv", sep="\t", index=False)
print(genes.sort_values("length_nt", ascending=False).head())
```

Biopython does the parsing that requires format knowledge: features, qualifiers, and correct extraction of subsequence regions including compound locations. pandas does the assembly, sorting, and export that requires table operations. Neither tool alone is awkward here. Together the script is twenty lines and reproducible.

## Worked Scenario 2: Joining Coordinates to a Count Matrix

You have a count matrix with gene identifiers and a GFF3 with coordinates, and you want per-gene length for downstream normalization. This task is pure table work, and forcing it through a sequence parser would be a mistake.

```python
import pandas as pd

counts = pd.read_csv("counts.tsv", sep="\t", index_col=0)
gff_cols = ["seqid", "source", "type", "start", "end",
            "score", "strand", "phase", "attributes"]
gff = pd.read_csv("genes.gff3", sep="\t", comment="#", names=gff_cols)

tx = gff[gff["type"] == "mRNA"].copy()
tx["gene_id"] = (tx["attributes"]
                 .str.extract(r"ID=([^;]+)"))
tx["length"] = tx["end"] - tx["start"] + 1

annotated = counts.join(tx.set_index("gene_id")["length"],
                        how="left")
missing = annotated["length"].isna().sum()
print(f"{missing} of {len(annotated)} genes lack annotation")
```

The regex on the attributes column is deliberate. GFF3 attributes are not a formal hierarchy, so a targeted extract is more robust than trying to parse the column as a nested structure. If an identifier mismatch appears, print both sides' sample IDs and fix the join keys before anything else.

## Worked Scenario 3: FASTQ Quality Triage Before Alignment

Before spending compute on an aligner, you want per-read mean quality and read length distributions across a large FASTQ. This is the streaming case.

```python
from Bio import SeqIO
import statistics

lengths = []
means = []
n = 0
for record in SeqIO.parse("run_lane1.fastq", "fastq"):
    quals = record.letter_annotations["phred_scores"]
    lengths.append(len(record.seq))
    means.append(statistics.mean(quals))
    n += 1
    if n >= 1_000_000:
        break

print("reads sampled:", n)
print("median length:", statistics.median(lengths))
print("median mean Q:", round(statistics.median(means), 1))
```

Sampling the first million reads gives a fast triage signal. pandas has no role here because the file is not a table, and that boundary is the whole point of the comparison.

## Combining Biopython and pandas in One Pipeline

![Flowchart from parsing FASTA with SeqIO through building a DataFrame, binning lengths and grouping, plus the reverse path writing filtered FASTA.](/article-images/illustrations/biopython-vs-pandas-combined-pipeline.webp)

The strongest position in the biopython vs pandas question is refusing to choose. The standard pattern is Biopython for reading, a list of dictionaries as the bridge, pandas for analysis.

```python
from Bio import SeqIO
import pandas as pd

records = []
for rec in SeqIO.parse("assembly.fasta", "fasta"):
    records.append({
        "contig": rec.id,
        "length": len(rec.seq),
        "gc": round(gc_fraction(rec.seq), 3),
    })

df = pd.DataFrame(records)
df["length_bin"] = pd.cut(df["length"],
                          bins=[0, 1000, 10000, 1_000_000],
                          labels=["small", "medium", "large"])
summary = df.groupby("length_bin")["gc"].mean()
print(summary)
```

The reverse direction also works. Write a filtered table and let Biopython consume it:

```python
from Bio import SeqIO
import pandas as pd

df = pd.read_csv("kept_contigs.tsv", sep="\t")
keep = set(df["contig"])
with open("filtered.fasta", "w") as out:
    SeqIO.write((r for r in SeqIO.parse("assembly.fasta", "fasta")
                 if r.id in keep), out, "fasta")
```

The generator expression inside SeqIO.write keeps memory flat even for large assemblies. This reader-writer pairing, where one format-aware layer feeds another, mirrors the design goal of libraries built to let a single reader and writer serve many formats through a common abstraction [1].

## Where Biopython Does Not Reach, and Where pandas Does Not Either

Honest boundaries prevent bad architecture. Biopython does not parse every format in the field. It does not read VCF natively, for example, and dedicated parsers exist for that job. Alignment and phylogenetic formats keep evolving, and supporting their full metadata features across formats remains an active engineering problem in the ecosystem [1]. Before building around any parser, check the current format list in its documentation rather than assuming coverage.

pandas has a sharper boundary. It will happily read any delimited file, including files it should not, such as a FASTA misread as fixed-width text. There is no biological validation anywhere in the library, so a corrupted sequence column imports without complaint. That silence is the main risk in the biopython vs pandas decision when junior analysts pick pandas for everything because it is familiar.

## Common Mistakes and Limitations

- **Parsing FASTQ by hand.** Hand-rolled FASTQ parsing breaks on wrapped sequence lines and legacy quality encodings. Biopython decodes Phred+33 and Phred+64 correctly and exposes scores through letter_annotations.
- **Loading entire large files into memory.** Calling list(SeqIO.parse(...)) on a multi-gigabyte FASTQ or read_csv without chunksize on a huge TSV both end the same way. Stream when you can, chunk when you must.
- **Trusting the join key.** Gene identifiers differ across GFF versions, annotation releases, and count matrices. Verifying key overlap with a quick set intersection before merging is standard practice.
- **Confusing attribute columns with [structured data](/blog/data-analysis/what-is-structured-data).** The GFF3 attributes field is a delimited string, not nested columns. Use str.extract with an explicit pattern and test it against real records from your own files.
- **Using deprecated Biopython APIs from old tutorials.** A biopython tutorial written before version 1.78 may call the GC function that has since been renamed gc_fraction. Pin versions in your environment file and check the changelog when example code throws AttributeError.
- **Forgetting that pandas strips leading zeros and coerces types.** Identifiers such as chromosome names or loci that look numeric get silently converted. Pass dtype={"seqid": str} or keep_default_na=False when identifier fidelity matters.
- **Re-runningEntrez queries without throttling.** NCBI enforces request limits on programmatic access. Batch identifiers, add delays, and cache results to local files so a crashed script does not refetch everything.
- **Treating one tool as a replacement for the other.** Biopython cannot groupby or merge efficiently, and pandas cannot translate a CDS or parse a compound GenBank location. Pipelines that pretend otherwise accumulate fragile string-manipulation code.
- **Ignoring format version drift.** Just as repertoire analysis tools long produced divergent output formats and labels that required a dedicated standard to reconcile [2], your own pipeline's inputs will change between releases. Log the parser version and the file's origin with every result table.

## Frequently Asked Questions

### Should I learn Biopython or pandas first?

Learn pandas first if your work centers on count matrices, annotation tables, and result files, and Biopython first if you handle raw sequences and records daily. Most analysts end up needing both, and the two skills reinforce each other.

### Can pandas read FASTA files?

Not meaningfully. pandas can force a FASTA through read_csv with custom separators, but the result loses record structure and gains nothing. Use Biopython's SeqIO for FASTA, FASTQ, GenBank, and alignment formats, then convert to a DataFrame if you need table operations.

### Does Biopython replace pandas?

No. Biopython reads biological formats and provides sequence algorithms, while pandas provides fast, labeled table operations that Biopython does not attempt. They solve adjacent problems and are designed to coexist in one environment.

### How do I install Biopython?

Run pip install biopython inside a virtual environment, then confirm with import Bio and print(Bio.__version__). Pin the version number in your requirements file so shared scripts behave identically across machines.

### Which is faster for large files?

Biopython streams records with constant memory, and pandas loads tables quickly but can exceed RAM unless you use chunked reading. Measured comparisons of pandas against spreadsheet tools on datasets beyond one million rows show comparable execution times across loading, cleaning, aggregation, and search, so design for memory profile first and raw speed second.

### Can I use both in the same script?

Yes, and it is the standard pattern. Parse with Biopython into records, flatten each record into a dictionary, build a pandas DataFrame from the list, and do your filtering, grouping, and export there.

<script type="application/ld+json">
{
  "@context": "https://schema.org",
  "@type": "FAQPage",
  "mainEntity": [
    {
      "@type": "Question",
      "name": "Should I learn Biopython or pandas first?",
      "acceptedAnswer": {
        "@type": "Answer",
        "text": "Learn pandas first if your work centers on count matrices, annotation tables, and result files, and Biopython first if you handle raw sequences and records daily. Most analysts end up needing both, and the two skills reinforce each other."
      }
    },
    {
      "@type": "Question",
      "name": "Can pandas read FASTA files?",
      "acceptedAnswer": {
        "@type": "Answer",
        "text": "Not meaningfully. pandas can force a FASTA through read_csv with custom separators, but the result loses record structure and gains nothing. Use Biopython's SeqIO for FASTA, FASTQ, GenBank, and alignment formats, then convert to a DataFrame if you need table operations."
      }
    },
    {
      "@type": "Question",
      "name": "Does Biopython replace pandas?",
      "acceptedAnswer": {
        "@type": "Answer",
        "text": "No. Biopython reads biological formats and provides sequence algorithms, while pandas provides fast, labeled table operations that Biopython does not attempt. They solve adjacent problems and are designed to coexist in one environment."
      }
    },
    {
      "@type": "Question",
      "name": "How do I install Biopython?",
      "acceptedAnswer": {
        "@type": "Answer",
        "text": "Run pip install biopython inside a virtual environment, then confirm with import Bio and print(Bio.__version__). Pin the version number in your requirements file so shared scripts behave identically across machines."
      }
    },
    {
      "@type": "Question",
      "name": "Which is faster for large files?",
      "acceptedAnswer": {
        "@type": "Answer",
        "text": "Biopython streams records with constant memory, and pandas loads tables quickly but can exceed RAM unless you use chunked reading. Measured comparisons of pandas against spreadsheet tools on datasets beyond one million rows show comparable execution times across loading, cleaning, aggregation, and search, so design for memory profile first and raw speed second."
      }
    },
    {
      "@type": "Question",
      "name": "Can I use both in the same script?",
      "acceptedAnswer": {
        "@type": "Answer",
        "text": "Yes, and it is the standard pattern. Parse with Biopython into records, flatten each record into a dictionary, build a pandas DataFrame from the list, and do your filtering, grouping, and export there."
      }
    }
  ]
}
</script>

## Sources

1. [JPhyloIO: a Java library for event-based reading and writing of different phylogenetic file formats through a common interface.](https://pubmed.ncbi.nlm.nih.gov/31331268/)
2. [VDJML: a file format with tools for capturing the results of inferring immune receptor rearrangements.](https://pubmed.ncbi.nlm.nih.gov/27766961/)
3. [Biopython: freely available Python tools for computational molecular biology and bioinformatics.](https://pubmed.ncbi.nlm.nih.gov/19304878/)
4. [Metrics-Based Evaluation and Comparison of Visualization Notations](https://pubmed.ncbi.nlm.nih.gov/37874719/)
5. [A Performance Comparison of Microsoft Excel and Python for Tabular Data Analysis](https://doi.org/10.29304/jqcsm.2026.18.12585)