| name | python-bio-data-types |
| description | 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. |
| tool_type | python |
| primary_tool | Python |
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
gene_name = "BRCA1"
chromosome = 17
gc_content = 0.423
is_tumor_suppressor = True
gene_function = None
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.
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,
"chrom": int(chrom),
"start": int(start),
"end": int(end),
"gc_fraction": float(gc),
"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.
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))
print(is_valid_dna("ATGCGATCGA"))
print(is_valid_dna("ATGCXYZ"))
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.
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