diff --git a/.travis.yml b/.travis.yml index 8df7829..c4e123b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -27,4 +27,4 @@ before_install: # return to the top directory - popd after_success: - - Rscript -e 'covr::codecov()' + - Rscript -e 'covr::codecov(exclusions = "R/log.R", function_exclusions = c("print\\."))' diff --git a/DESCRIPTION b/DESCRIPTION index 9b26e78..02a687f 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,6 +1,6 @@ Package: admixr Title: An Interface for Running 'ADMIXTOOLS' Analyses -Version: 0.8.7 +Version: 0.9.0 Authors@R: person(given = "Martin", family = "Petr", diff --git a/NAMESPACE b/NAMESPACE index 1f9037b..a62f713 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -15,6 +15,8 @@ export(keep_transitions) export(loginfo) export(merge_eigenstrat) export(qpAdm) +export(qpAdm_filter) +export(qpAdm_rotation) export(qpWave) export(read_geno) export(read_ind) @@ -28,3 +30,4 @@ export(write_ind) export(write_snp) import(rlang) importFrom(magrittr,"%>%") +importFrom(utils,combn) diff --git a/NEWS.md b/NEWS.md index 6624299..245dc8b 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,6 +1,16 @@ +# admixr 0.9.0 + +* New prototype implementation of exhaustive search of qpAdm models + through rotation of sources and outgroups (`qpAdm_rotation()`). +* New tutorial describing the `qpAdm_rotation()` procedure. +* Fixed broken regex parsing of qpWave log files in some cases. +* Added type checking to a couple of functions (not all wrappers type + checked yet). + # admixr 0.8.7 -* qpAdm proportions table now shows p-values and SNP counts (both for overall analysis but also for the target sample). +* qpAdm proportions table now shows p-values and SNP counts (both for + overall analysis but also for the target sample). # admixr 0.8.6 @@ -25,114 +35,124 @@ # admixr 0.8.1 -* Fixed a minor bug in single-source qpAdm analyses. The package now detects - this and correctly handles missing "subsets/patterns" information. +* Fixed a minor bug in single-source qpAdm analyses. The package now + detects this and correctly handles missing "subsets/patterns" + information. # admixr 0.8.0 -Finally resumed the development of _admixr_! Apologies to everyone -for having to wait so long. Thank you for your patience and feedback since the last -release. I hope that things will start moving a little bit faster and we will -reach version 1.0 in the next couple of months. +Finally resumed the development of _admixr_! Apologies to everyone for +having to wait so long. Thank you for your patience and feedback since +the last release. I hope that things will start moving a little bit +faster and we will reach version 1.0 in the next couple of months. New features and improvements: -* New function `loginfo()` which operates on any output object from an admixr wrapper - and shows the full log output (the "log file" in ADMIXTOOLS jargon) - associated with the analysis. It also has options for saving the log file to - a permanent location and to only show a log file for a target sample of interest - (relevant for _qpAdm_ analyses with multiple targets at once). -* _admixr_ can now (hopefully) detect broken/truncated output files generated - by ADMIXTOOLS whenever there is some fatal issue with an analysis. - The package should now detect this and inform the user. -* Removed _qpAdm_ argument `details` - the user will always want to see the - full analysis summary so this option is redundant. It is still kept in - _qpWave_ - for now, until I figure out how useful the full output actually is. - Given the implementation of `loginfo()` above, we might remove the argument - from _qpWave_ too at some point soon. -* The function `download_data()` now fetches data from a more stable location. +* New function `loginfo()` which operates on any output object from an + admixr wrapper and shows the full log output (the "log file" in + ADMIXTOOLS jargon) associated with the analysis. It also has options + for saving the log file to a permanent location and to only show a + log file for a target sample of interest (relevant for _qpAdm_ + analyses with multiple targets at once). +* _admixr_ can now (hopefully) detect broken/truncated output files + generated by ADMIXTOOLS whenever there is some fatal issue with an + analysis. The package should now detect this and inform the user. +* Removed _qpAdm_ argument `details` - the user will always want to + see the full analysis summary so this option is redundant. It is + still kept in _qpWave_ - for now, until I figure out how useful the + full output actually is. Given the implementation of `loginfo()` + above, we might remove the argument from _qpWave_ too at some point + soon. +* The function `download_data()` now fetches data from a more stable + location. # admixr 0.7.1 -* Parse admixture proportions from the "best coefficients" line of the qpAdm - output file. +* Parse admixture proportions from the "best coefficients" line of the + qpAdm output file. # admixr 0.7.0 * Fixed handling of warnings when dealing with duplicated populations. -* 9s are now replaced with NAs in `read_geno()` and `write_geno()`, which makes - it more convenient to write custom analytic code working on data.frames. -* Renamed `qpAdm()` output elements and changed its function signature. +* 9s are now replaced with NAs in `read_geno()` and `write_geno()`, + which makes it more convenient to write custom analytic code working + on data.frames. +* Renamed `qpAdm()` output elements and changed its function + signature. # admixr 0.6.3 -* Rename `keep_transversions()` to `transversions_only()`. The old function is - now deprecated. -* `print.EIGENSTRAT()` now uses a pre-calculated numbers of removed/remaining - sites, instead of calculating them each and every time. -* Printing EIGENSTRAT objects now also shows "group" and "exclude" modifiers - only if present. +* Rename `keep_transversions()` to `transversions_only()`. The old + function is now deprecated. +* `print.EIGENSTRAT()` now uses a pre-calculated numbers of + removed/remaining sites, instead of calculating them each and every + time. +* Printing EIGENSTRAT objects now also shows "group" and "exclude" + modifiers only if present. # admixr 0.6.2 * `read_output()` made public. -* Fixed issues with parsing of mis-formatted ind files (tabs at the ends of - lines etc.). +* Fixed issues with parsing of mis-formatted ind files (tabs at the + ends of lines etc.). # admixr 0.6.1 -* It turned out that dragging along Rcpp and Boost dependencies just for the - VCF -> EIGENSTRAT conversion function causes unnecessary complications in - the installation process. It's not worth having it in the package if it - would be used only by a small fraction of potential users. +* It turned out that dragging along Rcpp and Boost dependencies just + for the VCF -> EIGENSTRAT conversion function causes unnecessary + complications in the installation process. It's not worth having it + in the package if it would be used only by a small fraction of + potential users. - This function has been removed and the `vcf2eigenstrat` program is maintained - in its own repository. + This function has been removed and the `vcf2eigenstrat` program is + maintained in its own repository. # admixr 0.6.0 -* Conversion of VCF to EIGENSTRAT format is now implemented in C++ and should - be approximately infinitely faster than the old conversion function written - in pure R. +* Conversion of VCF to EIGENSTRAT format is now implemented in C++ and + should be approximately infinitely faster than the old conversion + function written in pure R. * Conversion of EIGENSTRAT _into_ VCF has been removed. # admixr 0.5.0 * Added full implementations of `qpWave()` and `qpAdm()` functions. -* `filter_bed()` now implemented simply by calling `bedtools` in the background. - This turned out to be way faster and memory efficient than the previous - data.table-based solution. +* `filter_bed()` now implemented simply by calling `bedtools` in the + background. This turned out to be way faster and memory efficient + than the previous data.table-based solution. # admixr 0.4.1 * Fixed missing `group_labels()` update. -* Removed the huge built-in data set. Implemented `download_data()` function - that fetches the example data set from the web. +* Removed the huge built-in data set. Implemented `download_data()` + function that fetches the example data set from the web. # admixr 0.4.0 -* The package now has a tutorial vignette describing the main functionality. +* The package now has a tutorial vignette describing the main + functionality. * Simple SNP dataset is now included with the package. -* The API of many utility functions has been simplified and their internals - re-written. -* `filter_sites` is now implemented using `data.table` and allows overlap with - an arbitrary BED file. +* The API of many utility functions has been simplified and their + internals re-written. +* `filter_sites` is now implemented using `data.table` and allows + overlap with an arbitrary BED file. # admixr 0.3.0 * All wrappers have been given simpler names (`qpDstat()` -> `d()`, `qpF4ratio()` -> `f4ratio()`, etc). -* F4 statistic can now be calculated using a separate `f4()` function (`f4mode` - parameter remains in the `d()` function though, as `f4()` calls `d()` - internally). -* All tests are performed on Travis CI using installed and compiled ADMIXTOOLS - software. +* F4 statistic can now be calculated using a separate `f4()` function + (`f4mode` parameter remains in the `d()` function though, as `f4()` + calls `d()` internally). +* All tests are performed on Travis CI using installed and compiled + ADMIXTOOLS software. # admixr 0.2.0 * The package now includes qpAdm functionality. -* Formal tests for all implemented wrapper functions have been implemented. +* Formal tests for all implemented wrapper functions have been + implemented. # admixr 0.1.0 diff --git a/R/config_files.R b/R/config_files.R index 954325d..4f507d8 100644 --- a/R/config_files.R +++ b/R/config_files.R @@ -43,6 +43,17 @@ create_qpDstat_pop_file <- function(W = NULL, X = NULL, Y = NULL, Z = NULL, file } +# Generate a file with populations for a qpDstat run based on given +# list of quartets. +create_qpDstat_pop_file_quartets <- function(quartets, file) { + lines <- c() + for (q in quartets) { + lines <- c(lines, sprintf("%s %s %s %s", q[1], q[2], q[3], q[4])) + } + writeLines(lines, file) +} + + # Generate a file with populations for a qp3Pop run. create_qp3Pop_pop_file <- function(A, B, C, file) { lines <- c() diff --git a/R/log.R b/R/log.R index dda1e36..445ac9c 100644 --- a/R/log.R +++ b/R/log.R @@ -18,23 +18,23 @@ loginfo <- function(x, target = NA, save = FALSE, prefix = NA, dir = ".", suffix log_output <- attr(x, "log_output") targets <- names(log_output) - if (!is.na(target) && cmd != "qpAdm") + if (!is.na(target) && !cmd %in% c("qpAdm", "qpAdm_rotation")) stop(glue::glue("Specifying target does not make sense for examining the log output of {cmd}"), call. = FALSE) if (!is.na(target) && !target %in% targets) - stop(glue::glue("Target '{target}' is not present in the output (choices are: {paste(targets, collapse = ', ')})"), + stop(glue::glue("Target/model '{target}' is not present in the output (choices are: {paste(targets, collapse = ', ')})"), call. = FALSE) - # qpAdm's log output are stored as a list of character vectors but everything - # else is simply a character vector - we convert everything to a list to - # iterate over log outputs below + ## qpAdm's log output are stored as a list of character vectors but everything + ## else is simply a character vector - we convert everything to a list to + ## iterate over log outputs below if (!is.list(log_output)) log_output <- list(log_output) for (i in seq_along(log_output)) { - # write only single target log if requested by the user - if (!is.na(target) && !is.null(targets) && target != targets[i]) next + ## write only single target/model log if requested by the user + if (!is.na(target) && !is.null(targets) && target != targets[i]) next if (save) { if (is.na(prefix)) prefix <- cmd @@ -49,14 +49,17 @@ loginfo <- function(x, target = NA, save = FALSE, prefix = NA, dir = ".", suffix } else { if (cmd == "qpAdm") { title <- glue::glue("qpAdm for target '{targets[i]}'") + } else if (cmd == "qpAdm_rotation") { + title <- glue::glue("qpAdm rotation for model '{targets[i]}'") } else { title <- cmd } if (i > 1 && is.na(target)) cat("\n\n") cat(paste0("Full output log of ", title, ":\n")) - cat("==================================================\n\n") + cat("===================================================\n\n") cat(paste(log_output[[i]], collapse = "\n")) + cat("\n") } } } @@ -70,7 +73,7 @@ loginfo <- function(x, target = NA, save = FALSE, prefix = NA, dir = ".", suffix #' #' @export print.admixr_result <- function(x, ...) { - if (attr(x, "command") == "qpAdm") { + if (attr(x, "command") %in% c("qpAdm", "qpAdm_rotation") && length(x) == 3) { print.default(list( proportions = x$proportions, ranks = x$ranks, diff --git a/R/output_parsers.R b/R/output_parsers.R index 7ada10c..e7854af 100644 --- a/R/output_parsers.R +++ b/R/output_parsers.R @@ -171,7 +171,7 @@ read_qpWave <- function(log_lines, details = FALSE) { a_end <- c(test_pos[-c(1, 2)], which(stringr::str_detect(log_lines, "## end of run"))) test_df <- log_lines[test_pos + 1] %>% - stringr::str_replace_all(" *[a-z0-9]+: ", "") %>% + stringr::str_replace_all("[a-z0-9]+: ", "") %>% stringr::str_replace_all(" +", "\t") %>% paste0(collapse = "\n") %>% readr::read_tsv(col_names = c("rank", "df", "chisq", "tail", "dfdiff", diff --git a/R/qpAdm.R b/R/qpAdm.R new file mode 100644 index 0000000..2d5130c --- /dev/null +++ b/R/qpAdm.R @@ -0,0 +1,157 @@ +#' Fit qpAdm models based on the rotation strategy described in +#' Harney et al. 2020 (bioRxiv) +#' +#' @param data EIGENSTRAT dataset +#' @param target Target population that is modeled as admixed +#' @param candidates Potential candidates for sources and outgroups +#' @param minimize Test also all possible subsets of outgroups? (default TRUE) +#' @param nsources Number of sources to pull from the candidates +#' @param ncores Number of CPU cores to utilize for model fitting +#' @param fulloutput Report also 'ranks' and 'subsets' analysis from +#' qpAdm in addition to the admixture proportions results? (default FALSE) +#' +#' @return qpAdm list with proportions, ranks and subsets elements (as +#' with a traditional qpAdm run) or just the proportions +#' (determined by the value of the 'fulloutput' argument) +#' +#' @importFrom utils combn +#' @export +qpAdm_rotation <- function(data, target, candidates, minimize = TRUE, nsources = 2, ncores = 1, fulloutput = FALSE) { + check_type(data, "EIGENSTRAT") + + ## generate combinations of possible sources and outgroups + sources <- t(combn(candidates, nsources)) + sources_outgroups <- unlist(lapply(1:nrow(sources), function(i) { + outgroups <- setdiff(candidates, sources[i, ]) + if (minimize) { + outgroups <- unlist(lapply((nsources + 1):length(outgroups), function(nout) { + outcomb <- t(combn(outgroups, nout)) + lapply(1:nrow(outcomb), function(j) outcomb[j, ]) + }), recursive = FALSE) + } else { + outgroups <- list(outgroups) + } + + lapply(outgroups, function(out) { list(sources = sources[i, ], outgroups = out) }) + }), recursive = FALSE) + + ## run qpAdm for all combinations of sources and outgroups + results_list <- parallel::mclapply(sources_outgroups, function(x) { + result <- qpAdm( + data, + target = target, sources = x$sources, outgroups = x$outgroups + ) + + names(x$sources) <- paste0("source", 1:nsources) + sources_df <- as.data.frame(t(as.matrix(x$sources))) + + ## rename sources and stderr columns (by default, proportion and + ## stderr columns are named based on the source populations - we + ## don't want that here because we want to merge all the individual + ## proportion tables, columns have to have the same name) + names(result$proportions)[2:(1 + nsources)] <- paste0("prop", 1:nsources) + names(result$proportions)[4:(3 + nsources)] <- paste0("stderr", 1:nsources) + ## add source names as two new columns + result$proportions <- cbind(result$proportions, sources_df) %>% + dplyr::mutate(outgroups = paste0(x$outgroups, collapse = " & "), + noutgroups = length(x$outgroups)) + ## rearrange columns + result$proportions <- dplyr::select( + result$proportions, target, names(x$sources), outgroups, noutgroups, pvalue, + dplyr::everything() + ) + + ## reformat rank table + names(result$subsets)[7:(6 + nsources)] <- paste0("prop", 1:nsources) + ## add source names as two new columns + result$subsets <- cbind(result$subsets, sources_df) + result$subsets <- dplyr::select( + result$subsets, target, names(x$sources), pattern, + dplyr::everything() + ) + + result + }, mc.cores = ncores) + + ## extract log information before further processing of the results + log_lines <- sapply(results_list, function(i) attr(i, "log_output")) + names(log_lines) <- paste0("m", seq_along(sources_outgroups)) + + proportions <- dplyr::bind_rows(lapply(results_list, `[[`, "proportions")) + ranks <- dplyr::bind_rows(lapply(results_list, `[[`, "ranks")) + subsets <- dplyr::bind_rows(lapply(results_list, `[[`, "subsets")) + + ## add model identifier to each row in the proportions table, ... + models <- paste0("m", seq_along(sources_outgroups)) + proportions$model <- models + proportions <- dplyr::as_tibble(proportions) %>% dplyr::select(model, dplyr::everything()) + ## ... ranks table, ... + ranks$model <- sort(rep(models, 2)) + ranks <- dplyr::as_tibble(ranks) %>% dplyr::select(model, dplyr::everything()) + ## and subsets table + subsets$model <- sort(rep(models, 1 + 2^(nsources - 1))) + subsets <- dplyr::as_tibble(subsets) %>% dplyr::select(model, dplyr::everything()) + + ## add metadata to the results object + if (fulloutput) + results <- list(proportions = proportions, ranks = ranks, subsets = subsets) + else + results <- proportions + + attr(results, "command") <- "qpAdm_rotation" + attr(results, "log_output") <- log_lines + class(results) <- c("admixr_result", class(results)) + + results +} + + +#' Filter qpAdm rotation results for only 'sensible' models +#' +#' Filter for p-value larger than a specified cuttof and admixture +#' proportions between 0 and 1. +#' +#' @param x Output of a qpAdm_rotation() function +#' @param p p-value cutoff (default 0: will only filter for sensible +#' admixture proportions) +#' +#' @return qpAdm_rotation object filtered down based on p-value +#' +#' @export +qpAdm_filter <- function(x, p = 0.05) { + check_type(x, "admixr_result") + if (attr(x, "command") != "qpAdm_rotation") { + stop("Filtering implemented only for results of the qpAdm rotation procedure", + call. = FALSE) + } + + if (length(x) == 3) + proportions <- x$proportions + else + proportions <- x + + ## get positions of columns with estimated admixture proportions + prop_columns <- stringr::str_which(names(proportions), "prop") + + ## find out rows/models for which all proportions are in [0, 1] and + ## pvalue is larger than the required cutoff + pvalue <- proportions$pvalue > p + constr_0 <- proportions[, prop_columns] >= 0 + constr_1 <- proportions[, prop_columns] <= 1 + constr <- apply(pvalue & constr_0 & constr_1, 1, all) + + ## filter all three sub-tables to only those models that fit the criteria + proportions <- dplyr::arrange(proportions[constr, ], -pvalue) + + if (length(x) == 3) { + x$proportions <- proportions + x$ranks <- x$ranks[x$ranks$model %in% proportions$model, ] + x$subsets <- x$subsets[x$subsets$model %in% proportions$model, ] + ## filter also only to relevant remaining log output information + attr(x, "log_output") <- attr(x, "log_output")[unique(proportions$model)] + return(x) + } else { + attr(proportions, "log_output") <- attr(x, "log_output")[unique(proportions$model)] + return(proportions) + } +} diff --git a/R/utils.R b/R/utils.R index ed34a8f..ebb3d14 100644 --- a/R/utils.R +++ b/R/utils.R @@ -58,6 +58,15 @@ get_files <- function(dir_name, prefix) { +# Check that the provided object is of the required type +check_type <- function(x, type) { + if (!inherits(x, type)) { + stop(glue::glue("Object is not of the type {type}"), call. = FALSE) + } +} + + + # Check for the presence of a given set of labels in an 'ind' file. # Fail if there a sample was not found. check_presence <- function(labels, data) { @@ -95,7 +104,8 @@ download_data <- function(dirname = tempdir()) { utils::globalVariables( names = c("#CHROM", "POS", "ID", "REF", "ALT", "QUAL", "FILTER", "INFO", "FORMAT", "chrom", "pos", "snp_id", "ref", "alt", "gen_dist", - "sample_id", "name", "target", ".", "start", "end"), + "sample_id", "name", "target", ".", "start", "end", + "model", "noutgroups", "outgroups", "pattern", "pvalue"), package = "admixr") diff --git a/R/wrappers.R b/R/wrappers.R index ebe066f..2374a17 100644 --- a/R/wrappers.R +++ b/R/wrappers.R @@ -25,16 +25,25 @@ f4ratio <- function(data, X, A, B, C, O, outdir = NULL, params = NULL) { #' @rdname f4ratio #' #' @param f4mode Calculate the f4 statistic instead of the D statistic. +#' @param quartets List of character vectors (quartets of population/sample labels) #' #' @export -d <- function(data, W, X, Y, Z, outdir = NULL, f4mode = FALSE, params = NULL) { - check_presence(c(W, X, Y, Z), data) +d <- function(data, W, X, Y, Z, quartets = NULL, outdir = NULL, f4mode = FALSE, params = NULL) { + if (is.null(quartets)) { + check_presence(c(W, X, Y, Z), data) + } else { + check_presence(unique(unlist(quartets)), data) + } # get the path to the population, parameter and log files config_prefix <- paste0("qpDstat__", as.integer(stats::runif(1, 0, .Machine$integer.max))) files <- get_files(outdir, config_prefix) - create_qpDstat_pop_file(W, X, Y, Z, file = files[["pop_file"]]) + if (is.null(quartets)) { + create_qpDstat_pop_file(W, X, Y, Z, file = files[["pop_file"]]) + } else { + create_qpDstat_pop_file_quartets(quartets, file = files[["pop_file"]]) + } create_par_file(files, data, params) if (f4mode) { @@ -54,8 +63,8 @@ d <- function(data, W, X, Y, Z, outdir = NULL, f4mode = FALSE, params = NULL) { #' @rdname f4ratio #' #' @export -f4 <- function(data, W, X, Y, Z, outdir = NULL, params = NULL) { - d(data, W, X, Y, Z, outdir, f4mode = TRUE) +f4 <- function(data, W, X, Y, Z, quartets = NULL, outdir = NULL, params = NULL) { + d(data, W, X, Y, Z, quartets, outdir, f4mode = TRUE) } @@ -99,6 +108,11 @@ f3 <- function(data, A, B, C, outdir = NULL, inbreed = FALSE, params = NULL) { #' @export qpAdm <- function(data, target, sources, outgroups, outdir = NULL, params = list(allsnps = "YES", summary = "YES", details = "YES")) { + if (length(outgroups) < length(sources) + 1) { + stop("The number of outgroup samples has to be larger or equal than the number of sources + 1", + call. = FALSE) + } + check_presence(c(target, sources, outgroups), data) results <- lapply(target, function(X) { diff --git a/docs/LICENSE-text.html b/docs/LICENSE-text.html index 5fede6e..c115f0e 100644 --- a/docs/LICENSE-text.html +++ b/docs/LICENSE-text.html @@ -71,7 +71,7 @@ admixr - 0.8.7 + 0.9.0 @@ -94,7 +94,10 @@ diff --git a/docs/LICENSE.html b/docs/LICENSE.html index 6ad743b..2084894 100644 --- a/docs/LICENSE.html +++ b/docs/LICENSE.html @@ -71,7 +71,7 @@ admixr - 0.8.7 + 0.9.0 @@ -94,7 +94,10 @@ diff --git a/docs/articles/index.html b/docs/articles/index.html index 61f71e4..8c4ef07 100644 --- a/docs/articles/index.html +++ b/docs/articles/index.html @@ -71,7 +71,7 @@ admixr - 0.8.7 + 0.9.0 @@ -94,7 +94,10 @@ @@ -130,7 +133,9 @@

