Basis¶
De vingerafdruk van DNA¶
DNA wordt al tientallen jaren gebruikt in het strafrecht. Maar hoe stelt een onderzoeker eigenlijk vast van wie een DNA-monster is? Je bouwt in deze opgave de kern van dat programma, en het is minder ingewikkeld dan je zou denken.
DNA is een lange opeenvolging van de letters A, C, G en T. Op sommige
plekken herhaalt een kort stukje zich een aantal keer achter elkaar, en het
aantal herhalingen verschilt sterk van mens tot mens. Zo’n herhaald stukje
heet een Short Tandem Repeat, of STR.
Kijk naar deze twee stukjes DNA, met de STR AGAT:
Anouk: ...TTCC AGATAGATAGATAGAT CTCA... 4 keer
Bram: ...ACAC AGATAGATAGATAGATAGAT GTGC... 5 keer
Eén STR zegt weinig, want de kans dat twee mensen toevallig hetzelfde aantal hebben is groot. Maar kijk je naar een stuk of tien STR’s tegelijk, dan wordt die kans verwaarloosbaar klein. Zo werkt DNA-profilering: niet één kenmerk, maar een combinatie.
De FBI gebruikt er twintig, in Europa worden er tien gebruikt. Wij houden het op drie.
De gegevens¶
Je krijgt een klein bestandje met profielen. Elke rij is een persoon, en de drie
getallen zijn hoe vaak AGAT, AATG en TATC bij die persoon achter elkaar
voorkomen.
Naam |
AGAT |
AATG |
TATC |
|---|---|---|---|
Anouk |
4 |
1 |
5 |
Bram |
5 |
2 |
3 |
Chiara |
3 |
5 |
1 |
In Python zetten we dat neer als een lijst van lijsten. Voer deze cel uit; je hebt hem straks nodig.
database = [
["Anouk", 4, 1, 5],
["Bram", 5, 2, 3],
["Chiara", 3, 5, 1],
]
patterns = ["AGAT", "AATG", "TATC"]
De sequenties die je moet identificeren staan in assets/dna/, als
1.txt tot en met 4.txt. Eén ervan is van niemand in de tabel.
Waarom heet het patterns en niet strs?
str is in Python de naam van het ingebouwde type voor strings. Gebruik je die
naam zelf, dan kun je het type niet meer aanroepen. Hetzelfde geldt voor list,
sum en string.
Wat je gaat maken¶
Zes functies. De eerste twee gaan over het bestand, de middelste twee over het tellen, en de laatste twee zetten er een antwoord van in elkaar.
Stap |
Functie |
Doet |
|---|---|---|
1 |
|
het regeleinde van een regel afhalen |
2 |
|
een sequentie uit een bestand lezen |
3 |
|
tellen hoe vaak een STR herhaalt vanaf één positie |
4 |
|
de langste reeks herhalingen in de hele sequentie |
5 |
|
de tellingen voor alle STR’s op een rij |
6 |
|
het profiel opzoeken in de database |
Stap 1: strip_newline(line)¶
Geeft de regel terug zonder het regeleinde aan het eind. Staat er geen regeleinde, dan blijft de regel zoals hij is.
Aanroep |
Resultaat |
|---|---|
|
|
|
|
Dit is geen flauwe eerste opgave. Een tekstbestand bevat namelijk letterlijk het
teken \n aan het eind van elke regel: dat regeleinde is gewoon een van de
tekens in het bestand, net als een A of een G. Lees je een regel in, dan zit
het er dus nog aan vast.
Hint
line[-1] is het laatste teken van de string, en line[:-1] is alles behalve
het laatste teken.
Kijk eerst of de regel überhaupt tekens bevat: line[-1] op een lege string
levert een foutmelding op.
# jouw oplossing
assert strip_newline("AGAT\n") == "AGAT"
assert strip_newline("AGAT") == "AGAT"
assert strip_newline("") == ""
Stap 2: read_sequence(filename)¶
Leest het bestand met die naam en geeft de sequentie terug als één string, zonder regeleinde.
Aanroep |
Resultaat |
|---|---|
|
|
|
|
Zo open je een bestand in Python:
with open(filename) as file:
for line in file:
...
Lees dit als één zin: open dit bestand, en noem het file zolang dit blok
duurt. Aan het eind van het blok wordt het bestand vanzelf weer gesloten, ook
als er onderweg iets misgaat. Waarom dat zo werkt zie je in Programmeren 2; voor
nu is het de manier waarop je een bestand opent.
De lus eronder geeft je de regels van het bestand, één voor één. Onze sequenties staan op één regel, dus die lus draait precies één keer.
# jouw oplossing
assert len(read_sequence("assets/dna/1.txt")) == 67
assert read_sequence("assets/dna/1.txt")[0:4] == "AGCT"
assert read_sequence("assets/dna/3.txt")[0:4] == "CCCC"
Stap 3: count_repeats(sequence, pattern, start)¶
Geeft terug hoe vaak pattern vanaf positie start direct achter elkaar
voorkomt. Staat het er op die plek helemaal niet, dan is het antwoord 0.
Aanroep |
Resultaat |
|---|---|
|
|
|
|
|
|
|
|
Hint
Met sequence[a:b] pak je het stuk van positie a tot b. Als dat stuk gelijk
is aan pattern, schuif je len(pattern) posities op en kijk je opnieuw. Een
while-lus is hier handiger dan een for-lus, want je weet vooraf niet hoe
vaak je moet kijken.
# jouw oplossing
assert count_repeats("AGATAGATCC", "AGAT", 0) == 2
assert count_repeats("AGATAGATCC", "AGAT", 4) == 1
assert count_repeats("AGATAGATCC", "AGAT", 8) == 0
assert count_repeats("CCAGATCC", "AGAT", 0) == 0
Stap 4: longest_match(sequence, pattern)¶
Geeft de langste reeks opeenvolgende herhalingen van pattern die ergens in
sequence voorkomt.
Aanroep |
Resultaat |
|---|---|
|
|
|
|
|
|
Je hebt stap 3 al, dus je hoeft alleen nog elke positie in de sequentie langs te gaan en de grootste uitkomst te onthouden.
# jouw oplossing
assert longest_match("AGATCCAGATAGATAGAT", "AGAT") == 3
assert longest_match("CCCCCC", "AGAT") == 0
assert longest_match(read_sequence("assets/dna/1.txt"), "AGAT") == 4
assert longest_match(read_sequence("assets/dna/2.txt"), "TATC") == 3
Stap 5: profile(sequence, patterns)¶
Geeft een lijst met voor elke STR uit patterns het aantal uit stap 4, in
dezelfde volgorde.
Aanroep |
Resultaat |
|---|---|
|
|
|
|
Hint
Bouw de lijst op met result = result + [...], zoals je dat bij de lussen hebt
gedaan.
# jouw oplossing
assert profile(read_sequence("assets/dna/1.txt"), patterns) == [4, 1, 5]
assert profile(read_sequence("assets/dna/2.txt"), patterns) == [5, 2, 3]
assert profile(read_sequence("assets/dna/4.txt"), patterns) == [2, 3, 2]
Stap 6: identify(sequence, patterns, database)¶
Geeft de naam van de persoon wiens profiel precies overeenkomt met dat van de
sequentie. Komt niemand overeen, dan is het antwoord "Geen match".
Aanroep |
Resultaat |
|---|---|
|
|
|
|
Hint
Elke rij in database is [naam, getal, getal, getal]. Met row[0] heb je de
naam en met row[1:] de drie getallen, en die kun je in één keer vergelijken
met het profiel.
# jouw oplossing
assert identify(read_sequence("assets/dna/1.txt"), patterns, database) == "Anouk"
assert identify(read_sequence("assets/dna/2.txt"), patterns, database) == "Bram"
assert identify(read_sequence("assets/dna/3.txt"), patterns, database) == "Chiara"
assert identify(read_sequence("assets/dna/4.txt"), patterns, database) == "Geen match"
Tot slot¶
Je hebt nu een werkend forensisch programma, en het is opgebouwd uit zes functies die je stuk voor stuk kon testen.
Twee dingen om over na te denken.
De database staat hier in je code, als een lijst die je zelf hebt ingetypt. In
werkelijkheid staat zoiets in een bestand, met duizenden rijen. Je kunt nu een
bestand lezen, dus dat zou je kunnen. Maar dan wil je bij een rij niet row[1]
schrijven maar row["AGAT"], en daarvoor heb je iets anders nodig dan een
lijst. Dat heet een dictionary, en dat leer je in Programmeren 2.
En bedenk wat je programma níet zegt. Het zegt dat een profiel overeenkomt, niet dat iemand schuldig is. Met drie STR’s is de kans op toeval nog aanzienlijk; daarom gebruiken echte laboratoria er tien of twintig. Wie zulke software schrijft, hoort te weten waar de grens van de uitspraak ligt.