Feature tree (GFF)¶
Open this tutorial as a notebook
While the fundamental model of this package is substantially complete, many biological convenience functions using this model need to be implemented.
Simple parsing and basic queries¶
First, generate a Genome from a GFF3 file:
%use biokotlin
import biokotlin.featureTree.*
import java.io.File
val genome = Genome.fromFile("resources/b73_shortened.gff")
Comments not yet supported. Comment at line 1 discarded: ##gff-version 3
Comments not yet supported. Comment at line 1 discarded: ##gff-version 3
Let's query a gene within this genome! The byID function is constant-time and returns a Feature?
gene is a Feature instance because it contains the data in the 9 columns of a GFF file. Let's query some of this data.
println("""
Source: ${gene.source}
Start: ${gene.start}
biotype Attribute: ${gene.attribute("biotype")}
""".trimIndent())
Source: NAM
Start: 34617
biotype Attribute: [protein_coding]
gene is a Parent because it is also the root of a tree of Feature. Let's get access to the direct children of gene. In this case, there is only one child, a transcript.
Zm00001eb000010_T001
Of course, you can also walk back up the tree with parent.
true
It is often useful to apply an operation for every node below the parent on the tree, not only its immediate children. This is where descendants comes in, which produces a sequence of all nodes below the parent. These nodes are in depth-first, left-to-right order. This sequence can be combined with the powerful Kotlin collections framework to make a range of interesting queries.
[mRNA, five_prime_UTR, exon, exon, CDS, three_prime_UTR, three_prime_UTR]
subtree is quite similar except that it inclues the receiver as well. Observe the inclusion of "gene" in the output.
[gene, mRNA, five_prime_UTR, exon, exon, CDS, three_prime_UTR, three_prime_UTR]
Mutability¶
The package supports quickly shifting from mutable to immutable representations of your feature tree. This allows for mutability when you need to modify something, but deep immutability when you want an extra assurance of correctness.
The package is highly opinionated and does not allow the client to form feature trees that do not constitute a valid GFF3 file.
Getting a mutable genome¶
MutableGenome instances can be obtained either through creating a mutable clone of an existing Genome through the mutable function, parsing a file directly into a MutableGenome, or creating a blank instance.
// Mutable cloning
val immutable1 = Genome.fromFile("resources/b73_shortened.gff")
val mutable1 = immutable1.mutable()
// Directly parsing to mutable
val mutable2 = MutableGenome.fromFile("resources/b73_shortened.gff")
//Creating a blank MutableGenome
val mutable3 = MutableGenome.blank()
Comments not yet supported. Comment at line 1 discarded: ##gff-version 3
Comments not yet supported. Comment at line 1 discarded: ##gff-version 3
Comments not yet supported. Comment at line 1 discarded: ##gff-version 3
Comments not yet supported. Comment at line 1 discarded: ##gff-version 3
Of course, when you're done with your mutations, you can clone to an immutable instance if desired.
Point mutations¶
A "point mutation" is one that does not affect the topology of the tree. The feature tree framework supports all the point mutations that you'd expect, allowing for convnient modification of all nine columns of data. Let's make some modifications to an exon. First, let's print out the starting state of this exon.
chr1 NAM exon 34617 35318 . + . Parent=Zm00001eb000010_T001;Name=Zm00001eb000010_T001.exon.1;ensembl_end_phase=0;ensembl_phase=0;exon_id=Zm00001eb000010_T001.exon.1;rank=1;
Now let's do some mutations. Note that start and end cannot be directly modified and setRange must be used instead, see Discontinuous Features.
exon.setRange(34000..36000)
exon.addAttribute("custom_attr", "42")
exon.setID("my_favorite_exon_id")
exon.name = "my_favorite_exon_name"
exon.strand = Strand.MINUS
exon.score = 1.0
exon.source = "my_source"
println(exon)
chr1 my_source exon 34000 36000 1.0 - . ID=my_favorite_exon_id;Parent=Zm00001eb000010_T001;Name=my_favorite_exon_name;ensembl_end_phase=0;ensembl_phase=0;exon_id=Zm00001eb000010_T001.exon.1;rank=1;custom_attr=42;
Illegal point mutations¶
The package is highly opinionated and will not allow states that do not represent valid GFF3 files. While the full documentation of these illegal mutations can be found in the API specification, it is worth discussing common illegal mutations.
The Parent attribute is defined by the actual topology of the tree and may not be directly modified as other attributes can.
try {
exon.setAttribute("Parent", "NewParent")
} catch (e: IllegalArgumentException) {
println(e.message)
}
The Parent attribute may not be directly modified. Its value is based on the actual structure of the tree.
Hint: use copyTo or moveTo.
IDs must be unique within a Genome!
IDs must be unique within a Genome.
ID in conflict: Zm00001eb000010
Feature already having the ID:
chr1 NAM gene 34617 40204 . + . ID=Zm00001eb000010;biotype=protein_coding;logic_name=cshl_gene;
Feature that would conflict:
chr1 my_source exon 34000 36000 1.0 - . ID=my_favorite_exon_id;Parent=Zm00001eb000010_T001;Name=my_favorite_exon_name;ensembl_end_phase=0;ensembl_phase=0;exon_id=Zm00001eb000010_T001.exon.1;rank=1;custom_attr=42;
Hint: within the feature tree framework, discontinuous features are represented as a single Feature object with
several start-end ranges and an equal number of phases, not as distinct objects.
Topological mutations¶
A topological mutation is one that modifies the shape of the true through insertion, deletion, or sorting.
Insertion adds new features to the tree, either as children of the root Genome or of a particular Feature.
val mRNA = mutable1.byID("Zm00001eb000010_T001")!!
mRNA.insert(
seqid = "my_seqid",
source = "my_source",
type = "exon",
range = 100..200,
score = 42.0,
strand = Strand.PLUS,
phase = Phase.ONE,
attributes = mapOf("ID" to listOf("my_exon"), "custom_attr" to listOf("custom value"))
)
my_seqid my_source exon 100 200 42.0 + 1 ID=my_exon;Parent=Zm00001eb000010_T001;custom_attr=custom value;
Now observe our inserted exon!
[chr1 my_source exon 34000 36000 1.0 - . ID=my_favorite_exon_id;Parent=Zm00001eb000010_T001;Name=my_favorite_exon_name;ensembl_end_phase=0;ensembl_phase=0;exon_id=Zm00001eb000010_T001.exon.1;rank=1;custom_attr=42;
, my_seqid my_source exon 100 200 42.0 + 1 ID=my_exon;Parent=Zm00001eb000010_T001;custom_attr=custom value;
]
Deletion removes a feature and all of its orphaned descendants from a tree.
val gene2 = mRNA.parent
println("Before deletion: ${gene2.children}")
mRNA.delete()
println("After deletion: ${gene2.children}")
Before deletion: [chr1 NAM mRNA 34617 40204 . + . ID=Zm00001eb000010_T001;Parent=Zm00001eb000010;biotype=protein_coding;transcript_id=Zm00001eb000010_T001;canonical_transcript=1;
]
After deletion: []
Still have a pointer to the deleted feature? Attempting to read or write to it throws a DeletedAccessException so that you don't accidentally modify anything that is no longer presnet in the Genome.
Do not access deleted features or any of their orphaned descendants
Illegal topological mutations¶
To maintain the correctness of your model, some topological mutations are prohibited.
Creating a parent/child relationship that does not comport with the sequence ontology is prohibited. For the rare cases where you do intend to override the Sequence Ontology, see Advanced Type Schema.
try {
gene2.insert(
seqid = "my_seqid",
source = "my_source",
type = "contig",
range = 100..200,
score = 42.0,
strand = Strand.PLUS,
phase = Phase.ONE,
)
} catch (e: TypeSchemaException) {
println(e.message)
}
contig does not have a part-of relationship with gene and cannot be inserted in a feature with type gene.
Hint: If you wish to insert something that does not follow the Sequence Ontology, use MutableGenome.defineType first.
In addition, you cannt modify the topology of the tree while you are iterating over descendants or subtree. This is because these sequences iterate over the tree, and changing the tree out from under them in the course of their iteration would lead to unpredictable results.
try {
mutable1.descendants().forEach {
it.sort { one, two -> one.start - two.start }
}
}
catch (e: ConcurrentModificationException) {
println("Don't do concurrent modification!")
}
Don't do concurrent modification!
Discontinuous features¶
GFF3 files represents discontinuous features as multiple rows that share an ID. Since these rows logically constitute the same row, and consequently should have identical properties except start, end, and phase, they are represented as a singular object within the featureTree package. This ensures that every instance of a Feature has a unique ID attribute and that changes made to a discontinuous feature affect all the discontinuities, ensuring they do not become "out-of-sync" through mutation. However, this does present some drawbacks that need to be accounted for.
Firstly, the number of descendants that a genome has does not necesarily match the number of rows in the source GFF3 file. If you want to count the number of distinct regions rather than the number of logical features within the file, you should use the multiplicity property. This property is equivalent to the number of distinct regions within a Feature and is 1 for continuous features.
println("Number of logical features: ${genome.descendants().count()}")
println("Number of distinct regions (equivalent to number of rows): ${genome.descendants().sumOf { it.multiplicity }}")
Number of logical features: 14
Number of distinct regions (equivalent to number of rows): 20
Accessing the distinct regions of these features is done through the ranges phases and lengths properties. ranges and phases always "line up," meaning that they have the same size (which is equivalent to multiplicity) and that ranges[i] represents the range with phase phases[i]. Of course lengths simply represents the length at each range.
val cds = genome.byID("Zm00001eb000010_P001")!!
println("Ranges: ${cds.ranges}")
println("Phases: ${cds.phases}")
println("Lengths: ${cds.lengths}")
Ranges: [34722..35318, 36037..36174, 36259..36504, 36600..36713, 36822..37004, 37416..37633, 38021..38366]
Phases: [ZERO, ZERO, ZERO, ZERO, ZERO, ZERO, ONE]
Lengths: [597, 138, 246, 114, 183, 218, 346]
Of course, you can still use start, end, range, phase, and length for discontinuous features, though not all are useful.
println("Start returns the least start value among ranges: ${cds.start}")
println("End returns the greatest end value among ranges: ${cds.end}")
println("Range returns the leftmost range among ranges, but these are not always ordered meaningfully: ${cds.range}")
println("Phase returns the leftmost phase among phases, but these are not always ordered meaningfully: ${cds.phase}")
println("Length returns the length between start and end: ${cds.length}")
Start returns the least start value among ranges: 34722
End returns the greatest end value among ranges: 38366
Range returns the leftmost range among ranges, but these are not always ordered meaningfully: 34722..35318
Phase returns the leftmost phase among phases, but these are not always ordered meaningfully: ZERO
Length returns the length between start and end: 3645
Observe that the toString function for a discontinuous feature returns multiple lines of text.
chr1 NAM CDS 34722 35318 . + 0 ID=Zm00001eb000010_P001;Parent=Zm00001eb000010_T001;protein_id=Zm00001eb000010_P001;
chr1 NAM CDS 36037 36174 . + 0 ID=Zm00001eb000010_P001;Parent=Zm00001eb000010_T001;protein_id=Zm00001eb000010_P001;
chr1 NAM CDS 36259 36504 . + 0 ID=Zm00001eb000010_P001;Parent=Zm00001eb000010_T001;protein_id=Zm00001eb000010_P001;
chr1 NAM CDS 36600 36713 . + 0 ID=Zm00001eb000010_P001;Parent=Zm00001eb000010_T001;protein_id=Zm00001eb000010_P001;
chr1 NAM CDS 36822 37004 . + 0 ID=Zm00001eb000010_P001;Parent=Zm00001eb000010_T001;protein_id=Zm00001eb000010_P001;
chr1 NAM CDS 37416 37633 . + 0 ID=Zm00001eb000010_P001;Parent=Zm00001eb000010_T001;protein_id=Zm00001eb000010_P001;
chr1 NAM CDS 38021 38366 . + 1 ID=Zm00001eb000010_P001;Parent=Zm00001eb000010_T001;protein_id=Zm00001eb000010_P001;
Mutability with discontinuous features¶
Discontinuities can be modified in a variety of ways, though some limitations are imposed. Most importantly, ranges and phases must always be the same size. Because of this, all functions that allow you to modify the discontinuities of a feature require you to specify these properties simultaneously.
val mutable4 = mutable2.copy()
val cds4 = mutable4.byID("Zm00001eb000010_P001")!!
println("cds4 ranges: ${cds4.ranges}")
println("cds4 phases: ${cds4.phases}")
cds4.addDiscontinuity(4000..41000, Phase.TWO)
println("~muation 1~")
println("cds4 ranges: ${cds4.ranges}")
println("cds4 phases: ${cds4.phases}")
println("~mutation 2~")
cds4.setDiscontinuities(listOf(100..200 to Phase.TWO, 300..400 to Phase.ZERO))
println("cds4 ranges: ${cds4.ranges}")
println("cds4 phases: ${cds4.phases}")
cds4 ranges: [34722..35318, 36037..36174, 36259..36504, 36600..36713, 36822..37004, 37416..37633, 38021..38366]
cds4 phases: [ZERO, ZERO, ZERO, ZERO, ZERO, ZERO, ONE]
~muation 1~
cds4 ranges: [34722..35318, 36037..36174, 36259..36504, 36600..36713, 36822..37004, 37416..37633, 38021..38366, 4000..41000]
cds4 phases: [ZERO, ZERO, ZERO, ZERO, ZERO, ZERO, ONE, TWO]
~mutation 2~
cds4 ranges: [100..200, 300..400]
cds4 phases: [TWO, ZERO]
Using the setPhase or setRange will make the feature continuous.
cds4.setPhase(Phase.ONE)
println("~mutation 3~")
println("cds4 ranges: ${cds4.ranges}")
println("cds4 phases: ${cds4.phases}")
~mutation 3~
cds4 ranges: [100..200]
cds4 phases: [ONE]
Observe that the range defaults to the leftmost existing range (alternatively you can explicitly specify).
Any feature with a defined ID can be made discontinuous easily.
val chrom = mutable4.byID("1")!!
chrom.addDiscontinuity(100..200, Phase.UNSPECIFIED)
println(chrom.ranges)
[1..308452471, 100..200]
Keep in mind that discontinuous features must always have an ID! This ensures that the GFF3 output can recognize that the features are indeed discontinuous.
val mutable5 = mutable2.copy()
val cds5 = mutable5.byID("Zm00001eb000010_P001")!!
try {
cds5.setID(null)
} catch (e: DiscontinuousLacksID) {
println(e.message)
}
Discontinuous features must contain an ID property.
Feature: chr1 NAM CDS 34722 35318 . + 0 ID=Zm00001eb000010_P001;Parent=Zm00001eb000010_T001;protein_id=Zm00001eb000010_P001;
chr1 NAM CDS 36037 36174 . + 0 ID=Zm00001eb000010_P001;Parent=Zm00001eb000010_T001;protein_id=Zm00001eb000010_P001;
chr1 NAM CDS 36259 36504 . + 0 ID=Zm00001eb000010_P001;Parent=Zm00001eb000010_T001;protein_id=Zm00001eb000010_P001;
chr1 NAM CDS 36600 36713 . + 0 ID=Zm00001eb000010_P001;Parent=Zm00001eb000010_T001;protein_id=Zm00001eb000010_P001;
chr1 NAM CDS 36822 37004 . + 0 ID=Zm00001eb000010_P001;Parent=Zm00001eb000010_T001;protein_id=Zm00001eb000010_P001;
chr1 NAM CDS 37416 37633 . + 0 ID=Zm00001eb000010_P001;Parent=Zm00001eb000010_T001;protein_id=Zm00001eb000010_P001;
chr1 NAM CDS 38021 38366 . + 1 ID=Zm00001eb000010_P001;Parent=Zm00001eb000010_T001;protein_id=Zm00001eb000010_P001;
Finally, it is forbidden to try to insert a feature with a different number of phases and ranges.
val gene5 = mutable5.byID("Zm00001eb000010")!!
try {
gene5.insert(
seqid = "my_seqid",
source = "my_source",
type = "mRNA",
ranges = listOf(100..200, 300..400),
score = 42.0,
strand = Strand.PLUS,
phases = listOf(Phase.ONE)
)
} catch (e: MixedMultiplicity) {
println(e.message)
}
Features must have one range and one phase for each continuous region of the feature, but the supplied ranges
and phases do not agree in number.
Ranges: [100..200, 300..400]
Phases: [ONE]
Advanced parsing¶
While the vast majority of input files do not require any special parameters, these parameters can extend the functionality of the parser, particularly its ability to correct non-standard GFF3 files.
Text corrector¶
The textCorrector is a function applied to each line of text prior to the parsing attempting to parse it. This provides you an opportunity to correct errors in your GFF3 file, such as the prescense of illegal characters, etc. The provided file below accidentally included multiple ID attributes in some rows. Let's cut those out.
println("UNCORRECTED")
println(File("resources/b73_multi_id.gff").readText())
val genomeCorrected = Genome.fromFile(
path = "resources/b73_multi_id.gff",
textCorrecter = { text ->
// True if more than one instance of ID
if (text.split("ID=").size > 2) {
text.replaceFirst(Regex(";ID=.*;"), ";")
} else {
text
}
}
)
println("CORRECTED")
println(genomeCorrected)
UNCORRECTED
##gff-version 3
chr1 assembly chromosome 1 308452471 . . . ID=1;ID=2;Name=chromosome:Zm-B73-REFERENCE-NAM-5.0:1:1:308452471:1
chr1 NAM gene 34617 40204 . + . ID=Zm00001eb000010;ID=3;biotype=protein_coding;logic_name=cshl_gene
chr1 NAM mRNA 34617 40204 . + . ID=Zm00001eb000010_T001;ID=4;Parent=Zm00001eb000010;biotype=protein_coding;transcript_id=Zm00001eb000010_T001;canonical_transcript=1
chr1 NAM five_prime_UTR 34617 34721 . + . Parent=Zm00001eb000010_T001
chr1 NAM exon 34617 35318 . + . Parent=Zm00001eb000010_T001;Name=Zm00001eb000010_T001.exon.1;ensembl_end_phase=0;ensembl_phase=0;exon_id=Zm00001eb000010_T001.exon.1;rank=1
Comments not yet supported. Comment at line 1 discarded: ##gff-version 3
Comments not yet supported. Comment at line 1 discarded: ##gff-version 3
CORRECTED
chr1 assembly chromosome 1 308452471 . . . ID=1;Name=chromosome:Zm-B73-REFERENCE-NAM-5.0:1:1:308452471:1;
chr1 NAM gene 34617 40204 . + . ID=Zm00001eb000010;logic_name=cshl_gene;
chr1 NAM mRNA 34617 40204 . + . ID=Zm00001eb000010_T001;canonical_transcript=1;
chr1 NAM five_prime_UTR 34617 34721 . + . Parent=Zm00001eb000010_T001;
chr1 NAM exon 34617 35318 . + . Parent=Zm00001eb000010_T001;Name=Zm00001eb000010_T001.exon.1;ensembl_end_phase=0;ensembl_phase=0;exon_id=Zm00001eb000010_T001.exon.1;rank=1;
Parent resolver¶
Since there are added difficulties of working with multiple parentage (see Multiple Parentage), it may be desirable to resolve these instances while the file is being parsed. A parent resovler is a function that takes as input the parsed line of the child, and the feature objects that represent its listed parents, in the order that they are listed. Then, it returns an int representing the index of the desired parent within the provided list of options. Any lambda can be used, but in practice, the provided LEFT and RIGHT may be most useful, which pick the leftmost and rightmost parent respeictively.
In this file, the exons have multiple parents, but you only want the leftmost parents.
println("UNRESOLVED")
println(File("resources/b73_multi_parent.gff").readText())
val genomeResolved = Genome.fromFile(
path = "resources/b73_multi_parent.gff",
parentResolver = LEFT
)
println("\nRESOLVED")
println(genomeResolved)
UNRESOLVED
##gff-version 3
chr1 assembly chromosome 1 308452471 . . . ID=1;Name=chromosome:Zm-B73-REFERENCE-NAM-5.0:1:1:308452471:1
chr1 NAM gene 34617 40204 . + . ID=Zm00001eb000010;biotype=protein_coding;logic_name=cshl_gene
chr1 NAM mRNA 34617 40204 . + . ID=Zm00001eb000010_T001;Parent=Zm00001eb000010;biotype=protein_coding;transcript_id=Zm00001eb000010_T001;canonical_transcript=1
chr1 NAM five_prime_UTR 34617 34721 . + . Parent=Zm00001eb000010_T001
chr1 NAM exon 34617 35318 . + . Parent=Zm00001eb000010_T001,Zm00001eb000010_T001;Name=Zm00001eb000010_T001.exon.1;ensembl_end_phase=0;ensembl_phase=0;exon_id=Zm00001eb000010_T001.exon.1;rank=1
chr1 NAM exon 36037 36174 . + . Parent=Zm00001eb000010_T001,Zm00001eb000010_T001;Name=Zm00001eb000010_T001.exon.2;ensembl_end_phase=0;ensembl_phase=0;exon_id=Zm00001eb000010_T001.exon.2;rank=2
Comments not yet supported. Comment at line 1 discarded: ##gff-version 3
Comments not yet supported. Comment at line 1 discarded: ##gff-version 3
RESOLVED
chr1 assembly chromosome 1 308452471 . . . ID=1;Name=chromosome:Zm-B73-REFERENCE-NAM-5.0:1:1:308452471:1;
chr1 NAM gene 34617 40204 . + . ID=Zm00001eb000010;biotype=protein_coding;logic_name=cshl_gene;
chr1 NAM mRNA 34617 40204 . + . ID=Zm00001eb000010_T001;Parent=Zm00001eb000010;biotype=protein_coding;transcript_id=Zm00001eb000010_T001;canonical_transcript=1;
chr1 NAM five_prime_UTR 34617 34721 . + . Parent=Zm00001eb000010_T001;
chr1 NAM exon 34617 35318 . + . Parent=Zm00001eb000010_T001;Name=Zm00001eb000010_T001.exon.1;ensembl_end_phase=0;ensembl_phase=0;exon_id=Zm00001eb000010_T001.exon.1;rank=1;
chr1 NAM exon 36037 36174 . + . Parent=Zm00001eb000010_T001;Name=Zm00001eb000010_T001.exon.2;ensembl_end_phase=0;ensembl_phase=0;exon_id=Zm00001eb000010_T001.exon.2;rank=2;
Enabling multiple parentage¶
Due to some added complexities of features containing multiple parents (see Multiple Parentage), it must be specifically enabled.
Using the same multiple parentage file as above, observe the exception that occurs when the multipleParentage parameter is allowed to default to false and no parentResolver is specified.
val multipleParentage = try {
Genome.fromFile("resources/b73_multi_parent.gff")
} catch (e: ParseException) {
println(e.message)
}
Comments not yet supported. Comment at line 1 discarded: ##gff-version 3
Comments not yet supported. Comment at line 1 discarded: ##gff-version 3
Error parsing GFF file resources/b73_multi_parent.gff at line number 6.
Text of line:
chr1 NAM exon 34617 35318 . + . Parent=Zm00001eb000010_T001,Zm00001eb000010_T001;Name=Zm00001eb000010_T001.exon.1;ensembl_end_phase=0;ensembl_phase=0;exon_id=Zm00001eb000010_T001.exon.1;rank=1
Must enable multipleParentage to have features with multiple parents
Now, with it enabled, it parses fine.
val multipleParentage = Genome.fromFile(
path = "resources/b73_multi_parent.gff",
multipleParentage = true
)
println(multipleParentage)
Comments not yet supported. Comment at line 1 discarded: ##gff-version 3
Comments not yet supported. Comment at line 1 discarded: ##gff-version 3
chr1 assembly chromosome 1 308452471 . . . ID=1;Name=chromosome:Zm-B73-REFERENCE-NAM-5.0:1:1:308452471:1;
chr1 NAM gene 34617 40204 . + . ID=Zm00001eb000010;biotype=protein_coding;logic_name=cshl_gene;
chr1 NAM mRNA 34617 40204 . + . ID=Zm00001eb000010_T001;Parent=Zm00001eb000010;biotype=protein_coding;transcript_id=Zm00001eb000010_T001;canonical_transcript=1;
chr1 NAM five_prime_UTR 34617 34721 . + . Parent=Zm00001eb000010_T001;
chr1 NAM exon 34617 35318 . + . Parent=Zm00001eb000010_T001, Zm00001eb000010_T001;Name=Zm00001eb000010_T001.exon.1;ensembl_end_phase=0;ensembl_phase=0;exon_id=Zm00001eb000010_T001.exon.1;rank=1;
chr1 NAM exon 34617 35318 . + . Parent=Zm00001eb000010_T001, Zm00001eb000010_T001;Name=Zm00001eb000010_T001.exon.1;ensembl_end_phase=0;ensembl_phase=0;exon_id=Zm00001eb000010_T001.exon.1;rank=1;
chr1 NAM exon 36037 36174 . + . Parent=Zm00001eb000010_T001, Zm00001eb000010_T001;Name=Zm00001eb000010_T001.exon.2;ensembl_end_phase=0;ensembl_phase=0;exon_id=Zm00001eb000010_T001.exon.2;rank=2;
chr1 NAM exon 36037 36174 . + . Parent=Zm00001eb000010_T001, Zm00001eb000010_T001;Name=Zm00001eb000010_T001.exon.2;ensembl_end_phase=0;ensembl_phase=0;exon_id=Zm00001eb000010_T001.exon.2;rank=2;
Modify schema¶
While in general the tree must obey the Sequence Ontology, it is sometimes necesary to use non-standard types or use types in a non-standard way. Schema modifications allow you to selectively make the schema more permissive. While the schema of a MutableGenome can be modified at any time, for a non-standard file to parse correctly these modifications must be made at parse time. See Advanced Type Schema for more.
In this example, an Arabidopsis GFF3 file uses the non-standard type "satellite." Say you wish to treat this type as a synonym of the standard "satellite_DNA".
println("Raw file")
println(File("resources/arabidopsis_ssr.gff").readText())
val modifiedSchema = Genome.fromFile(
path = "resources/arabidopsis_ssr.gff",
modifySchema = { addSynonym("satellite_DNA", "satellite") }
)
Raw file
Chr1 TandemRepeatsFinder_v4.04 satellite 1 115 . + . ID=SSR000001;Name=SSR000001;Unit=CCCTAAA;Length=115;Period=7;Copy=15.3
Chr1 TandemRepeatsFinder_v4.04 satellite 1 106 . + . ID=SSR000002;Name=SSR000002;Unit=CCCTAAAT;Length=106;Period=8;Copy=13.9
Chr1 TandemRepeatsFinder_v4.04 satellite 3 90 . + . ID=SSR000003;Name=SSR000003;Unit=CTAAATCCTTAATCCCTAAATCCCTAAACCT;Length=88;Period=31;Copy=2.9
Chr1 TandemRepeatsFinder_v4.04 satellite 4 91 . + . ID=SSR000004;Name=SSR000004;Unit=TAAATCCTAAATCCA;Length=88;Period=15;Copy=5.8
Chr1 TandemRepeatsFinder_v4.04 satellite 3 106 . + . ID=SSR000005;Name=SSR000005;Unit=CTAAATCCTAAATCCATAAATCCCTAAATCT;Length=104;Period=31;Copy=3.4
Multiple parentage¶
While the parser accepts mutliple parentage, its support is currently experimental. descendants and any function dependent on it will produce duplicates, which is generally not what you want. The best workaround curently is to simply call toSet on descendants to remove these duplicates and then perform your operation.
Advanced type schema¶
The type schema governing the permissible vocabulary and parent/child relationships can be modified at parse-time, or, for MutableGenome, whenever youwant. The type schema can only be modified to be more permissive than the base schema defined by the Sequence Ontology.