///|
pub(all) struct QualitySummary {
count : Int
missing : Int
minimum : Double?
maximum : Double?
mean : Double?
} derive(Debug, Eq)
///|
pub fn analyze_quality(records : Array[VcfRecord]) -> QualitySummary {
let mut count = 0
let mut missing = 0
let mut total = 0.0
let mut minimum : Double? = None
let mut maximum : Double? = None
for record in records {
match record.qual {
Some(value) => {
count = count + 1
total = total + value
match minimum {
Some(old) if old <= value => ()
_ => minimum = Some(value)
}
match maximum {
Some(old) if old >= value => ()
_ => maximum = Some(value)
}
}
None => missing = missing + 1
}
}
{
count,
missing,
minimum,
maximum,
mean: if count == 0 {
None
} else {
Some(total / count.to_double())
},
}
}
///|
pub fn quality_passes(summary : QualitySummary, threshold : Double) -> Bool {
match summary.minimum {
Some(value) => value >= threshold
None => false
}
}
///|
pub fn quality_range(summary : QualitySummary) -> Double? {
match (summary.minimum, summary.maximum) {
(Some(low), Some(high)) => Some(high - low)
_ => None
}
}
///|
pub fn quality_missing_rate(summary : QualitySummary) -> Double {
let total = summary.count + summary.missing
if total == 0 {
0.0
} else {
summary.missing.to_double() / total.to_double()
}
}
///|
pub fn collect_reference_bases(
records : Array[VcfRecord],
) -> Array[(String, Int)] {
let counts : Array[(String, Int)] = []
for record in records {
increment_string_count(counts, record.ref_allele)
}
counts
}
///|
pub fn collect_alternate_bases(
records : Array[VcfRecord],
) -> Array[(String, Int)] {
let counts : Array[(String, Int)] = []
for record in records {
for alt in record.alt_alleles {
increment_string_count(counts, alt)
}
}
counts
}
///|
fn increment_string_count(counts : Array[(String, Int)], key : String) -> Unit {
for index, pair in counts {
if pair.0 == key {
counts[index] = (key, pair.1 + 1)
return
}
}
counts.push((key, 1))
}
///|
pub fn analyze_transitions(records : Array[VcfRecord]) -> Int {
let mut count = 0
for record in records {
if record.ref_allele.length() == 1 {
for alt in record.alt_alleles {
if is_transition_base(record.ref_allele, alt) {
count = count + 1
}
}
}
}
count
}
///|
fn is_transition_base(ref_allele : String, alt : String) -> Bool {
if alt.length() != 1 {
false
} else {
match (ref_allele.get_char(0), alt.get_char(0)) {
(Some(r), Some(a)) =>
(r == 'A' && a == 'G') ||
(r == 'G' && a == 'A') ||
(r == 'C' && a == 'T') ||
(r == 'T' && a == 'C')
_ => false
}
}
}
///|
pub fn analyze_transversions(records : Array[VcfRecord]) -> Int {
let mut count = 0
for record in records {
if record.ref_allele.length() == 1 {
for alt in record.alt_alleles {
if alt.length() == 1 &&
!is_transition_base(record.ref_allele, alt) &&
alt != record.ref_allele {
count = count + 1
}
}
}
}
count
}
///|
pub fn transition_transversion_ratio(records : Array[VcfRecord]) -> Double? {
let transitions = analyze_transitions(records)
let transversions = analyze_transversions(records)
if transversions == 0 {
None
} else {
Some(transitions.to_double() / transversions.to_double())
}
}
///|
pub fn analyze_filter_rate(records : Array[VcfRecord]) -> Double {
if records.length() == 0 {
0.0
} else {
let filtered = records
.filter(fn(record) {
record.filters.length() > 0 && !record.filters.contains("PASS")
})
.length()
filtered.to_double() / records.length().to_double()
}
}
///|
pub fn analyze_info_presence(records : Array[VcfRecord], key : String) -> Int {
records.filter(fn(record) { record_has_info(record, key) }).length()
}
///|
pub fn analyze_id_count(records : Array[VcfRecord]) -> Int {
records
.map(fn(record) { record.ids.length() })
.fold(init=0, fn(total, count) { total + count })
}
///|
pub fn analyze_alt_count(records : Array[VcfRecord]) -> Int {
records.map(record_alt_count).fold(init=0, fn(total, count) { total + count })
}
///|
pub fn analyze_class_counts(
records : Array[VcfRecord],
) -> Array[(VariantClass, Int)] {
pipeline_record_counts_by_class({
header: VcfHeader::{ fileformat: "", meta: [], columns: [], samples: [] },
records,
})
}
///|
pub fn analyze_record_length(record : VcfRecord) -> Int {
record.ref_allele.length() +
record.alt_alleles
.map(fn(alt) { alt.length() })
.fold(init=0, fn(total, length) { total + length })
}
///|
pub fn analyze_total_bases(records : Array[VcfRecord]) -> Int {
records
.map(analyze_record_length)
.fold(init=0, fn(total, length) { total + length })
}
///|
pub fn analyze_contig_span(
records : Array[VcfRecord],
chrom : String,
) -> GenomicInterval? {
let mut first : Int? = None
let mut last : Int? = None
for record in records {
if record.chrom == chrom {
match first {
None => first = Some(record.pos)
Some(_) => ()
}
match last {
Some(old) if old >= record.pos => ()
_ => last = Some(record.pos)
}
}
}
match (first, last) {
(Some(start), Some(end)) => make_interval(start, end)
_ => None
}
}
///|
pub fn analyze_sample_count(records : Array[VcfRecord]) -> Int {
let names : Array[String] = []
for record in records {
for sample in record.samples {
if !names.contains(sample.name) {
names.push(sample.name)
}
}
}
names.length()
}
///|
pub fn analyze_duplicate_keys(records : Array[VcfRecord]) -> Int {
let seen : Array[String] = []
let mut duplicates = 0
for record in records {
let key = record_key(record)
if seen.contains(key) {
duplicates = duplicates + 1
} else {
seen.push(key)
}
}
duplicates
}