# Python Bio Data Types

> Use Python's int, float, str, bool, and None types to represent and validate biological data (sequence lengths, GC content, DNA/RNA strings, missing annotations) and convert between them when parsing text records. Use when writing beginner Python for bioinformatics, explaining type() output, fixing float-equality bugs in p-value/GC-content comparisons, or converting split() fields from a FASTA/GFF/tab-delimited line into int/float.

- Skill: `pavel-kravchenko/python-bio-data-types` (Agent Skill)
- Install (CLI): `npx skillmds@latest add pavel-kravchenko/python-bio-data-types`
- Raw SKILL.md: https://api.skillmd.com/api/skills/pavel-kravchenko/python-bio-data-types/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: pavel-kravchenko (https://skillmd.com/u/pavel-kravchenko)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/pavel-kravchenko/python-bio-data-types

---


# Python Data Types for Bioinformatics

## When to Use

- Explaining or debugging why `0.1 + 0.2 == 0.3` is `False` in a GC-content or p-value comparison.
- Converting raw text fields (from `line.split("\t")`, a FASTA header, or a VCF row) into `int`/`float` for coordinates, depth, or scores.
- Choosing between `None` and `""` to represent missing vs. empty biological data.
- Writing sequence-validation functions that return `bool` (valid nucleotide check, stop-codon check).
- Teaching Python fundamentals (variables, naming, type conversion) using DNA/RNA/protein examples instead of generic ones.

## Version Compatibility

Python ≥ 3.9 (f-strings with `=`, `str.maketrans`/`translate` all stable since 3.6+). No third-party packages required — this skill is pure standard library.

## Prerequisites

- None — this is a foundational skill. Assumes only that Python is installed (`python3 --version`).
- Useful follow-on skills: `bio-sequence-manipulation-seq-objects`, `bio-sequence-io-read-sequences`.

## Core Data Types

```python
# type        example                      bioinformatics use
# int         42                           sequence length, read count, coordinate
# float       0.487                        GC content, E-value, p-value
# str         "ATGCGA"                     DNA/RNA/protein sequence, gene name
# bool        True / False                 is the sequence valid? has a stop codon?
# NoneType    None                         missing data, "not yet annotated"

gene_name = "BRCA1"
chromosome = 17
gc_content = 0.423
is_tumor_suppressor = True
gene_function = None   # not yet annotated

for value in (gene_name, chromosome, gc_content, is_tumor_suppressor, gene_function):
    print(f"{str(value):25s} -> {type(value).__name__}")
```

Naming rules (break these = `SyntaxError`): letters/digits/underscores only, cannot start with a digit,
case-sensitive, cannot use a keyword (`if`, `for`, `class`, ...).
Naming conventions: `snake_case` for variables/functions (`gene_name`), `UPPER_CASE` for constants
(`AVOGADRO = 6.022e23`), descriptive names (`gc_content`, not `gc` or `x`).

**Goal:** parse a tab-delimited genomic record into the right types and derive a value from it.
**Approach:** split the line on tabs, then explicitly convert each field with `int()`/`float()` —
fields from `split()` are always `str`, even if they look numeric.

```python
def parse_gene_record(line):
    """Parse a tab-delimited record: gene, chrom, start, end, gc_fraction.

    Returns a dict with correctly-typed fields, or None if the line is malformed.
    """
    fields = line.rstrip("\n").split("\t")
    if len(fields) != 5:
        return None
    gene, chrom, start, end, gc = fields
    return {
        "gene": gene,                 # already a str
        "chrom": int(chrom),          # str -> int
        "start": int(start),          # str -> int (0-based, inclusive)
        "end": int(end),              # str -> int (exclusive, half-open)
        "gc_fraction": float(gc),     # str -> float
        "length": int(end) - int(start),
    }

record = parse_gene_record("BRCA1\t17\t43044295\t43170245\t0.423")
print(f"{record['gene']} chr{record['chrom']}:{record['start']}-{record['end']} "
      f"({record['length']:,} bp, GC={record['gc_fraction']*100:.1f}%)")
```

**Goal:** compare floating-point scores (p-values, GC fractions) without falling into the
`0.1 + 0.2 != 0.3` trap, and validate a sequence's nucleotide alphabet.
**Approach:** use a tolerance instead of `==` for floats; use `all()`/`set` membership for
sequence validation, which returns a `bool` you can branch on directly.

```python
def floats_close(a, b, tol=1e-9):
    """Compare two floats with a tolerance instead of exact equality."""
    return abs(a - b) < tol

def is_valid_dna(sequence, alphabet="ATGC"):
    """Return True if every character (case-insensitive) is in the given alphabet."""
    valid = set(alphabet)
    return all(nuc in valid for nuc in sequence.upper())

print(floats_close(0.1 + 0.2, 0.3))          # True (0.1 + 0.2 == 0.3 is False!)
print(is_valid_dna("ATGCGATCGA"))            # True
print(is_valid_dna("ATGCXYZ"))               # False -- X, Y, Z are not nucleotides
```

**Goal:** reverse-complement a DNA sequence and report its GC content, tying together
`str` slicing, `str.translate`, and `int`/`float` conversion.
**Approach:** build a translation table once with `str.maketrans`, apply it, then reverse
with slice step `-1`; compute GC% with `str.count` and integer/float division.

```python
def reverse_complement(sequence):
    """Return the reverse complement of a DNA sequence (upper- or lower-case input)."""
    table = str.maketrans("ATGCatgc", "TACGtacg")
    return sequence.translate(table)[::-1]

def gc_content(sequence):
    """Calculate GC content as a percentage (float), 0.0 for an empty sequence."""
    seq = sequence.upper()
    if not seq:
        return 0.0
    return (seq.count("G") + seq.count("C")) / len(seq) * 100

dna = "ATGCGATCGATCGTAGC"
print(f"Original:  5'-{dna}-3'")
print(f"Rev comp:  5'-{reverse_complement(dna)}-3'")
print(f"GC content: {gc_content(dna):.1f}%")
```

## Pitfalls

- **Floating-point arithmetic is not exact:** `0.1 + 0.2` is `0.30000000000000004`, not `0.3`. This affects every language using IEEE 754 doubles. Always compare floats with a tolerance (`abs(a - b) < 1e-9`), never `==`.
- **`int()` truncates, it does not round:** `int(3.9)` is `3`. Use `round(3.9)` to get `4`. This matters when converting a read-depth fraction or coverage estimate to an integer.
- **String immutability:** you cannot write `dna[0] = "G"` (`TypeError`). Every "modification" creates a new string: `mutated = "G" + dna[1:]`.
- **`None` vs. empty string:** `None` means "this value does not exist"; `""` means "this value exists but has zero length". Do not conflate them — a missing FASTA/VCF field is semantically different from an empty one.
- **`is None`, not `== None`:** always check with `if result is None`; `==` can give surprising results if a class overrides equality.
- **`split()` never converts types:** `line.split("\t")` always returns a list of `str`, even for numeric-looking fields — you must call `int()`/`float()` explicitly, or a later arithmetic op will raise `TypeError`.
- **Off-by-one errors:** Python slices are half-open `[start, stop)`, but genomic coordinates are often 1-based/inclusive (GFF, VCF) vs. 0-based/half-open (BED) — check which convention a file format uses before slicing.

## See Also

- `bio-sequence-manipulation-seq-objects`
- `bio-sequence-manipulation-sequence-properties`
- `bio-sequence-io-read-sequences`
- `biopython`

