diff --git a/DESCRIPTION b/DESCRIPTION index fc88253..872198d 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,7 +1,7 @@ Package: mixder Type: Package Title: A workflow for performing SNP mixture deconvolution -Version: 0.9.0 +Version: 0.10.0 Author: Rebecca Mitchell Maintainer: Rebecca Mitchell Description: A workflow for performing SNP mixture deconvolution of ForenSeq Kintelligence SNPs. Mixture deconvolution is performed using EuroForMix (https://github.com/oyvble/euroformix/). After mixture deconvolution, the user can choose to calculate metrics such as genotype accuracy and heterozygosity for a range of allele probability thresholds (useful for validation work) or create GEDmatch PRO reports utilizing specified allele probability thresholds. diff --git a/NAMESPACE b/NAMESPACE index 3bc58b8..28ae4e2 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -13,6 +13,7 @@ export(check_ratios) export(check_reads) export(checking_af) export(compile_metrics) +export(convert_table_to_list) export(create_at) export(create_config) export(create_evid) @@ -33,6 +34,7 @@ export(process_efm_files) export(process_kinreport) export(processing_evid_sample_reports) export(processing_ref_sample_reports) +export(read_in_freq) export(read_in_table) export(reverse_comp) export(run_efm) @@ -49,8 +51,10 @@ import(kgp) import(parallel) import(plotly) import(prompter) +import(rrapply) import(shiny) import(shinyFiles) +importFrom(data.table,fread) importFrom(grDevices,dev.off) importFrom(grDevices,png) importFrom(methods,show) diff --git a/NEWS.md b/NEWS.md index fe63512..4608b40 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,3 +1,17 @@ +# mixder 0.10.0 + +## Changes in version 0.10.0 +- Created more flexibility with file format for sample manifest +- Implemented support for custom SNP panels: + - In GUI, Drop down menu to select either "Kintelligence" or "Custom" assay; will require SNP positions file if using custom + - Using R API, `assay` and `snp_pos` arguments used to specify if a custom panel is used + - Wrote functions to read in and create EFM-compatible files more efficiently + - Save large data files (i.e. reference SNP profiles, frequency files, etc.) as `.rda` files and will auto-detect these to load instead of reading in large file (significantly faster) +- Load frequency data only once instead of with each sample when using Shiny app +- Improved error handling in Shiny app (pop-up window will appear if required files or settings are missing) +- Added option to change number of threads used by EFM; useful if running on a server or grid (default is 0) + + # mixder 0.9.0 ## Changes in version 0.9.0 diff --git a/R/ancestry_prediction.R b/R/ancestry_prediction.R index 918c284..bd567ae 100644 --- a/R/ancestry_prediction.R +++ b/R/ancestry_prediction.R @@ -28,12 +28,12 @@ ancestry_prediction = function(report, path, id, analysis_type, contrib_status, testsnps, groups) { if (testsnps == "All Autosomal SNPs") { plotid="AllSNPs" - geno=mixder::ancestry_1000G_allsamples + geno=data.frame(mixder::ancestry_1000G_allsamples) } else { plotid="AncestrySNPsOnly" geno=mixder::ancestrysnps_1000G_allsamples } - ncols=ncol(geno) + ncols = ncol(geno) geno_filt=geno[,c(7:ncols)] snps = data.frame("snp_id"=colnames(geno_filt)) snps = snps %>% @@ -69,7 +69,6 @@ ancestry_prediction = function(report, path, id, analysis_type, contrib_status, newcol=ncols+1 newcol2=ncols+4 PCs_anc = cbind(geno_ancestry[,c(newcol:newcol2)], data.frame(PCs[,c(1:10)])) - centroids(groups, PCs_anc, glue("{path}/PCA_plots"), glue("{id}_{contrib_status}_{analysis_type}_{plotid}")) dir.create(file.path(path, "PCA_plots"), showWarnings = FALSE, recursive=TRUE) diff --git a/R/app.R b/R/app.R index 8e723d0..58cbd61 100644 --- a/R/app.R +++ b/R/app.R @@ -62,11 +62,12 @@ mixder = function() { name = "question-circle", ) ) |> - add_prompt(message = "Select a folder containing the Mixture Sample Reports.", position = "right"), + add_prompt(message = "Select a folder containing the Mixture Sample Reports or a TSV file containing mixture genotypes.", position = "right"), textOutput("kin_inpath"), conditionalPanel(condition = "input.method == 'Calculate Metrics' | input.cond == 1", uiOutput("ref_GetFile"), uiOutput("ref_text")), conditionalPanel(condition = "input.cond == 1", uiOutput("ref_selector")), - conditionalPanel(condition = "input.skip_ancestry == 1", uiOutput("runmd"), uiOutput("twofreqs"), uiOutput("method")), + conditionalPanel(condition = "input.skip_ancestry == 1", uiOutput("runmd"), uiOutput("twofreqs"), uiOutput("method"), uiOutput("assay_choice")), + conditionalPanel(condition = "input.assay_choice == 'Custom'", uiOutput("assay_GetFile"), uiOutput("assay_text")), conditionalPanel(condition = "input.skip_ancestry == 1 & input.twofreqs == 0", uiOutput("freqselect")), conditionalPanel(condition = "input.twofreqs == 1", uiOutput("freqselect_major"), uiOutput("freqselect_minor")), conditionalPanel(condition = "input.skip_ancestry == 1 & input.twofreqs == 0 & input.uploadfreq == 'Upload Custom'", uiOutput("freq_GetFile"), uiOutput("freq_text")), @@ -148,6 +149,29 @@ server = function(input, output, session) { add_prompt(message = "Optional- if selecting Calculate Metrics,\nmust include reference genotypes.", position = "right") ), c("", "Calculate Metrics", "Create GEDmatch PRO Report")) }) + output$assay_choice = renderUI({ + selectInput("assay_choice", tags$span("Select Assay", tags$span(icon( + name = "question-circle", + ) + ) |> + add_prompt(message = "Select assay used to develop genotypes", position = "right") + ), c("Kintelligence", "Custom")) + }) + output$assay_GetFile = renderUI({ + fluidRow(column(10,shinyFilesButton("assay_GetFile", "Select a custom SNP positions file" , + title = "Select a custom SNP positions file", multiple = FALSE, + buttonType = "default", class = NULL), + tags$span( + icon( + name = "question-circle", + ) + ) |> + add_prompt(message = "File containing chromosomal locations for custom SNP panels.\nThis is not required if using the Kintelligence assay.\nColumns required: rsID, chromosome (e.g. 1, 2, 3,), and position (GRCh37).", position = "right") + )) + }) + output$assay_text = renderUI({ + textOutput("assay_file") + }) output$twofreqs = renderUI({ checkboxInput("twofreqs", tags$span("Use Different Allele Frequency Files For Each Contributor?", tags$span(icon( name = "question-circle", @@ -371,92 +395,154 @@ server = function(input, output, session) { add_prompt(message = "When calculating metrics, a range of allele 2 probability thresholds\ncan be used to calculate the metrics at each combination of allele 1 and allele 2 probability thresholds.\nThis sets the maximum allele 2 probability threshold.\nThe threshold increases in increments of 0.01.", position = "right") ), value=1, min = 0, max = 1) }) - volumes = getVolumes() ## sample file - shinyFileChoose(input, "sample_GetFile", roots=volumes, session=session) - samplefile = reactive({parseFilePaths(volumes, input$sample_GetFile)}) - observe({ - if(!is.null(samplefile)){ - output$sample_file = renderText({if(input$Submit==0){as.character(samplefile()$datapath)} else {return()}}) - } - }) - + roots = c(home="~", wd=".") + shinyFileChoose(input, "sample_GetFile", roots=roots, session=session, defaultRoot="home") + samplefile = reactive({parseFilePaths(roots, input$sample_GetFile)}) + if(!is.null(samplefile)){ + observe({ + output$sample_file = renderText({as.character(samplefile()$datapath)}) + }) + } ## freq files - shinyFileChoose(input, "freq_GetFile", roots=volumes, session=session) - freq = reactive({parseFilePaths(volumes, input$freq_GetFile)}) + shinyFileChoose(input, "freq_GetFile", roots=roots, session=session, defaultRoot="home") + freq = reactive({parseFilePaths(roots, input$freq_GetFile)}) if (!is.null(freq)) { observe({ - output$freq_file = renderText({if(input$Submit==0){as.character(freq()$datapath)} else {return()}}) + output$freq_file = renderText({as.character(freq()$datapath)}) }) } - shinyFileChoose(input, "freq_GetFile_major", roots=volumes, session=session) - freq_major = reactive({parseFilePaths(volumes, input$freq_GetFile_major)}) + shinyFileChoose(input, "freq_GetFile_major", roots=roots, session=session, defaultRoot="home") + freq_major = reactive({parseFilePaths(roots, input$freq_GetFile_major)}) if (!is.null(freq_major)) { observe({ - output$freq_file_major = renderText({if(input$Submit==0){as.character(freq_major()$datapath)} else {return()}}) + output$freq_file_major = renderText({as.character(freq_major()$datapath)}) }) } - shinyFileChoose(input, "freq_GetFile_minor", roots=volumes, session=session) - freq_minor = reactive({parseFilePaths(volumes, input$freq_GetFile_minor)}) + shinyFileChoose(input, "freq_GetFile_minor", roots=roots, session=session, defaultRoot="home") + freq_minor = reactive({parseFilePaths(roots, input$freq_GetFile_minor)}) if (!is.null(freq_minor)) { observe({ - output$freq_file_minor = renderText({if(input$Submit==0){as.character(freq_minor()$datapath)} else {return()}}) + output$freq_file_minor = renderText({as.character(freq_minor()$datapath)}) + }) + } + + shinyFileChoose(input, "assay_GetFile", roots=roots, session=session, defaultRoot="home") + assay = reactive({parseFilePaths(roots, input$assay_GetFile)}) + if (!is.null(assay)) { + observe({ + output$assay_file = renderText({as.character(assay()$datapath)}) }) } ## refs - shinyDirChoose(input, "ref_GetFile", roots=volumes(), session=session) - refs = reactive({parseDirPath(volumes, input$ref_GetFile)}) + shinyDirChoose(input, "ref_GetFile", roots=roots, session=session, defaultRoot="home") + refs = reactive({parseDirPath(roots, input$ref_GetFile)}) if (!is.null(refs)) { observe({ - output$refs_file = renderText({if(input$Submit==0){as.character(refs())} else {return()}}) + output$refs_file = renderText({as.character(refs())}) }) } - shinyDirChoose(input, "kin_prefix", roots=volumes(), session=session) - kin_inpath = reactive({parseDirPath(volumes, input$kin_prefix)}) + shinyDirChoose(input, "kin_prefix", roots=roots, session=session, defaultRoot="home") + kin_inpath = reactive({parseDirPath(roots, input$kin_prefix)}) observe({ if(!is.null(kin_inpath)){ - output$kin_inpath = renderText({if(input$Submit==0){as.character(kin_inpath())} else {return()}}) + output$kin_inpath = renderText({as.character(kin_inpath())}) } }) ## Input the sample manifest and run the workflow on each line (sample) observeEvent(input$Submit, { - sample_list = read.table(samplefile()$datapath, sep="\t", header=T) - date = glue("{Sys.Date()}_{format(Sys.time(), '%H_%M_%S')}") - create_config(date, input$twofreqs, ifelse(!isTruthy(freq()$datapath), input$uploadfreq, freq()$datapath), ifelse(!isTruthy(freq_major()$datapath), input$uploadfreq_major, freq_major()$datapath), ifelse(!isTruthy(freq_minor()$datapath), input$uploadfreq_minor, freq_minor()$datapath), refs(), samplefile()$datapath, input$output, input$run_mixdeconv, input$uncond, input$ref_selector, input$method, input$sets, kin_inpath(), input$dynamicAT, input$staticAT, input$minimum_snps, input$A1_threshold, input$A2_threshold, input$A1_threshmin_metrics, input$A1_threshmax_metrics, input$A2_threshmin_metrics, input$A2_threshmax_metrics, input$major_selector, input$minor_selector, input$filter_missing, input$skip_ancestry, input$ancestry_snps, input$pcagroups) - if (isTruthy(refs())) { - withProgress(message = "Loading References", value = 0, { - if (!file.exists(glue("{refs()}/EFM_references.csv"))) { - refData = euroformix::sample_tableToList(data.frame(processing_ref_sample_reports(refs()))) - } else { - refData = euroformix::sample_tableToList(euroformix::tableReader(glue("{refs()}/EFM_references.csv"))) + if (!isTruthy(samplefile()$datapath)) { + showModal(modalDialog( + title = "Missing Input", + "Please provide a sample manifest before proceeding.", + easyClose = TRUE, + footer = modalButton("Dismiss") + )) + } else if (!isTruthy(kin_inpath())) { + showModal(modalDialog( + title = "Missing Input", + "Please provide a folder containing mixture data (Kintelligence Sample Reports or a .tsv file of genotypes).", + easyClose = TRUE, + footer = modalButton("Dismiss") + )) + } else if (!isTruthy(input$cond) & !isTruthy(input$uncond)) { + showModal(modalDialog( + title = "Input Error", + "Please select either an Unconditioned or Conditioned analysis (or both!) before proceeding.", + easyClose = TRUE, + footer = modalButton("Dismiss") + )) + } else if (input$assay_choice=="Custom" & !isTruthy(assay()$datapath)) { + showModal(modalDialog( + title = "Missing Input", + "When running a custom SNP panel, please provide the SNP positions file.", + easyClose = TRUE, + footer = modalButton("Dismiss") + )) + } else if ((input$method == "Calculate Metrics" | isTruthy(input$cond)) & !isTruthy(refs())) { + showModal(modalDialog( + title = "Missing Input", + "Please provide reference genotypes before proceeding.", + easyClose = TRUE, + footer = modalButton("Dismiss") + )) + } else { + if (isTruthy(refs())) { + withProgress(message = "Loading References", value = 0, { + if (file.exists(glue("{refs()}/EFM_references.rda"))) { + load(glue("{refs()}/EFM_references.rda")) + } else if (!file.exists(glue("{refs()}/EFM_references.csv"))) { + refData = convert_table_to_list(data.frame(processing_ref_sample_reports(refs()))) + } else { + refs = data.frame(fread(glue("{refs()}/EFM_references.csv"))) + refData = convert_table_to_list(refs) + save(refData, file=glue("{refs()}/EFM_references.rda")) + } + }) + } else { + refData=NULL + } + sample_list = suppressWarnings(euroformix::tableReader(samplefile()$datapath)) + date = glue("{Sys.Date()}_{format(Sys.time(), '%H_%M_%S')}") + out_path = glue("{kin_inpath()}/snp_sets/{input$output}/") + create_config(date, input$twofreqs, ifelse(!isTruthy(freq()$datapath), input$uploadfreq, freq()$datapath), ifelse(!isTruthy(freq_major()$datapath), input$uploadfreq_major, freq_major()$datapath), ifelse(!isTruthy(freq_minor()$datapath), input$uploadfreq_minor, freq_minor()$datapath), refs(), samplefile()$datapath, NULL, NULL, out_path, input$run_mixdeconv, input$uncond, input$ref_selector, input$method, input$sets, kin_inpath(), input$dynamicAT, input$staticAT, input$minimum_snps, input$A1_threshold, input$A2_threshold, input$A1_threshmin_metrics, input$A1_threshmax_metrics, input$A2_threshmin_metrics, input$A2_threshmax_metrics, input$major_selector, input$minor_selector, input$filter_missing, input$skip_ancestry, input$ancestry_snps, input$pcagroups, input$assay_choice, assay()$datapath) + withProgress(message = "Loading Allele Frequency Data", value = 0, { + freq_both = ifelse(!isTruthy(freq()$datapath), input$uploadfreq, freq()$datapath) + freq_major = ifelse(!isTruthy(freq_major()$datapath), input$uploadfreq_major, freq_major()$datapath) + freq_minor = ifelse(!isTruthy(freq_minor()$datapath), input$uploadfreq_minor, freq_minor()$datapath) + popFreq = load_freq(input$twofreqs, freq_both, freq_major, freq_minor) + }) + if (isTruthy(assay()$datapath)) { + snp_positions = read.table(assay()$datapath, sep="\t", header=T) + } else { + snp_positions = mixder::kintelligence_snp_positions + } + withProgress(message = "Running Samples", value = 0, { + n = nrow(sample_list) + for (row in 1:n) { + id = sample_list[row, 1] + if (ncol(sample_list)>1) { + replicate_id = ifelse(is.na(sample_list[row, 2]), "", sample_list[row, 2]) + } else { + replicate_id = "" + } + incProgress((row-1)/n, detail = glue("On Sample {row} of {n}")) + withCallingHandlers({ + shinyjs::html(id = "text", html = "") + run_workflow(date, id, replicate_id, input$twofreqs, popFreq, refData, refs(), out_path, input$run_mixdeconv, input$uncond, input$ref_selector, input$method, input$sets, kin_inpath(), input$dynamicAT, input$staticAT, input$minimum_snps, input$A1_threshold, input$A2_threshold, input$A1_threshmin_metrics, input$A1_threshmax_metrics, input$A2_threshmin_metrics, input$A2_threshmax_metrics, input$major_selector, input$minor_selector, input$min_cont_prob, input$keep_bins, input$filter_missing, input$skip_ancestry, input$ancestry_snps, input$pcagroups, input$assay_choice, snp_positions) + }, + message = function(m) { + shinyjs::html(id = "text", html = m$message, add = TRUE) + }) } }) - } else if(input$method == "Calculate Metrics" | isTruthy(input$cond)) { - stop("No references provided but selected conditioned analyses or calculating metrics. Please re-run!") - } else { - refData = NULL } - withProgress(message = "Running Samples", value = 0, { - n = nrow(sample_list) - for (row in 1:n) { - id = sample_list[row, 1] - replicate_id = ifelse(is.na(sample_list[row, 2]), "", sample_list[row, 2]) - incProgress((row-1)/n, detail = glue("On Sample {row} of {n}")) - withCallingHandlers({ - shinyjs::html(id = "text", html = "") - run_workflow(date, id, replicate_id, input$twofreqs, ifelse(!isTruthy(freq()$datapath), input$uploadfreq, freq()$datapath),ifelse(!isTruthy(freq_major()$datapath), input$uploadfreq_major, freq_major()$datapath), ifelse(!isTruthy(freq_minor()$datapath), input$uploadfreq_minor, freq_minor()$datapath), refData, refs(), input$output, input$run_mixdeconv, input$uncond, input$ref_selector, input$method, input$sets, kin_inpath(), input$dynamicAT, input$staticAT, input$minimum_snps, input$A1_threshold, input$A2_threshold, input$A1_threshmin_metrics, input$A1_threshmax_metrics, input$A2_threshmin_metrics, input$A2_threshmax_metrics, input$major_selector, input$minor_selector, input$min_cont_prob, input$keep_bins, input$filter_missing, input$skip_ancestry, input$ancestry_snps, input$pcagroups) - }, - message = function(m) { - shinyjs::html(id = "text", html = m$message, add = TRUE) - }) - } - }) }) } diff --git a/R/assigned_A2.R b/R/assigned_A2.R index bd3b720..00e0a34 100644 --- a/R/assigned_A2.R +++ b/R/assigned_A2.R @@ -12,15 +12,21 @@ #' #' @param x Data frame of allele calls #' @param thresh Allele 2 probability threshold +#' @param pos SNP positions data frame #' #' @return data frame #' @export -assigned_A2 = function(x, thresh) { +assigned_A2 = function(x, thresh, pos) { x$Allele2 = ifelse(x$A2_Prob>=thresh & x$A2!="99", x$A2, x$A1) names(x)[names(x) == "A1"] = "Allele1" x$Locus = ifelse(x$Locus=="RS201326893_Y152OCH", "rs201326893_Y152OCH", ifelse(x$Locus=="N29INSA", "N29insA", tolower(x$Locus))) - rsids = merge(mixder::kintelligence_snp_positions, x, by.x="rsid", by.y="Locus") - final_x = rsids[,c("rsid", "chromosome", "position", "Allele1", "Allele2")] - final_x_sorted = final_x[order(final_x$chromosome, final_x$position),] + colname = colnames(pos) + lcol = grep("rsid|marker|locus|snp", colname, ignore.case=TRUE, value=TRUE) + chrcol = grep("chr", colname, ignore.case=TRUE, value=TRUE) + poscol = grep("pos", colname, ignore.case=TRUE, value=TRUE) + rsids = merge(pos, x, by.x=lcol, by.y="Locus") + final_x = rsids[,c(lcol, chrcol, poscol, "Allele1", "Allele2")] + final_x_sorted = final_x %>% + arrange(as.numeric(!!sym(chrcol)), as.numeric(!!sym(poscol))) return(final_x_sorted) } diff --git a/R/check_nas.R b/R/check_nas.R index 40a796d..0457169 100644 --- a/R/check_nas.R +++ b/R/check_nas.R @@ -16,13 +16,16 @@ #' @export #' check_nas = function(df) { - if (sum(!is.na(df$Allele.4))==0) { - df[,c("Allele.4","Height.4")] = list(NULL) + if ("Allele.4" %in% names(df)) { + if (sum(!is.na(df$Allele.4))==0) { + df[,c("Allele.4","Height.4")] = list(NULL) + } } - if (sum(!is.na(df$Allele.3))==0) { - df[,c("Allele.3","Height.3")] = list(NULL) + if ("Allele.3" %in% names(df)) { + if (sum(!is.na(df$Allele.3))==0) { + df[,c("Allele.3","Height.3")] = list(NULL) + } + df[is.na(df)] = 0 } - df[is.na(df)] = 0 - #df %>% replace(is.na(.data), 0) return(df) } diff --git a/R/checking_af.R b/R/checking_af.R index 7c72ec4..a306723 100644 --- a/R/checking_af.R +++ b/R/checking_af.R @@ -1,19 +1,36 @@ - +# ------------------------------------------------------------------------------------------------- +# Copyright (c) 2024, DHS. +# +# This file is part of MixDeR and is licensed under the BSD license: see LICENSE. +# +# This software was prepared for the Department of Homeland Security (DHS) by the Battelle National +# Biodefense Institute, LLC (BNBI) as part of contract HSHQDC-15-C-00064 to manage and operate the +# National Biodefense Analysis and Countermeasures Center (NBACC), a Federally Funded Research and +# Development Center. +# ------------------------------------------------------------------------------------------------- #' Checking AF file for correct format; formatting if necessary #' #' @param affile Allele Frequency file -#' @param outpath Path to write formatted AF file to if necessary +#' @param contrib contributor assigned to AF file #' #' @return Frequency file loaded for EFM #' @export -checking_af = function(affile, outpath) { - af=utils::read.csv(affile, header=T) - if (isTruthy(af[c(1:4),1] == c("A", "C", "G", "T"))) { - finalfreq = euroformix::freqImport(affile)[[1]] +#' +#' @importFrom data.table fread +checking_af = function(affile) { + if (grepl(".rda", affile, ignore.case = TRUE)) { + load(affile) } else { - af_formatted = format_af(af) - utils::write.csv(af_formatted, glue("{outpath}/custom_AF_formatted.csv"), row.names=F, quote=F) - finalfreq = euroformix::freqImport(glue("{outpath}/custom_AF_formatted.csv"))[[1]] + af=data.frame(fread(affile, header=T, sep=",")) + filename = gsub(".csv","", affile) + if (!isTruthy(af[c(1:4),1] == c("A", "C", "G", "T"))) { + af_formatted = format_af(af) + utils::write.csv(af_formatted, glue("{filename}_EFMformatted.csv"), row.names=F, quote=F) + } else { + af_formatted = af + } + popFreq=read_in_freq(af_formatted) + save(popFreq, file=glue("{filename}.rda")) } - return(finalfreq) + return(popFreq) } diff --git a/R/convert_table_to_list.R b/R/convert_table_to_list.R new file mode 100644 index 0000000..077546b --- /dev/null +++ b/R/convert_table_to_list.R @@ -0,0 +1,64 @@ +# ------------------------------------------------------------------------------------------------- +# Copyright (c) 2026, DHS. +# +# This file is part of MixDeR and is licensed under the BSD license: see LICENSE. +# +# This software was prepared for the Department of Homeland Security (DHS) by the Battelle National +# Biodefense Institute, LLC (BNBI) as part of contract HSHQDC-15-C-00064 to manage and operate the +# National Biodefense Analysis and Countermeasures Center (NBACC), a Federally Funded Research and +# Development Center. +# ------------------------------------------------------------------------------------------------- +#' Convert table to list +#' +#' @param input table +#' +#' @returns list of table +#' @export +#' +#' @importFrom data.table fread +#' @import rrapply +convert_table_to_list = function(table) { + #table=data.frame(fread(input, header=T)) + outL=rrapply(table[,c(1:4)], how="unmelt") + nestedlist = lapply(split(table, table$Sample.Name, drop = TRUE), + function(x) c(split(x, x[["Marker"]], drop = TRUE))) + colname = colnames(table) #colnames in file + lind = grep("marker",tolower(colname),fixed=TRUE) #locus col-ind + A_ind = grep("allele",tolower(colname),fixed=TRUE) #allele col-ind + H_ind = grep("height",tolower(colname),fixed=TRUE) #height col-ind + if(length(lind)==0) lind = grep("loc",tolower(colname),fixed=TRUE) #try another name + sind = grep("sample",tolower(colname),fixed=TRUE) #sample col-ind + if(length(sind)>1) sind = sind[grep("name",tolower(colname[sind]),fixed=TRUE)] #use only sample name + samplenames = unique(as.character(table[,sind])) #sample names + outL = list() #Init outList (insert non-empty characters): + for(samplename in samplenames) { #for each sample in matrix + filt_table=subset(table, table[,sind]==samplename) + locs = unique(toupper(filt_table[,lind])) #locus names: Use uniques and Convert to upper case + for(loc in locs) { #for each locus + loc_lower=ifelse(loc=="N29INSA", "N29insA", ifelse(loc=="RS201326893_Y152OCH", "rs201326893_Y152OCH", tolower(loc))) + if (length(A_ind) == 2) { + outL[[samplename]][[loc]]$adata = c(nestedlist[[samplename]][[loc_lower]][[A_ind[[1]]]], nestedlist[[samplename]][[loc_lower]][[A_ind[[2]]]]) + if(length(H_ind)>0) { + outL[[samplename]][[loc]]$hdata = c(nestedlist[[samplename]][[loc_lower]][[H_ind[[1]]]], nestedlist[[samplename]][[loc_lower]][[H_ind[[2]]]]) + } + } else if (length(A_ind) == 3) { + if (!is.na(nestedlist[[samplename]][[loc_lower]][[A_ind[[3]]]])) { + outL[[samplename]][[loc]]$adata = c(nestedlist[[samplename]][[loc_lower]][[A_ind[[1]]]], nestedlist[[samplename]][[loc_lower]][[A_ind[[2]]]], nestedlist[[samplename]][[loc_lower]][[A_ind[[3]]]]) + outL[[samplename]][[loc]]$hdata = c(nestedlist[[samplename]][[loc_lower]][[H_ind[[1]]]], nestedlist[[samplename]][[loc_lower]][[H_ind[[2]]]], nestedlist[[samplename]][[loc_lower]][[H_ind[[3]]]]) + } else { + outL[[samplename]][[loc]]$adata = c(nestedlist[[samplename]][[loc_lower]][[A_ind[[1]]]], nestedlist[[samplename]][[loc_lower]][[A_ind[[2]]]]) + outL[[samplename]][[loc]]$hdata = c(nestedlist[[samplename]][[loc_lower]][[H_ind[[1]]]], nestedlist[[samplename]][[loc_lower]][[H_ind[[2]]]]) + } + } else if (length(A_ind) == 4) { + if (!is.na(nestedlist[[samplename]][[loc_lower]][[A_ind[[4]]]])) { + outL[[samplename]][[loc]]$adata = c(nestedlist[[samplename]][[loc_lower]][[A_ind[[1]]]], nestedlist[[samplename]][[loc_lower]][[A_ind[[2]]]], nestedlist[[samplename]][[loc_lower]][[A_ind[[3]]]], nestedlist[[samplename]][[loc_lower]][[A_ind[[4]]]]) + outL[[samplename]][[loc]]$hdata = c(nestedlist[[samplename]][[loc_lower]][[H_ind[[1]]]], nestedlist[[samplename]][[loc_lower]][[H_ind[[2]]]], nestedlist[[samplename]][[loc_lower]][[H_ind[[3]]]], nestedlist[[samplename]][[loc_lower]][[H_ind[[4]]]]) + } else { + outL[[samplename]][[loc]]$adata = c(nestedlist[[samplename]][[loc_lower]][[A_ind[[1]]]], nestedlist[[samplename]][[loc_lower]][[A_ind[[2]]]], nestedlist[[samplename]][[loc_lower]][[A_ind[[3]]]]) + outL[[samplename]][[loc]]$hdata = c(nestedlist[[samplename]][[loc_lower]][[H_ind[[1]]]], nestedlist[[samplename]][[loc_lower]][[H_ind[[2]]]], nestedlist[[samplename]][[loc_lower]][[H_ind[[3]]]]) + } + } + } + } + return(outL) +} diff --git a/R/create_config.R b/R/create_config.R index c2ae1a3..c68ad19 100644 --- a/R/create_config.R +++ b/R/create_config.R @@ -42,13 +42,19 @@ #' @param skipancestry TRUE/FALSE whether to skip ancestry prediction step #' @param pcasnps SNPs used for PCA (ancestry prediction) #' @param pcagroups Groups used for PCA (ancestry prediction), either Superpopulations or Subpopulations +#' @param assay name of sequencing assay, either `kintelligence` or `custom` +#' @param pos Path to file containing SNP positions of custom assay, required if assay==`custom` #' #' @export #' -create_config = function(date, twofreqs, freq_all, freq_major, freq_minor, refs, sample_manifest, sample, replicate, out_path, run_mixdeconv, unconditioned, cond, method, sets, kinpath, dynamicAT, staticAT, minimum_snps, A1_threshold, A2_threshold, A1min, A1max, A2min, A2max, major, minor, filter_missing, skipancestry, pcasnps, pcagroups){ +create_config = function(date, twofreqs, freq_all, freq_major, freq_minor, refs, sample_manifest, sample, replicate, out_path, run_mixdeconv, unconditioned, cond, method, sets, kinpath, dynamicAT, staticAT, minimum_snps, A1_threshold, A2_threshold, A1min, A1max, A2min, A2max, major, minor, filter_missing, skipancestry, pcasnps, pcagroups, assay, pos){ config = setNames(data.frame(matrix(ncol=2, nrow=0)), c("Setting", "Value")) config = rbind(config, data.frame(Setting="MixDeR Version:", Value=getNamespaceVersion("mixder")[["version"]])) config = rbind(config, data.frame(Setting="EuroForMix Version:", Value=getNamespaceVersion("euroformix")[["version"]])) + onfig = rbind(config, data.frame(Setting="Assay:", Value=assay)) + if (assay == "custom") { + config = rbind(config, data.frame(Setting="SNP hg19 positions file for custom assay:", Value=pos)) + } if (isTruthy(sample_manifest)) { config = rbind(config, data.frame(Setting="Path to sample manifest:", Value=sample_manifest)) } else if (isTruthy(sample)) { @@ -73,7 +79,7 @@ create_config = function(date, twofreqs, freq_all, freq_major, freq_minor, refs, config = rbind(config, data.frame(Setting="Frequency data for Minor Contributor:", Value=freq_all)) } } - config = rbind(config, data.frame(Setting="Output path:", Value=glue("{kinpath}/snp_sets/{out_path}/"))) + config = rbind(config, data.frame(Setting="Output path:", Value=out_path)) config = rbind(config, data.frame(Setting="Number of SNP sets:", Value=sets)) config = rbind(config, data.frame(Setting="Minimum number of SNPs:", Value=minimum_snps)) config = rbind(config, data.frame(Setting="Static AT:", Value=staticAT)) @@ -102,6 +108,6 @@ create_config = function(date, twofreqs, freq_all, freq_major, freq_minor, refs, config = rbind(config, data.frame(Setting="Assumed major contributor:", Value=major)) config = rbind(config, data.frame(Setting="Assumed minor contributor:", Value=minor)) } - dir.create(file.path(kinpath, "snp_sets", out_path, "config_log_files", date), showWarnings = FALSE, recursive=TRUE) - write.table(config, glue("{kinpath}/snp_sets/{out_path}/config_log_files/{date}/config_settings_run_{date}.txt"), row.names=F, quote=F, col.names=T, sep="\t") + dir.create(file.path(out_path, "config_log_files", date), showWarnings = FALSE, recursive=TRUE) + write.table(config, glue("{out_path}/config_log_files/{date}/config_settings_run_{date}.txt"), row.names=F, quote=F, col.names=T, sep="\t") } diff --git a/R/create_evid.R b/R/create_evid.R index fa905fd..ae2aa9a 100644 --- a/R/create_evid.R +++ b/R/create_evid.R @@ -22,7 +22,8 @@ create_evid = function(sample, rep_sample, inpath) { if (check_reads(evidfn)) { evidData = vector() } else { - evidData = euroformix::sample_tableToList(read_in_table(evidfn)) + evid_df_form = read_in_table(evidfn) + evidData = convert_table_to_list(evid_df_form) } } else { if (check_reads(evidfn)) { @@ -45,7 +46,7 @@ create_evid = function(sample, rep_sample, inpath) { final_dfs = equalize_samples(corrected_tables[[1]], corrected_tables[[2]]) } merge_table = rbind(final_dfs[[1]], final_dfs[[2]]) - evidData = euroformix::sample_tableToList(merge_table) + evidData = convert_table_to_list(merge_table) } return(evidData) } diff --git a/R/create_evid_all.R b/R/create_evid_all.R index 831de98..7697ee1 100644 --- a/R/create_evid_all.R +++ b/R/create_evid_all.R @@ -16,11 +16,15 @@ #' @param keep_bins To use existing SNP bins or create new bins (and files) #' @export -create_evid_all = function(inpath, id, nsets, keep_bins){ +create_evid_all = function(inpath, id, nsets, keep_bins, assay){ evid_all = processing_evid_sample_reports(inpath, id) evid_all$Total_Reads = rowSums(evid_all[,grepl("Height",colnames(evid_all))], na.rm = TRUE) evid_sort = evid_all[order(evid_all$Total_Reads),] - bin_size = round(10039/nsets) + if (assay == "Kintelligence") { + bin_size = round(10039/nsets) + } else { + bin_size = round(nrow(evid_sort)/nsets) + } dir.create(file.path(inpath, "snp_sets"), showWarnings = FALSE, recursive=TRUE) evid_final = cbind(Sample.Name = id, evid_sort) write.table(evid_final, glue("{inpath}/snp_sets/{id}_snpsetscombined_evidence.tsv"), row.names=F, quote=F, col.names=T, sep="\t") diff --git a/R/create_gedmatchpro_report.R b/R/create_gedmatchpro_report.R index faaff87..82f7eef 100644 --- a/R/create_gedmatchpro_report.R +++ b/R/create_gedmatchpro_report.R @@ -23,12 +23,13 @@ #' @param A2max Maximum value for allele 2 probability threshold #' @param minor_threshold If apply allele 1 probability to minor contributor #' @param filter_missing TRUE/FALSE to filter SNPs with missing allele 2 values +#' @param pos SNP position data frame #' #' @return list of two data frames, the GEDmatch PRO report and a data frame of metrics calculated for that report #' @export -create_gedmatchpro_report = function(path, x, contrib, contrib_status, minimum_snps, A1, A2, A1min, A1max, A2min, A2max, minor_threshold, filter_missing) { +create_gedmatchpro_report = function(path, x, contrib, contrib_status, minimum_snps, A1, A2, A1min, A1max, A2min, A2max, minor_threshold, filter_missing, pos) { formatted_df = suppressWarnings(process_efm_files(x, contrib, NULL, minimum_snps, A1min, A1max, A2min, A2max, metrics=FALSE, filter_missing)) gedmatch_metrics = gedmatch_metrics(formatted_df, A1, A2, minimum_snps, path) - report = filter_alleles(formatted_df, contrib_status, minimum_snps, A1, A2, minor_threshold, filter_missing) + report = filter_alleles(formatted_df, contrib_status, minimum_snps, A1, A2, minor_threshold, filter_missing, pos) return(list(report, gedmatch_metrics[[1]], gedmatch_metrics[[2]])) } diff --git a/R/filter_alleles.R b/R/filter_alleles.R index ba1a55c..abbf742 100644 --- a/R/filter_alleles.R +++ b/R/filter_alleles.R @@ -18,10 +18,11 @@ #' @param A2_threshold Allele 2 probability threshold #' @param minor_threshold If apply allele 1 probability threshold to minor contributor #' @param filter_missing TRUE/FALSE to filter SNPs with missing allele 2 values +#' @param pos SNP position data frame #' #' @return Data frame with filtered allele calls #' @export -filter_alleles = function(all_files, contrib_status, minimum_snps, A1_threshold, A2_threshold, minor_threshold, filter_missing) { +filter_alleles = function(all_files, contrib_status, minimum_snps, A1_threshold, A2_threshold, minor_threshold, filter_missing, pos) { . = NULL if (filter_missing) { all_files = subset(all_files, !(all_files$A2 == 99 & all_files$A2_Prob% .[c(1:minimum_snps),] } - final_df = assigned_A2(filt_df, A2_threshold) + final_df = assigned_A2(filt_df, A2_threshold, pos) return(final_df) } diff --git a/R/format_ref.R b/R/format_ref.R index ef9c34b..1042703 100644 --- a/R/format_ref.R +++ b/R/format_ref.R @@ -10,22 +10,17 @@ # ------------------------------------------------------------------------------------------------- #' @title Formatting reference file #' -#' @param refData Data frame of the reference genotypes -#' @param refid Reference order in the reference genotypes file +#' @param refid ID of the reference genotypes of interest #' @param refs Path of the reference genotypes file #' #' @return Data frame containing reference of interest genotypes #' @export #' #' @importFrom rlang .data -format_ref = function(refData, refid, refs) { - if (is.numeric(refid)) { - ref_int = names(refData)[refid] - } else { - ref_int = refid - } - ref_profile = euroformix::tableReader(glue("{refs}/EFM_references.csv")) %>% - filter(.data$Sample.Name == ref_int) +#' @importFrom data.table fread +format_ref = function(refid, refs) { + ref_profile = data.frame(fread(glue("{refs}/EFM_references.csv"))) %>% + filter(.data$Sample.Name == refid) ref_profile$A1_order = ifelse(ref_profile$`Allele1`>ref_profile$`Allele2`, ref_profile$`Allele1`, ref_profile$`Allele2`) ref_profile$A2_order = ifelse(ref_profile$`Allele1`>ref_profile$`Allele2`, ref_profile$`Allele2`, ref_profile$`Allele1`) return(ref_profile) diff --git a/R/get_ids.R b/R/get_ids.R index d6a0de3..88d0d12 100644 --- a/R/get_ids.R +++ b/R/get_ids.R @@ -15,7 +15,10 @@ #' @return command to get list of files #' @export get_ids = function(inpath) { - if (file.exists(glue("{inpath}/EFM_references.csv"))) { + if (file.exists(glue("{inpath}/EFM_references.rda"))) { + load(glue("{inpath}/EFM_references.rda")) + return(names(refData)) + } else if (file.exists(glue("{inpath}/EFM_references.csv"))) { all_samples = utils::read.csv(glue("{inpath}/EFM_references.csv")) if ("Sample.Name" %in% colnames(all_samples)) { return(unique(all_samples$Sample.Name)) diff --git a/R/load_freq.R b/R/load_freq.R index ed5be2d..a971c62 100644 --- a/R/load_freq.R +++ b/R/load_freq.R @@ -8,9 +8,8 @@ # National Biodefense Analysis and Countermeasures Center (NBACC), a Federally Funded Research and # Development Center. # ------------------------------------------------------------------------------------------------- -#' Load in frequency data +#' Load frequency data #' -#'@param out_path outpath directory #' @param twofreqs If two different allele frequency files are to be used #' @param freq_both_input Allele frequency file, if only using one #' @param freq_major_input Allele frequency file for major contributor @@ -18,11 +17,11 @@ #' #' @return list of major AF data data frame and minor AF data data frame #' @export -load_freq = function(out_path, twofreqs, freq_both_input, freq_major_input, freq_minor_input) { +load_freq = function(twofreqs, freq_both_input, freq_major_input, freq_minor_input) { freqs_allowed = c("Global - 1000G", "Global - gnomAD", "AFR - 1000G", "AMR - 1000G", "EAS - 1000G", "EUR - 1000G", "SAS - 1000G") if (!twofreqs) { if (file.exists(freq_both_input)) { - freq_minor = checking_af(freq_both_input, out_path) + freq_minor = checking_af(freq_both_input) freq_major = freq_minor } else { freq_both = decode_freq_name(tolower(freq_both_input)) @@ -56,7 +55,7 @@ load_freq = function(out_path, twofreqs, freq_both_input, freq_major_input, freq } } else { if (file.exists(freq_major_input)) { - freq_major = checking_af(freq_major_input, out_path) + freq_major = checking_af(freq_major_input) } else { freq_major = decode_freq_name(tolower(freq_major_input)) if (!freq_major %in% freqs_allowed) { @@ -81,7 +80,7 @@ load_freq = function(out_path, twofreqs, freq_both_input, freq_major_input, freq } } if (file.exists(freq_minor_input)) { - freq_minor = checking_af(freq_minor_input, out_path) + freq_minor = checking_af(freq_minor_input) } else { freq_minor = decode_freq_name(tolower(freq_minor_input)) if (!freq_minor %in% freqs_allowed) { diff --git a/R/read_in_freq.R b/R/read_in_freq.R new file mode 100644 index 0000000..2e133b9 --- /dev/null +++ b/R/read_in_freq.R @@ -0,0 +1,34 @@ +# ------------------------------------------------------------------------------------------------- +# Copyright (c) 2026, DHS. +# +# This file is part of MixDeR and is licensed under the BSD license: see LICENSE. +# +# This software was prepared for the Department of Homeland Security (DHS) by the Battelle National +# Biodefense Institute, LLC (BNBI) as part of contract HSHQDC-15-C-00064 to manage and operate the +# National Biodefense Analysis and Countermeasures Center (NBACC), a Federally Funded Research and +# Development Center. +# ------------------------------------------------------------------------------------------------- +#' Read in Frequency tables already formatted for EFM +#' +#' @param freq PATH to frequency table +#' +#' @returns list of frequency data in correct EFM list format +#' @export +#' +#' @importFrom data.table fread +#' +read_in_freq = function(freq){ + #efmaf=fread(freq, header=T, sep=",") + tab=data.frame(freq) + Anames = tab[,1] #first column is allele frequencies + tab = tab[,-1,drop=FALSE] + freqlist = vector("list", ncol(tab)) + for(j in 1:ncol(tab)) { #for each locus + tmp = tab[,j] + tmp2 = tmp[!is.na(tmp) & as.numeric(tmp)>0] #require that allele is not NA and is>0 + names(tmp2) = Anames[!is.na(tmp)] + freqlist[[j]] = tmp2 + } + names(freqlist) = toupper(colnames(tab)) #LOCUS-names are assigned as Upper-case! This is important to do! + return(freqlist) +} diff --git a/R/read_in_table.R b/R/read_in_table.R index aa4bd2e..56fab49 100644 --- a/R/read_in_table.R +++ b/R/read_in_table.R @@ -15,14 +15,15 @@ #' @return data frame of SNPs #' @export #' +#' @importFrom data.table fread read_in_table = function(df) { - evid_df = read.table(df, header=T, sep="\t") + evid_df = data.frame(fread(df, header=T, sep="\t")) myColClasses = sapply(evid_df, class) needed_cols = c("Allele.1", "Allele.2", "Allele.3", "Allele.4") myColClasses = ifelse(names(myColClasses) %in% needed_cols, "character", myColClasses) - evid_df_final = read.table(df, header=T, sep="\t", colClasses=myColClasses) - return(evid_df_final) + evid_df_form = data.frame(fread(df, header=T, sep="\t", colClasses=myColClasses)) + return(evid_df_form) } diff --git a/R/run_efm.R b/R/run_efm.R index 7438ebc..a87856e 100644 --- a/R/run_efm.R +++ b/R/run_efm.R @@ -19,28 +19,30 @@ #' @param out_path Output path #' @param attable AT table #' @param nsets Number of SNP sets -#' @param ancestry if not skipping ancestry prediction +#' @param ancestry TRUE/FALSE if to skip ancestry +#' @param assay assay used (kintelligence or custom) #' @param cond Sample IDs to condition on #' @param uncond TRUE/FALSE if performing unconditioned analysis #' @param keep_bins To use existing SNP bins or create new bins (and files) +#' @param threads number of threads used for EFM #' #' @export #' #' @import parallel #' -run_efm = function(date, popFreq, refData, id, replicate_id, inpath, out_path, attable, nsets, ancestry, cond = NULL, uncond=TRUE, keep_bins=TRUE) { +run_efm = function(date, popFreq, refData, id, replicate_id, inpath, out_path, attable, nsets, ancestry, assay, cond = NULL, uncond=TRUE, keep_bins=TRUE, threads=0, parallel=FALSE) { if (!ancestry) { new_path = glue("{out_path}/ancestry_prediction/") } else { new_path = out_path } if (replicate_id == "") { - create_evid_all(inpath, id, nsets, keep_bins) + create_evid_all(inpath, id, nsets, keep_bins, assay) write_path = glue("{new_path}Single/{id}") log_name = id } else { - create_evid_all(inpath, id, nsets, keep_bins) - create_evid_all(inpath, replicate_id, nsets, keep_bins) + create_evid_all(inpath, id, nsets, keep_bins, assay) + create_evid_all(inpath, replicate_id, nsets, keep_bins, assay) write_path = glue("{new_path}Replicates/{id}") log_name = glue("{id}_{replicate_id}") } @@ -51,9 +53,17 @@ run_efm = function(date, popFreq, refData, id, replicate_id, inpath, out_path, a } else { ids = NULL } - results = list() - for (i in 1:nsets) { - results[[i]] = run_indiv_efm_set(i, ids, snps_input, popFreq, refData, id, replicate_id, write_path, attable, cond=cond, uncond=uncond) + if (parallel) { + numCores = ifelse(detectCores()")) + sample_at = create_at(evidData, sample, replicate, attable) + } ratio_row = list() ratio_row[glue("Set{i}_C1_Prob_uncond")] = NA ratio_row[glue("Set{i}_C2_Prob_uncond")] = NA if (uncond) { + print(glue("Running unconditioned analysis for set {i}")) message("Running unconditioned mixture deconvolution
") dir.create(file.path(write_path, "unconditioned"), showWarnings = FALSE, recursive=TRUE) ##unconditioned analysis @@ -50,9 +63,9 @@ run_indiv_efm_set = function(i, ids, snps_input, popFreq, refData, id, replicate repeat { message(glue("Running unconditioned analysis for set {i}, attempt #{repeat_num+1}
")) if (substr(efm_v, 1,3)!="4.0" & substr(efm_v, 1,2) != "3.") { - uncond_results = euroformix::calcMLE(2, evidData, popFreq, AT=sample_at, BWS=FALSE, FWS=FALSE, DEG=FALSE, steptol=0.001, pC=0.01, lambda=0.05, fst=0.01, resttol=0) + uncond_results = euroformix::calcMLE(2, evidData, popFreq, AT=sample_at, BWS=FALSE, FWS=FALSE, DEG=FALSE, steptol=0.001, pC=0.01, lambda=0.05, fst=0.01, resttol=0, maxThreads=threads) } else { - uncond_results = euroformix::calcMLE(2, evidData, popFreq, AT=sample_at, BWS=FALSE, FWS=FALSE, DEG=FALSE, steptol=0.001, pC=0.01, lambda=0.05, fst=0.01) + uncond_results = euroformix::calcMLE(2, evidData, popFreq, AT=sample_at, BWS=FALSE, FWS=FALSE, DEG=FALSE, steptol=0.001, pC=0.01, lambda=0.05, fst=0.01, maxThreads=threads) } uncond_finaltable = euroformix::deconvolve(uncond_results) if (check_allele_probabilities(data.frame(uncond_finaltable[["table4"]]), i)) break @@ -63,14 +76,10 @@ run_indiv_efm_set = function(i, ids, snps_input, popFreq, refData, id, replicate if (repeat_num < 10) { ratio_row[glue("Set{i}_C1_Prob_uncond")] = uncond_results[["fit"]][["thetahat2"]][["Mix-prop. C1"]] ratio_row[glue("Set{i}_C2_Prob_uncond")] = uncond_results[["fit"]][["thetahat2"]][["Mix-prop. C2"]] - #ratio_row = list(Set_uncond=i, C1_Prob_uncond=uncond_results[["fit"]][["thetahat2"]][["Mix-prop. C1"]], C2_Prob_uncond=uncond_results[["fit"]][["thetahat2"]][["Mix-prop. C2"]]) - #uncond_ratios = rbind(uncond_ratios, ratio_row) write.table(uncond_finaltable[["table4"]], glue("{write_path}/unconditioned/{id}_set{i}_uncond.tsv"), quote=F, row.names=F, sep="\t") - #uncond_finaltable_all = rbind(uncond_finaltable_all, uncond_finaltable[["table4"]]) - #set_results = uncond_finaltable[["table4"]] + write.table(uncond_finaltable[["table3"]], glue("{write_path}/unconditioned/{id}_set{i}_uncond_table3.tsv"), quote=F, row.names=F, sep="\t") } else { message(glue("Repeated unconditioned analysis 10 times unsuccessfully. Will skip set {i}!
")) - #set_results = data.frame() } } final_list = c(ratio_row) @@ -80,20 +89,21 @@ run_indiv_efm_set = function(i, ids, snps_input, popFreq, refData, id, replicate cond_vector = rep.int(0, total_refs) for (cond_on in cond) { ratio_row = list() + print(glue("Running conditioned analysis on {cond_on} for set {i}")) message(glue("Running mixture deconvolution conditioned on {cond_on}
")) - #nam = glue("df_{cond_on}") list_num = match(cond_on, names(refData)) dir.create(file.path(write_path, glue("/conditioned/cond_on_{cond_on}")), showWarnings = FALSE, recursive=TRUE) message(glue("Running conditioned analysis on {cond_on} for set {i}.
")) if (substr(efm_v, 1,3)!="4.0" & substr(efm_v, 1,2) != "3.") { - condresults = euroformix::calcMLE(2, evidData, popFreq, refData, AT=sample_at, condOrder=replace(cond_vector, list_num, 1), BWS=FALSE, FWS=FALSE, DEG=FALSE, steptol=0.001, pC=0.01, lambda=0.05, fst=0.01, resttol=0) + condresults = euroformix::calcMLE(2, evidData, popFreq, refData, AT=sample_at, condOrder=replace(cond_vector, list_num, 1), BWS=FALSE, FWS=FALSE, DEG=FALSE, steptol=0.001, pC=0.01, lambda=0.05, fst=0.01, resttol=0, maxThreads=threads) } else { - condresults = euroformix::calcMLE(2, evidData, popFreq, refData, AT=sample_at, condOrder=replace(cond_vector, list_num, 1), BWS=FALSE, FWS=FALSE, DEG=FALSE, steptol=0.001, pC=0.01, lambda=0.05, fst=0.01) + condresults = euroformix::calcMLE(2, evidData, popFreq, refData, AT=sample_at, condOrder=replace(cond_vector, list_num, 1), BWS=FALSE, FWS=FALSE, DEG=FALSE, steptol=0.001, pC=0.01, lambda=0.05, fst=0.01, maxThreads=threads) } final_condresults = euroformix::deconvolve(condresults) ratio_row[glue("Set{i}_C1_Prob_cond_on_{cond_on}")] = condresults[["fit"]][["thetahat2"]][["Mix-prop. C1"]] ratio_row[glue("Set{i}_C2_Prob_cond_on_{cond_on}")] = condresults[["fit"]][["thetahat2"]][["Mix-prop. C2"]] write.table(final_condresults[["table4"]], glue("{write_path}/conditioned/cond_on_{cond_on}/unknown_cond_on_{cond_on}_set{i}.tsv"), quote=F, row.names=F, sep="\t") + write.table(final_condresults[["table3"]], glue("{write_path}/conditioned/cond_on_{cond_on}/unknown_cond_on_{cond_on}_set{i}_table3.tsv"), quote=F, row.names=F, sep="\t") final_list = c(final_list, ratio_row) } } diff --git a/R/run_mixder_ancestry.R b/R/run_mixder_ancestry.R index 798ab2d..9e11c8e 100644 --- a/R/run_mixder_ancestry.R +++ b/R/run_mixder_ancestry.R @@ -28,23 +28,31 @@ #' @param A2_threshold Allele 2 probability threshold (default=0.60) #' @param minor_contrib_threshold Whether to apply the allele 1 probability threshold to the minor contributor, regardless of the minimum number of SNPs (default=FALSE) #' @param keep_bins Use existing binned SNP data, if exists (default=TRUE) -#' @param snps SNPs to use for ancestry prediction (either ancestry only or all SNPs) -#' @param pcagroups How to color PCA plots (superpopulations and/or subpopulations) +#' @param snps SNPs to use for ancestry prediction (either ancestry only or all SNPs; default="ancestry") +#' @param pcagroups How to color PCA plots (superpopulations and/or subpopulations, default="superpopulations") +#' @param threads threads for EFM (default=0) +#' @param parallel parallelize EFM sets, will detect number of cores on machine and use all available (default=FALSE) #' #' @export #' -run_mixder_ancestry = function(sample_manifest=NULL, sample=NULL, replicate=NULL, sample_reports = getwd(), output = "output", refpath=NULL, refs=NULL, mixdeconv=TRUE, uncond=TRUE, cond=FALSE, sets=10, dynamicAT=0.015, staticAT=10, minimum_snps=6000, A1_threshold=0.99, A2_threshold=0.6, minor_contrib_threshold=FALSE, keep_bins=TRUE, snps, pcagroups) { +run_mixder_ancestry = function(sample_manifest=NULL, sample=NULL, replicate="", sample_reports = getwd(), output = "output", refpath=NULL, refs=NULL, mixdeconv=TRUE, uncond=TRUE, cond=FALSE, sets=10, dynamicAT=0.015, staticAT=10, minimum_snps=6000, A1_threshold=0.99, A2_threshold=0.6, minor_contrib_threshold=FALSE, keep_bins=TRUE, snps="ancestry", pcagroups="superpopulations", threads=0, parallel=FALSE) { date = glue("{Sys.Date()}_{format(Sys.time(), '%H_%M_%S')}") + popFreq = list(mixder::popFreq_1000G, mixder::popFreq_1000G) + out_path = glue("{sample_reports}/snp_sets/{output}/") ## load in references if (isTruthy(refpath)) { print("loading references") - if (!file.exists(glue("{refpath}/EFM_references.csv"))) { - refData = euroformix::sample_tableToList(data.frame(processing_ref_sample_reports(refpath))) + if (file.exists(glue("{refpath}/EFM_references.rda"))) { + load(glue("{refpath}/EFM_references.rda")) } else { - refData = euroformix::sample_tableToList(euroformix::tableReader(glue("{refpath}/EFM_references.csv"))) + if (!file.exists(glue("{refpath}/EFM_references.csv"))) { + refData = convert_table_to_list(data.frame(processing_ref_sample_reports(refpath))) + } else { + refsdf = data.frame(fread(glue("{refpath}/EFM_references.csv"))) + refData = convert_table_to_list(refsdf) + } + save(refData, file=glue("{refpath}/EFM_references.rda")) } - } else if (cond) { - stop("No references provided but selected conditioned analyses. Please re-run!") } else { refData = NULL } @@ -65,8 +73,9 @@ run_mixder_ancestry = function(sample_manifest=NULL, sample=NULL, replicate=NULL if (!isTruthy(sample_manifest) & !isTruthy(sample)){ stop("Please provide sample manifest or sample ID!") } + snp_positions = mixder::kintelligence_snp_positions print("Running ancestry prediction") - create_config(date, FALSE, "1000G_global", NA, NA, refpath, sample_manifest, sample, replicate, output, mixdeconv, uncond, refs, "", sets, sample_reports, dynamicAT, staticAT, minimum_snps, A1_threshold, A2_threshold, NA, NA, NA, NA, NA, NA, FALSE, FALSE, snpset, pcagroupcat) + create_config(date, FALSE, "1000G_global", NA, NA, refpath, sample_manifest, sample, replicate, out_path, mixdeconv, uncond, refs, "", sets, sample_reports, dynamicAT, staticAT, minimum_snps, A1_threshold, A2_threshold, NA, NA, NA, NA, NA, NA, FALSE, FALSE, snpset, pcagroupcat, "kintelligence", NULL) if (isTruthy(sample_manifest)) { ## sample manifest; loop through each sample manifest=suppressWarnings(euroformix::tableReader(sample_manifest)) @@ -75,12 +84,11 @@ run_mixder_ancestry = function(sample_manifest=NULL, sample=NULL, replicate=NULL replicate_id = ifelse(is.na(manifest[i, 2]), "", manifest[i, 2]) message(glue("Sample ID: {id}")) message(glue("Replicate ID: {replicate_id}")) - run_workflow(date, id, replicate_id, FALSE, "1000G_global", NA, NA, refData, refpath, output, mixdeconv, uncond, refs, "", sets, sample_reports, dynamicAT, staticAT, minimum_snps, A1_threshold, A2_threshold, NA, NA, NA, NA, NA, NA, minor_contrib_threshold, keep_bins, FALSE, FALSE, snpset, pcagroupcat) + run_workflow(date, id, replicate_id, FALSE, popFreq, refData, refpath, out_path, mixdeconv, uncond, refs, "", sets, sample_reports, dynamicAT, staticAT, minimum_snps, A1_threshold, A2_threshold, NA, NA, NA, NA, NA, NA, minor_contrib_threshold, keep_bins, FALSE, FALSE, snpset, pcagroupcat, "kintelligence", snp_positions, threads, parallel) } } else if (isTruthy(sample)){ - replicate_id = ifelse(isTruthy(replicate), replicate, "") message(glue("Sample ID: {sample}")) - message(glue("Replicate ID: {replicate_id}")) - run_workflow(date, sample, replicate_id, FALSE, "1000G_global", NA, NA, refData, refpath, output, mixdeconv, uncond, refs, "", sets, sample_reports, dynamicAT, staticAT, minimum_snps, A1_threshold, A2_threshold, NA, NA, NA, NA, NA, NA, minor_contrib_threshold, keep_bins, FALSE, FALSE, snpset, pcagroupcat) + message(glue("Replicate ID: {replicate}")) + run_workflow(date, sample, replicate, FALSE, popFreq, refData, refpath, out_path, mixdeconv, uncond, refs, "", sets, sample_reports, dynamicAT, staticAT, minimum_snps, A1_threshold, A2_threshold, NA, NA, NA, NA, NA, NA, minor_contrib_threshold, keep_bins, FALSE, FALSE, snpset, pcagroupcat, "kintelligence", snp_positions, threads, parallel) } } diff --git a/R/run_mixder_metrics.R b/R/run_mixder_metrics.R index aff5bce..9b4e6e0 100644 --- a/R/run_mixder_metrics.R +++ b/R/run_mixder_metrics.R @@ -37,21 +37,35 @@ #' @param filter_missing Whether to remove SNPs with a missing value for the allele 2 (default=FALSE) #' @param minor_contrib_threshold Whether to apply the allele 1 probability threshold to the minor contributor, regardless of the minimum number of SNPs (default=FALSE) #' @param keep_bins Use existing binned SNP data, if exists (default=TRUE) +#' @param threads number of threads for EFM (default=0) +#' @param parallel parallelize EFM sets, will detect number of cores on machine and use all available (default=FALSE) #' #' @export #' -run_mixder_metrics = function(sample_manifest=NULL, sample=NULL, replicate=NULL, sample_reports = getwd(), output = "output", twofreqs=FALSE, freq_both="global_1000g", freq_major=NULL, freq_minor=NULL, refpath=NULL, refs=NULL, mixdeconv=TRUE, uncond=TRUE, cond=FALSE, sets=10, dynamicAT=0.015, staticAT=10, minimum_snps=6000, A1min=0.95, A1max=0.99, A2min=0.5, A2max=0.65, major=NULL, minor=NULL, filter_missing=FALSE, minor_contrib_threshold=FALSE, keep_bins=TRUE) { +run_mixder_metrics = function(sample_manifest=NULL, sample=NULL, replicate="", sample_reports = getwd(), output = "output", assay="kintelligence", twofreqs=FALSE, freq_both="global_1000g", freq_major=NULL, freq_minor=NULL, refpath=NULL, refs=NULL, mixdeconv=TRUE, uncond=TRUE, cond=FALSE, sets=10, dynamicAT=0.015, staticAT=10, minimum_snps=6000, A1min=0.95, A1max=0.99, A2min=0.5, A2max=0.65, major=NULL, minor=NULL, filter_missing=FALSE, minor_contrib_threshold=FALSE, keep_bins=TRUE, threads=0, parallel=FALSE) { date = glue("{Sys.Date()}_{format(Sys.time(), '%H_%M_%S')}") + out_path = glue("{sample_reports}/snp_sets/{output}/") if (!isTruthy(major) | !isTruthy(minor)) { stop("Major and/or minor contributor not specified. Please rerun.") } + popFreq = load_freq(twofreqs, freq_both, freq_major, freq_minor) ## load in references + print("loading references") if (isTruthy(refpath)) { - print("loading references") - if (!file.exists(glue("{refpath}/EFM_references.csv"))) { - refData = euroformix::sample_tableToList(data.frame(processing_ref_sample_reports(refpath))) + if (cond) { + if (file.exists(glue("{refpath}/EFM_references.rda"))) { + load(glue("{refpath}/EFM_references.rda")) + } else { + if (!file.exists(glue("{refpath}/EFM_references.csv"))) { + refData = convert_table_to_list(data.frame(processing_ref_sample_reports(refpath))) + } else { + refsdf = data.frame(fread(glue("{refpath}/EFM_references.csv"))) + refData = convert_table_to_list(refsdf) + } + save(refData, file=glue("{refpath}/EFM_references.rda")) + } } else { - refData = euroformix::sample_tableToList(euroformix::tableReader(glue("{refpath}/EFM_references.csv"))) + refData = NULL } } else { stop("No references provided. Please re-run!") @@ -60,7 +74,7 @@ run_mixder_metrics = function(sample_manifest=NULL, sample=NULL, replicate=NULL, if (!isTruthy(sample_manifest) & !isTruthy(sample)){ stop("Please provide sample manifest or sample ID!") } - create_config(date, twofreqs, freq_both, freq_major, freq_minor, refpath, sample_manifest, sample, replicate, output, mixdeconv, uncond, refs, "Calculate Metrics", sets, sample_reports, dynamicAT, staticAT, minimum_snps, NA, NA, A1min, A1max, A2min, A2max, major, minor, filter_missing, TRUE, NA, NA) + create_config(date, twofreqs, freq_both, freq_major, freq_minor, refpath, sample_manifest, sample, replicate, out_path, mixdeconv, uncond, refs, "Calculate Metrics", sets, sample_reports, dynamicAT, staticAT, minimum_snps, NA, NA, A1min, A1max, A2min, A2max, major, minor, filter_missing, TRUE, NA, NA, assay, NULL) if (isTruthy(sample_manifest)) { manifest=suppressWarnings(euroformix::tableReader(sample_manifest)) for (i in nrow(manifest)) { @@ -68,12 +82,11 @@ run_mixder_metrics = function(sample_manifest=NULL, sample=NULL, replicate=NULL, replicate_id = ifelse(is.na(manifest[i, 2]), "", manifest[i, 2]) message(glue("Sample ID: {id}")) message(glue("Replicate ID: {replicate_id}")) - run_workflow(date, id, replicate_id, twofreqs, freq_both, freq_major, freq_minor, refData, refpath, output, mixdeconv, uncond, refs, "Calculate Metrics", sets, sample_reports, dynamicAT, staticAT, minimum_snps, NA, NA, A1min, A1max, A2min, A2max, major, minor, minor_contrib_threshold, keep_bins, filter_missing, TRUE, NA, NA) + run_workflow(date, id, replicate_id, twofreqs, popFreq, refData, refpath, out_path, mixdeconv, uncond, refs, "Calculate Metrics", sets, sample_reports, dynamicAT, staticAT, minimum_snps, NA, NA, A1min, A1max, A2min, A2max, major, minor, minor_contrib_threshold, keep_bins, filter_missing, TRUE, NA, NA, assay, NULL, threads, parallel) } } else if (isTruthy(sample)){ - replicate_id = ifelse(isTruthy(replicate), replicate, "") message(glue("Sample ID: {sample}")) - message(glue("Replicate ID: {replicate_id}")) - run_workflow(date, sample, replicate_id, twofreqs, freq_both, freq_major, freq_minor, refData, refpath, output, mixdeconv, uncond, refs, "Calculate Metrics", sets, sample_reports, dynamicAT, staticAT, minimum_snps, NA, NA, A1min, A1max, A2min, A2max, major, minor, minor_contrib_threshold, keep_bins, filter_missing, TRUE, NA, NA) + message(glue("Replicate ID: {replicate}")) + run_workflow(date, sample, replicate, twofreqs, popFreq, refData, refpath, out_path, mixdeconv, uncond, refs, "Calculate Metrics", sets, sample_reports, dynamicAT, staticAT, minimum_snps, NA, NA, A1min, A1max, A2min, A2max, major, minor, minor_contrib_threshold, keep_bins, filter_missing, TRUE, NA, NA, assay, NULL, threads, parallel) } } diff --git a/R/run_mixder_report.R b/R/run_mixder_report.R index 3b5c0ab..80c5255 100644 --- a/R/run_mixder_report.R +++ b/R/run_mixder_report.R @@ -15,6 +15,8 @@ #' @param replicate If running individual sample along with a replicate, specify the replicate ID; requires sample_manifest=NULL (default=NULL) #' @param sample_reports Path to directory of Sample Reports or CSV file of mixture genotypes (default=current directory) #' @param output name of output directory, will be outputted into the sample reports directory (default="output") +#' @param assay name of sequencing assay, either `kintelligence` or `custom` (default=kintelligence) +#' @param snp_pos Path to file containing SNP positions of custom assay, required if assay==`custom` (default=NULL) #' @param twofreqs TRUE if using different allele frequency data for each contributor (default=FALSE) #' @param freq_both frequency data for both contributors; requires twofreqs=FALSE (default=1000G global) #' @param freq_major frequency data for major contributor; requires twofreqs=TRUE (default=NULL) @@ -33,18 +35,39 @@ #' @param filter_missing Whether to remove SNPs with a missing value for the allele 2 (default=FALSE) #' @param minor_contrib_threshold Whether to apply the allele 1 probability threshold to the minor contributor, regardless of the minimum number of SNPs (default=FALSE) #' @param keep_bins Use existing binned SNP data, if exists (default=TRUE) +#' @param threads number of threads for EFM (default=0) +#' @param parallel parallelize EFM sets, will detect number of cores on machine and use all available (default=FALSE) #' #' @export #' -run_mixder_report = function(sample_manifest=NULL, sample=NULL, replicate=NULL, sample_reports = getwd(), output = "output", twofreqs=FALSE, freq_both="global_1000g", freq_major=NULL, freq_minor=NULL, refpath=NULL, refs=NULL, mixdeconv=TRUE, uncond=TRUE, cond=FALSE, sets=10, dynamicAT=0.015, staticAT=10, minimum_snps=6000, A1_threshold=0.99, A2_threshold=0.6, filter_missing=FALSE, minor_contrib_threshold=FALSE, keep_bins=TRUE) { +run_mixder_report = function(sample_manifest=NULL, sample=NULL, replicate="", sample_reports = getwd(), output = "output", assay="kintelligence", snp_pos=NULL, twofreqs=FALSE, freq_both="global_1000g", freq_major=NULL, freq_minor=NULL, refpath=NULL, refs=NULL, mixdeconv=TRUE, uncond=TRUE, cond=FALSE, sets=10, dynamicAT=0.015, staticAT=10, minimum_snps=6000, A1_threshold=0.99, A2_threshold=0.6, filter_missing=FALSE, minor_contrib_threshold=FALSE, keep_bins=TRUE, threads=0, parallel=FALSE) { date = glue("{Sys.Date()}_{format(Sys.time(), '%H_%M_%S')}") + out_path = glue("{sample_reports}/snp_sets/{output}/") + popFreq = load_freq(twofreqs, freq_both, freq_major, freq_minor) + if (tolower(assay)=="kintelligence") { + snp_positions = mixder::kintelligence_snp_positions + } else if (tolower(assay)=="custom") { + if (file.exists(snp_pos)) { + snp_positions = euroformix::tableReader(snp_pos) + } else { + stop("Custom assay specified but no SNP positions file provided.") + } + } else { + stop("Problem with assay name. Please specify either 'kintelligence' or 'custom'.") + } ## load in references if (isTruthy(refpath)) { print("loading references") - if (!file.exists(glue("{refpath}/EFM_references.csv"))) { - refData = euroformix::sample_tableToList(data.frame(processing_ref_sample_reports(refpath))) + if (file.exists(glue("{refpath}/EFM_references.rda"))) { + load(glue("{refpath}/EFM_references.rda")) } else { - refData = euroformix::sample_tableToList(euroformix::tableReader(glue("{refpath}/EFM_references.csv"))) + if (!file.exists(glue("{refpath}/EFM_references.csv"))) { + refData = convert_table_to_list(data.frame(processing_ref_sample_reports(refpath))) + } else { + refsdf = data.frame(fread(glue("{refpath}/EFM_references.csv"))) + refData = convert_table_to_list(refsdf) + } + save(refData, file=glue("{refpath}/EFM_references.rda")) } } else if (cond) { stop("No references provided but selected conditioned analyses. Please re-run!") @@ -54,7 +77,7 @@ run_mixder_report = function(sample_manifest=NULL, sample=NULL, replicate=NULL, if (!isTruthy(sample_manifest) & !isTruthy(sample)){ stop("Please provide sample manifest or sample ID!") } - create_config(date, twofreqs, freq_both, freq_major, freq_minor, refpath, sample_manifest, sample, replicate, output, mixdeconv, uncond, refs, "Create GEDmatch PRO Report", sets, sample_reports, dynamicAT, staticAT, minimum_snps, A1_threshold, A2_threshold, NA, NA, NA, NA, NA, NA, filter_missing, TRUE, NA, NA) + create_config(date, twofreqs, freq_both, freq_major, freq_minor, refpath, sample_manifest, sample, replicate, out_path, mixdeconv, uncond, refs, "Create GEDmatch PRO Report", sets, sample_reports, dynamicAT, staticAT, minimum_snps, A1_threshold, A2_threshold, NA, NA, NA, NA, NA, NA, filter_missing, TRUE, NA, NA, assay, snp_pos) if (isTruthy(sample_manifest)) { manifest=suppressWarnings(euroformix::tableReader(sample_manifest)) for (i in nrow(manifest)) { @@ -62,12 +85,11 @@ run_mixder_report = function(sample_manifest=NULL, sample=NULL, replicate=NULL, replicate_id = ifelse(is.na(manifest[i, 2]), "", manifest[i, 2]) message(glue("Sample ID: {id}")) message(glue("Replicate ID: {replicate_id}")) - run_workflow(date, id, replicate_id, twofreqs, freq_both, freq_major, freq_minor, refData, refpath, output, mixdeconv, uncond, refs, "Create GEDmatch PRO Report", sets, sample_reports, dynamicAT, staticAT, minimum_snps, A1_threshold, A2_threshold, NA, NA, NA, NA, NA, NA, minor_contrib_threshold, keep_bins, filter_missing, TRUE, NA, NA) + run_workflow(date, id, replicate_id, twofreqs, popFreq, refData, refpath, out_path, mixdeconv, uncond, refs, "Create GEDmatch PRO Report", sets, sample_reports, dynamicAT, staticAT, minimum_snps, A1_threshold, A2_threshold, NA, NA, NA, NA, NA, NA, minor_contrib_threshold, keep_bins, filter_missing, TRUE, NA, NA, assay, snp_positions, threads, parallel) } } else if (isTruthy(sample)){ - replicate_id = ifelse(isTruthy(replicate), replicate, "") message(glue("Sample ID: {sample}")) - message(glue("Replicate ID: {replicate_id}")) - run_workflow(date, sample, replicate_id, twofreqs, freq_both, freq_major, freq_minor, refData, refpath, output, mixdeconv, uncond, refs, "Create GEDmatch PRO Report", sets, sample_reports, dynamicAT, staticAT, minimum_snps, A1_threshold, A2_threshold, NA, NA, NA, NA, NA, NA, minor_contrib_threshold, keep_bins, filter_missing, TRUE, NA, NA) + message(glue("Replicate ID: {replicate}")) + run_workflow(date, sample, replicate, twofreqs, popFreq, refData, refpath, out_path, mixdeconv, uncond, refs, "Create GEDmatch PRO Report", sets, sample_reports, dynamicAT, staticAT, minimum_snps, A1_threshold, A2_threshold, NA, NA, NA, NA, NA, NA, minor_contrib_threshold, keep_bins, filter_missing, TRUE, NA, NA, assay, snp_positions, threads, parallel) } } diff --git a/R/run_workflow.R b/R/run_workflow.R index 50e6a10..c4d515d 100644 --- a/R/run_workflow.R +++ b/R/run_workflow.R @@ -16,9 +16,7 @@ #' @param id Sample ID #' @param replicate_id Sample ID of replicate, if specified #' @param twofreqs TRUE if using separate AF data for major and minor contributors -#' @param freq_both Path (or name) of allele frequency data if using same data for both -#' @param freq_major Path (or name) of allele frequency data for major contributor -#' @param freq_minor Path (or name) of allele frequency data for minor contributor +#' @param popFreq List of properly formatted allele frequency data #' @param refData Reference data (if available) #' @param refs Path of reference genotype(s) file #' @param output Name of output directory @@ -45,6 +43,9 @@ #' @param skipancestry TRUE/FALSE whether to skip ancestry prediction #' @param ancestrysnps SNPs to use for ancestry prediction (either ancestry only or all SNPs) #' @param pcagroups How to color PCA plots (superpopulations and/or subpopulations) +#' @param assay assay used (kintelligence or custom) +#' @param positions SNP chromosomal positions +#' @param threads number of threads to use for EFM #' #' @export #' @@ -53,8 +54,7 @@ #'@importFrom utils write.table write.csv read.table #'@importFrom grDevices dev.off png #'@importFrom methods show -run_workflow = function(date, id, replicate_id, twofreqs, freq_both, freq_major, freq_minor, refData, refs, output, run_mixdeconv, unconditioned, cond, method, sets, kinpath, dynamicAT, staticAT, minimum_snps, A1_threshold, A2_threshold, A1min, A1max, A2min, A2max, major, minor, minor_threshold, keep_bins, filter_missing, skipancestry, ancestrysnps, pcagroups) { - out_path = glue("{kinpath}/snp_sets/{output}/") +run_workflow = function(date, id, replicate_id, twofreqs, popFreq, refData, refs, out_path, run_mixdeconv, unconditioned, cond, method, sets, kinpath, dynamicAT, staticAT, minimum_snps, A1_threshold, A2_threshold, A1min, A1max, A2min, A2max, major, minor, minor_threshold, keep_bins, filter_missing, skipancestry, ancestrysnps, pcagroups, assay, positions, threads=0, parallel=FALSE) { if (replicate_id == "") { logfile = file(glue("{out_path}config_log_files/{date}/run_log_{id}_{date}.txt"), open = "wt") } else { @@ -75,17 +75,20 @@ run_workflow = function(date, id, replicate_id, twofreqs, freq_both, freq_major, message(glue("Replicate Sample: {replicate_id}
")) ## run EFM if (run_mixdeconv | !skipancestry) { - message("Loading Frequency Data
") - popFreq = load_freq(out_path, twofreqs, freq_both, freq_major, freq_minor) - attable = process_kinreport(id, replicate_id, kinpath, dynamicAT, staticAT) + if (dynamicAT != 0) { + message("creating AT table
") + attable = process_kinreport(id, replicate_id, kinpath, dynamicAT, staticAT) + } else { + attable = staticAT + } if (!skipancestry) { - efm_results_major = run_efm(date, popFreq[[1]], refData, id, replicate_id, kinpath, out_path, attable, sets, skipancestry, cond, uncond=unconditioned, keep_bins) + efm_results_major = run_efm(date, popFreq[[1]], refData, id, replicate_id, kinpath, out_path, attable, sets, skipancestry, assay, cond, uncond=unconditioned, keep_bins, threads=threads, parallel=parallel) efm_results_minor = efm_results_major } else if (twofreqs) { - efm_results_major = run_efm(date, popFreq[[1]], refData, id, replicate_id, kinpath, out_path, attable, sets, skipancestry, cond, uncond=unconditioned, keep_bins) - efm_results_minor = run_efm(date, popFreq[[2]], refData, id, replicate_id, kinpath, out_path, attable, sets, skipancestry, cond, uncond=unconditioned, keep_bins) + efm_results_major = run_efm(date, popFreq[[1]], refData, id, replicate_id, kinpath, out_path, attable, sets, skipancestry, assay, cond, uncond=unconditioned, keep_bins, threads=threads, parallel=parallel) + efm_results_minor = run_efm(date, popFreq[[2]], refData, id, replicate_id, kinpath, out_path, attable, sets, skipancestry, assay, cond, uncond=unconditioned, keep_bins, threads=threads, parallel=parallel) } else { - efm_results_major = run_efm(date, popFreq[[1]], refData, id, replicate_id, kinpath, out_path, attable, sets, skipancestry, cond, uncond=unconditioned, keep_bins) + efm_results_major = run_efm(date, popFreq[[1]], refData, id, replicate_id, kinpath, out_path, attable, sets, skipancestry, assay, cond, uncond=unconditioned, keep_bins, threads=threads, parallel=parallel) efm_results_minor = efm_results_major } } @@ -126,21 +129,21 @@ run_workflow = function(date, id, replicate_id, twofreqs, freq_both, freq_major, uncond_filename_major = glue("{write_path}/{id}/unconditioned/{id}_efm_output_unconditioned_major.tsv") uncond_filename_minor = glue("{write_path}/{id}/unconditioned/{id}_efm_output_unconditioned_minor.tsv") if (file.exists(uncond_filename_major)) { - uncond_table_major = read.table(uncond_filename_major, header=T, sep="\t") + uncond_table_major = fread(uncond_filename_major, header=T, sep="\t") } else { stop(glue("{uncond_filename_major} does not exist. You may need to run EFM or check the correct SNP file input folder and Output folder are correct!")) } if (file.exists(uncond_filename_minor)) { - uncond_table_minor = read.table(uncond_filename_minor, header=T, sep="\t") + uncond_table_minor = fread(uncond_filename_minor, header=T, sep="\t") } else { stop(glue("{uncond_filename_minor} does not exist. You may need to run EFM or check the correct SNP file input folder and Output folder are correct!")) } } if (method == "Create GEDmatch PRO Report" | !skipancestry) { message("Creating GEDmatch PRO report for major contributor in unconditioned analysis.
") - major_report = create_gedmatchpro_report(write_path, uncond_table_major, major_c, "major", minimum_snps, A1_threshold, A2_threshold, A1min, A1max, A2min, A2max, minor_threshold, filter_missing) + major_report = create_gedmatchpro_report(write_path, uncond_table_major, major_c, "major", minimum_snps, A1_threshold, A2_threshold, A1min, A1max, A2min, A2max, minor_threshold, filter_missing, positions) message("Creating GEDmatch PRO report for minor contributor in unconditioned analysis.
") - minor_report = create_gedmatchpro_report(write_path, uncond_table_minor, "C2", "minor", minimum_snps, A1_threshold, A2_threshold, A1min, A1max, A2min, A2max, minor_threshold, filter_missing) + minor_report = create_gedmatchpro_report(write_path, uncond_table_minor, "C2", "minor", minimum_snps, A1_threshold, A2_threshold, A1min, A1max, A2min, A2max, minor_threshold, filter_missing, positions) if (method == "Create GEDmatch PRO Report") { write.table(major_report[[1]], glue("{write_path}/GEDMatchPROReports/{id}_uncond_major_{type}_GEDmatchPROReport.txt"), col.names=T, sep="\t", row.names=F, quote=F) write.csv(major_report[[2]], glue("{write_path}/GEDMatchPROReports/Metrics/{id}_uncond_major_{type}_GEDmatchPROReport_Metrics.csv"), row.names=F, quote=F) @@ -158,24 +161,24 @@ run_workflow = function(date, id, replicate_id, twofreqs, freq_both, freq_major, write.table(minor_report[[1]], glue("{write_path}/{id}/unconditioned/{id}_uncond_minor_{type}_Inferred_Genotypes.txt"), col.names=T, sep="\t", row.names=F, quote=F) ancestry_prediction(minor_report[[1]], glue("{write_path}/{id}/unconditioned/"), id, "unconditioned", "minor", ancestrysnps, pcagroups) } - major_report = create_gedmatchpro_report(write_path, uncond_table_major, major_c, "major", minimum_snps, A1_threshold, A2_threshold, A1min, A1max, A2min, A2max, minor_threshold, filter_missing) + major_report = create_gedmatchpro_report(write_path, uncond_table_major, major_c, "major", minimum_snps, A1_threshold, A2_threshold, A1min, A1max, A2min, A2max, minor_threshold, filter_missing, positions) write.table(major_report[[1]], glue("{write_path}/GEDMatchPROReports/{id}_uncond_major_{type}_GEDmatchPROReport.txt"), col.names=T, sep="\t", row.names=F, quote=F) write.csv(major_report[[2]], glue("{write_path}/GEDMatchPROReports/Metrics/{id}_uncond_major_{type}_GEDmatchPROReport_Metrics.csv"), row.names=F, quote=F) png(glue("{write_path}/GEDMatchPROReports/Metrics/{id}_uncond_major_{type}_GEDmatchPROReport_Allele1_Probabilities_Density_Plot.png")) show(major_report[[3]]) dev.off() message("Creating GEDmatch PRO report for minor contributor in unconditioned analysis.
") - minor_report = create_gedmatchpro_report(write_path, uncond_table_minor, minor_c, "minor", minimum_snps, A1_threshold, A2_threshold, A1min, A1max, A2min, A2max, minor_threshold, filter_missing) + minor_report = create_gedmatchpro_report(write_path, uncond_table_minor, minor_c, "minor", minimum_snps, A1_threshold, A2_threshold, A1min, A1max, A2min, A2max, minor_threshold, filter_missing, positions) write.table(minor_report[[1]], glue("{write_path}/GEDMatchPROReports/{id}_uncond_minor_{type}_GEDmatchPROReport.txt"), col.names=T, sep="\t", row.names=F, quote=F) write.csv(minor_report[[2]], glue("{write_path}/GEDMatchPROReports/Metrics/{id}_uncond_minor_{type}_GEDmatchPROReport_metrics.csv"), row.names=F, quote=F) png(glue("{write_path}/GEDMatchPROReports/Metrics/{id}_uncond_minor_{type}_GEDmatchPROReport_Allele1_Probabilities_Density_Plot.png")) show(minor_report[[3]]) dev.off() } else if (method == "Calculate Metrics") { - major_ref = format_ref(refData, major, refs) + major_ref = format_ref(major, refs) major_tables = suppressWarnings(process_efm_files(uncond_table_major, major_c, major_ref, minimum_snps, A1min, A1max, A2min, A2max, metrics=TRUE, filter_missing)) write_tables(major_tables, glue("{write_path}/{id}/unconditioned/{major}"), minimum_snps) - minor_ref = format_ref(refData, minor, refs) + minor_ref = format_ref(minor, refs) minor_tables = suppressWarnings(process_efm_files(uncond_table_minor, minor_c, minor_ref, minimum_snps, A1min, A1max, A2min, A2max, metrics=TRUE, filter_missing)) write_tables(minor_tables, glue("{write_path}/{id}/unconditioned/{minor}"), minimum_snps) } @@ -212,7 +215,7 @@ run_workflow = function(date, id, replicate_id, twofreqs, freq_both, freq_major, } if (method == "Create GEDmatch PRO Report" | !skipancestry) { message(glue("Creating GEDmatch PRO report for {contrib_status} contributor conditioned on {cond_on} in conditioned analysis.
")) - cond_report = create_gedmatchpro_report(write_path, get(glue("efm_table_{contrib_status}")), "C2", contrib_status, minimum_snps, A1_threshold, A2_threshold, A1min, A1max, A2min, A2max, minor_threshold, filter_missing) + cond_report = create_gedmatchpro_report(write_path, get(glue("efm_table_{contrib_status}")), "C2", contrib_status, minimum_snps, A1_threshold, A2_threshold, A1min, A1max, A2min, A2max, minor_threshold, filter_missing, positions) if (method == "Create GEDmatch PRO Report") { write.table(cond_report[[1]], glue("{write_path}/GEDMatchPROReports/{id}_{contrib_status}_contrib_conditioned_on_{cond_on}_{type}_GEDmatchPROReport.txt"), col.names=T, sep="\t", row.names=F, quote=F) write.csv(cond_report[[2]], glue("{write_path}/GEDMatchPROReports/Metrics/{id}_{contrib_status}_contrib_conditioned_on_{cond_on}_{type}_GEDmatchPROReport_Metrics.csv"), row.names=F, quote=F) @@ -225,7 +228,7 @@ run_workflow = function(date, id, replicate_id, twofreqs, freq_both, freq_major, } } else if (method == "Calculate Metrics") { unk = ifelse(contrib_status == "major", major, minor) - ref = format_ref(refData, unk, refs) + ref = format_ref(unk, refs) geno_correct_tables = suppressWarnings(process_efm_files(get(glue("efm_table_{contrib_status}")), "C2", ref, minimum_snps, A1min, A1max, A2min, A2max, metrics=TRUE, filter_missing)) write_tables(geno_correct_tables, glue("{write_path}/{id}/conditioned/{unk}"), minimum_snps) } diff --git a/README.Rmd b/README.Rmd index 8dd2021..04ecd6e 100644 --- a/README.Rmd +++ b/README.Rmd @@ -13,13 +13,13 @@ knitr::opts_chunk$set( ) ``` -# MixDeR - Current Version: 0.9.0 +# MixDeR - Current Version: 0.10.0 -MixDeR (**Mix**ture **De**convolution in **R**) is a workflow (with a Shiny app) for performing mixture deconvolution of ForenSeq Kintelligence SNP data for two-person mixtures using [EuroForMix](https://github.com/oyvble/euroformix/) and creating GEDmatch PRO reports for the individual contributor SNP profiles. +MixDeR (**Mix**ture **De**convolution in **R**) is a workflow (with a Shiny app) for performing mixture deconvolution of genotypes developed from either the ForenSeq Kintelligence assay or a custom SNP panel. MixDeR performs the deconvolution of two-contributor mixtures using [EuroForMix](https://github.com/oyvble/euroformix/), ultimately developing single source GEDmatch PRO/FamilyTreeDNA reports of the individual contributor genotypes. This method requires extensive validation of the settings. MixDeR provides the option of calculating various metrics for evaluating the accuracy of the deduced SNP genotypes. This is extremely useful when determining settings, specifically the allele 1 probability threshold, the allele 2 probability threshold, and the minimum number of SNPs. @@ -44,6 +44,6 @@ library(mixder) mixder() ``` -To use the command line, please see the CLI page in the MixDeR documentation. +To use the command line, please see the R API page in the MixDeR documentation. Please see [MixDeR documentation](bioforensics.github.io/mixder/) for further information. diff --git a/README.md b/README.md index c87ee24..9437c97 100644 --- a/README.md +++ b/README.md @@ -1,17 +1,19 @@ -# MixDeR - Current Version: 0.9.0 +# MixDeR - Current Version: 0.10.0 MixDeR (**Mix**ture **De**convolution in **R**) is a workflow (with a -Shiny app) for performing mixture deconvolution of ForenSeq -Kintelligence SNP data for two-person mixtures using -[EuroForMix](https://github.com/oyvble/euroformix/) and creating -GEDmatch PRO reports for the individual contributor SNP profiles. +Shiny app) for performing mixture deconvolution of genotypes developed +from either the ForenSeq Kintelligence assay or a custom SNP panel. +MixDeR performs the deconvolution of two-contributor mixtures using +[EuroForMix](https://github.com/oyvble/euroformix/), ultimately +developing single source GEDmatch PRO/FamilyTreeDNA reports of the +individual contributor genotypes. This method requires extensive validation of the settings. MixDeR provides the option of calculating various metrics for evaluating the @@ -43,7 +45,7 @@ To launch the shiny app: library(mixder) mixder() -To use the command line, please see the CLI page in the MixDeR +To use the command line, please see the R API page in the MixDeR documentation. Please see [MixDeR documentation](bioforensics.github.io/mixder/) for diff --git a/_pkgdown.yml b/_pkgdown.yml index 92d28ef..3ed0337 100644 --- a/_pkgdown.yml +++ b/_pkgdown.yml @@ -10,6 +10,7 @@ articles: - Installation - Required_Files - Running_MixDecon + - Custom_SNPs - Ancestry_Prediction_Tool - Valmetrics - gedmatch diff --git a/docs/404.html b/docs/404.html index 0bf73ce..7f05c11 100644 --- a/docs/404.html +++ b/docs/404.html @@ -20,7 +20,7 @@ mixder - 0.9.0 + 0.10.0 '),expand.on("click",(function(n){return e.toggleCurrent(t),n.stopPropagation(),!1})),t.prepend(expand)}))},reset:function(){var n=encodeURI(window.location.hash)||"#";try{var e=$(".wy-menu-vertical"),t=e.find('[href="'+n+'"]');if(0===t.length){var i=$('.document [id="'+n.substring(1)+'"]').closest("div.section");0===(t=e.find('[href="#'+i.attr("id")+'"]')).length&&(t=e.find('[href="#"]'))}if(t.length>0){$(".wy-menu-vertical .current").removeClass("current").attr("aria-expanded","false"),t.addClass("current").attr("aria-expanded","true"),t.closest("li.toctree-l1").parent().addClass("current").attr("aria-expanded","true");for(let n=1;n<=10;n++)t.closest("li.toctree-l"+n).addClass("current").attr("aria-expanded","true");t[0].scrollIntoView()}}catch(n){console.log("Error expanding nav for anchor",n)}},onScroll:function(){this.winScroll=!1;var n=this.win.scrollTop(),e=n+this.winHeight,t=this.navBar.scrollTop()+(n-this.winPosition);n<0||e>this.docHeight||(this.navBar.scrollTop(t),this.winPosition=n)},onResize:function(){this.winResize=!1,this.winHeight=this.win.height(),this.docHeight=$(document).height()},hashChange:function(){this.linkScroll=!0,this.win.one("hashchange",(function(){this.linkScroll=!1}))},toggleCurrent:function(n){var e=n.closest("li");e.siblings("li.current").removeClass("current").attr("aria-expanded","false"),e.siblings().find("li.current").removeClass("current").attr("aria-expanded","false");var t=e.find("> ul li");t.length&&(t.removeClass("current").attr("aria-expanded","false"),e.toggleClass("current").attr("aria-expanded",(function(n,e){return"true"==e?"false":"true"})))}},"undefined"!=typeof window&&(window.SphinxRtdTheme={Navigation:n.exports.ThemeNav,StickyNav:n.exports.ThemeNav}),function(){for(var n=0,e=["ms","moz","webkit","o"],t=0;t a.language.name.localeCompare(b.language.name)); - - const languagesHTML = ` -
-
Languages
- ${languages - .map( - (translation) => ` -
- ${translation.language.code} -
- `, - ) - .join("\n")} -
- `; - return languagesHTML; - } - - function renderVersions(config) { - if (!config.versions.active.length) { - return ""; - } - const versionsHTML = ` -
-
Versions
- ${config.versions.active - .map( - (version) => ` -
- ${version.slug} -
- `, - ) - .join("\n")} -
- `; - return versionsHTML; - } - - function renderDownloads(config) { - if (!Object.keys(config.versions.current.downloads).length) { - return ""; - } - const downloadsNameDisplay = { - pdf: "PDF", - epub: "Epub", - htmlzip: "HTML", - }; - - const downloadsHTML = ` -
-
Downloads
- ${Object.entries(config.versions.current.downloads) - .map( - ([name, url]) => ` -
- ${downloadsNameDisplay[name]} -
- `, - ) - .join("\n")} -
- `; - return downloadsHTML; - } - - document.addEventListener("readthedocs-addons-data-ready", function (event) { - const config = event.detail.data(); - - const flyout = ` -
- - Read the Docs - v: ${config.versions.current.slug} - - -
-
- ${renderLanguages(config)} - ${renderVersions(config)} - ${renderDownloads(config)} -
-
On Read the Docs
-
- Project Home -
-
- Builds -
-
- Downloads -
-
-
-
Search
-
-
- -
-
-
-
- - Hosted by Read the Docs - -
-
- `; - - // Inject the generated flyout into the body HTML element. - document.body.insertAdjacentHTML("beforeend", flyout); - - // Trigger the Read the Docs Addons Search modal when clicking on the "Search docs" input from inside the flyout. - document - .querySelector("#flyout-search-form") - .addEventListener("focusin", () => { - const event = new CustomEvent("readthedocs-search-show"); - document.dispatchEvent(event); - }); - }) -} - -if (themeLanguageSelector || themeVersionSelector) { - function onSelectorSwitch(event) { - const option = event.target.selectedIndex; - const item = event.target.options[option]; - window.location.href = item.dataset.url; - } - - document.addEventListener("readthedocs-addons-data-ready", function (event) { - const config = event.detail.data(); - - const versionSwitch = document.querySelector( - "div.switch-menus > div.version-switch", - ); - if (themeVersionSelector) { - let versions = config.versions.active; - if (config.versions.current.hidden || config.versions.current.type === "external") { - versions.unshift(config.versions.current); - } - const versionSelect = ` - - `; - - versionSwitch.innerHTML = versionSelect; - versionSwitch.firstElementChild.addEventListener("change", onSelectorSwitch); - } - - const languageSwitch = document.querySelector( - "div.switch-menus > div.language-switch", - ); - - if (themeLanguageSelector) { - if (config.projects.translations.length) { - // Add the current language to the options on the selector - let languages = config.projects.translations.concat( - config.projects.current, - ); - languages = languages.sort((a, b) => - a.language.name.localeCompare(b.language.name), - ); - - const languageSelect = ` - - `; - - languageSwitch.innerHTML = languageSelect; - languageSwitch.firstElementChild.addEventListener("change", onSelectorSwitch); - } - else { - languageSwitch.remove(); - } - } - }); -} - -document.addEventListener("readthedocs-addons-data-ready", function (event) { - // Trigger the Read the Docs Addons Search modal when clicking on "Search docs" input from the topnav. - document - .querySelector("[role='search'] input") - .addEventListener("focusin", () => { - const event = new CustomEvent("readthedocs-search-show"); - document.dispatchEvent(event); - }); -}); \ No newline at end of file diff --git a/docs/_build/html/_static/language_data.js b/docs/_build/html/_static/language_data.js deleted file mode 100644 index c7fe6c6..0000000 --- a/docs/_build/html/_static/language_data.js +++ /dev/null @@ -1,192 +0,0 @@ -/* - * This script contains the language-specific data used by searchtools.js, - * namely the list of stopwords, stemmer, scorer and splitter. - */ - -var stopwords = ["a", "and", "are", "as", "at", "be", "but", "by", "for", "if", "in", "into", "is", "it", "near", "no", "not", "of", "on", "or", "such", "that", "the", "their", "then", "there", "these", "they", "this", "to", "was", "will", "with"]; - - -/* Non-minified version is copied as a separate JS file, if available */ - -/** - * Porter Stemmer - */ -var Stemmer = function() { - - var step2list = { - ational: 'ate', - tional: 'tion', - enci: 'ence', - anci: 'ance', - izer: 'ize', - bli: 'ble', - alli: 'al', - entli: 'ent', - eli: 'e', - ousli: 'ous', - ization: 'ize', - ation: 'ate', - ator: 'ate', - alism: 'al', - iveness: 'ive', - fulness: 'ful', - ousness: 'ous', - aliti: 'al', - iviti: 'ive', - biliti: 'ble', - logi: 'log' - }; - - var step3list = { - icate: 'ic', - ative: '', - alize: 'al', - iciti: 'ic', - ical: 'ic', - ful: '', - ness: '' - }; - - var c = "[^aeiou]"; // consonant - var v = "[aeiouy]"; // vowel - var C = c + "[^aeiouy]*"; // consonant sequence - var V = v + "[aeiou]*"; // vowel sequence - - var mgr0 = "^(" + C + ")?" + V + C; // [C]VC... is m>0 - var meq1 = "^(" + C + ")?" + V + C + "(" + V + ")?$"; // [C]VC[V] is m=1 - var mgr1 = "^(" + C + ")?" + V + C + V + C; // [C]VCVC... is m>1 - var s_v = "^(" + C + ")?" + v; // vowel in stem - - this.stemWord = function (w) { - var stem; - var suffix; - var firstch; - var origword = w; - - if (w.length < 3) - return w; - - var re; - var re2; - var re3; - var re4; - - firstch = w.substr(0,1); - if (firstch == "y") - w = firstch.toUpperCase() + w.substr(1); - - // Step 1a - re = /^(.+?)(ss|i)es$/; - re2 = /^(.+?)([^s])s$/; - - if (re.test(w)) - w = w.replace(re,"$1$2"); - else if (re2.test(w)) - w = w.replace(re2,"$1$2"); - - // Step 1b - re = /^(.+?)eed$/; - re2 = /^(.+?)(ed|ing)$/; - if (re.test(w)) { - var fp = re.exec(w); - re = new RegExp(mgr0); - if (re.test(fp[1])) { - re = /.$/; - w = w.replace(re,""); - } - } - else if (re2.test(w)) { - var fp = re2.exec(w); - stem = fp[1]; - re2 = new RegExp(s_v); - if (re2.test(stem)) { - w = stem; - re2 = /(at|bl|iz)$/; - re3 = new RegExp("([^aeiouylsz])\\1$"); - re4 = new RegExp("^" + C + v + "[^aeiouwxy]$"); - if (re2.test(w)) - w = w + "e"; - else if (re3.test(w)) { - re = /.$/; - w = w.replace(re,""); - } - else if (re4.test(w)) - w = w + "e"; - } - } - - // Step 1c - re = /^(.+?)y$/; - if (re.test(w)) { - var fp = re.exec(w); - stem = fp[1]; - re = new RegExp(s_v); - if (re.test(stem)) - w = stem + "i"; - } - - // Step 2 - re = /^(.+?)(ational|tional|enci|anci|izer|bli|alli|entli|eli|ousli|ization|ation|ator|alism|iveness|fulness|ousness|aliti|iviti|biliti|logi)$/; - if (re.test(w)) { - var fp = re.exec(w); - stem = fp[1]; - suffix = fp[2]; - re = new RegExp(mgr0); - if (re.test(stem)) - w = stem + step2list[suffix]; - } - - // Step 3 - re = /^(.+?)(icate|ative|alize|iciti|ical|ful|ness)$/; - if (re.test(w)) { - var fp = re.exec(w); - stem = fp[1]; - suffix = fp[2]; - re = new RegExp(mgr0); - if (re.test(stem)) - w = stem + step3list[suffix]; - } - - // Step 4 - re = /^(.+?)(al|ance|ence|er|ic|able|ible|ant|ement|ment|ent|ou|ism|ate|iti|ous|ive|ize)$/; - re2 = /^(.+?)(s|t)(ion)$/; - if (re.test(w)) { - var fp = re.exec(w); - stem = fp[1]; - re = new RegExp(mgr1); - if (re.test(stem)) - w = stem; - } - else if (re2.test(w)) { - var fp = re2.exec(w); - stem = fp[1] + fp[2]; - re2 = new RegExp(mgr1); - if (re2.test(stem)) - w = stem; - } - - // Step 5 - re = /^(.+?)e$/; - if (re.test(w)) { - var fp = re.exec(w); - stem = fp[1]; - re = new RegExp(mgr1); - re2 = new RegExp(meq1); - re3 = new RegExp("^" + C + v + "[^aeiouwxy]$"); - if (re.test(stem) || (re2.test(stem) && !(re3.test(stem)))) - w = stem; - } - re = /ll$/; - re2 = new RegExp(mgr1); - if (re.test(w) && re2.test(w)) { - re = /.$/; - w = w.replace(re,""); - } - - // and turn initial Y back to y - if (firstch == "y") - w = firstch.toLowerCase() + w.substr(1); - return w; - } -} - diff --git a/docs/_build/html/_static/minus.png b/docs/_build/html/_static/minus.png deleted file mode 100644 index d96755f..0000000 Binary files a/docs/_build/html/_static/minus.png and /dev/null differ diff --git a/docs/_build/html/_static/plus.png b/docs/_build/html/_static/plus.png deleted file mode 100644 index 7107cec..0000000 Binary files a/docs/_build/html/_static/plus.png and /dev/null differ diff --git a/docs/_build/html/_static/pygments.css b/docs/_build/html/_static/pygments.css deleted file mode 100644 index 6f8b210..0000000 --- a/docs/_build/html/_static/pygments.css +++ /dev/null @@ -1,75 +0,0 @@ -pre { line-height: 125%; } -td.linenos .normal { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; } -span.linenos { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; } -td.linenos .special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } -span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } -.highlight .hll { background-color: #ffffcc } -.highlight { background: #f8f8f8; } -.highlight .c { color: #3D7B7B; font-style: italic } /* Comment */ -.highlight .err { border: 1px solid #F00 } /* Error */ -.highlight .k { color: #008000; font-weight: bold } /* Keyword */ -.highlight .o { color: #666 } /* Operator */ -.highlight .ch { color: #3D7B7B; font-style: italic } /* Comment.Hashbang */ -.highlight .cm { color: #3D7B7B; font-style: italic } /* Comment.Multiline */ -.highlight .cp { color: #9C6500 } /* Comment.Preproc */ -.highlight .cpf { color: #3D7B7B; font-style: italic } /* Comment.PreprocFile */ -.highlight .c1 { color: #3D7B7B; font-style: italic } /* Comment.Single */ -.highlight .cs { color: #3D7B7B; font-style: italic } /* Comment.Special */ -.highlight .gd { color: #A00000 } /* Generic.Deleted */ -.highlight .ge { font-style: italic } /* Generic.Emph */ -.highlight .ges { font-weight: bold; font-style: italic } /* Generic.EmphStrong */ -.highlight .gr { color: #E40000 } /* Generic.Error */ -.highlight .gh { color: #000080; font-weight: bold } /* Generic.Heading */ -.highlight .gi { color: #008400 } /* Generic.Inserted */ -.highlight .go { color: #717171 } /* Generic.Output */ -.highlight .gp { color: #000080; font-weight: bold } /* Generic.Prompt */ -.highlight .gs { font-weight: bold } /* Generic.Strong */ -.highlight .gu { color: #800080; font-weight: bold } /* Generic.Subheading */ -.highlight .gt { color: #04D } /* Generic.Traceback */ -.highlight .kc { color: #008000; font-weight: bold } /* Keyword.Constant */ -.highlight .kd { color: #008000; font-weight: bold } /* Keyword.Declaration */ -.highlight .kn { color: #008000; font-weight: bold } /* Keyword.Namespace */ -.highlight .kp { color: #008000 } /* Keyword.Pseudo */ -.highlight .kr { color: #008000; font-weight: bold } /* Keyword.Reserved */ -.highlight .kt { color: #B00040 } /* Keyword.Type */ -.highlight .m { color: #666 } /* Literal.Number */ -.highlight .s { color: #BA2121 } /* Literal.String */ -.highlight .na { color: #687822 } /* Name.Attribute */ -.highlight .nb { color: #008000 } /* Name.Builtin */ -.highlight .nc { color: #00F; font-weight: bold } /* Name.Class */ -.highlight .no { color: #800 } /* Name.Constant */ -.highlight .nd { color: #A2F } /* Name.Decorator */ -.highlight .ni { color: #717171; font-weight: bold } /* Name.Entity */ -.highlight .ne { color: #CB3F38; font-weight: bold } /* Name.Exception */ -.highlight .nf { color: #00F } /* Name.Function */ -.highlight .nl { color: #767600 } /* Name.Label */ -.highlight .nn { color: #00F; font-weight: bold } /* Name.Namespace */ -.highlight .nt { color: #008000; font-weight: bold } /* Name.Tag */ -.highlight .nv { color: #19177C } /* Name.Variable */ -.highlight .ow { color: #A2F; font-weight: bold } /* Operator.Word */ -.highlight .w { color: #BBB } /* Text.Whitespace */ -.highlight .mb { color: #666 } /* Literal.Number.Bin */ -.highlight .mf { color: #666 } /* Literal.Number.Float */ -.highlight .mh { color: #666 } /* Literal.Number.Hex */ -.highlight .mi { color: #666 } /* Literal.Number.Integer */ -.highlight .mo { color: #666 } /* Literal.Number.Oct */ -.highlight .sa { color: #BA2121 } /* Literal.String.Affix */ -.highlight .sb { color: #BA2121 } /* Literal.String.Backtick */ -.highlight .sc { color: #BA2121 } /* Literal.String.Char */ -.highlight .dl { color: #BA2121 } /* Literal.String.Delimiter */ -.highlight .sd { color: #BA2121; font-style: italic } /* Literal.String.Doc */ -.highlight .s2 { color: #BA2121 } /* Literal.String.Double */ -.highlight .se { color: #AA5D1F; font-weight: bold } /* Literal.String.Escape */ -.highlight .sh { color: #BA2121 } /* Literal.String.Heredoc */ -.highlight .si { color: #A45A77; font-weight: bold } /* Literal.String.Interpol */ -.highlight .sx { color: #008000 } /* Literal.String.Other */ -.highlight .sr { color: #A45A77 } /* Literal.String.Regex */ -.highlight .s1 { color: #BA2121 } /* Literal.String.Single */ -.highlight .ss { color: #19177C } /* Literal.String.Symbol */ -.highlight .bp { color: #008000 } /* Name.Builtin.Pseudo */ -.highlight .fm { color: #00F } /* Name.Function.Magic */ -.highlight .vc { color: #19177C } /* Name.Variable.Class */ -.highlight .vg { color: #19177C } /* Name.Variable.Global */ -.highlight .vi { color: #19177C } /* Name.Variable.Instance */ -.highlight .vm { color: #19177C } /* Name.Variable.Magic */ -.highlight .il { color: #666 } /* Literal.Number.Integer.Long */ \ No newline at end of file diff --git a/docs/_build/html/_static/searchtools.js b/docs/_build/html/_static/searchtools.js deleted file mode 100644 index 2c774d1..0000000 --- a/docs/_build/html/_static/searchtools.js +++ /dev/null @@ -1,632 +0,0 @@ -/* - * Sphinx JavaScript utilities for the full-text search. - */ -"use strict"; - -/** - * Simple result scoring code. - */ -if (typeof Scorer === "undefined") { - var Scorer = { - // Implement the following function to further tweak the score for each result - // The function takes a result array [docname, title, anchor, descr, score, filename] - // and returns the new score. - /* - score: result => { - const [docname, title, anchor, descr, score, filename, kind] = result - return score - }, - */ - - // query matches the full name of an object - objNameMatch: 11, - // or matches in the last dotted part of the object name - objPartialMatch: 6, - // Additive scores depending on the priority of the object - objPrio: { - 0: 15, // used to be importantResults - 1: 5, // used to be objectResults - 2: -5, // used to be unimportantResults - }, - // Used when the priority is not in the mapping. - objPrioDefault: 0, - - // query found in title - title: 15, - partialTitle: 7, - // query found in terms - term: 5, - partialTerm: 2, - }; -} - -// Global search result kind enum, used by themes to style search results. -class SearchResultKind { - static get index() { return "index"; } - static get object() { return "object"; } - static get text() { return "text"; } - static get title() { return "title"; } -} - -const _removeChildren = (element) => { - while (element && element.lastChild) element.removeChild(element.lastChild); -}; - -/** - * See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions#escaping - */ -const _escapeRegExp = (string) => - string.replace(/[.*+\-?^${}()|[\]\\]/g, "\\$&"); // $& means the whole matched string - -const _displayItem = (item, searchTerms, highlightTerms) => { - const docBuilder = DOCUMENTATION_OPTIONS.BUILDER; - const docFileSuffix = DOCUMENTATION_OPTIONS.FILE_SUFFIX; - const docLinkSuffix = DOCUMENTATION_OPTIONS.LINK_SUFFIX; - const showSearchSummary = DOCUMENTATION_OPTIONS.SHOW_SEARCH_SUMMARY; - const contentRoot = document.documentElement.dataset.content_root; - - const [docName, title, anchor, descr, score, _filename, kind] = item; - - let listItem = document.createElement("li"); - // Add a class representing the item's type: - // can be used by a theme's CSS selector for styling - // See SearchResultKind for the class names. - listItem.classList.add(`kind-${kind}`); - let requestUrl; - let linkUrl; - if (docBuilder === "dirhtml") { - // dirhtml builder - let dirname = docName + "/"; - if (dirname.match(/\/index\/$/)) - dirname = dirname.substring(0, dirname.length - 6); - else if (dirname === "index/") dirname = ""; - requestUrl = contentRoot + dirname; - linkUrl = requestUrl; - } else { - // normal html builders - requestUrl = contentRoot + docName + docFileSuffix; - linkUrl = docName + docLinkSuffix; - } - let linkEl = listItem.appendChild(document.createElement("a")); - linkEl.href = linkUrl + anchor; - linkEl.dataset.score = score; - linkEl.innerHTML = title; - if (descr) { - listItem.appendChild(document.createElement("span")).innerHTML = - " (" + descr + ")"; - // highlight search terms in the description - if (SPHINX_HIGHLIGHT_ENABLED) // set in sphinx_highlight.js - highlightTerms.forEach((term) => _highlightText(listItem, term, "highlighted")); - } - else if (showSearchSummary) - fetch(requestUrl) - .then((responseData) => responseData.text()) - .then((data) => { - if (data) - listItem.appendChild( - Search.makeSearchSummary(data, searchTerms, anchor) - ); - // highlight search terms in the summary - if (SPHINX_HIGHLIGHT_ENABLED) // set in sphinx_highlight.js - highlightTerms.forEach((term) => _highlightText(listItem, term, "highlighted")); - }); - Search.output.appendChild(listItem); -}; -const _finishSearch = (resultCount) => { - Search.stopPulse(); - Search.title.innerText = _("Search Results"); - if (!resultCount) - Search.status.innerText = Documentation.gettext( - "Your search did not match any documents. Please make sure that all words are spelled correctly and that you've selected enough categories." - ); - else - Search.status.innerText = Documentation.ngettext( - "Search finished, found one page matching the search query.", - "Search finished, found ${resultCount} pages matching the search query.", - resultCount, - ).replace('${resultCount}', resultCount); -}; -const _displayNextItem = ( - results, - resultCount, - searchTerms, - highlightTerms, -) => { - // results left, load the summary and display it - // this is intended to be dynamic (don't sub resultsCount) - if (results.length) { - _displayItem(results.pop(), searchTerms, highlightTerms); - setTimeout( - () => _displayNextItem(results, resultCount, searchTerms, highlightTerms), - 5 - ); - } - // search finished, update title and status message - else _finishSearch(resultCount); -}; -// Helper function used by query() to order search results. -// Each input is an array of [docname, title, anchor, descr, score, filename, kind]. -// Order the results by score (in opposite order of appearance, since the -// `_displayNextItem` function uses pop() to retrieve items) and then alphabetically. -const _orderResultsByScoreThenName = (a, b) => { - const leftScore = a[4]; - const rightScore = b[4]; - if (leftScore === rightScore) { - // same score: sort alphabetically - const leftTitle = a[1].toLowerCase(); - const rightTitle = b[1].toLowerCase(); - if (leftTitle === rightTitle) return 0; - return leftTitle > rightTitle ? -1 : 1; // inverted is intentional - } - return leftScore > rightScore ? 1 : -1; -}; - -/** - * Default splitQuery function. Can be overridden in ``sphinx.search`` with a - * custom function per language. - * - * The regular expression works by splitting the string on consecutive characters - * that are not Unicode letters, numbers, underscores, or emoji characters. - * This is the same as ``\W+`` in Python, preserving the surrogate pair area. - */ -if (typeof splitQuery === "undefined") { - var splitQuery = (query) => query - .split(/[^\p{Letter}\p{Number}_\p{Emoji_Presentation}]+/gu) - .filter(term => term) // remove remaining empty strings -} - -/** - * Search Module - */ -const Search = { - _index: null, - _queued_query: null, - _pulse_status: -1, - - htmlToText: (htmlString, anchor) => { - const htmlElement = new DOMParser().parseFromString(htmlString, 'text/html'); - for (const removalQuery of [".headerlink", "script", "style"]) { - htmlElement.querySelectorAll(removalQuery).forEach((el) => { el.remove() }); - } - if (anchor) { - const anchorContent = htmlElement.querySelector(`[role="main"] ${anchor}`); - if (anchorContent) return anchorContent.textContent; - - console.warn( - `Anchored content block not found. Sphinx search tries to obtain it via DOM query '[role=main] ${anchor}'. Check your theme or template.` - ); - } - - // if anchor not specified or not found, fall back to main content - const docContent = htmlElement.querySelector('[role="main"]'); - if (docContent) return docContent.textContent; - - console.warn( - "Content block not found. Sphinx search tries to obtain it via DOM query '[role=main]'. Check your theme or template." - ); - return ""; - }, - - init: () => { - const query = new URLSearchParams(window.location.search).get("q"); - document - .querySelectorAll('input[name="q"]') - .forEach((el) => (el.value = query)); - if (query) Search.performSearch(query); - }, - - loadIndex: (url) => - (document.body.appendChild(document.createElement("script")).src = url), - - setIndex: (index) => { - Search._index = index; - if (Search._queued_query !== null) { - const query = Search._queued_query; - Search._queued_query = null; - Search.query(query); - } - }, - - hasIndex: () => Search._index !== null, - - deferQuery: (query) => (Search._queued_query = query), - - stopPulse: () => (Search._pulse_status = -1), - - startPulse: () => { - if (Search._pulse_status >= 0) return; - - const pulse = () => { - Search._pulse_status = (Search._pulse_status + 1) % 4; - Search.dots.innerText = ".".repeat(Search._pulse_status); - if (Search._pulse_status >= 0) window.setTimeout(pulse, 500); - }; - pulse(); - }, - - /** - * perform a search for something (or wait until index is loaded) - */ - performSearch: (query) => { - // create the required interface elements - const searchText = document.createElement("h2"); - searchText.textContent = _("Searching"); - const searchSummary = document.createElement("p"); - searchSummary.classList.add("search-summary"); - searchSummary.innerText = ""; - const searchList = document.createElement("ul"); - searchList.setAttribute("role", "list"); - searchList.classList.add("search"); - - const out = document.getElementById("search-results"); - Search.title = out.appendChild(searchText); - Search.dots = Search.title.appendChild(document.createElement("span")); - Search.status = out.appendChild(searchSummary); - Search.output = out.appendChild(searchList); - - const searchProgress = document.getElementById("search-progress"); - // Some themes don't use the search progress node - if (searchProgress) { - searchProgress.innerText = _("Preparing search..."); - } - Search.startPulse(); - - // index already loaded, the browser was quick! - if (Search.hasIndex()) Search.query(query); - else Search.deferQuery(query); - }, - - _parseQuery: (query) => { - // stem the search terms and add them to the correct list - const stemmer = new Stemmer(); - const searchTerms = new Set(); - const excludedTerms = new Set(); - const highlightTerms = new Set(); - const objectTerms = new Set(splitQuery(query.toLowerCase().trim())); - splitQuery(query.trim()).forEach((queryTerm) => { - const queryTermLower = queryTerm.toLowerCase(); - - // maybe skip this "word" - // stopwords array is from language_data.js - if ( - stopwords.indexOf(queryTermLower) !== -1 || - queryTerm.match(/^\d+$/) - ) - return; - - // stem the word - let word = stemmer.stemWord(queryTermLower); - // select the correct list - if (word[0] === "-") excludedTerms.add(word.substr(1)); - else { - searchTerms.add(word); - highlightTerms.add(queryTermLower); - } - }); - - if (SPHINX_HIGHLIGHT_ENABLED) { // set in sphinx_highlight.js - localStorage.setItem("sphinx_highlight_terms", [...highlightTerms].join(" ")) - } - - // console.debug("SEARCH: searching for:"); - // console.info("required: ", [...searchTerms]); - // console.info("excluded: ", [...excludedTerms]); - - return [query, searchTerms, excludedTerms, highlightTerms, objectTerms]; - }, - - /** - * execute search (requires search index to be loaded) - */ - _performSearch: (query, searchTerms, excludedTerms, highlightTerms, objectTerms) => { - const filenames = Search._index.filenames; - const docNames = Search._index.docnames; - const titles = Search._index.titles; - const allTitles = Search._index.alltitles; - const indexEntries = Search._index.indexentries; - - // Collect multiple result groups to be sorted separately and then ordered. - // Each is an array of [docname, title, anchor, descr, score, filename, kind]. - const normalResults = []; - const nonMainIndexResults = []; - - _removeChildren(document.getElementById("search-progress")); - - const queryLower = query.toLowerCase().trim(); - for (const [title, foundTitles] of Object.entries(allTitles)) { - if (title.toLowerCase().trim().includes(queryLower) && (queryLower.length >= title.length/2)) { - for (const [file, id] of foundTitles) { - const score = Math.round(Scorer.title * queryLower.length / title.length); - const boost = titles[file] === title ? 1 : 0; // add a boost for document titles - normalResults.push([ - docNames[file], - titles[file] !== title ? `${titles[file]} > ${title}` : title, - id !== null ? "#" + id : "", - null, - score + boost, - filenames[file], - SearchResultKind.title, - ]); - } - } - } - - // search for explicit entries in index directives - for (const [entry, foundEntries] of Object.entries(indexEntries)) { - if (entry.includes(queryLower) && (queryLower.length >= entry.length/2)) { - for (const [file, id, isMain] of foundEntries) { - const score = Math.round(100 * queryLower.length / entry.length); - const result = [ - docNames[file], - titles[file], - id ? "#" + id : "", - null, - score, - filenames[file], - SearchResultKind.index, - ]; - if (isMain) { - normalResults.push(result); - } else { - nonMainIndexResults.push(result); - } - } - } - } - - // lookup as object - objectTerms.forEach((term) => - normalResults.push(...Search.performObjectSearch(term, objectTerms)) - ); - - // lookup as search terms in fulltext - normalResults.push(...Search.performTermsSearch(searchTerms, excludedTerms)); - - // let the scorer override scores with a custom scoring function - if (Scorer.score) { - normalResults.forEach((item) => (item[4] = Scorer.score(item))); - nonMainIndexResults.forEach((item) => (item[4] = Scorer.score(item))); - } - - // Sort each group of results by score and then alphabetically by name. - normalResults.sort(_orderResultsByScoreThenName); - nonMainIndexResults.sort(_orderResultsByScoreThenName); - - // Combine the result groups in (reverse) order. - // Non-main index entries are typically arbitrary cross-references, - // so display them after other results. - let results = [...nonMainIndexResults, ...normalResults]; - - // remove duplicate search results - // note the reversing of results, so that in the case of duplicates, the highest-scoring entry is kept - let seen = new Set(); - results = results.reverse().reduce((acc, result) => { - let resultStr = result.slice(0, 4).concat([result[5]]).map(v => String(v)).join(','); - if (!seen.has(resultStr)) { - acc.push(result); - seen.add(resultStr); - } - return acc; - }, []); - - return results.reverse(); - }, - - query: (query) => { - const [searchQuery, searchTerms, excludedTerms, highlightTerms, objectTerms] = Search._parseQuery(query); - const results = Search._performSearch(searchQuery, searchTerms, excludedTerms, highlightTerms, objectTerms); - - // for debugging - //Search.lastresults = results.slice(); // a copy - // console.info("search results:", Search.lastresults); - - // print the results - _displayNextItem(results, results.length, searchTerms, highlightTerms); - }, - - /** - * search for object names - */ - performObjectSearch: (object, objectTerms) => { - const filenames = Search._index.filenames; - const docNames = Search._index.docnames; - const objects = Search._index.objects; - const objNames = Search._index.objnames; - const titles = Search._index.titles; - - const results = []; - - const objectSearchCallback = (prefix, match) => { - const name = match[4] - const fullname = (prefix ? prefix + "." : "") + name; - const fullnameLower = fullname.toLowerCase(); - if (fullnameLower.indexOf(object) < 0) return; - - let score = 0; - const parts = fullnameLower.split("."); - - // check for different match types: exact matches of full name or - // "last name" (i.e. last dotted part) - if (fullnameLower === object || parts.slice(-1)[0] === object) - score += Scorer.objNameMatch; - else if (parts.slice(-1)[0].indexOf(object) > -1) - score += Scorer.objPartialMatch; // matches in last name - - const objName = objNames[match[1]][2]; - const title = titles[match[0]]; - - // If more than one term searched for, we require other words to be - // found in the name/title/description - const otherTerms = new Set(objectTerms); - otherTerms.delete(object); - if (otherTerms.size > 0) { - const haystack = `${prefix} ${name} ${objName} ${title}`.toLowerCase(); - if ( - [...otherTerms].some((otherTerm) => haystack.indexOf(otherTerm) < 0) - ) - return; - } - - let anchor = match[3]; - if (anchor === "") anchor = fullname; - else if (anchor === "-") anchor = objNames[match[1]][1] + "-" + fullname; - - const descr = objName + _(", in ") + title; - - // add custom score for some objects according to scorer - if (Scorer.objPrio.hasOwnProperty(match[2])) - score += Scorer.objPrio[match[2]]; - else score += Scorer.objPrioDefault; - - results.push([ - docNames[match[0]], - fullname, - "#" + anchor, - descr, - score, - filenames[match[0]], - SearchResultKind.object, - ]); - }; - Object.keys(objects).forEach((prefix) => - objects[prefix].forEach((array) => - objectSearchCallback(prefix, array) - ) - ); - return results; - }, - - /** - * search for full-text terms in the index - */ - performTermsSearch: (searchTerms, excludedTerms) => { - // prepare search - const terms = Search._index.terms; - const titleTerms = Search._index.titleterms; - const filenames = Search._index.filenames; - const docNames = Search._index.docnames; - const titles = Search._index.titles; - - const scoreMap = new Map(); - const fileMap = new Map(); - - // perform the search on the required terms - searchTerms.forEach((word) => { - const files = []; - const arr = [ - { files: terms[word], score: Scorer.term }, - { files: titleTerms[word], score: Scorer.title }, - ]; - // add support for partial matches - if (word.length > 2) { - const escapedWord = _escapeRegExp(word); - if (!terms.hasOwnProperty(word)) { - Object.keys(terms).forEach((term) => { - if (term.match(escapedWord)) - arr.push({ files: terms[term], score: Scorer.partialTerm }); - }); - } - if (!titleTerms.hasOwnProperty(word)) { - Object.keys(titleTerms).forEach((term) => { - if (term.match(escapedWord)) - arr.push({ files: titleTerms[term], score: Scorer.partialTitle }); - }); - } - } - - // no match but word was a required one - if (arr.every((record) => record.files === undefined)) return; - - // found search word in contents - arr.forEach((record) => { - if (record.files === undefined) return; - - let recordFiles = record.files; - if (recordFiles.length === undefined) recordFiles = [recordFiles]; - files.push(...recordFiles); - - // set score for the word in each file - recordFiles.forEach((file) => { - if (!scoreMap.has(file)) scoreMap.set(file, {}); - scoreMap.get(file)[word] = record.score; - }); - }); - - // create the mapping - files.forEach((file) => { - if (!fileMap.has(file)) fileMap.set(file, [word]); - else if (fileMap.get(file).indexOf(word) === -1) fileMap.get(file).push(word); - }); - }); - - // now check if the files don't contain excluded terms - const results = []; - for (const [file, wordList] of fileMap) { - // check if all requirements are matched - - // as search terms with length < 3 are discarded - const filteredTermCount = [...searchTerms].filter( - (term) => term.length > 2 - ).length; - if ( - wordList.length !== searchTerms.size && - wordList.length !== filteredTermCount - ) - continue; - - // ensure that none of the excluded terms is in the search result - if ( - [...excludedTerms].some( - (term) => - terms[term] === file || - titleTerms[term] === file || - (terms[term] || []).includes(file) || - (titleTerms[term] || []).includes(file) - ) - ) - break; - - // select one (max) score for the file. - const score = Math.max(...wordList.map((w) => scoreMap.get(file)[w])); - // add result to the result list - results.push([ - docNames[file], - titles[file], - "", - null, - score, - filenames[file], - SearchResultKind.text, - ]); - } - return results; - }, - - /** - * helper function to return a node containing the - * search summary for a given text. keywords is a list - * of stemmed words. - */ - makeSearchSummary: (htmlText, keywords, anchor) => { - const text = Search.htmlToText(htmlText, anchor); - if (text === "") return null; - - const textLower = text.toLowerCase(); - const actualStartPosition = [...keywords] - .map((k) => textLower.indexOf(k.toLowerCase())) - .filter((i) => i > -1) - .slice(-1)[0]; - const startWithContext = Math.max(actualStartPosition - 120, 0); - - const top = startWithContext === 0 ? "" : "..."; - const tail = startWithContext + 240 < text.length ? "..." : ""; - - let summary = document.createElement("p"); - summary.classList.add("context"); - summary.textContent = top + text.substr(startWithContext, 240).trim() + tail; - - return summary; - }, -}; - -_ready(Search.init); diff --git a/docs/_build/html/_static/sphinx_highlight.js b/docs/_build/html/_static/sphinx_highlight.js deleted file mode 100644 index 8a96c69..0000000 --- a/docs/_build/html/_static/sphinx_highlight.js +++ /dev/null @@ -1,154 +0,0 @@ -/* Highlighting utilities for Sphinx HTML documentation. */ -"use strict"; - -const SPHINX_HIGHLIGHT_ENABLED = true - -/** - * highlight a given string on a node by wrapping it in - * span elements with the given class name. - */ -const _highlight = (node, addItems, text, className) => { - if (node.nodeType === Node.TEXT_NODE) { - const val = node.nodeValue; - const parent = node.parentNode; - const pos = val.toLowerCase().indexOf(text); - if ( - pos >= 0 && - !parent.classList.contains(className) && - !parent.classList.contains("nohighlight") - ) { - let span; - - const closestNode = parent.closest("body, svg, foreignObject"); - const isInSVG = closestNode && closestNode.matches("svg"); - if (isInSVG) { - span = document.createElementNS("http://www.w3.org/2000/svg", "tspan"); - } else { - span = document.createElement("span"); - span.classList.add(className); - } - - span.appendChild(document.createTextNode(val.substr(pos, text.length))); - const rest = document.createTextNode(val.substr(pos + text.length)); - parent.insertBefore( - span, - parent.insertBefore( - rest, - node.nextSibling - ) - ); - node.nodeValue = val.substr(0, pos); - /* There may be more occurrences of search term in this node. So call this - * function recursively on the remaining fragment. - */ - _highlight(rest, addItems, text, className); - - if (isInSVG) { - const rect = document.createElementNS( - "http://www.w3.org/2000/svg", - "rect" - ); - const bbox = parent.getBBox(); - rect.x.baseVal.value = bbox.x; - rect.y.baseVal.value = bbox.y; - rect.width.baseVal.value = bbox.width; - rect.height.baseVal.value = bbox.height; - rect.setAttribute("class", className); - addItems.push({ parent: parent, target: rect }); - } - } - } else if (node.matches && !node.matches("button, select, textarea")) { - node.childNodes.forEach((el) => _highlight(el, addItems, text, className)); - } -}; -const _highlightText = (thisNode, text, className) => { - let addItems = []; - _highlight(thisNode, addItems, text, className); - addItems.forEach((obj) => - obj.parent.insertAdjacentElement("beforebegin", obj.target) - ); -}; - -/** - * Small JavaScript module for the documentation. - */ -const SphinxHighlight = { - - /** - * highlight the search words provided in localstorage in the text - */ - highlightSearchWords: () => { - if (!SPHINX_HIGHLIGHT_ENABLED) return; // bail if no highlight - - // get and clear terms from localstorage - const url = new URL(window.location); - const highlight = - localStorage.getItem("sphinx_highlight_terms") - || url.searchParams.get("highlight") - || ""; - localStorage.removeItem("sphinx_highlight_terms") - url.searchParams.delete("highlight"); - window.history.replaceState({}, "", url); - - // get individual terms from highlight string - const terms = highlight.toLowerCase().split(/\s+/).filter(x => x); - if (terms.length === 0) return; // nothing to do - - // There should never be more than one element matching "div.body" - const divBody = document.querySelectorAll("div.body"); - const body = divBody.length ? divBody[0] : document.querySelector("body"); - window.setTimeout(() => { - terms.forEach((term) => _highlightText(body, term, "highlighted")); - }, 10); - - const searchBox = document.getElementById("searchbox"); - if (searchBox === null) return; - searchBox.appendChild( - document - .createRange() - .createContextualFragment( - '" - ) - ); - }, - - /** - * helper function to hide the search marks again - */ - hideSearchWords: () => { - document - .querySelectorAll("#searchbox .highlight-link") - .forEach((el) => el.remove()); - document - .querySelectorAll("span.highlighted") - .forEach((el) => el.classList.remove("highlighted")); - localStorage.removeItem("sphinx_highlight_terms") - }, - - initEscapeListener: () => { - // only install a listener if it is really needed - if (!DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS) return; - - document.addEventListener("keydown", (event) => { - // bail for input elements - if (BLACKLISTED_KEY_CONTROL_ELEMENTS.has(document.activeElement.tagName)) return; - // bail with special keys - if (event.shiftKey || event.altKey || event.ctrlKey || event.metaKey) return; - if (DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS && (event.key === "Escape")) { - SphinxHighlight.hideSearchWords(); - event.preventDefault(); - } - }); - }, -}; - -_ready(() => { - /* Do not call highlightSearchWords() when we are on the search page. - * It will highlight words from the *previous* search query. - */ - if (typeof Search === "undefined") SphinxHighlight.highlightSearchWords(); - SphinxHighlight.initEscapeListener(); -}); diff --git a/docs/_build/html/ancestry.html b/docs/_build/html/ancestry.html deleted file mode 100644 index ec097ed..0000000 --- a/docs/_build/html/ancestry.html +++ /dev/null @@ -1,119 +0,0 @@ - - - - - - - - - Ancestry Prediction — my-project 0.0.1 documentation - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -
-

Ancestry Predictionïƒ

-

Background: -The mixture deconvolution algorithm requires allele frequency data. While we’ve found that using global allele frequencies generally performs quite well, using allele frequencies as closely matched to the population group of the contributor can result in increased accuracy. -Given forensic samples are almost always from individuals of unknown ancestry, we’ve developed a method using the inferred genotypes from the mixture deconvolution method to predict the ancestry of each contributor using principal component analysis (PCA). -PCA uses the genotypes from individuals of known ancestry, in this case the 2,504 1000 Genomes samples, to create a statistical framework for predicting the ancestry of the unknown sample. PCA transforms high dimensionality data (here, the genomic data) into a lower-dimensionality space in the form of principal components, or PCs. PC1 explains the highest amount of variance in the data, PC2 explains the second highest amount of variance, and so forth. Using genetic data, the biogeographical ancestry is driving the top PCs given it accounts for most of the variation in the data. -Plotting the PCs against each other (and coloring samples by population) allows one to visually see the separation and clustering of populations, oftentimes down to the subpopulation level. Where the unknown sample falls within the plot (along with some distance calculations) allows the user to assign a population to the sample, assuming it falls within a known cluster. -Generally, ancestry can be visualized by plotting PC1 vs. PC2. However, we found that adding the 3rd PC in a 3-dimensional space provides the best separation of the populations. MixDeR creates 3-D ancestry plots and saves them as a .html file. These interactive plots are more insightful than the standard 2-D PCA plots. -MixDeR calculates the Euclidean distance from the centroid of each superpopulation to the unknown sample and rank the superpopulations based on the distance with the smallest distance on top. It should be noted here that the top ranked superpopulation does NOT always match the actual ancestry of the unknown but instead lists the closest population distance-wise. The user must consider the actual value of that distance and examine the accompanying PCA plot to determine the potential accuracy of the prediction. If the unknown sample is falling outside of the top-ranked population, it likely does not closely match that population. While this can be a reflection of the quality of the sample and/or PCA, it could also occur because the unknown ancestry does not match those in the 1000 Genomes database. For example, Ashkenazi Jews are not included in the 1000 Genomes dataset and given their genetic divergence from other European populations, would fall outside of the EUR superpopulation (at least that’s what we’ve found!). All of these factors must be considered when evaluating the ancestry prediction results. -MixDeR does not make its own ancestry determination, but provides the data and plots for the user to make the determination themselves. Analyzing samples of known ancestry is pertinent to understanding the strengths and weakness of the method and the developers strongly encourage users to perform validation studies before making any ancestry predictions.

-
-
-

Running the Ancestry Prediction toolïƒ

-

When first launching the Shiny app, the ancestry prediction tool first appears. This tool can be skipped by checking the Skip Ancestry Prediction Step box.
-The user must select whether they want to use the Superpopulation groups (AFR, AMR, EAS, EUR, and SAS) and/or the subpopulation groups (e.g. Toscani in Italy, Esan in Nigeria, etc.). If both are selected, PCA plots (coloring by either superpopulation or subpopulation) and centroid calculations are performed for both. -The ancestry prediction tool will first perform mixture deconvolution using the 1000 Genomes Global allele frequency data and perform genotype filtering using the specified settings (allele 1/2 probability thresholds, # of SNP bins, the static/dynamic AT, minimum # of SNPs). A conditioned and/or unconditioned deconvolution can be performed. A sample manifest and folder containing the mixture Sample Reports (as well the reference Sample Reports, if performing a conditioned deconvolution) are required. Please see the section entitled “Running mixture deconvolution using EuroForMix†for further information and guidance on these settings. -The user can select whether to use the only 94 ancestry SNPs or all autosomal SNPs for PCA (the deconvolution step is performed using all SNPs). While the 94 ancestry SNPs do an ample job with good quality samples and is quite fast, using all SNPs provides better clustering but is significantly slower (several minutes per contributor). It is important the user weights the benefits vs. drawbacks of each set of SNPs to determine the best option for their data and system.

-
- - -
-
- -
-
-
-
- - - - \ No newline at end of file diff --git a/docs/_build/html/genindex.html b/docs/_build/html/genindex.html deleted file mode 100644 index 3fddc5b..0000000 --- a/docs/_build/html/genindex.html +++ /dev/null @@ -1,105 +0,0 @@ - - - - - - - - Index — MixDeR 1.0.0 documentation - - - - - - - - - - - - - - - -
- - -
- -
-
-
-
    -
  • - -
  • -
  • -
-
-
-
-
- - -

Index

- -
- -
- - -
-
- -
-
-
-
- - - - \ No newline at end of file diff --git a/docs/_build/html/index.html b/docs/_build/html/index.html deleted file mode 100644 index c06535d..0000000 --- a/docs/_build/html/index.html +++ /dev/null @@ -1,162 +0,0 @@ - - - - - - - - - reStructuredText — MixDeR 1.0.0 documentation - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -
-

reStructuredTextïƒ

-

This main document is in ‘reStructuredText’ (“rstâ€) format, -which differs in many ways from standard markdown commonly used in R packages. -rst is richer and more powerful than markdown. The remainder of this main -document demonstrates some of the features, with links to additional rst -documentation to help you get started. The definitive argument for the benefits -of rst over markdown is the official language format documentation, which starts with a very clear -explanation of the benefits.

-
-

Examplesïƒ

-

All of the following are defined within the docs/index.rst file. Here is -some coloured text which demonstrates how raw HTML commands can be -incorporated. The following are examples of rst “admonitionsâ€:

-
-

Note

-

Here is a note

-
-

Warning

-

With a warning inside the note

-
-
-
-

See also

-

The full list of ‘restructuredtext’ directives or a similar list of admonitions.

-
-

-This is a line of centered text

    -
  • and here is

  • -
  • A list of

  • -
-
    -
  • short items

  • -
  • that are

  • -
-
    -
  • displayed

  • -
  • in 3 columns

  • -
-
-

The remainder of this document shows three tables of contents for the main -README (under “Introductionâ€), and the vignettes and R directories of -a package. These can be restructured any way you like by changing the main -docs/index.rst file. The contents of this file – and indeed the contents -of any readthedocs file – can be viewed by -clicking View page source at the top left of any page.

-
-

Introduction:

- -
-
-
-
-
-
-
- - -
-
- -
-
-
-
- - - - \ No newline at end of file diff --git a/docs/_build/html/mixder.html b/docs/_build/html/mixder.html deleted file mode 100644 index 25ffbfb..0000000 --- a/docs/_build/html/mixder.html +++ /dev/null @@ -1,607 +0,0 @@ - - - - - - - - - MixDeR - Current Version: 1.0 — MixDeR 1.0.0 documentation - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- - -
-

MixDeR - Current Version: 1.0ïƒ

- - -

MixDeR (Mixture Deconvolution in R) is a workflow (with a -Shiny app) for performing mixture deconvolution of ForenSeq -Kintelligence SNP data for two-person mixtures using -EuroForMix and creating -GEDmatch PRO reports for the individual contributor SNP profiles.

-

This method requires extensive validation of the settings. MixDeR -provides the option of calculating various metrics for evaluating the -accuracy of the deduced SNP genotypes. This is extremely useful when -determining settings, specifically the allele 1 probability threshold, -the allele 2 probability threshold, and the minimum number of SNPs.

-

Note: MixDeR (and EFM) assume the mixture samples are composed of two -contributors. MixDeR is able to identify and alert the user to samples -that may be potentially either single source or consist of a mixture -with a large mixture ratio (i.e. > 1:100 ratio between contributors). -In these scenarios, the user is warned to be cautious with the minor -contributor inferred genotyping results.

-

Please cite the following paper if utilizing MixDeR:

-
Mitchell, R., Peck, M., Gorden, E., & Just, R. (2025). MixDeR: A SNP mixture 
-deconvolution workflow for forensic genetic genealogy. Forensic Science 
-International: Genetics, 76, 103224, doi: 10.1016/j.fsigen.2025.103224
-
-
-
-

Installationïƒ

-

For any installation, EuroForMix must be installed. Please follow the -instructions from the EuroForMix GitHub -page. EuroForMix version 4.2.4 -and earlier have been tested and are compatible with MixDeR. If using a -newer version, please be aware it has not been tested and errors may -occur!

-

If installing from GitHub:
-The R package devtools is required to install from GitHub:

-
install.packages("devtools")
-devtools::install_github("bioforensics/mixder")
-
-
-

If installing from source, first install the following R packages:

-
install.packages(c("dplyr", "ggplot2", "glue", "prompter", "readxl", "rlang", "shiny", "shinyFiles", "shinyjs", "tibble", "tidyr"))
-
-
-

To install MixDeR from source (i.e. the mixder_0.1.0.tar.gz file):

-
install.packages("/path/to/mixder_0.1.0.tar.gz", repos = NULL, type="source")
-
-
-

For example, if the mixder_0.1.0.tar.gz file is located in your -Documents folder:

-
install.packages("~/Documents/mixder_0.1.0.tar.gz", repos = NULL, type="source")
-
-
-
-
-

Usageïƒ

-

To launch the shiny app:

-
library(mixder)
-mixder()
-
-
-
-
-

Required filesïƒ

-
    -
  1. Mixture Kintelligence SNP profiles
    -This can be in the form of the UAS Sample Report or a TSV file with -the below format:

  2. -
- - - - - - - - - - - - - - - - - - - - - - - - - -

Marker

Allele

Reads

rs12615742

T

0

rs12615742

C

134

rs16885694

G

43

rs16885694

A

63

-

The files should be tab delimited and should be named as a .tsv file, -such as: SampleID.tsv.

-

If using the Shiny app, you must specify the folder containing these SNP -files. Multiple samples (with multiple files each) can be in the same -folder. Additional files may be present in the folder and will be -ignored by MixDeR.

-

MixDeR will divide the entire Kintelligence dataset into more manageable -sets (organized by total SNP read depth) to run through EFM (ideal for -best performance). The user may specify how many sets the program will -use (see below); the default is 10 sets. The user must then specify how -many sets are provided so MixDeR knows how many files to process per -sample.

-

The default is for MixDeR to use previously-created SNP sets, if present -in the specified input folder. If this option is unselected, MixDeR will -create new SNP sets files, overwriting any previously made files.

-
    -
  1. The sample manifest
    -This file lists the Sample IDs of the files to run. The SampleID -is extracted from the Sample Name field in the Sample Report. The -columns names do not matter, just the order and both columns -must be present even if no replicates are included. If a single -sample is run, only the single ID needs to be in the first column, -the second column should be left blank. If a second sample is to be -run in replicate, the replicate ID should be listed in the second -column.

  2. -
- - - - - - - - - - - - - - -

SampleID

ReplicateID

Sample01a

Sample01a

Sample01b

-
-
-

Other files which may be requiredïƒ

-

Allele frequency file

-

MixDeR provides general population allele frequencies for Kintelligence -SNPs from either 1000 Genomes Phase 3 -dataset or the gnomAD v4 -dataset. However, it is -ideal to use allele frequencies derived from the population that closely -matches the contributor(s) of interest. Therefore, MixDeR provides the -user the opportunity to upload a different allele frequency file. -EuroForMix requires the below format for allele frequency files, for all -SNPs with each SNP as its own column:

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

Allele

rs6690515

rs424079

rs2055204

A

0.122837

0.64677

0.501441

C

NA

0.35323

NA

G

0.877163

NA

0.498559

T

NA

NA

NA

-

Given the difficulty of formatting the data as such, MixDeR will create -this format for the user if the user provides the frequency data in a -CSV file with the following format (NOTE: the column names MUST match -below; the order of columns and additional columns will not affect it).

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

SNP

Ref

Alt

Ref_AF

Alt_AF

rs6690515

A

C

0.35323

0.64677

rs424079

T

C

0.122837

0.877163

rs2055204

G

A

0.501441

0.498559

-

MixDeR provides the option to use the same allele frequency file for -both the major and minor contributor or select different allele -frequency files for the major and minor contributor. If two different -frequency files are selected, MixDeR will run EFM twice, once using the -allele frequency file for the major contributor (and extracting the -inferred genotypes for the major contributor from those results) and -once using the allele frequency file for the minor contributor (and -extracting the inferred genotypes for the minor contributor from those -results).

-

Reference Genotypes

-

If calculating validation metrics or performing a conditioned -deconvolution, the reference genotypes are required.

-

There are two options for providing reference genotypes. MixDeR accepts -the UAS Sample Report, stored in a separate folder. A second option is -to provide a single CSV file containing all references named -EFM_references.csv with the following format:

- - - - - - - - - - - - - - - - - - - - -

Sample.Name

Marker

Allele1

Allele2

Sample01

rs12615742

T

C

Sample01

rs16885694

G

G

-

The user must provide the folder containing either the Sample Reports or -the CSV reference file.
-NOTE: MixDeR first searches for the CSV file in the provided folder. If -there are additional wanted references not contained in this file, -please remove the CSV file and run MixDeR again. MixDeR creates the CSV -file containing genotypes from the Sample Reports within the provided -folder.
-________

-
-
-

MixDeR Detailsïƒ

-

MixDeR has four modules:

-
    -
  1. Ancestry Prediction

  2. -
  3. EFM mixture deconvolution

  4. -
  5. Calculate validation metrics

  6. -
  7. Create GEDmatch PRO reports

  8. -
-

EFM mixture deconvolution must be run at least once. If it’s been run -previously, the other modules can be run using the existing -deconvolution data.

-

MixDeR can either calculate validation metrics OR create the GEDmatch -PRO reports during a single run, not both.

-

NOTE about the allele probability thresholds:
-This workflow utilizes individual probabilities for each allele call -from EFM. The reported allele 1 is the allele with the higher -probability; allele 2 is the allele with the lower probability. When -applying the allele 1 probability threshold, any SNP with a probability -below the threshold will be removed completely from the dataset. When -applying the allele 2 probability threshold to the remaining SNPs, if -the probability is below the threshold, allele 2 is reported as the same -allele as allele 1. If it is above, the allele will be reported as -called. For example, if the genotype for SNP rs12615742 is C,T but the -allele 2 probability is below the threshold, the SNP genotype will be -reported as C,C. If it is above the threshold, the SNP genotype will be -reported as C,T.

-
-
-

Ancestry Prediction - Backgroundïƒ

-

The mixture deconvolution algorithm requires allele frequency data. While we’ve found that using global allele frequencies generally performs quite well, using allele frequencies as closely matched to the population group of the contributor can result in increased accuracy.

-

Given forensic samples are almost always from individuals of unknown ancestry, we’ve developed a method using the inferred genotypes from the mixture deconvolution method to predict the ancestry of each contributor using principal component analysis (PCA).

-

PCA uses the genotypes from individuals of known ancestry, in this case the 2,504 1000 Genomes samples, to create a statistical framework for predicting the ancestry of the unknown sample. PCA transforms high dimensionality data (here, the genomic data) into a lower-dimensionality space in the form of principal components, or PCs. PC1 explains the highest amount of variance in the data, PC2 explains the second highest amount of variance, and so forth. Using genetic data, the biogeographical ancestry is driving the top PCs given it accounts for most of the variation in the data.

-

Plotting the PCs against each other (and coloring samples by population) allows one to visually see the separation and clustering of populations, oftentimes down to the subpopulation level. Where the unknown sample falls within the plot (along with some distance calculations) allows the user to assign a population to the sample, assuming it falls within a known cluster.

-

Generally, ancestry can be visualized by plotting PC1 vs. PC2. However, we found that adding the 3rd PC in a 3-dimensional space provides the best separation of the populations. MixDeR creates 3-D ancestry plots and saves them as a .html file. These interactive plots are more insightful than the standard 2-D PCA plots.

-

MixDeR calculates the Euclidean distance from the centroid of each superpopulation to the unknown sample and rank the superpopulations based on the distance with the smallest distance on top. It should be noted here that the top ranked superpopulation does NOT always match the actual ancestry of the unknown but instead lists the closest population distance-wise. The user must consider the actual value of that distance and examine the accompanying PCA plot to determine the potential accuracy of the prediction. If the unknown sample is falling outside of the top-ranked population, it likely does not closely match that population. While this can be a reflection of the quality of the sample and/or PCA, it could also occur because the unknown ancestry does not match those in the 1000 Genomes database. For example, Ashkenazi Jews are not included in the 1000 Genomes dataset and given their genetic divergence from other European populations, would fall outside of the EUR superpopulation (at least that’s what we’ve found!). All of these factors must be considered when evaluating the ancestry prediction results.

-

MixDeR does not make its own ancestry determination, but provides the data and plots for the user to make the determination themselves. Analyzing samples of known ancestry is pertinent to understanding the strengths and weakness of the method and the developers strongly encourage users to perform validation studies before making any ancestry predictions.

-
-
-

Running the Ancestry Prediction toolïƒ

-

When first launching the Shiny app, the ancestry prediction tool first appears. This tool can be skipped by checking the Skip Ancestry Prediction Step box.

-

The user must select whether they want to use the Superpopulation groups (AFR, AMR, EAS, EUR, and SAS) and/or the subpopulation groups (e.g. Toscani in Italy, Esan in Nigeria, etc.). If both are selected, PCA plots (coloring by either superpopulation or subpopulation) and centroid calculations are performed for both.
-The ancestry prediction tool will first perform mixture deconvolution using the 1000 Genomes Global allele frequency data and perform genotype filtering using the specified settings (allele 1/2 probability thresholds, # of SNP bins, the static/dynamic AT, minimum # of SNPs). A conditioned and/or unconditioned deconvolution can be performed. A sample manifest and folder containing the mixture Sample Reports (as well the reference Sample Reports, if performing a conditioned deconvolution) are required. Please see the section entitled “Running mixture deconvolution using EuroForMix†for further information and guidance on these settings.

-

The user can select whether to use the only 94 ancestry SNPs or all autosomal SNPs for PCA (the deconvolution step is performed using all SNPs). While the 94 ancestry SNPs do an ample job with good quality samples and is quite fast, using all SNPs provides better clustering but is significantly slower (several minutes per contributor). It is important the user weights the benefits vs. drawbacks of each set of SNPs to determine the best option for their data and system.

-
-
-

Running mixture deconvolution using EuroForMixïƒ

-

There are several options/settings to run EFM mixture deconvolution:
-The type of mixture deconvolution analysis to perform (one or both can -be selected at once):

-
    -
  • Unconditioned analysis

  • -
  • Conditioned analysis

  • -
-

Allele Frequency Data: The user must choose which allele frequency -data to use: 1000Genomes Phase 3 data, gnomAD v4 data, or upload a -custom file. See above for more details about the format for uploading a -custom AF file.
-References to Condition on: IF running a conditioned analysis, once -the reference folder has been uploaded, this dropdown menu will -auto-populate containing the reference sample IDs. The user must select -which references to condition on. As of now, MixDeR can only condition -on a single reference sample. However, multiple references can be -selected for conditioning; the conditioned analyses will be run -separately.
-Number of SNP Bins: The number of SNP sets for each sample. The -default is 10.
-Static Analytical Threshold: The minimum number of reads required -for a SNP to be included (default = 10).
-Dynamic Analytical Threshold: The percent of total SNP reads -required for a SNP to. be included (default = 0.015 or 1.5%). (A quick -note on the ATs: both the static and dynamic ATs are applied and the one -producing the higher AT will be used in the EFM software).
-Output Folder Name: The name of the folder created within the folder -containing the original SNP files to store the generated output for the -entire workflow.
-Minimum Number of SNPs: The minimum number of SNPs

-
-
-

Calculating validation metricsïƒ

-

There are several options/settings to calculate the validation -metrics:
-Minimum Allele 1 Probability Threshold and Maximum Allele 1 -Probability Threshold: The range of allele 1 probability thresholds -for calculating the validation metrics, increasing in increments of 0.01 -(i.e. if minimum is set to 0.5 and maximum is set to 1, will calculate -metrics using a threshold of 0.5, 0.51, 0.52, 0.53, up to 1).
-Minimum Allele 2 Probability Threshold and Maximum Allele 2 -Probability Threshold: The range of allele 2 probability thresholds -for calculating the validation metrics, increasing in increments of 0.01 -(i.e. if minimum is set to 0.5 and maximum is set to 1, will calculate -metrics using a threshold of 0.5, 0.51, 0.52, 0.53, up to 1).
-Major Contributor Sample ID: The ID of the major contributor of the -mixture. Once the reference folder is uploaded, this dropdown menu will -auto-populate with the reference sample IDs.
-Minor Contributor Sample ID: The ID of the minor contributor of the -mixture. Once the reference folder is uploaded, this dropdown menu will -auto-populate with the reference sample IDs.
-Remove SNPs If Missing Either Allele?: If an allele 1 is inferred to -be missing (reported as 99), that SNP will be automatically dropped -from the final dataset. By default, if an allele 2 is inferred to be -missing and the allele 2 probability is above the allele 2 probability -threshold, the SNP is reported as homozygous for allele 1. However, -selecting this option will result in dropping the SNP if the allele 2 -probability of the missing allele 2 is above the allele 2 probability -threshold, instead of reporting the SNP as homozygous for allele 1.

-

If calculating validation metrics, reference genotypes are required to -calculate genotype accuracy. MixDerR will calculate the metrics for the -range of allele 1 probability thresholds and allele 2 probability -thresholds specified by the user. The final output file looks as such:

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

A1 cutoff

A2 cutoff

Total SNPs

N No Ref

N SNPs tested

N Genotypes Correct

Genotype Accuracy

Heterozygosity

0.99

0.01

9735

8

9727

9549

0.9817

0.456

0.99

0.02

9735

8

9727

9548

0.9815

0.456

0.99

0.03

9735

8

9727

9548

0.9815

0.456

-
-
-

Creating GEDmatch PRO Reportsïƒ

-

Allele 1 Probability Threshold to create GEDmatch PRO Report and -Allele 2 Probability Threshold to create GEDmatch PRO Report: The -allele 1 and allele 2 probability thresholds. If the contributor is the -major contributor to the mixture, MixDeR will apply the allele 1 and -allele 2 probability thresholds UNLESS this results in a profile with -fewer SNPs than the specified minimum (6000 is the default). If it does -not meet this minimum, the top 6,000 SNPs (or whatever the user -specifies as the minimum) with the highest allele 1 probabilities are -used and then the allele 2 probability threshold is applied. For minor -contributors, the default setting is that the minimum number of SNPs is -automatically used and then the allele 2 probability threshold is -applied. The option to apply the allele 1 probability threshold (similar -in manner to the major contributor) can be applied.
-Remove SNPs If Missing Either Allele?: If an allele 1 is inferred to -be missing (reported as 99), that SNP will be automatically dropped -from the final dataset. By default, if an allele 2 is inferred to be -missing and the allele 2 probability is above the allele 2 probability -threshold, the SNP is reported as homozygous for allele 1. However, -selecting this option will result in dropping the SNP if the allele 2 -probability of the missing allele 2 is above the allele 2 probability -threshold, instead of reporting the SNP as homozygous for allele 1.

-

As a way to assist the analyst in evaluating the inferred genotypes of a -mixture of unknown contributors, several metrics are calculated in this -step for three different scenarios: (1) only the allele 2 probability -threshold applied; (2) the allele 1 and allele 2 probability thresholds -applied; and (3) the minimum number of SNPs used and the allele 2 -probability threshold applied. For each dataset, the follow metrics are -calculated: number of SNPs, mean allele 1 probabilities, median allele 1 -probabilities, standard deviation (SD) of the allele 1 probabilities and -heterozygosity. Below is an example of the table created by MixDeR in -this step. A density plot of allele 1 probability thresholds is also -created. In general, the more SNPs with higher allele 1 probabilities, -the higher the accuracy of the inferred genotypes. However, determining -exactly what qualifies as acceptable should be determined by individual -labs.

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

Allele1 Threshold Applied

Allele2 Threshold Applied

Total SNPs

Mean A1 Prob

Median A1 Prob

SD A1 Prob

Heterozygosity

No

Yes

10024

0.9984

1

0.0096

0.4626

Yes

Yes

9718

0.9998

1

9.00E-04

0.4569

Minimum # of SNPs

Yes

6000

1

1

0

0.4495

-
-
- - -
-
- -
-
-
-
- - - - \ No newline at end of file diff --git a/docs/_build/html/objects.inv b/docs/_build/html/objects.inv deleted file mode 100644 index 94bc57f..0000000 --- a/docs/_build/html/objects.inv +++ /dev/null @@ -1,8 +0,0 @@ -# Sphinx inventory version 2 -# Project: MixDeR -# Version: -# The remainder of this file is compressed using zlib. -xÚ…ŽÁ -ƒ0DïùŠý¥^{m/=¤–Þ­;h &eÝ@üû"±T¡ÐÛ2óÞ2=¼õŒD“òѵO8**ê×´tttYNóÅ8t ´!Jì4 -øŽ¤f´‰![:'¿ÚtÆ -:Ex¥d²Á©*f ücÒk.>Åú$ptXÇmÚ?Z=ëüÞžÐJ7ìÅœe§É}Ýö0o‰Äkc \ No newline at end of file diff --git a/docs/_build/html/search.html b/docs/_build/html/search.html deleted file mode 100644 index 1713d11..0000000 --- a/docs/_build/html/search.html +++ /dev/null @@ -1,120 +0,0 @@ - - - - - - - - Search — MixDeR 1.0.0 documentation - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
-
    -
  • - -
  • -
  • -
-
-
-
-
- - - - -
- -
- -
-
- -
-
-
-
- - - - - - - - - \ No newline at end of file diff --git a/docs/_build/html/searchindex.js b/docs/_build/html/searchindex.js deleted file mode 100644 index 71c819b..0000000 --- a/docs/_build/html/searchindex.js +++ /dev/null @@ -1 +0,0 @@ -Search.setIndex({"alltitles": {"Ancestry Prediction - Background": [[1, "ancestry-prediction-background"]], "Calculating validation metrics": [[1, "calculating-validation-metrics"]], "Creating GEDmatch PRO Reports": [[1, "creating-gedmatch-pro-reports"]], "Examples": [[0, "examples"]], "Installation": [[1, "installation"]], "Introduction:": [[0, null]], "MixDeR - Current Version: 1.0": [[1, null]], "MixDeR Details": [[1, "mixder-details"]], "Other files which may be required": [[1, "other-files-which-may-be-required"]], "Required files": [[1, "required-files"]], "Running mixture deconvolution using EuroForMix": [[1, "running-mixture-deconvolution-using-euroformix"]], "Running the Ancestry Prediction tool": [[1, "running-the-ancestry-prediction-tool"]], "Usage": [[1, "usage"]], "reStructuredText": [[0, null]]}, "docnames": ["index", "mixder"], "envversion": {"sphinx": 64, "sphinx.domains.c": 3, "sphinx.domains.changeset": 1, "sphinx.domains.citation": 1, "sphinx.domains.cpp": 9, "sphinx.domains.index": 1, "sphinx.domains.javascript": 3, "sphinx.domains.math": 2, "sphinx.domains.python": 4, "sphinx.domains.rst": 2, "sphinx.domains.std": 2, "sphinx.ext.intersphinx": 1}, "filenames": ["index.rst", "mixder.md"], "indexentries": {}, "objects": {}, "objnames": {}, "objtypes": {}, "terms": {"": 1, "0": 0, "000": 1, "0096": 1, "00e": 1, "01": 1, "015": 1, "02": 1, "03": 1, "04": 1, "1": 0, "10": 1, "100": 1, "1000": 1, "1000genom": 1, "10024": 1, "1016": 1, "103224": 1, "122837": 1, "134": 1, "2": 1, "2025": 1, "3": [0, 1], "35323": 1, "3rd": 1, "4": 1, "43": 1, "4495": 1, "456": 1, "4569": 1, "4626": 1, "498559": 1, "5": 1, "501441": 1, "504": 1, "51": 1, "52": 1, "53": 1, "6": 1, "6000": 1, "63": 1, "64677": 1, "76": 1, "8": 1, "877163": 1, "9": 1, "94": 1, "9548": 1, "9549": 1, "9718": 1, "9727": 1, "9735": 1, "9815": 1, "9817": 1, "99": 1, "9984": 1, "9998": 1, "A": [0, 1], "AT": 1, "ATs": 1, "As": 1, "By": 1, "For": 1, "IF": 1, "If": 1, "In": 1, "It": 1, "NOT": 1, "No": 1, "OR": 1, "The": [0, 1], "There": 1, "These": [0, 1], "To": 1, "With": 0, "________": 1, "a1": 1, "a2": 1, "abl": 1, "about": 1, "abov": 1, "accept": 1, "accompani": 1, "account": 1, "accuraci": 1, "actual": 1, "ad": 1, "addit": [0, 1], "admonit": 0, "af": 1, "affect": 1, "afr": 1, "again": 1, "against": 1, "alert": 1, "algorithm": 1, "all": [0, 1], "allel": 1, "allele1": 1, "allele2": 1, "allow": 1, "almost": 1, "along": 1, "also": 1, "alt": 1, "alt_af": 1, "alwai": 1, "amount": 1, "ampl": 1, "amr": 1, "an": 1, "analys": 1, "analysi": 1, "analyst": 1, "analyt": 1, "analyz": 1, "ani": [0, 1], "app": 1, "appear": 1, "appli": 1, "ar": [0, 1], "argument": 0, "ashkenazi": 1, "assign": 1, "assist": 1, "assum": 1, "auto": 1, "automat": 1, "autosom": 1, "avail": [], "awar": 1, "background": [], "base": 1, "becaus": 1, "been": 1, "befor": 1, "below": 1, "benefit": [0, 1], "best": 1, "better": 1, "between": 1, "bin": 1, "bioforens": 1, "biogeograph": 1, "blank": 1, "both": 1, "box": 1, "c": 1, "calcul": [], "call": 1, "can": [0, 1], "case": 1, "cautiou": 1, "center": 0, "centroid": 1, "chang": 0, "check": 1, "choos": 1, "cite": 1, "clear": 0, "click": 0, "close": 1, "closest": 1, "cluster": 1, "color": 1, "colour": 0, "column": [0, 1], "command": 0, "commonli": 0, "compat": 1, "complet": 1, "compon": 1, "compos": 1, "condit": 1, "consid": 1, "consist": 1, "contain": 1, "content": 0, "contributor": 1, "convolut": 1, "correct": 1, "could": 1, "creat": [], "csv": 1, "current": 0, "custom": 1, "cutoff": 1, "d": 1, "data": 1, "databas": 1, "dataset": 1, "de": 1, "deconvolut": [], "deduc": 1, "default": 1, "defin": 0, "definit": 0, "delimit": 1, "demonstr": 0, "densiti": 1, "depth": 1, "deriv": 1, "determin": 1, "develop": 1, "deviat": 1, "devtool": 1, "differ": [0, 1], "difficulti": 1, "dimension": 1, "direct": 0, "directori": 0, "displai": 0, "distanc": 1, "diverg": 1, "divid": 1, "do": 1, "doc": 0, "document": [0, 1], "doe": 1, "doi": 1, "down": 1, "dplyr": 1, "drawback": 1, "drive": 1, "drop": 1, "dropdown": 1, "dure": 1, "dynam": 1, "e": 1, "ea": 1, "each": 1, "earlier": 1, "efm": 1, "efm_refer": 1, "either": 1, "encourag": 1, "entir": 1, "entitl": 1, "error": 1, "esan": 1, "etc": 1, "euclidean": 1, "eur": 1, "euroformix": [], "european": 1, "evalu": 1, "even": 1, "exactli": 1, "examin": 1, "exampl": 1, "exist": 1, "explain": 1, "explan": 0, "extens": 1, "extract": 1, "extrem": 1, "factor": 1, "fall": 1, "fast": 1, "featur": 0, "fewer": 1, "field": 1, "file": 0, "filter": 1, "final": 1, "first": 1, "folder": 1, "follow": [0, 1], "forens": 1, "forenseq": 1, "form": 1, "format": [0, 1], "forth": 1, "found": 1, "four": 1, "framework": 1, "frequenc": 1, "from": [0, 1], "fsigen": 1, "full": 0, "further": 1, "g": 1, "genealogi": 1, "gener": 1, "genet": 1, "genom": 1, "genotyp": 1, "get": 0, "ggplot2": 1, "github": 1, "given": 1, "global": 1, "glue": 1, "gnomad": 1, "good": 1, "gorden": 1, "group": 1, "guidanc": 1, "gz": 1, "ha": 1, "have": 1, "help": 0, "here": [0, 1], "heterozygos": 1, "high": 1, "higher": 1, "highest": 1, "homozyg": 1, "how": [0, 1], "howev": 1, "html": [0, 1], "i": [0, 1], "id": 1, "ideal": 1, "identifi": 1, "ignor": 1, "import": 1, "includ": 1, "incorpor": 0, "increas": 1, "increment": 1, "inde": 0, "index": 0, "individu": 1, "infer": 1, "inform": 1, "input": 1, "insid": 0, "insight": 1, "install_github": 1, "instead": 1, "instruct": 1, "interact": 1, "interest": 1, "intern": 1, "itali": 1, "item": 0, "its": 1, "j": 1, "jew": 1, "job": 1, "just": 1, "kintellig": 1, "know": 1, "known": 1, "lab": 1, "languag": 0, "larg": 1, "launch": 1, "least": 1, "left": [0, 1], "level": 1, "librari": 1, "like": [0, 1], "line": 0, "link": 0, "list": [0, 1], "locat": 1, "look": 1, "lower": 1, "m": 1, "made": 1, "main": 0, "major": 1, "make": 1, "manag": 1, "mani": [0, 1], "manifest": 1, "manner": 1, "manuscript": [], "markdown": 0, "marker": 1, "match": 1, "matter": 1, "maximum": 1, "mean": 1, "median": 1, "meet": 1, "menu": 1, "method": 1, "minimum": 1, "minor": 1, "minut": 1, "miss": 1, "mitchel": 1, "mix": 1, "mixder": 0, "mixder_0": 1, "mixderr": 1, "mixtur": [], "modul": 1, "more": [0, 1], "most": 1, "multipl": 1, "must": 1, "n": 1, "na": 1, "name": 1, "need": 1, "new": 1, "newer": 1, "nigeria": 1, "note": [0, 1], "now": 1, "null": 1, "number": 1, "occur": 1, "offici": 0, "oftentim": 1, "onc": 1, "one": 1, "onli": 1, "opportun": 1, "option": 1, "order": 1, "organ": 1, "origin": 1, "other": [], "output": 1, "outsid": 1, "over": 0, "overwrit": 1, "own": 1, "packag": [0, 1], "page": [0, 1], "paper": 1, "path": 1, "pc": 1, "pc1": 1, "pc2": 1, "pca": 1, "peck": 1, "per": 1, "percent": 1, "perform": 1, "person": 1, "pertin": 1, "phase": 1, "pleas": 1, "plot": 1, "popul": 1, "potenti": 1, "power": 0, "preprint": [], "present": 1, "previous": 1, "princip": 1, "prob": 1, "probabl": 1, "process": 1, "produc": 1, "profil": 1, "program": 1, "prompter": 1, "provid": 1, "qualifi": 1, "qualiti": 1, "quick": 1, "quit": 1, "r": [0, 1], "rang": 1, "rank": 1, "ratio": 1, "raw": 0, "read": 1, "readm": 0, "readthedoc": 0, "readxl": 1, "ref": 1, "ref_af": 1, "refer": 1, "reflect": 1, "remain": 1, "remaind": 0, "remov": 1, "replic": 1, "replicateid": 1, "repo": 1, "report": [], "requir": [], "restructur": 0, "result": 1, "richer": 0, "rlang": 1, "rs12615742": 1, "rs16885694": 1, "rs2055204": 1, "rs424079": 1, "rs6690515": 1, "rst": 0, "sa": 1, "same": 1, "sampl": 1, "sample01": 1, "sample01a": 1, "sample01b": 1, "sampleid": 1, "save": 1, "scenario": 1, "scienc": 1, "sd": 1, "search": 1, "second": 1, "section": 1, "see": 1, "select": 1, "separ": 1, "set": 1, "sever": 1, "shini": 1, "shinyfil": 1, "shinyj": 1, "short": 0, "should": 1, "show": 0, "significantli": 1, "similar": [0, 1], "singl": 1, "skip": 1, "slower": 1, "smallest": 1, "snp": 1, "so": 1, "softwar": 1, "some": [0, 1], "sourc": [0, 1], "space": 1, "specif": 1, "specifi": 1, "standard": [0, 1], "start": 0, "static": 1, "statist": 1, "step": 1, "store": 1, "strength": 1, "strongli": 1, "studi": 1, "subpopul": 1, "superpopul": 1, "system": 1, "t": 1, "tab": 1, "tabl": [0, 1], "tar": 1, "test": 1, "text": 0, "than": [0, 1], "thei": 1, "them": 1, "themselv": 1, "therefor": 1, "thi": [0, 1], "those": 1, "three": [0, 1], "threshold": 1, "through": 1, "tibbl": 1, "tidyr": 1, "top": [0, 1], "toscani": 1, "total": 1, "transform": 1, "tsv": 1, "ture": 1, "twice": 1, "two": 1, "type": 1, "ua": 1, "uncondit": 1, "under": 0, "understand": 1, "unknown": 1, "unless": 1, "unselect": 1, "up": 1, "upload": 1, "us": 0, "user": 1, "util": 1, "v": 1, "v4": 1, "valid": [], "valu": 1, "varianc": 1, "variat": 1, "variou": 1, "ve": 1, "veri": 0, "version": 0, "view": 0, "vignett": 0, "visual": 1, "wai": [0, 1], "want": 1, "warn": [0, 1], "we": 1, "weak": 1, "weight": 1, "well": 1, "what": 1, "whatev": 1, "when": 1, "where": 1, "whether": 1, "which": 0, "while": 1, "wise": 1, "within": [0, 1], "workflow": 1, "would": 1, "ye": 1, "you": [0, 1], "your": 1}, "titles": ["reStructuredText", "MixDeR - Current Version: 1.0"], "titleterms": {"0": 1, "1": 1, "ancestri": 1, "background": 1, "calcul": 1, "creat": 1, "current": 1, "deconvolut": 1, "detail": 1, "euroformix": 1, "exampl": 0, "file": 1, "gedmatch": 1, "instal": 1, "introduct": 0, "mai": 1, "metric": 1, "mixder": 1, "mixtur": 1, "other": 1, "predict": 1, "pro": 1, "report": 1, "requir": 1, "restructuredtext": 0, "run": 1, "tool": 1, "us": 1, "usag": 1, "valid": 1, "version": 1, "which": 1}}) \ No newline at end of file diff --git a/docs/articles/Ancestry_Prediction_Tool.html b/docs/articles/Ancestry_Prediction_Tool.html index 6f03b03..e8a7fc9 100644 --- a/docs/articles/Ancestry_Prediction_Tool.html +++ b/docs/articles/Ancestry_Prediction_Tool.html @@ -20,7 +20,7 @@ mixder - 0.9.0 + 0.10.0 + + + + +
+
+ + + + +
+
+ + + +
+

Background Information +

+

MixDeR has the ability to process much larger custom SNP sets beyond +the Kintelligence kit. A few extra files are required:
+- The allele frequency file containing the SNPs within the panel (please +see Required Files for proper +formatting).
+- A file containing the GRCh37 locations for each SNP, specifically the +rsID, chromosome and position. This is required for creating a database +report, but not required for generating validation metrics.

+

Some things to keep in mind:
+- Updates were made to MixDeR to increase the speed of uploading and +formatting the required files for EuroForMix. However, +the more SNPs included in the panel, the longer it’ll take to upload in +MixDeR (think several hours!). To account for this, files that +can take a long time to format and may be used in future MixDeR runs +(such as the allele frequency file, the file containing reference +genotypes, and the binned SNP mixture data (i.e. SNP set 1, SNP set 2, +etc.)) will be automatically saved as an .rda file. In +future runs, the .rda frequency file can then be specified, +and the references and binned SNP data will be automatically used if +created. A .rda file will take only seconds to load the R +object with the appropriately formatted EFM data instead of the hours it +may take to format a data frame into the required format for EFM.
+- With Kintelligence, the UAS applies both a dynamic and analytical +threshold. If using non-Kintelligence sequencing data, keep in mind +these types of thresholds may not have been applied (or applied +differently). A likely threshold setting should be +staticAT=1 and dynamicAT=0.
+- Please use at your own discretion! While we’ve had success running +several thousand SNPs, please optimize for your own data and SNP +panel!
+- For now, the ancestry prediction tool only uses the Kintelligence +SNPs; while custom SNP data can be uploaded for this module, only the +Kintelligence SNPs will be used.

+
+
+
+

Running Custom SNP sets +

+

If using the GUI, please select the drop-down “Assay†menu as +“Customâ€. Once this is selected, a button will appear to upload the SNP +position information file.

+

If using the R API, please specify the assay="custom" +and snp_pos="/path/to/snp_positions.txt arguments. Further, +the frequency file will need to be provided as well. Below is an example +of the run_mixder_report() function running an +unconditioned deconvolution using the same frequency file (the +.rda file) for both contributors, with no analytical +thresholds applied:

+
run_mixder_report(sample="Sample01", assay="custom", snp_pos="custom_panel_snp_positions.txt", freq_both="AF/1000G_allsnps_efmformatted.rda", staticAT=1, dynamicAT=0)
+

All other settings can be adjusted as wanted, please see the R API page for additional options.

+
+
+
+ + + +
+ + + +
+
+ + + + + + + diff --git a/docs/articles/Installation.html b/docs/articles/Installation.html index f5eaf64..f53838b 100644 --- a/docs/articles/Installation.html +++ b/docs/articles/Installation.html @@ -20,7 +20,7 @@ mixder - 0.9.0 + 0.10.0 + + + + + +
+
+
+ +
+

Convert table to list

+
+ +
+

Usage

+
convert_table_to_list(table)
+
+ +
+

Arguments

+ + +
input
+

table

+ +
+
+

Value

+

list of table

+
+ +
+ + +
+ + + +
+ + + + + + + diff --git a/docs/reference/create_at.html b/docs/reference/create_at.html index 5e3ff3c..a5593d6 100644 --- a/docs/reference/create_at.html +++ b/docs/reference/create_at.html @@ -7,7 +7,7 @@ mixder - 0.9.0 + 0.10.0 + + + + + +
+
+
+ +
+

Read in Frequency tables already formatted for EFM

+
+ +
+

Usage

+
read_in_freq(freq)
+
+ +
+

Arguments

+ + +
freq
+

PATH to frequency table

+ +
+
+

Value

+

list of frequency data in correct EFM list format

+
+ +
+ + +
+ + + +
+ + + + + + + diff --git a/docs/reference/read_in_table.html b/docs/reference/read_in_table.html index 8dd4a8d..5f0de0e 100644 --- a/docs/reference/read_in_table.html +++ b/docs/reference/read_in_table.html @@ -7,7 +7,7 @@ mixder - 0.9.0 + 0.10.0