All vignettes

-
admixr - Tutorial
+
Fitting qpAdm models with a 'rotation' strategy
+
+
Tutorial and basic overview of the admixr R package
diff --git a/docs/articles/qpAdm_files/figure-html/unnamed-chunk-5-1.png b/docs/articles/qpAdm_files/figure-html/unnamed-chunk-5-1.png new file mode 100644 index 0000000..4313677 Binary files /dev/null and b/docs/articles/qpAdm_files/figure-html/unnamed-chunk-5-1.png differ diff --git a/docs/articles/qpAdm_files/figure-html/unnamed-chunk-7-1.png b/docs/articles/qpAdm_files/figure-html/unnamed-chunk-7-1.png new file mode 100644 index 0000000..1bf07fa Binary files /dev/null and b/docs/articles/qpAdm_files/figure-html/unnamed-chunk-7-1.png differ diff --git a/docs/articles/qpAdm_files/figure-html/unnamed-chunk-8-1.png b/docs/articles/qpAdm_files/figure-html/unnamed-chunk-8-1.png new file mode 100644 index 0000000..4c58ef2 Binary files /dev/null and b/docs/articles/qpAdm_files/figure-html/unnamed-chunk-8-1.png differ diff --git a/docs/articles/qpAdm_files/header-attrs-2.2/header-attrs.js b/docs/articles/qpAdm_files/header-attrs-2.2/header-attrs.js new file mode 100644 index 0000000..dd57d92 --- /dev/null +++ b/docs/articles/qpAdm_files/header-attrs-2.2/header-attrs.js @@ -0,0 +1,12 @@ +// Pandoc 2.9 adds attributes on both header and div. We remove the former (to +// be compatible with the behavior of Pandoc < 2.8). +document.addEventListener('DOMContentLoaded', function(e) { + var hs = document.querySelectorAll("div.section[class*='level'] > :first-child"); + var i, h, a; + for (i = 0; i < hs.length; i++) { + h = hs[i]; + if (!/^h[1-6]$/i.test(h.tagName)) continue; // it should be a header h1-h6 + a = h.attributes; + while (a.length > 0) h.removeAttribute(a[0].name); + } +}); diff --git a/docs/articles/tutorial.html b/docs/articles/tutorial.html index cc2bbb4..671b3c3 100644 --- a/docs/articles/tutorial.html +++ b/docs/articles/tutorial.html @@ -5,13 +5,13 @@ -admixr - Tutorial • admixr +Tutorial and basic overview of the admixr R package • admixr - +