Python I: Values, variables and strings
In the UNIX lectures you ran programs, glued them together with pipes, and wrote bash scripts with variables, if and loops. Python is the next tool: a general-purpose programming language that is easy to read, has a huge collection of add-on packages for science (Biopython, pandas, numpy, matplotlib), and is one of the most-used languages in bioinformatics.
What you’ll learn
- what Python is and when to use it instead of bash
- three ways to run Python code: the interactive interpreter, scripts, and notebooks
- the basic kinds of values (types): integers, decimals, text,
True/False,None - variables, arithmetic, and converting between types
- strings: indexing, slicing and string methods - the tools for working with DNA and protein sequences
- printing results with f-strings
- how to read an error message (a traceback) and fix the problem
By the end you will be able to compute the GC content and reverse complement of a DNA sequence and print a nicely formatted result.
Python and bash
Both bash and Python are interpreted: you write the code in a text file and a program (the interpreter) reads it and runs it line by line. You don’t compile anything.
| Use bash when … | Use Python when … |
|---|---|
| you are running other programs (BLAST, samtools) | you are doing calculations or complicated logic |
| looping over files and submitting jobs | parsing a file format (FASTA, GFF, VCF) |
a quick grep/cut/sort/awk pipeline works | you need to keep data in memory and look it up |
| moving, renaming and compressing files | you want statistics, tables (pandas) or plots |
In practice you will use both: a bash script that runs a Python script on every sample, or a Python script whose output you pipe into sort and head.
There were two major versions of Python. Python 2 ended in 2020; everything in this course is Python 3. If you see print "hello" (without parentheses) online, it is old Python 2 code.
Running Python
Is Python installed?
On the cluster (and on most Macs and Linux computers) the command is python3:
python3 --version
Python 3.12.14
Your version may be different; anything 3.9 or newer works for these lectures. (The outputs in these notes were made with Python 3.12.) In Python IV we set up a conda environment with a newer Python and the packages we need. On some systems python (no 3) is also Python 3; on others it doesn’t exist. Use python3 to be safe.
The interactive interpreter
Type python3 by itself to start an interactive session. The >>> prompt means Python is waiting for you to type something. It runs each line as soon as you press Enter and shows you the result (the version line you see will be different):
python3
Python 3.12.14 (main, Sep 1 2026, 14:09:38) [Clang 22.1.3 ] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> 2 + 3
5
>>> print("hello world")
hello world
>>> exit()
Leave with exit() or Ctrl-D. The interpreter is a great calculator and a place to try things out. In these notes, examples that start with >>> show an interactive session: type the part after >>> and Python prints the line below it. Examples that follow each other in the same section continue the same session, so a variable made in one example is still there in the next.
Scripts
For anything you want to keep or rerun, write a script: a text file ending in .py. Make a file called hello.py (with nano hello.py, or the editor in Jupyter or VS Code) that contains this one line:
print("Hello World!")
Then run it by giving the file name to python3 - just like bash myscript.sh:
python3 hello.py
Hello World!
Output from print goes to standard output (STDOUT), so everything you learned about redirection and pipes works:
python3 hello.py > message.txt
cat message.txt
Hello World!
Making a script executable
As with bash scripts, you can add a shebang line at the top so the script can be run by name. #!/usr/bin/env python3 means “find python3 on my PATH and use it”. Save this as hello2.py:
#!/usr/bin/env python3
# hello2.py - my first Python script
print("Hello from a script!")
chmod +x hello2.py
./hello2.py
Hello from a script!
The shebang must be the very first line. Lines starting with # are comments: Python ignores everything from # to the end of the line. Use comments to explain why the code does something.
Jupyter notebooks and VS Code
Through OnDemand you can start a Jupyter notebook or VS Code on the cluster in your web browser. A notebook is made of cells; you type code in a cell and press Shift-Enter to run it, and the result of the last line is shown below the cell (like the >>> prompt). Notebooks are good for exploring, but homework is turned in as .py scripts, so practice both. Everything in these notes works in either place.
Values and types
Every value in Python has a type, which decides what you can do with it. The five basic types are:
| Type | What it holds | Examples |
|---|---|---|
int | whole numbers | 42, -7, 3000000 |
float | decimal numbers | 0.52, 3.14, 1e-5 |
str | text (a string) | "ATGC", 'chrI', "42" |
bool | true or false | True, False |
None | “nothing”, no value | None |
The type() function tells you the type of a value:
>>> type(42)
<class 'int'>
>>> type(0.52)
<class 'float'>
>>> type("ATGC")
<class 'str'>
>>> type("42")
<class 'str'>
>>> type(True)
<class 'bool'>
>>> type(None)
<class 'NoneType'>
Notice that "42" in quotes is a string, not a number. This matters a lot: every line you read from a file is a string, even if it looks like a number, and you have to convert it before doing math (see Converting between types).
1e-5 is scientific notation for 0.00001 (you will see it in BLAST E-values). True, False and None must be capitalized exactly like that.
Variables
A variable is a name that refers to a value. Create one with = (the assignment operator). Unlike bash, spaces around = are fine and you don’t need $ to use the variable:
>>> seq = "ATGGCGTACGCTTAG"
>>> gene_name = "YFG1"
>>> length = 15
>>> seq
'ATGGCGTACGCTTAG'
>>> length * 2
30
In the interpreter, typing a variable name shows its value. In a script you have to print() it.
A variable can be given a new value at any time, even one of a different type. The augmented assignment operators +=, -=, *= and /= update a variable using its current value, which is handy for counting:
>>> count = 0
>>> count += 1
>>> count += 1
>>> count
2
count += 1 means exactly the same as count = count + 1.
Naming rules. Names can contain letters, digits and _, but can’t start with a digit. They are case-sensitive (Seq and seq are different variables). By convention Python code uses lowercase words joined with underscores (gc_content, num_genes). Choose names that say what the value is: exon_length is much easier to read later than x. Don’t use names that Python already uses for something, such as str, list, len, sum or type - it works, but then the built-in function stops working in your program.
A variable doesn’t exist until you assign to it. Using it before that is a NameError:
>>> print(total_length)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'total_length' is not defined
Numbers and arithmetic
| Operator | Meaning | Example | Result |
|---|---|---|---|
+ | add | 7 + 2 | 9 |
- | subtract | 7 - 2 | 5 |
* | multiply | 7 * 2 | 14 |
/ | divide (always gives a float) | 7 / 2 | 3.5 |
// | integer (floor) division | 7 // 2 | 3 |
% | remainder (modulo) | 7 % 2 | 1 |
** | power | 7 ** 2 | 49 |
>>> 10 / 2
5.0
>>> 10 / 3
3.3333333333333335
>>> 10 // 3
3
>>> 10 % 3
1
>>> 2 ** 10
1024
/ always gives a float, even when the answer is a whole number (5.0). Use // when you want a whole number.
// and % come up all the time with sequences. A coding sequence should have a length that is a multiple of 3; % tells you the remainder:
>>> cds_length = 1236
>>> cds_length // 3
412
>>> cds_length % 3
0
>>> 1237 % 3
1
So this CDS has 412 codons and nothing left over; a length of 1237 would leave one extra base. // is also how you put values into bins. To find which 500 bp bin a gene of length 1812 falls into, divide by the bin size, drop the remainder, and multiply back:
>>> 1812 // 500
3
>>> 1812 // 500 * 500
1500
Order of operations is the usual math order: ** first, then *, /, //, %, then + and -. Operators at the same level go left to right. Use parentheses whenever you are unsure - they make the code easier to read too:
>>> 2 + 3 * 4
14
>>> (2 + 3) * 4
20
>>> g = 21
>>> c = 19
>>> total = 80
>>> g + c / total * 100
44.75
>>> (g + c) / total * 100
50.0
The first GC calculation is wrong because / and * happen before +.
Decimals are not exact. Computers store floats in binary, so some decimal numbers can’t be stored exactly:
>>> 0.1 + 0.2
0.30000000000000004
>>> round(0.1 + 0.2, 2)
0.3
This is normal and not a bug in your code. Round when you print a result (see f-strings), and don’t test floats for exact equality.
Some other useful built-in functions for numbers are abs(), round(), min() and max():
>>> abs(-5)
5
>>> round(3.14159, 2)
3.14
>>> min(1812, 750, 2210)
750
>>> max(1812, 750, 2210)
2210
Converting between types
int(), float() and str() convert a value to another type:
>>> int("1812")
1812
>>> float("0.52")
0.52
>>> str(1812)
'1812'
>>> int(3.99)
3
>>> float(7)
7.0
int() of a float throws away the decimal part (it does not round). Why do we need this? Because text read from a file is always a string. In a BED file the start and end of a feature are read as the strings "21408673" and "21408826". Adding two strings joins them rather than doing arithmetic, and subtracting them is an error:
>>> start = "21408673"
>>> end = "21408826"
>>> start + end
'2140867321408826'
>>> end - start
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unsupported operand type(s) for -: 'str' and 'str'
>>> int(end) - int(start)
153
Converting only works if the text really is a number:
>>> int("12.5")
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ValueError: invalid literal for int() with base 10: '12.5'
>>> int(float("12.5"))
12
Strings
A string (str) is a sequence of characters: a DNA sequence, a gene name, a line from a file. Most of what we do with biological data is string processing.
Quotes and special characters
Make a string with single '...' or double "..." quotes; they mean the same thing. Pick the one that lets you put the other kind inside:
>>> "ATGC"
'ATGC'
>>> 'ATGC'
'ATGC'
>>> "5' untranslated region"
"5' untranslated region"
A backslash \ starts an escape sequence for characters you can’t type directly. The two you will use constantly are \t (tab) and \n (newline):
| Escape | Meaning |
|---|---|
\t | tab |
\n | newline |
\\ | a single backslash |
\' \" | a quote inside the same kind of quotes |
print("gene\tlength")
print("YFG1\t1812")
print("line one\nline two")
gene length
YFG1 1812
line one
line two
Triple quotes ("""...""") make a string that can span several lines; they are often used for long comments at the top of a script.
Joining and repeating strings
+ joins (concatenates) strings, and * repeats a string:
>>> "chr" + "I"
'chrI'
>>> "ATG" + "GCG" + "TAA"
'ATGGCGTAA'
>>> "N" * 10
'NNNNNNNNNN'
>>> "-" * 20
'--------------------'
+ only works if both sides are strings. "chr" + 1 is a TypeError; write "chr" + str(1) instead (or use an f-string, below).
Length, indexing and slicing
len() gives the number of characters in a string:
>>> seq = "ATGGCGTACGCTTAG"
>>> len(seq)
15
Each character has a position number called its index. Python counts from 0, so the first character is seq[0]. Negative indexes count back from the end: seq[-1] is the last character.
index: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14
base: A T G G C G T A C G C T T A G
negative: -15 -14 -13 -12 -11 -10 -9 -8 -7 -6 -5 -4 -3 -2 -1
>>> seq[0]
'A'
>>> seq[3]
'G'
>>> seq[-1]
'G'
>>> seq[-3]
'T'
A slice seq[start:end] gives the part of the string from index start up to but not including end. The length of a slice is end - start. Leave out start to begin at the start, or end to go to the end:
>>> seq[0:3]
'ATG'
>>> seq[3:6]
'GCG'
>>> seq[:3]
'ATG'
>>> seq[-3:]
'TAG'
>>> seq[3:]
'GCGTACGCTTAG'
So seq[0:3] is the start codon and seq[-3:] is the stop codon. The n-th codon (counting from 0) is seq[3*n : 3*n+3]:
>>> n = 2
>>> seq[3*n : 3*n+3]
'TAC'
A third number in a slice is the step. seq[::3] takes every third base, and a step of -1 walks backward, which reverses the string:
>>> seq[::-1]
'GATTCGCATGCGGTA'
Asking for a single index past the end is an IndexError, but slices past the end are allowed and just stop at the end:
>>> seq[20]
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
IndexError: string index out of range
>>> seq[10:100]
'CTTAG'
Genome coordinates and slices. BED files use the same “start at 0, end not included” convention as Python, so a BED feature chrI 100 200 is exactly chrom_seq[100:200]. GFF and VCF files count from 1 and include the end, so a GFF feature from 101 to 200 is chrom_seq[100:200] - subtract 1 from the start only. Off-by-one errors like this are among the most common bugs in bioinformatics.
Strings can’t be changed
Strings are immutable: once made, you can’t change a character in place:
>>> seq[0] = "T"
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'str' object does not support item assignment
Instead, build a new string and (if you want) assign it back to the same name:
>>> seq = "T" + seq[1:]
>>> seq
'TTGGCGTACGCTTAG'
String methods
A method is a function that belongs to a value; you call it with a dot: value.method(). Methods never change the original string (they can’t - it’s immutable); they return a new value. These are the ones you’ll use most:
| Method | What it does |
|---|---|
s.upper(), s.lower() | upper- or lowercase copy |
s.strip() | remove spaces, tabs and newlines from both ends |
s.count(sub) | how many times sub occurs (non-overlapping) |
s.find(sub) | index of the first sub, or -1 if not found |
s.replace(old, new) | copy with every old replaced by new |
s.startswith(prefix) | True if s starts with prefix (also endswith) |
s.split(sep) | break into a list of pieces at each sep |
sep.join(pieces) | glue a list of strings together with sep |
>>> dna = "atgGCGtaaNNacgt"
>>> dna.upper()
'ATGGCGTAANNACGT'
>>> dna
'atgGCGtaaNNacgt'
>>> dna = dna.upper()
>>> dna.count("A")
4
>>> dna.count("N")
2
>>> dna.find("TAA")
6
>>> dna.find("TGA")
-1
>>> dna.replace("N", "")
'ATGGCGTAAACGT'
>>> dna.startswith("ATG")
True
Notice that dna.upper() did not change dna until we assigned the result back.
strip() removes whitespace (spaces, tabs, newlines) from the ends. Every line you read from a file ends with a newline character, so you will call strip() on almost every line. The repr() function shows a string with its hidden characters visible:
>>> line = " ATGGCG\n"
>>> line.strip()
'ATGGCG'
>>> print(repr(line))
' ATGGCG\n'
split() turns a string into a list of strings (lists are covered in Python II). This is how we pull the columns out of a line of a tab-delimited file. With no argument, split() splits on any run of whitespace:
>>> bed_line = "Chr7\t21408673\t21408826"
>>> bed_line.split("\t")
['Chr7', '21408673', '21408826']
>>> "YFG1 1812 wild type".split()
['YFG1', '1812', 'wild', 'type']
Put [ ] after the split to take one piece. Here is the last column of a GFF line, which holds key=value pairs separated by ;:
>>> attributes = "ID=YAL069W;Name=YAL069W;orf_classification=Dubious"
>>> attributes.split(";")
['ID=YAL069W', 'Name=YAL069W', 'orf_classification=Dubious']
>>> attributes.split(";")[0]
'ID=YAL069W'
>>> attributes.split(";")[0].split("=")[1]
'YAL069W'
join() is the opposite of split(). It is called on the separator and joins a list of strings:
>>> "\t".join(["YFG1", "chrI", "1812"])
'YFG1\tchrI\t1812'
>>> "-".join(["ATG", "GCG", "TAA"])
'ATG-GCG-TAA'
Methods can be chained: each one works on the result of the one before, left to right:
>>> " atgcNNatgc\n".strip().upper().replace("N", "")
'ATGCATGC'
There are many more methods; see the Python documentation on string methods.
Printing results and f-strings
print() writes its arguments to STDOUT separated by spaces and followed by a newline. You can change the separator with sep= and the ending with end=:
print("YFG1", 1812, 0.52)
print("YFG1", 1812, 0.52, sep="\t")
print("no newline here", end="")
print(" - so this continues the same line")
YFG1 1812 0.52
YFG1 1812 0.52
no newline here - so this continues the same line
The best way to build output is an f-string: put an f before the opening quote, and anything inside { } is replaced by its value. You can put any expression inside the braces, not just a variable name:
gene = "YFG1"
length = 1812
print(f"{gene} is {length} bp long")
print(f"{gene} has {length // 3} codons")
YFG1 is 1812 bp long
YFG1 has 604 codons
After a : inside the braces you can give a format: the number of decimal places, a percentage, or a column width. This is how you avoid printing 0.30000000000000004:
gc = 7 / 15
print(gc)
print(f"GC = {gc:.2f}")
print(f"GC = {gc:.1%}")
print(f"{'gene':<8}|{'length':>8}|")
print(f"{'YFG1':<8}|{1812:>8}|")
print(f"{3000000:,} bp")
0.4666666666666667
GC = 0.47
GC = 46.7%
gene | length|
YFG1 | 1812|
3,000,000 bp
| Format | Meaning | f"{x:...}" with x = 1234.5678 |
|---|---|---|
.2f | 2 decimal places | 1234.57 |
.0f | no decimals (rounded) | 1235 |
.1% | multiply by 100, add %, 1 decimal | 123456.8% |
.2e | scientific notation | 1.23e+03 |
, | thousands separators | 1,234.5678 |
>10 | right-align in 10 characters | ' 1234.5678' |
<10 | left-align in 10 characters | '1234.5678 ' |
To make tab-delimited output that other programs (or sort, cut, R) can read, put \t between the fields:
gene = "YFG1"
chrom = "chrI"
length = 1812
gc = 0.4234
print(f"{gene}\t{chrom}\t{length}\t{gc:.3f}")
YFG1 chrI 1812 0.423
Older styles. You will see two other ways of formatting in older code and online examples. They do the same thing as the f-string above; you don’t need to write them:
print("%s is %d bp, GC=%.2f" % ("YFG1", 1812, 0.4234))
print("{} is {} bp, GC={:.2f}".format("YFG1", 1812, 0.4234))
YFG1 is 1812 bp, GC=0.42
YFG1 is 1812 bp, GC=0.42
Putting it together: GC content and reverse complement
GC content is the fraction of bases that are G or C. With count() and len() it is one line:
seq = "ATGGCGTACGCTTAGCCGATAA"
gc = (seq.count("G") + seq.count("C")) / len(seq)
print(f"length={len(seq)} GC={gc:.3f}")
length=22 GC=0.500
The reverse complement is the sequence of the other strand read 5’ to 3’: swap A<->T and C<->G, then reverse. A first idea is to use replace() for each base, but that doesn’t work:
seq = "AACG"
print(seq.replace("A", "T").replace("T", "A"))
AACG
After the first replace, the new Ts can’t be told apart from the original ones, so the second replace turns them all back into A. The right tool is translate(), which swaps every character at once using a table made with str.maketrans(): each character in the first string is replaced by the character at the same position in the second string.
seq = "ATGGCGTACGCTTAG"
complement = seq.translate(str.maketrans("ACGT", "TGCA"))
revcomp = complement[::-1]
print(seq)
print(complement)
print(revcomp)
ATGGCGTACGCTTAG
TACCGCATGCGAATC
CTAAGCGTACGCCAT
Here is everything as a script, seq_report.py. Note the comments and the descriptive variable names:
#!/usr/bin/env python3
# seq_report.py - print a short report about one DNA sequence
gene = "YFG1"
seq = "atgGCGTACGCTTAGccgataaNNN"
# clean up: uppercase, and drop the Ns (unknown bases)
seq = seq.upper().replace("N", "")
length = len(seq)
gc = (seq.count("G") + seq.count("C")) / length
revcomp = seq.translate(str.maketrans("ACGT", "TGCA"))[::-1]
print(f"gene: {gene}")
print(f"length: {length} bp ({length // 3} codons, {length % 3} left over)")
print(f"start: {seq[:3]} stop: {seq[-3:]}")
print(f"GC content: {gc:.1%}")
print(f"A={seq.count('A')} C={seq.count('C')} G={seq.count('G')} T={seq.count('T')}")
print(f"revcomp: {revcomp}")
python3 seq_report.py
gene: YFG1
length: 22 bp (7 codons, 1 left over)
start: ATG stop: TAA
GC content: 50.0%
A=6 C=5 G=6 T=5
revcomp: TTATCGGCTAAGCGTACGCCAT
Inside the f-string we used single quotes (seq.count('A')) because the f-string itself uses double quotes.
Reading error messages
Everyone’s code has errors, all the time. The skill is reading the message Python gives you. When something goes wrong Python stops and prints a traceback. Here is a script with a mistake in it:
seq = "ATGGCGTACGCTTAG"
length = len(seq)
print("Length is " + length)
python3 broken.py
Traceback (most recent call last):
File "/rhome/yourname/python1/broken.py", line 3, in <module>
print("Length is " + length)
~~~~~~~~~~~~~^~~~~~~~
TypeError: can only concatenate str (not "int") to str
Read a traceback from the bottom up:
- The last line is the most important: the type of error (
TypeError) and a description: you can only join a string (str) to another string, not to anint. - Above it is the file (with its full path) and the line number (
line 3), then the line itself. Python 3.11 and newer also underline the part that failed. - Fix:
print("Length is " + str(length)), or betterprint(f"Length is {length}").
The errors you will see most often:
| Error | Usually means | Example |
|---|---|---|
SyntaxError | Python can’t understand the line: a missing quote, bracket or : | print("ATG) |
IndentationError | the spaces at the start of a line are wrong | an indent for no reason |
NameError | a typo, or a variable used before it was assigned | pirnt(seq) |
TypeError | an operation on the wrong type | "chr" + 1 |
ValueError | the right type but a bad value | int("12.5") |
IndexError | an index past the end of a string or list | "ATG"[3] |
AttributeError | that type has no such method | (1812).upper() |
A NameError often suggests the name you probably meant:
sequence = "ATGGCGTACGCTTAG"
print(len(sequnce))
python3 typo.py
Traceback (most recent call last):
File "/rhome/yourname/python1/typo.py", line 2, in <module>
print(len(sequnce))
^^^^^^^
NameError: name 'sequnce' is not defined. Did you mean: 'sequence'?
A SyntaxError is found before the program runs at all, so nothing is printed except the error. The line number is where Python noticed the problem, which is sometimes the line after the real mistake (for example, a missing closing parenthesis):
print("start")
print("GC is", 0.5
print("done")
python3 syntax.py
File "/rhome/yourname/python1/syntax.py", line 2
print("GC is", 0.5
^
SyntaxError: '(' was never closed
Tips for fixing errors:
- Fix the first error first, then run again.
- Check the line reported and the line before it.
print()the variables involved and theirtype()just before the line that fails.- Copy the last line of the message into a search engine; someone has had it before.
(Older versions of Python give shorter messages without the ^^^ markers or the “Did you mean” hints, but the last line is the same.)
Common mistakes
- Counting from 1. The first character is
seq[0], andseq[1:4]is the 2nd to 4th characters. - Forgetting the end of a slice is not included.
seq[0:3]has 3 characters, indexes 0, 1 and 2. - Doing math on strings from a file.
"100" + "50"is'10050'. Convert withint()orfloat()first. - Expecting a method to change the string.
seq.upper()on its own line does nothing useful; writeseq = seq.upper(). - Mixing quote types.
"5' UTR'is not a complete string. Start and end with the same kind of quote. - Case matters.
printnotPrint;Truenottrue;seq.count("a")does not count"A". - Missing parentheses. In Python 3,
printis a function:print("hi"), notprint "hi".
Quick reference
| Task | Code |
|---|---|
| run a script | python3 script.py |
| shebang line | #!/usr/bin/env python3 |
| comment | # this is ignored |
| what type is it? | type(x) |
| convert | int("42"), float("0.5"), str(42) |
| integer division, remainder | n // 3, n % 3 |
| power, rounding | 2 ** 10, round(x, 2) |
| length | len(seq) |
| first, last character | seq[0], seq[-1] |
| slice (end not included) | seq[3:6], seq[:3], seq[-3:] |
| reverse | seq[::-1] |
| upper/lower case | seq.upper(), seq.lower() |
| remove whitespace at ends | line.strip() |
| count, find | seq.count("G"), seq.find("ATG") |
| replace | seq.replace("N", "") |
| split into columns | line.split("\t") |
| join with tabs | "\t".join(pieces) |
| complement | seq.translate(str.maketrans("ACGT", "TGCA")) |
| print with values | print(f"{name}\t{length}\t{gc:.2f}") |
Exercises
Write each answer as a script (ex1.py, …) with a comment at the top saying what it does. Use this sequence where a sequence is needed:
seq = "ATGAAACGCATTAGCACCACCATTACCACCACCATCACCATTACCACAGGTAACGGTGCGGGCTGA"
- Calculator. A haploid yeast genome is about 12,100,000 bp. A sequencing run gives 20,000,000 reads of 150 bp. Print the coverage (total bases / genome size) with one decimal place.
- Types. Predict, then check in the interpreter, the result and type of each of:
7 / 7,7 // 2,"7" * 2,int("7") * 2,float("7") / 2,str(7) + "7". - Counting bases. Print the length of
seq, the count of each of A, C, G and T, and check that the four counts add up to the length. - GC content. Print the GC content of
seqas a percentage with one decimal place, in the formGC: 12.3%. - Codons. Print the first codon, the last codon, and the 5th codon of
seq. Is the length ofseqa multiple of 3? Print how many complete codons it has. - Reverse complement. Print the reverse complement of
seq. Then take the reverse complement of your result and check that you getseqback. -
Parsing a line. Here is one line of the yeast GFF file (columns are separated by tabs):
line = "chrI\tSGD\tgene\t335\t649\t.\t+\t.\tID=YAL069W;Name=YAL069W\n"Split it into columns and print the chromosome, the feature type, the strand, the gene ID (from the last column) and the length of the gene. GFF coordinates count from 1 and include both ends, so length = end - start + 1.
- Formatted report. Using the values from exercise 7, print one tab-delimited line
gene chrom start end length strand, then the same information as a sentence:YAL069W is on chrI (+) from 335 to 649 (315 bp).
Hints and some solutions
- Exercise 1:
20000000 * 150 / 12100000is about 247.9. - Exercise 5: the last codon is
seq[-3:]; the 5th codon is codon number 4 counting from 0, soseq[12:15].len(seq) % 3 == 0would tell you if it is a multiple of 3 (==is explained in Python II); for now, printlen(seq) % 3. -
Exercise 7:
line = "chrI\tSGD\tgene\t335\t649\t.\t+\t.\tID=YAL069W;Name=YAL069W\n" cols = line.strip().split("\t") chrom = cols[0] feature = cols[2] start = int(cols[3]) end = int(cols[4]) strand = cols[6] gene_id = cols[8].split(";")[0].split("=")[1] length = end - start + 1 print(chrom, feature, strand, gene_id, length)chrI gene + YAL069W 315
Next
Python II: making decisions with if, lists, for loops, and reading and writing files - which is how we process whole data files instead of one line at a time.
Further reading
- The official Python tutorial, sections 3 (“An informal introduction”) and 7.1 (formatting output)
- Ekmekci, McAnany and Mura (2016) An Introduction to Programming for Bioscientists: A Python-Based Primer. PLoS Comp Bio. 10.1371/journal.pcbi.1004867
- Rosalind - bioinformatics problems to practice on; the first few (counting nucleotides, transcribing DNA, reverse complement) use only what is in this lecture