--- title: "QTL Mapping in Multi-Parent Populations" author: "Wenhao Li, Martin Boer, and Bart-Jan van Rossum" date: "`r Sys.Date()`" output: rmarkdown::html_vignette: toc: false number_sections: false bibliography: bibliography.bib link-citations: yes vignette: > %\VignetteIndexEntry{QTL Mapping in Multi-Parent Populations} %\VignetteEncoding{UTF-8} %\VignetteEngine{knitr::rmarkdown} --- ```{r setup, include = FALSE} knitr::opts_chunk$set( collapse = TRUE, comment = "#>", fig.dim = c(7, 4) ) library(statgenMPP) op <- options(width = 90, digits = 3) ``` ## The statgenMPP package The `statgenMPP` package is developed as an easy-to-use package for QTL mapping in biparental and multi-parent populations. The package has many ways of visualizing inputs and results. This vignette describes in detail how to perform the IBD calculations and how to do QTL mapping using the IBD probabilities in a linear mixed model framework, see @Li2021 and @Li2022 for details. The Linear Model Equations are solved using the [LMMsolver](https://biometris.github.io/LMMsolver/) R package [@boer2023]. This will be illustrated using three example data sets. The first data set is a simulated NAM data set with three parents, that is relatively small and runs fast and is mainly used to show the functionality of the package. Then two data sets from literature are used to show the validity of the results from the package, first a maize data set, the dent panel of the EU-NAM maize project (@Giraud2014). The second data set is a barley data set for awn length described in @Liller2017-jz. ## Example simulated data As a first example for performing IBD calculations and QTL mapping for a multi-parent population we use a relatively simple simulated data set. The example contains simulated data for two F4DH populations. The population type F4DH is cross between two parents, followed by 3 generations of selfings, followed by a DH generation, see [statgenIBD](https://biometris.github.io/statgenIBD/) for details. For the first population the parents where A and B, for the second the parents where A and C. The first population consists of 100 individuals, the second of 80 individuals. This is a simple example of a NAM population, having parent A as central parent. The data is simulated with three QTLs, on chromosome 1, 2, and 3. All necessary data for this population is available in the package. Before doing QTL detection we compute IBD probabilities on a grid of positions along the genome. This can be done using the `calcIBDMPP` function in the `statgenMPP` package. To perform IBD calculations a marker file is required for each of the populations. These files should be a tab-delimited file with first column ID identifying the genotype. The following columns should contain marker information. The first rows should contain the parents used in the cross. As an example, the file for the first cross, AxB, starts like this: ```{r simMarkerDat, results='asis', echo=FALSE} simMrkDat <- read.delim(system.file("extdata/multipop", "AxB.txt", package = "statgenMPP")) knitr::kable(simMrkDat[1:4, 1:5]) ``` In this example markers M1_1 and M1_4 are segregating in the AxB cross, M1_2 and M1_3 not.\ A map file is also required. This should also be a tab-delimited file with three columns, "marker name", "chromosome" and "position". The map file cannot contain a header and has to be identical for all crosses. Phenotypic data can be added as a `data.frame` when computing IBD probabilities. Such a `data.frame` should have a first column "genotype" and all other columns have to be numerical. For the simulated NAM population the `data.frame` with phenotypic data starts like this: ```{r simPhenoDat, results='asis', echo=FALSE} simPhenoDat <- read.delim(system.file("extdata/multipop", "AxBxCpheno.txt", package = "statgenMPP")) knitr::kable(head(simPhenoDat)) ``` The phenotypic data only contains a single trait, yield. When performing IBD calculations and specifying phenotypic data, the phenotypic data will be combined with computed IBD probabilities. For this, the genotype specified in the ID column in the marker file(s) will be matched with the genotype in the genotype column in the `data.frame` with phenotypic information. Phenotypic data for all crosses can either be added from a single `data.frame` containing phenotypic data for all crosses, or a `list` of `data.frames` each containing phenotypic data for a single cross. ```{r simIBD} ## Specify files containing markers. # One file for each of the two crosses. markerFiles <- c(system.file("extdata/multipop", "AxB.txt", package = "statgenMPP"), system.file("extdata/multipop", "AxC.txt", package = "statgenMPP")) ## Specify file containing map. # Both crosses use the same map file. mapFile <- system.file("extdata/multipop", "mapfile.txt", package = "statgenMPP") ## Read phenotypic data phenoDat <- read.delim(system.file("extdata/multipop", "AxBxCpheno.txt", package = "statgenMPP")) # Check contents. head(phenoDat) ## Perform IBD calculations. ABCMPP <- calcIBDMPP(crossNames = c("AxB", "AxC"), markerFiles = markerFiles, pheno = phenoDat, popType = "F4DH", mapFile = mapFile, evalDist = 5) ``` With `calcIBDMPP` IBD probabilities are computed on a grid for each of the crosses separately and then combined into a single output object. The value of `evalDist` can be used to specify the maximum distance (in cM) between two evaluation points on the grid. The exact distance depends on the length of the chromosomes. The output is stored in an object of class `gDataMPP` (genomic Data for Multi Parent Populations) in which information about map, markers, and phenotypic data is combined. With the `summary` function we can get some insight in this information. ```{r sumABCMPP} ## Print summary summary(ABCMPP) ``` To get a further idea about the population and the computed IBD probabilities we can visualize the results. First we have a look at structure of the pedigree of the population using `plotType = "pedigree"` to get a general idea of what the design looks like. ```{r plotPABCMPP} ## Plot structure of the pedigree. plot(ABCMPP, plotType = "pedigree") ``` Next we look at the genetic map using `plotType = "genMap"`. This will display the genetic map of the population showing the length of each of the chromosomes and indicating the positions where the IBD probabilities were calculated. Optionally it is possible to highlight one or more markers using `highlight` argument. ```{r plotGABCMPP} ## Plot genetic map. # Highlight marker on chromosome 3 at position 40. plot(ABCMPP, plotType = "genMap", highlight = "EXT_3_40") ``` Finally we visualize the computed IBD probabilities across the genome for a selected genotype using `plotType = "singleGeno"`. This plot will show the IBD probabilities for all parents for all positions on the genome for the selected genotype. ```{r plotSGABCMPP, fig.height=10} ## Plot IBD probabilities for genotype AxB0001. plot(ABCMPP, plotType = "singleGeno", genotype = "AxB0001") ``` Using the computed IBD probabilities we can now do the actual QTL Mapping using the `selQTLMPP` function. First we perform a SQM by setting `maxCofactors = 0`. Our trait of interest, yield, is specified in the `trait` argument of the function. ```{r ABCSQM} ## Perform Single-QTL Mapping. ABCSQM <- selQTLMPP(MPPobj = ABCMPP, trait = "yield", maxCofactors = 0) ``` The results of the SQM can be plotted using the `plot` function with `plotType = "QTLProfile"`. ```{r QPABCSQM} ## Plot QTL Profile for ABC SQM. plot(ABCSQM, plotType = "QTLProfile") ``` Already from the SQM the position of the three simulated QTLs is quite clear. We can get an even better result using MQM. For that, again we use the `selQTLMPP` function. We don't specify the `maxCofactors` to let the algorithm determine the number of cofactors based on the threshold and QTLwindow. As long as new markers are found with a $-log10(p)$ value above `threshold` and the maximum number of cofactors is not reached, a new round of scanning is done. In the new round of scanning the marker with the highest $-log10(p)$ value from the previous round is added to the cofactors. Based on the profile plot for the SQM the `threshold` is set to 3. The `QTLwindow` is not specified and therefore left at its default value of 10cM. ```{r ABCMQM} ## Perform Multi-QTL Mapping. ABCMQM <- selQTLMPP(MPPobj = ABCMPP, trait = "yield", threshold = 3) ``` It is possible to include a polygenic term in the model to control for background genetic information. To do this a list of chromosome specific kinship matrices can either be computed by the `selQTLMPP` function or specified by the user. In the former case it is enough to specify `computeKin = TRUE`. When doing so the kinship matrix is computed by averaging $Z Z^t$ over all markers, where $Z$ is the genotype $\times$ parents matrix with IBD probabilities for the marker. A list of precomputed chromosome specific kinship matrices can be specified in `K`. Note that adding a kinship matrix to the model increases the computation time a lot, especially for large populations. It is advisable to use parallel computing when doing so (see [Parallel computing](#parCom)). ```{r ABCMQM_kin, eval=FALSE} ## Perform Multi-QTL Mapping. # Compute kinship matrices. ABCMQM_kin <- selQTLMPP(MPPobj = ABCMPP, trait = "yield", threshold = 3, computeKin = TRUE) ``` Next we can visualize the results. First we plot the positions of the QTLs found on the genetic map. This will produce a plot similar the the genetic map plot we have seen before, but now the QTLs will be highlighted. This plot can be made by specifying `plotType = "QTLRegion"` ```{r plotQRABCMQM} ## Plot QTL Profile for ABC MQM. plot(ABCMQM, plotType = "QTLRegion") ``` As for the SQM we can also plot the QTL profile. The QTLs found will be highlighted in the profile in red. ```{r plotQPABCMQM} ## Plot QTL Profile for ABC MQM. plot(ABCMQM, plotType = "QTLProfile") ``` It is also possible to plot the size of the parental effects for each of the QTLs found. Positive effects of a parent on the trait will be indicated by shades of red, negative effects by shades of blue. The stronger the color, the larger the effect for the specific parent is. This plot can be made using `plotType = "parEffs"`. ```{r plotPEABCMQM} ## Plot QTL Profile for ABC MQM. plot(ABCMQM, plotType = "parEffs") ``` Finally a combined plot of the QTL profile and the parental can be made. In this plot the two previous plots are plotted above each other with the chromosomes and positions aligned to allow for easily getting an overview of which effect belongs to which QTL in the QTL profile. This plot can be made using `plotType = "QTLProfileExt"`. ```{r plotQPEABCMQM} ## Plot QTL Profile for ABC MQM. plot(ABCMQM, plotType = "QTLProfileExt") ``` A summary of the QTL-analyis gives a short overview containing the total number of markers and the number of QTLs found. Also for all QTL their position on the chromosome is shown as well as the nearest marker on the original map, the explained variance and the effects and the standard errors of all parents. ```{r sumABCMQM} ## Print summary summary(ABCMQM) ``` From the output of `selQTLMPP` the p-Values and effects for all markers can be extracted. They are stored in a `data.table` within the output object. The example below shows how to extract them. The output will contain columns evalPos, chr and pos with name, chromosome number and evaluation position and columns pValue and eff_par1, eff_par2, eff_par.. with the effects of all parents for that marker. ```{r extractABCRes} ## Extract results of QTL mapping. ABCMQMres <- ABCMQM$GWAResult$yield head(ABCMQMres[, 1:8]) ``` It is also possible to only extract the markers that are either QTLs or within the window of one of the selected QTLs, as specified when calling the `selQTLMPP` function. As for the full results, this information in stored in the output as a `data.table` that can be extracted as shown below. The columns in this `data.table` are identical to those in the full results except for an additional column at the end, snpStatus, that shows whether a marker is a QTL or within the window of a QTL. ```{r extractABCQTL} ## Extract QTLs and markers within QTL windows. ABCMQMQTL <- ABCMQM$signSnp$yield head(ABCMQMQTL[, c(2:8, 10)]) ``` ## Example maize As a second example we use data from a maize NAM population described in @Giraud2014. The NAM population consists of 10 bi-parental doubled haploid (DH) crosses with central parent F353. The total population consists of 841 individuals. Several traits were measured in four locations across Europe. We calculated the best linear unbiased estimations (BLUEs) of those traits using the R package [statgenSTA](https://biometris.github.io/statgenSTA/). As an example, we perform QTL mapping for only the mean value of the number of days to silking ("mean_DtSILK") across all locations. The data for this population is available from the package in zipped format. As for the simulated data, before doing QTL detection we first compute IBD probabilities on a grid of positions along the genome. ```{r maizeIBD} ## Define names of crosses. crosses <- paste0("F353x", c("B73", "D06", "D09", "EC169", "F252", "F618", "Mo17", "UH250", "UH304", "W117")) head(crosses) ## Specify files containing crosses. ## Extract them in a temporary directory. tempDir <- tempdir() crossFiles <- unzip(system.file("extdata/maize/maize.zip", package = "statgenMPP"), files = paste0(crosses, ".txt"), exdir = tempDir) ## Specify file containing map. mapFile <- unzip(system.file("extdata/maize/maize.zip", package = "statgenMPP"), files = "map.txt", exdir = tempDir) ## Read phenotypic data. phenoFile <- unzip(system.file("extdata/maize/maize.zip", package = "statgenMPP"), files = "EUmaizePheno.txt", exdir = tempDir) phenoDat <- read.delim(phenoFile) head(phenoDat[, 1:5]) ## Perform IBD calculations. maizeMPP <- calcIBDMPP(crossNames = crosses, markerFiles = crossFiles, pheno = phenoDat, popType = "DH", mapFile = mapFile, evalDist = 5) ``` We then have a look at the summary and some of the plots to get an overview of the pedigree and the computed probabilities. ```{r sumMaizeIBD} ## Print summary summary(maizeMPP) ``` ```{r plotPmaizeIBD} ## Plot structure of the pedigree. plot(maizeMPP, plotType = "pedigree") ``` Now we can use the computed IBD probabilities to perform SQM. ```{r maizeSQM, eval=FALSE} ## Perform Single-QTL Mapping. maizeSQM <- selQTLMPP(MPPobj = maizeMPP, trait = "mean_DtSILK", maxCofactors = 0) ``` We plot the QTL profile for the SQM to get an idea of reasonable values to use for the `threshold` and `QTLwindow` in the MQM that we do next. ```{r QPmaizeSQM} ## Plot QTL Profile for maize SQM. plot(maizeSQM, plotType = "QTLProfile") ``` Based on the plot for the SQM the `threshold` is set to 5 to restrict a bit the number of QTLs that will be detected in the MQM. The `QTLwindow` is not specified and therefore left at its default value of 10cM. ```{r maizeMQM, eval=FALSE} ## Perform Multi-QTL Mapping. maizeMQM <- selQTLMPP(MPPobj = maizeMPP, trait = "mean_DtSILK", threshold = 5) ``` We only look at the combined plot of the QTL Profile and the parental effects. This should give us the most direct insight in the QTLs found and the effects the different parents in the crosses have. ```{r plotQPEmaizeMQM} ## Plot QTL Profile for maize MQM. plot(maizeMQM, plotType = "QTLProfileExt") ``` ## Example barley Instead of performing IBD calculations directly with the package, it is also possible to import IBD probabilities computed using RABBIT software [@Zheng2014; @Zheng2015; @Zheng2018]. The main advantage of using RABBIT for IBD calculations is that it can handle complex pedigree populations and therefore can also be used in cases where the population structure is more complex than those that can be computed using `statgenIBD`, e.g. in the maize NAM population described before. As an example we use a barley population described in @Liller2017-jz. This MPP design consists of 5 parents. Four wild parents were crossed with the cultivar Morex and then backcrossed with Morex once. Individuals from the four families from the backcrosses were then crossed with each other as in a full diallel design, which generated six F6 families through five generations of selfing. The trait of interest for this population is awn length ("Awn_length"). As for the maize NAM population, the data for this population is available in zipped format in the package. RABBIT output can be read using the `readRABBITMPP` function in `statgenMPP`. This has as input the standard RABBIT output summary file and the pedigree file that needs to be provided to RABBIT as well. This pedigree file is an optional input and is only used for plotting the pedigree structure of the population. Without it QTL mapping can still be performed. As for `calcIBDMPP` the phenotypic data has to be provided as a `data.frame`. This `data.frame` has been included in the package. ```{r barleyIBD} ## Specify files containing RABBIT output. ## Extract in a temporary directory. tempDir <- tempdir() inFile <- unzip(system.file("extdata/barley/barley_magicReconstruct.zip", package = "statgenMPP"), exdir = tempDir) ## Specify pedigree file. pedFile <- system.file("extdata/barley/barley_pedInfo.csv", package = "statgenMPP") ## Read phenotypic data. data("barleyPheno") ## read RABBIT output. barleyMPP <- readRABBITMPP(infile = inFile, pedFile = pedFile, pheno = barleyPheno) ``` As for the maize example we can summarize and plot the imported data to get a first idea of its content. ```{r sumPbarleyIBD} ## Summary. summary(barleyMPP) ``` ```{r plotPbarleyIBD} ## Plot structure of the pedigree. plot(barleyMPP, plotType = "pedigree") ``` Performing SQM and MQM for imported RABBIT output works in the same way as for IBD probabilities computed directly in the package. Since a full scan would take long we precomputed the results and included them in the package. ```{r barleyMQM, eval=FALSE} ## Perform Multi-QTL Mapping with threshold 4. barleyMQM <- selQTLMPP(MPPobj = barleyMPP, trait = "Awn_length", threshold = 4) ``` There is a very large QTL on chromosome 7. To be able to more clearly distinguish the differences between the other QTLs they are plotted separately. ```{r QPbarleyMQM16} ## Plot QTL Profile for barley MQM - chromosome 1-6. plot(barleyMQM, plotType = "QTLProfileExt", chr = 1:6) ``` ```{r QPbarleyMQM7} ## Plot QTL Profile for barley MQM - chromosome 7. plot(barleyMQM, plotType = "QTLProfileExt", chr = 7) ``` The QTLs found are very similar in both position, size and effects as described in @Liller2017-jz. This can also be clearly seen by comparing the summary of the MQM with the table of effects in this paper. ```{r sumBarleyMQM} ## Summary. summary(barleyMQM) ``` ## Parallel computing {#parCom} To improve performance, it is possible to use parallel computing to perform the QTL mapping. To do this, a parallel back-end has to be specified by the user, e.g. by using `registerDoParallel` from the `doParallel` package (see the example below). Besides, in the `selQTLMPP` function the argument `parallel` has to be set to `TRUE`. With these settings the computations are done in parallel per chromosome. Of course, this doesn't have an effect on the output. The MQM below gives exactly the same results as the non-parallel one described earlier in the vignette. ```{r ABCMQMPar, eval = FALSE} ## Register parallel back-end with 2 cores. doParallel::registerDoParallel(cores = 2) ## Perform Multi-QTL Mapping. ABCMQM_Par <- selQTLMPP(MPPobj = ABCMPP, trait = "yield", threshold = 3, parallel = TRUE) ``` ## Summary The properties of using statgenMPP for QTL mapping in MPPs have been shown with several examples we demonstrated here. This easy-to-use R package, integrating the HMM method for IBD calculation and the mixed-model approach for QTL mapping, provides us with a general framework to estimate multi-allelic QTL effects in terms of parent origins. ```{r winddown, include = FALSE} options(op) ``` ------------------------------------------------------------------------ ## References