I implemented the SPLASH Chaung et al. (n.d.) data analysis to understand it. I use a subset of two SARS-CoV2 assembled genomes and simulate sample reads, some with read errors (noise), from that. From sample reads alone, we want to recover sequence variants. The SPLASH (née NOMAD), method tiles kmers–actually, a “reading frame” of an anchor and target. Then you can count up unique anchor-target pairs per sample (here, only 2).
extract_anc_tar <-function(input_string, kmer_length =27, step =5) { L <-nchar(input_string)# space R <-max(0, (L -2* kmer_length) /2)# Calculate the starting positions for each kmer start_positions <-seq(1, L - R - kmer_length +1, by = step)# Extract kmers based on the starting positions pairs <-map(start_positions, function(pos) {list(anc =substr(input_string, pos, pos + kmer_length -1),tar =substr(input_string, pos + R, pos + kmer_length + R -1) ) })return(pairs)}rand_mut <-function(input_string, max_mut =2) { n <-nchar(input_string)# Number of characters to change (randomly between 0 and 2) num_changes <-sample(0:max_mut, 1)# Generate random positions to change positions_to_change <-sample(1:n, num_changes, replace =FALSE)# Generate random characters to replace new_chars <-sample(c("G", "T","A", "C"), num_changes, replace =TRUE)# Convert string to a list of characters char_list <-strsplit(input_string, "")[[1]]# Perform the changes char_list[positions_to_change] <- new_chars# Convert back to a string and returnreturn(paste(char_list, collapse =""))}sim_pairs <-function(genome, n_reads =200, read_len =300, max_mut =10) {# Generate a random starting position start_positions <-sample(1:(nchar(genome) - (read_len -1)), n_reads) start_positions |># Extract a 300-character substringmap( ~substr(genome, start = .x, stop = .x + (read_len -1))) |>map(rand_mut, max_mut) |>map(as.character) |>map(extract_anc_tar)}# Function to convert each sublist to a tibble rowframe_to_row <-function(item, read, frame) {tibble(read = read, frame = frame, anc = item$anc, tar = item$tar)}read_to_rows <-function(read){map2_dfr(read, seq_along(read), ~frame_to_row(.x, 1, .y))}samp_to_rows <-function(samp){ samp |>map(read_to_rows) |>bind_rows()}
Simulation
Code
set.seed(123)# EXAMPLE PARAMETERS, many are defaults# n_reads <- 300# # L <- 300# k = 27# step = 5# R <- max(0, (L - 2 * k) / 2)# # read_len <- 300# # # max_mut = 30tbl_pairs_du <- sub_du |>sim_pairs(max_mut =0) |>samp_to_rows()tbl_pairs_uk <- sub_uk |>sim_pairs(max_mut =0) |>samp_to_rows()tbl_pairs_du_noise <- sub_du |>sim_pairs(max_mut =10) |>samp_to_rows()tbl_pairs_uk_noise <- sub_uk |>sim_pairs(max_mut =10) |>samp_to_rows()result <-bind_rows(uk = tbl_pairs_uk, du = tbl_pairs_du, .id ="samp")result_noise <-bind_rows(uk = tbl_pairs_uk_noise, du = tbl_pairs_du_noise, .id ="samp")cnt_res <- result |>count(anc, tar) |>filter(n() >1, .by =c(anc)) |>arrange(anc) |># TODO: this is awkwardleft_join(result |>distinct(samp, anc, tar)) |>arrange(anc) |>pivot_wider(names_from ="samp", values_from ="n", values_fill =0)
cnt_res |>summarise(du_sum =sum(du), uk_sum =sum(uk), .by = anc) |>mutate(M = du_sum + uk_sum) |>ggplot(aes(M)) +geom_histogram(binwidth =1) +labs(title ="Total obs M per contingency table, no noise")
Code
cnt_res_noise |>summarise(du_sum =sum(du), uk_sum =sum(uk), .by = anc) |>mutate(M = du_sum + uk_sum) |>ggplot(aes(M)) +geom_histogram(binwidth =1) +labs(title ="Total obs M per contingency table, with noise")
References
Chaung, Kaitlin, Tavor Z. Baharav, George Henderson, Ivan N. Zheludev, Peter L. Wang, and Julia Salzman. n.d. “SPLASH: A Statistical, Reference-Free Genomic Algorithm Unifies Biological Discovery.”https://doi.org/10.1101/2022.06.24.497555.