Regional_Push

The genesis tutorial

Jul 23rd, 2021 (edited)
43
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 59.20 KB | None | 0 0
  1. ---
  2. title: "GENESIS tutorial"
  3. output:
  4. html_document:
  5. toc: yes
  6. link-citations: yes
  7. ---
  8.  
  9. # GDS format
  10.  
  11. GDS is Genomic Data Structure, a storage format that can efficiently store genomic data and provide fast random access to subsets of the data. For more information on GDS for sequence data, read the [SeqArray package vignette](https://github.com/zhengxwen/SeqArray/blob/master/vignettes/SeqArrayTutorial.Rmd).
  12.  
  13. ## Convert a VCF to GDS
  14.  
  15. To use the R packages developed at the University of Washington Genetic Analysis Center for sequence data, we first need to convert a VCF file to GDS. (If the file is BCF, use [https://samtools.github.io/bcftools/bcftools.html](bcftools) to convert to VCF.)
  16.  
  17. ```{r vcf2gds, message=FALSE}
  18. library(SeqArray)
  19. # VCF file should be stored in sbgenomics/workspace directory
  20. vcffile <- "AnalysisFiles/1KG_phase3_subset_chr1.vcf.gz"
  21. gdsfile <- "AnalysisFiles/1KG_phase3_subset_chr1.gds"
  22.  
  23. # Convert VCF to GDS
  24. seqVCF2GDS(vcffile, gdsfile, fmt.import="GT", storage.option="LZMA_RA")
  25. ```
  26.  
  27. ## Exploring a GDS file
  28.  
  29. We can interact with the GDS file using the SeqArray package.
  30.  
  31. ```{r}
  32. gds <- seqOpen(gdsfile)
  33. gds
  34. ```
  35.  
  36. The `seqGetData` function is the basic function for reading in data from a GDS file
  37.  
  38. ```{r seqGetData}
  39. # the unique sample identifier comes from the VCF header
  40. sample.id <- seqGetData(gds, "sample.id")
  41. length(sample.id)
  42. head(sample.id)
  43. ```
  44.  
  45. ```{r}
  46. # a unique integer ID is assigned to each variant
  47. variant.id <- seqGetData(gds, "variant.id")
  48. length(variant.id)
  49. head(variant.id)
  50. ```
  51.  
  52. ```{r}
  53. chr <- seqGetData(gds, "chromosome")
  54. head(chr)
  55.  
  56. pos <- seqGetData(gds, "position")
  57. head(pos)
  58.  
  59. id <- seqGetData(gds, "annotation/id")
  60. head(id)
  61. ```
  62.  
  63. There are additional useful functions for summary level data.
  64.  
  65. ```{r ref_freq}
  66. # reference allele frequency of each variant
  67. afreq <- seqAlleleFreq(gds)
  68. head(afreq)
  69. summary(afreq)
  70. ```
  71.  
  72. ```{r}
  73. hist(afreq, breaks=50)
  74. ```
  75.  
  76. We can define a filter on the `gds` object. After using the `seqSetFilter` command, all subsequent reads from the `gds` object are restricted to the selected subset of data, until a new filter is defined or `seqResetFilter` is called.
  77.  
  78. ```{r filter}
  79. seqSetFilter(gds, variant.id=91:100, sample.id=sample.id[1:5])
  80. ```
  81.  
  82. Genotype data is stored in a 3-dimensional array, where the first dimension is always length 2 for diploid genotypes. The second and third dimensions are samples and variants, respectively. The values of the array denote alleles: `0` is the reference allele and `1` is the alternate allele. For multiallelic variants, other alternate alleles are represented as integers `> 1`.
  83.  
  84. ```{r genotypes}
  85. geno <- seqGetData(gds, "genotype")
  86. dim(geno)
  87. geno[,,1:2]
  88. ```
  89.  
  90. ```{r}
  91. geno[,,1:2]
  92. ```
  93.  
  94. The [SeqVarTools package](http://bioconductor.org/packages/SeqVarTools) has some additional functions for interacting with SeqArray-format GDS files. There are functions providing more intuitive ways to read in genotypes.
  95.  
  96. ```{r seqvartools_geno}
  97. library(SeqVarTools)
  98.  
  99. # return genotypes in matrix format
  100. getGenotype(gds)
  101. ```
  102.  
  103. ```{r}
  104. getGenotypeAlleles(gds)
  105. ```
  106.  
  107. ```{r}
  108. refDosage(gds)
  109. altDosage(gds)
  110. ```
  111.  
  112. There are functions to extract variant information.
  113.  
  114. ```{r seqvartools_varinfo}
  115. # look at reference and alternate alleles
  116. refChar(gds)
  117. altChar(gds)
  118. ```
  119.  
  120. ```{r}
  121. # data.frame of variant information
  122. variantInfo(gds)
  123. ```
  124.  
  125. ```{r}
  126. # reset the filter to all variants and samples
  127. seqResetFilter(gds)
  128.  
  129. # how many alleles for each variant?
  130. n <- seqNumAllele(gds)
  131. table(n)
  132.  
  133. # some variants have more than one alternate allele
  134. multi.allelic <- which(n > 2)
  135. altChar(gds)[multi.allelic]
  136. ```
  137.  
  138. ```{r}
  139. # extract a particular alternate allele
  140. # first alternate
  141. altChar(gds, n=1)[multi.allelic]
  142. # second alternate
  143. altChar(gds, n=2)[multi.allelic]
  144. ```
  145.  
  146. ```{r}
  147. # how many variants are biallelic SNVs?
  148. table(isSNV(gds, biallelic=TRUE))
  149. # how many variants are SNVs vs INDELs?
  150. table(isSNV(gds, biallelic=FALSE))
  151. # 11 SNVs are multi-allelic
  152. ```
  153.  
  154. We can also return variant information as a `GRanges` object from the [GenomicRanges package](https://bioconductor.org/packages/release/bioc/manuals/GenomicRanges/man/GenomicRanges.pdf). This format for representing sequence data is common across many Bioconductor packages. Chromosome is stored in the `seqnames` column. The `ranges` column has variant position, which can be a single base pair or a range.
  155.  
  156. ```{r granges}
  157. gr <- granges(gds)
  158. gr
  159. ```
  160.  
  161. Always use the `seqClose` command to close your connection to a GDS file when you are done working with it. Trying to open an already opened GDS will result in an error.
  162.  
  163. ```{r intro_close}
  164. seqClose(gds)
  165. ```
  166.  
  167. ## Exercises
  168.  
  169. 1. Set a filter selecting only multi-allelic variants. Inspect their genotypes using the different methods you learned above. Use the `alleleDosage` method to find dosage for the second (and third, etc.) alternate allele.
  170.  
  171. 2. Use the `hwe` function in SeqVarTools to run a Hardy-Weinberg Equilibrium test on each variant. Identify a variant with low p-value and inspect its genotypes. (Note that the HWE test is only valid for biallelic variants, and will return `NA` for multiallelic variants.)
  172.  
  173. [Solutions](.\Solutions.html#gds-format)
  174.  
  175.  
  176. # Phenotype harmonization
  177.  
  178. To increase your sample set, you may need to combine phenotype data from different studies in order to run a cross-study analysis.
  179. The studies involved may have collected data in different ways, used different protocols or measurement units, or used different cutpoints to determine case status.
  180. The process of manipulating the phenotype data from different studies so that they can be analyzed together is called "phenotype harmonization".
  181.  
  182. In this exercise, we assume that you have
  183. created a phenotype harmonization plan for height,
  184. sent it to members from three studies to perform the harmonization,
  185. and
  186. received a harmonized phenotype file from each study.
  187. We will generate some diagnostic information about the harmonized phenotype.
  188.  
  189. The exercise uses 1000 Genomes data, with simulated phenotypes for study, age, and height.
  190. The example phenotype files shown here are very simplified compared to how actual studies store and organize their their data.
  191.  
  192. In this exercise, we will be using `dplyr` for a lot of the data manipulation, so load it now.
  193.  
  194. ```{r, message = FALSE}
  195. library(dplyr)
  196. ```
  197.  
  198.  
  199. ### Inspect individual study data in R
  200.  
  201. First, read the study phenotype files into R.
  202. In this case, each file is tab-delimited.
  203.  
  204. ```{r}
  205. study_1 <- read.table("AnalysisFiles/pheno_data_study_1.txt", header = TRUE, sep = "\t", as.is = TRUE)
  206. head(study_1)
  207.  
  208. study_2 <- read.table("AnalysisFiles/pheno_data_study_2.txt", header = TRUE, sep = "\t", as.is = TRUE)
  209. head(study_2)
  210.  
  211. study_3 <- read.table("AnalysisFiles/pheno_data_study_3.txt", header = TRUE, sep = "\t", as.is = TRUE)
  212. head(study_3)
  213. ```
  214.  
  215. Look carefully at the output and see if anything looks suspicious.
  216.  
  217. You may have noticed that one of the studies has given their variables slightly different names than the others.
  218. Rename them as appropriate.
  219.  
  220. ```{r}
  221. names(study_2)
  222. ```
  223.  
  224. ```{r}
  225. study_2 <- study_2 %>%
  226. rename(sex = Sex, age = Age, height = Height)
  227. # Check that they are correct.
  228. names(study_2)
  229. ```
  230.  
  231. You'll also want to calculate summaries of the data values to see if anything looks very different than what you expect.
  232.  
  233. ```{r}
  234. summary(study_1$height)
  235.  
  236. ```
  237.  
  238. ```{r}
  239. summary(study_2$height)
  240. ```
  241.  
  242. ```{r}
  243. summary(study_3$height)
  244. ```
  245.  
  246. Here, the values that study_3 has given you don't seem to have the same range as those from study_1 and study_2.
  247. In cases like this, you'll want to follow up with whoever provided the harmonized data to see what's going on.
  248. It could represent an error in calculating the harmonized data values, a true property of the study (e.g., a study containing all children), or something else.
  249. In this case, the values were measured in inches instead of centimeters, so they will need to be converted to centimeters to be compatible with the other studies.
  250.  
  251. ```{r}
  252. study_3 <- study_3 %>%
  253. mutate(height = height * 2.54)
  254. ```
  255.  
  256. Calculate the summary again and compare it to the other studies above.
  257.  
  258. ```{r}
  259. summary(study_3$height)
  260. ```
  261.  
  262. The corrected values look much more similar now.
  263.  
  264. Note that this sort of error is easy to correct, but it is not uncommon to have more subtle issues that need to be addressed when working with phenotype data.
  265. Knowledge of the study design as well as the phenotype area of interest is essential to address them properly.
  266. Additionally, different decisions may need to be made for different analyses based on the specific questions they are trying to answer.
  267.  
  268. ### Compare study values
  269.  
  270. Next we will make some more direct comparisons between the three studies, so we will combine the data into one data frame.
  271.  
  272. First, add a study identifier to the data frame for organizational purposes.
  273.  
  274. ```{r}
  275. study_1$study <- "study_1"
  276. study_2$study <- "study_2"
  277. study_3$study <- "study_3"
  278. ```
  279.  
  280. Combine the three different study data frames into one large data frame for joint analysis.
  281. Double check that all column names are the same.
  282.  
  283. ```{r}
  284. all.equal(names(study_1), names(study_2))
  285. all.equal(names(study_1), names(study_3))
  286. ```
  287.  
  288. ```{r, message=FALSE}
  289. phen <- dplyr::bind_rows(study_1, study_2, study_3)
  290. ```
  291.  
  292.  
  293. We can look at the distribution of phenotype data with text-based reports or with plots.
  294.  
  295. First, inspect distributions with `table` for categorical traits and with `summary` for quantitative traits.
  296. The commads are shown here for study_1, but you should run them for study_2 and study_3 as well to see if you can see any differences.
  297.  
  298. ```{r}
  299. table(study_1$sex)
  300. ```
  301.  
  302. ```{r}
  303. summary(study_1$age)
  304. ```
  305.  
  306. ```{r}
  307. summary(study_1$height)
  308. ```
  309.  
  310. It is also helpful to use plots to inspect the distributions of phenotype data.
  311. Here, we will look at boxplots of height by study.
  312.  
  313. ```{r height_study, message = FALSE}
  314. library(ggplot2)
  315. ggplot(phen, aes(x = study, y = height)) + geom_boxplot()
  316. ```
  317.  
  318. You may also want to see the difference in height when you include both study and sex:
  319.  
  320. ```{r height_study_sex}
  321. ggplot(phen, aes(x = study, fill = sex, y = height)) + geom_boxplot()
  322. ```
  323.  
  324.  
  325. These diagnostics are helpful to get a feel for the data.
  326. They can help you see if one study is vastly different from the others or detect outlier values that you may want to look into further.
  327. Some of the differences could also be accounted for by covariates.
  328.  
  329. ## Using regression models to compare studies
  330.  
  331. The quick diagnostics in the previous section let you see if the data from one study are completely different from the others, but such differences could be due to other factors that could be adjusted for in analysis.
  332. To account for these other factors, we need to fit a statistical model to the data.
  333. In this case, because the phenotype is quantitative, we will use a linear regression model.
  334.  
  335. We use the `GENESIS` R package for fitting the regression model.
  336. It is also the same package that we use for the association analyses, so this exercise provides a brief introduction to the package and some of the associated data structures.
  337.  
  338. ### Create an Annotated Data Frame
  339.  
  340. The first step in fitting the regression model is to create an AnnotatedDataFrame.
  341. This data structure is provided by the Bioconductor [Biobase package](https://www.bioconductor.org/packages/release/bioc/manuals/Biobase/man/Biobase.pdf), and it contains both the data and metadata.
  342. You should include a description of each variable in the metadata.
  343.  
  344. ```{r, message=FALSE}
  345. library(Biobase)
  346. metadata <- data.frame(labelDescription = c(
  347. "subject identifier",
  348. "subject's sex",
  349. "age at measurement of height",
  350. "subject's height in cm",
  351. "study identifier"
  352. ))
  353.  
  354. annot <- AnnotatedDataFrame(phen, metadata)
  355.  
  356. # access the data with the pData() function
  357. head(pData(annot))
  358.  
  359. ```
  360.  
  361. ```{r}
  362. # access the metadata with the varMetadata() function
  363. varMetadata(annot)
  364. ```
  365.  
  366. Save the AnnotatedDataFrame for future use.
  367.  
  368. ```{r}
  369. save(annot, file = "OutputFiles/phenotype_annotation.RData")
  370. ```
  371.  
  372. The `GENESIS` code to fit the regression model also requires a `sample.id` column.
  373. Typically the `sample.id` column represents a sample identifier, not a subject id.
  374. In this case, we are only working with subject-level data, so we can use the subject identifier as the sample identifier for model-fitting purposes.
  375.  
  376. ```{r}
  377. annot$sample.id <- annot$subject_id
  378. ```
  379.  
  380.  
  381. ### Fit a regression model without study
  382.  
  383. We will first fit a regression model that allows us to see if the mean of the height phenotype is different by study after adjusting for other covariates.
  384. In this case, we will adjust for age and sex, but not for study, because we are interested in seeing differences in mean height by study. We use the `fitNullModel` function from the GENESIS package -- the name "null model" comes from association testing context and will be explained later.
  385.  
  386. ```{r, message = FALSE}
  387. outcome <- "height"
  388. covars <- c("sex", "age")
  389.  
  390. library(GENESIS)
  391. mod_1 <- GENESIS::fitNullModel(annot, outcome = outcome, covars = covars)
  392. ```
  393.  
  394. The output of `fitNullModel` is a list with a number of named elements
  395.  
  396. ```{r}
  397. names(mod_1)
  398. ```
  399.  
  400. The elements that we will work with in this exercise are:
  401.  
  402. * `converged`: an indicator of whether the model successfully converged
  403. * `model.matrix`: The matrix of subject-covariate values used to fit the model
  404. * `fixef`: The fitted fixed effects
  405. * `betaCov`: The covariance of the fitted fixed effects
  406. * `fit`: A data frame containing information about the fit, in particular:
  407. * `resid.marginal`: The (marginal) residuals from the model, which have been adjusted for the fixed effects but not for the covariance structure
  408. * `varComp`: The fitted variance components
  409.  
  410. Make sure the model converged.
  411.  
  412. ```{r}
  413. mod_1$converged
  414. ```
  415.  
  416. Now, add the residuals to the phenotype data frame for plotting.
  417. We need to make sure that we are matching each residual value to the correct subject.
  418. In this case, `model.matrix` is already in the same order as the input AnnotatedDataFrame, but this may not always be the case (for example, if subjects are excluded due to missing phentoype data).
  419. To match the same subject's values together, we use the rownames of the `fit` data frame to match to the `subject_id` column of the annotated data frame.
  420. We then match the row names (and therefore the residuals) to the sample identifier in the phenotype file using the base R function `match`.
  421.  
  422. ```{r}
  423. j <- match(annot$sample.id, rownames(mod_1$fit))
  424. annot$residuals <- mod_1$fit$resid.marginal[j]
  425. ```
  426.  
  427. Next, we want to check if the different studies have the same mean height after adjustment for other covariates (here, age and sex).
  428. We will first do this qualitatively by making a boxplot of the residuals by study.
  429.  
  430. ```{r resid_1}
  431. ggplot(pData(annot), aes(x = study, y = residuals)) + geom_boxplot()
  432. ```
  433.  
  434. From the boxplot, it is clear that the different studies have different mean heights, even after adjustment for sex and age.
  435. At this point, you would need to determine if the differences are acceptable for use in a combined analysis.
  436.  
  437. ### Fit a model with study
  438.  
  439. Next, we can look at a model that adjusts for other covariates as well as study.
  440. This model allows us to run a statistical test on the fitted study means and to qualitatively check if the variances are the same after adjusting for mean effects.
  441. The outcome is the same, but we now add the study as a covariate.
  442. We also allow for group-specific residual variance by study using the `group.var` argument to `fitNullModel`.
  443.  
  444. ```{r, results = 'hide', message = FALSE}
  445. # include the study in the covariates
  446. covars <- c("age", "sex", "study")
  447.  
  448. mod_2 <- GENESIS::fitNullModel(annot, outcome = outcome, covars = covars,
  449. group.var = "study")
  450. ```
  451.  
  452. The `fixef` element now includes effects for study:
  453. ```{r}
  454. mod_2$fixef
  455. ```
  456.  
  457. The regression model also shows the differences in mean height by study.
  458.  
  459. Finally, we want to check if the height distributions from the different studies have the same variance.
  460. Start by looking at the variance components (`varComp`) element of the model.
  461.  
  462. ```{r}
  463. mod_2$varComp
  464. ```
  465.  
  466. The variance components (`V_study_1`, `V_study_2`, and `V_study_3`) represent the residual variance in each study.
  467. The fitted values of the variance components are different for the different studies, indicating that the distributions of height in the three studies have different variance even after accounting for the other covariates.
  468.  
  469. We can also show the same information by plotting the residuals by study.
  470. We first have to add the residuals from this model to the AnnotatedDataFrame.
  471.  
  472. ```{r}
  473. annot$residuals <- mod_2$fit$resid.marginal[match(annot$sample.id, rownames(mod_2$fit))]
  474. ```
  475.  
  476. Next make a boxplot of the residuals by study.
  477.  
  478. ```{r resid_2}
  479. ggplot(pData(annot), aes(x = study, y = residuals)) +
  480. geom_boxplot()
  481. ```
  482.  
  483. Both methods of looking at the variance components indicate that study 1 has a smaller residual variance than the others.
  484.  
  485. ## Final considerations
  486.  
  487. We have determined that the different studies have both different mean and different variance by study for height.
  488. Before performing genotype-phenotype association tests with these data, you would need to think carefully about whether the phenotype is homogeneous enough to be analyzed together.
  489. In some cases, there may be a valid reason for different means or variances, for example:
  490.  
  491. * different heights in different study populations, such as a study composed primarily of Asian participants vs. a study with primarily European participants or a study of all men vs. a study of all women;
  492. * possible secular trends in height, such as comparing the Framingham Original cohort from ~1950 to a cohort from the present day.
  493.  
  494. In other cases, there may be good reasons to exclude one or more studies, for example:
  495.  
  496. * a systematic measurement error in one study
  497. * miscalculation or misinterpretation of the harmonization algorithm
  498. * study populations that are too different to be compared, such as trying to include a study composed primarily of children with one composed of adults in a height analysis
  499.  
  500. It may be necessary to look at other variables that you had not previously considered.
  501. Studies may have used different measurement equipment or calibrated their data differently.
  502. There might also be other batch effects due to lab procedures or assays that could result in differences in the variance or mean by study.
  503. The other variables that you may need to consider are highly dependent both on the phenotype being harmonized and on how a given study has been designed.
  504.  
  505. Unfortunately there is no single set of guidelines you can use to decide how to proceed with analysis of a phenotype.
  506. It is necessary to involve both domain experts and study experts to determine whether the phenotype is homogeneous enough to use in cross-study analysis.
  507.  
  508.  
  509. # Association tests - Part I
  510.  
  511. These exercises introduce genetic association testing: how to identify which genetic variants are associated with a phenotype. In this example, we will test for an association between variant genotypes and height, adjusting for sex, age, and study. Here, we introduce fitting the "null model" and single-variant association testing, as is commonly performed in GWAS (Genome Wide Association Studies).
  512.  
  513. ## Null model
  514.  
  515. The first step in our association testing procedure is to fit the null model -- i.e., a model fit under the null hypothesis of no individual variant association. Operationally, this is fitting a regression model with the desired outcome phenotype, fixed effect covariates, and random effects.
  516.  
  517. ### Prepare the data
  518.  
  519. To fit the null model, we will need to create an `AnnotatedDataFrame` with sample information and phenotype data; this class is defined in the Biobase package. We will merge our sample annotation file, which is indexed by a `sample.id` column matched to the GDS file, with our phenotype file, which is indexed by a `subject_id` column. We will use the [dplyr](http://dplyr.tidyverse.org) package for data.frame manipulation.
  520.  
  521. NOTE: In this example, we use the 1000 Genomes IDs for both sample and subject IDs, though we would generally advise using separate IDs for samples (sequencing instances) and subjects (individuals).
  522.  
  523. ```{r null_model, message = FALSE}
  524. # sample annotation
  525. sampfile <- "AnalysisFiles/sample_annotation.RData"
  526. samp <- get(load(sampfile))
  527.  
  528. library(Biobase)
  529. # access the data with the pData() function
  530. head(pData(samp))
  531. ```
  532.  
  533. ```{r}
  534. # access the metadata with the varMetadata() function
  535. varMetadata(samp)
  536. ```
  537.  
  538. ```{r}
  539. # phenotype data
  540. phenfile <- "AnalysisFiles/phenotype_annotation.RData"
  541. phen <- get(load(phenfile))
  542.  
  543. # access the data with the pData() function
  544. head(pData(phen))
  545. ```
  546.  
  547. ```{r}
  548. # access the metadata with the varMetadata() function
  549. varMetadata(phen)
  550. ```
  551.  
  552. ```{r}
  553. # merge sample annotation with phenotype data
  554. library(dplyr)
  555. dat <- pData(samp) %>%
  556. left_join(pData(phen), by=c("subject.id"="subject_id", "sex"="sex"))
  557. head(dat)
  558.  
  559. # merge the metadata
  560. meta <- bind_rows(varMetadata(samp), varMetadata(phen)[3:5,,drop=FALSE])
  561.  
  562. # make an AnnotatedDataFrame
  563. annot <- AnnotatedDataFrame(dat, meta)
  564. save(annot, file="OutputFiles/sample_phenotype_annotation.RData")
  565. ```
  566.  
  567. ### Fit the null model
  568.  
  569. We use the `fitNullModel` function from the GENESIS package to fit the null model. We need to specify the outcome (height) and the fixed effect covariates (sex, age, and study). If the sample set involves multiple distinct groups with different variances for the phenotype, we recommend allowing for heterogeneous residual variance among groups with the `group.var` parameter. We saw in a previous exercise that the variance of height differs by study.
  570. We will test for an association between genotype and height, adjusting for sex, age, and study as covariates. If the sample set involves multiple distinct groups with different variances for the phenotype, we recommend allowing the model to use heterogeneous variance among groups with the parameter *group.var*. We saw in a previous exercise that the variance differs by study.
  571.  
  572. ```{r null_model_fit}
  573. library(GENESIS)
  574.  
  575. # fit the null model
  576. nullmod <- fitNullModel(annot,
  577. outcome="height",
  578. covars=c("sex", "age", "study"),
  579. group.var="study",
  580. verbose=FALSE)
  581. save(nullmod, file="OutputFiles/null_model.RData")
  582. ```
  583.  
  584. The `fitNullModel` function returns a lot of information about the model that was fit. We examine some of that information below; to see all of the components, try `names(nullmod)`.
  585.  
  586. ```{r}
  587. # description of the model we fit
  588. nullmod$model
  589. ```
  590.  
  591. ```{r}
  592. # fixed effect regression estimates
  593. nullmod$fixef
  594. ```
  595.  
  596. ```{r}
  597. # residual variance estimates by group.var
  598. nullmod$varComp
  599. ```
  600.  
  601. ```{r}
  602. # model fit: fitted values, residuals
  603. head(nullmod$fit)
  604. ```
  605.  
  606. ```{r}
  607. # plot the residuals vs the fitted values
  608. library(ggplot2)
  609. ggplot(nullmod$fit, aes(x = fitted.values, y = resid.marginal)) +
  610. geom_point(alpha = 0.5) +
  611. geom_hline(yintercept = 0) +
  612. geom_smooth(method = 'lm')
  613. ```
  614.  
  615. ## Exercise
  616.  
  617. 1. As discussed in the lecture, we recommend a fully adjusted two-stage inverse Normalization procedure for fitting the null model when phenotypes have non-Normal distributions. Using the `two.stage` option in `fitNullModel`, fit a two-stage null model. Compare these residuals with the residuals from the original null model.
  618.  
  619. [Solutions](.\Solutions.html#association-tests---part-i)
  620.  
  621. ## Single-variant association tests
  622.  
  623. After fitting our null model, we can use score tests to test each variant across the genome individually for association with the outcome phenotype (i.e. height in our example). Performing these single-variant tests genome-wide is commonly referred to as a GWAS (Genome-Wide Association Study).
  624.  
  625. We use the `assocTestSingle` function in GENESIS. First, we have to create a `SeqVarData` object including both the GDS file and the sample annotation containing phenotype data. We then create a `SeqVarBlockIterator` object, which breaks the set of all variants in the `SeqVarData` object into blocks, allowing us to analyze genome-wide in manageable pieces. The `assocTestSingle` function iterates over all blocks of variants in the `SeqVarBlockIterator` object and then concatenates and returns the results.
  626.  
  627. ```{r assoc_single, message = FALSE}
  628. library(SeqVarTools)
  629. gdsfile <- "AnalysisFiles/1KG_phase3_subset_chr1.gds"
  630. gdsfmt::showfile.gds(closeall=TRUE) # make sure file is not already open
  631. gds <- seqOpen(gdsfile)
  632.  
  633. # make the seqVarData object
  634. seqData <- SeqVarData(gds, sampleData=annot)
  635.  
  636. # make the iterator object
  637. iterator <- SeqVarBlockIterator(seqData, verbose=FALSE)
  638. iterator
  639. ```
  640.  
  641. ```{r}
  642. # run the single-variant association test
  643. assoc <- assocTestSingle(iterator, nullmod)
  644. dim(assoc)
  645. ```
  646.  
  647. ```{r}
  648. head(assoc)
  649. ```
  650.  
  651. We make a QQ plot to examine the results.
  652.  
  653. ```{r assoc_single_qq}
  654. library(ggplot2)
  655. qqPlot <- function(pval) {
  656. pval <- pval[!is.na(pval)]
  657. n <- length(pval)
  658. x <- 1:n
  659. dat <- data.frame(obs=sort(pval),
  660. exp=x/n,
  661. upper=qbeta(0.025, x, rev(x)),
  662. lower=qbeta(0.975, x, rev(x)))
  663.  
  664. ggplot(dat, aes(-log10(exp), -log10(obs))) +
  665. geom_line(aes(-log10(exp), -log10(upper)), color="gray") +
  666. geom_line(aes(-log10(exp), -log10(lower)), color="gray") +
  667. geom_point() +
  668. geom_abline(intercept=0, slope=1, color="red") +
  669. xlab(expression(paste(-log[10], "(expected P)"))) +
  670. ylab(expression(paste(-log[10], "(observed P)"))) +
  671. theme_bw()
  672. }
  673.  
  674. qqPlot(assoc$Score.pval)
  675. ```
  676.  
  677. A lot of the variants we tested are very rare -- the alternate allele is not observed for many samples. Single-variant tests do not perform well for very rare variants (we will discuss testing rare variants in more detail in the next session). We can use the minor allele count (MAC) observed in the sample to filter rare variants that we may expect to have unreliable test results.
  678.  
  679. ```{r mac}
  680. summary(assoc$MAC)
  681. sum(assoc$MAC < 5)
  682. ```
  683.  
  684. ```{r}
  685. qqPlot(assoc$Score.pval[assoc$MAC >= 5])
  686. ```
  687.  
  688. We should expect the majority of variants to fall near the red `y=x` line in the QQ plot. The deviation above the line, commonly referred to as "inflation" is indicative of some model issue. In this example, the issue is likely driven by the fact that we've ignored genetic ancestry and relatedness among these subjects -- more to come later when we discuss mixed models.
  689.  
  690. ```{r assoc_close1}
  691. # close the GDS file!
  692. seqClose(seqData)
  693. ```
  694.  
  695. ## Exercise
  696.  
  697. 2. GENESIS also supports testing binary (e.g. case/control) outcomes. We can fit a null model using logistic regression by specifying the argument `family=binomial` in the `fitNullModel` function. Use the `status` column in the sample annotation to fit a null model for simulated case/control status, with `sex` and `Population` as covariates. Run single-variant association tests using this model and make a QQ plot of all variants with MAC >= 5.
  698.  
  699. [Solutions](.\Solutions.html#association-tests---part-i)
  700.  
  701.  
  702. # Association tests - Part II
  703.  
  704. These exercises continue the introduction to genetic association testing. Here, we introduce multiple-variant association tests, which are commonly used for testing rare variants in aggregate.
  705.  
  706. ## Sliding window tests
  707.  
  708. We can perform burden, SKAT, SKAT-O, fastSKAT, and SMMAT tests using the GENESIS function `assocTestAggregate`. First, we need to load the null model and `AnnotatedDataFrame` (sample annotation + phenotype data) that we created in the previous session, and we need to create our `SeqVarData` object linking the GDS file to the `AnnotatedDataFrame`.
  709.  
  710. ```{r assoc_window_load, message=FALSE}
  711. # load our null model
  712. nullmodfile <- "AnalysisFiles/null_model.RData"
  713. nullmod <- get(load(nullmodfile))
  714. ```
  715.  
  716. ```{r}
  717. # load our sample annotation
  718. annotfile <- "AnalysisFiles/sample_phenotype_annotation.RData"
  719. annot <- get(load(annotfile))
  720. ```
  721.  
  722. ```{r}
  723. # open the GDS file
  724. library(SeqVarTools)
  725. gdsfile <- "AnalysisFiles/1KG_phase3_subset_chr1.gds"
  726. gdsfmt::showfile.gds(closeall=TRUE) # make sure file is not already open
  727. gds <- seqOpen(gdsfile)
  728.  
  729. # make the seqVarData object
  730. seqData <- SeqVarData(gds, sampleData=annot)
  731. ```
  732.  
  733. ### Burden test
  734.  
  735. First, we perform a burden test. We restrict the test to variants with alternate allele frequency < 0.1. (For real data, this threshold would be lower, perhaps 0.05 or 0.01.) We use a flat weighting scheme -- i.e. every variant gets the same weight. We define a sliding window across the genome using a `SeqVarWindowIterator` object.
  736.  
  737. ```{r assoc_window_burden}
  738. # make the window iterator object
  739. iterator <- SeqVarWindowIterator(seqData, windowSize=10000, windowShift=5000, verbose=FALSE)
  740.  
  741. # run the burden test
  742. library(GENESIS)
  743. assoc <- assocTestAggregate(iterator,
  744. nullmod,
  745. test="Burden",
  746. AF.max=0.1,
  747. weight.beta=c(1,1),
  748. verbose = FALSE)
  749. ```
  750.  
  751. ```{r assoc_window_output}
  752. names(assoc)
  753. ```
  754.  
  755. The function returns the primary results for each window in one table.
  756.  
  757. ```{r}
  758. # results for each window
  759. head(assoc$results)
  760. ```
  761.  
  762. ```{r}
  763. # how many variants in each window?
  764. table(assoc$results$n.site)
  765. ```
  766.  
  767. It also returns a list of tables that contain the variant details for each window tested.
  768.  
  769. ```{r}
  770. # variant details for windows with > 1 variant
  771. idx <- which(assoc$results$n.site > 1)
  772. head(assoc$variantInfo[idx])
  773. ```
  774.  
  775. We can make a QQ plot of the burden p-values from the main results table.
  776.  
  777. ```{r assoc_burden_qq}
  778. library(ggplot2)
  779. qqPlot <- function(pval) {
  780. pval <- pval[!is.na(pval)]
  781. n <- length(pval)
  782. x <- 1:n
  783. dat <- data.frame(obs=sort(pval),
  784. exp=x/n,
  785. upper=qbeta(0.025, x, rev(x)),
  786. lower=qbeta(0.975, x, rev(x)))
  787.  
  788. ggplot(dat, aes(-log10(exp), -log10(obs))) +
  789. geom_line(aes(-log10(exp), -log10(upper)), color="gray") +
  790. geom_line(aes(-log10(exp), -log10(lower)), color="gray") +
  791. geom_point() +
  792. geom_abline(intercept=0, slope=1, color="red") +
  793. xlab(expression(paste(-log[10], "(expected P)"))) +
  794. ylab(expression(paste(-log[10], "(observed P)"))) +
  795. theme_bw()
  796. }
  797.  
  798. # make a QQ plot of the burden test p-values
  799. qqPlot(assoc$results$Score.pval)
  800. ```
  801.  
  802. ### SKAT test
  803.  
  804. We can also perform a SKAT test. This time, we will use the Wu weights, which give larger weights to rarer variants.
  805.  
  806. ```{r assoc_window_skat, message = FALSE}
  807. # reset the iterator to the first window
  808. resetIterator(iterator)
  809.  
  810. # run the SKAT test
  811. assoc <- assocTestAggregate(iterator,
  812. nullmod,
  813. test="SKAT",
  814. AF.max=0.1,
  815. weight.beta=c(1,25),
  816. verbose = FALSE)
  817. ```
  818.  
  819. ```{r}
  820. # results for each window
  821. head(assoc$results)
  822. ```
  823.  
  824. ```{r}
  825. # variant details for windows with > 1 variant
  826. idx <- which(assoc$results$n.site > 1)
  827. head(assoc$variantInfo[idx])
  828. ```
  829.  
  830. ```{r}
  831. # make a QQ plot of the SKAT test p-values
  832. qqPlot(assoc$results$pval)
  833. ```
  834.  
  835. ```{r assoc_close2}
  836. seqClose(seqData)
  837. ```
  838.  
  839.  
  840. ## Exercise
  841.  
  842. 1. Perform a sliding window SKAT test for the outcome status. Adjust your model for the covariates sex and study. When performing your SKAT test, use all variants with alternate allele frequency < 20%, and use the Wu weights to give larger weights to rarer variants. Use the same `windowSize` and `windowShift` as in the examples. How many windows have >1 variant? Make a QQ plot of the SKAT p-values.
  843.  
  844. [Solutions](.\Solutions.html#association-tests---part-ii)
  845.  
  846.  
  847. # Ancestry and Relatedness Inference
  848.  
  849. ## LD-pruning
  850.  
  851. We generally advise that population structure and relatedness inference be performed using a set of (nearly) independent genetic variants. To find this set of variants, we perform linkage-disequilibrium (LD) pruning on the study sample set. We typically use an LD threshold of `r^2 < 0.1` to select variants.
  852.  
  853. ```{r ld-pruning, message = FALSE}
  854. library(SeqArray)
  855. gdsfile <- "AnalysisFiles/1KG_phase3_subset.gds"
  856. gdsfmt::showfile.gds(closeall=TRUE) # make sure file is not already open
  857. gds <- seqOpen(gdsfile)
  858.  
  859. # run LD pruning
  860. library(SNPRelate)
  861. set.seed(100) # LD pruning has a random element; so make this reproducible
  862. snpset <- snpgdsLDpruning(gds,
  863. method="corr",
  864. slide.max.bp=10e6,
  865. ld.threshold=sqrt(0.1))
  866. ```
  867.  
  868. ```{r}
  869. # how many variants on each chr?
  870. sapply(snpset, length)
  871. ```
  872.  
  873. ```{r}
  874. # get the full list of LD-pruned variants
  875. pruned <- unlist(snpset, use.names=FALSE)
  876. length(pruned)
  877. save(pruned, file = "OutputFiles/ld_pruned_variants.RData")
  878. ```
  879.  
  880. ## Computing a GRM
  881.  
  882. We can use the [SNPRelate package](https://github.com/zhengxwen/SNPRelate) to compute a Genetic Relationship matrix (GRM). A GRM captures genetic relatedness due to both distant ancestry (i.e. population structure) and recent kinship (i.e. family structure) in a single matrix.
  883.  
  884. SNPRelate offers several algorithms for computing a GRM, including the commonly-used GCTA [Yang et al 2011](https://www.ncbi.nlm.nih.gov/pubmed/21167468). The most recent algorithm added to the package is "IndivBeta" [Weir and Goudet 2017](https://www.ncbi.nlm.nih.gov/pubmed/28550018).
  885.  
  886. ```{r grm}
  887. # compute the GRM
  888. library(SNPRelate)
  889. grm <- snpgdsGRM(gds, method="GCTA", snp.id = pruned)
  890. ```
  891.  
  892. ```{r}
  893. names(grm)
  894. dim(grm$grm)
  895. ```
  896.  
  897. ```{r}
  898. # look at the top corner of the matrix
  899. grm$grm[1:5,1:5]
  900. ```
  901.  
  902. ## De-convoluting ancestry and relatedness
  903.  
  904. To disentangle distant ancestry (i.e. population structure) from recent kinship (i.e. familial relatedness), we implement the analysis described in [Conomos et al., 2016](https://www.cell.com/ajhg/fulltext/S0002-9297(15)00496-6). This approach uses the [KING](http://www.ncbi.nlm.nih.gov/pubmed/20926424), [PC-AiR](http://www.ncbi.nlm.nih.gov/pubmed/25810074), and [PC-Relate](http://www.ncbi.nlm.nih.gov/pubmed/26748516) methods.
  905.  
  906. ### KING
  907.  
  908. Step 1 is to get initial kinship estimates using [KING-robust](http://www.ncbi.nlm.nih.gov/pubmed/20926424), which is robust to discrete population structure but not ancestry admixture. KING-robust will be able to identify close relatives (e.g. 1st and 2nd degree) reliably, but may identify spurious pairs or miss more distant pairs of relatives in the presence of admixture. KING is available as its own software, but the KING-robust algorithm is also available in SNPRelate.
  909.  
  910. ```{r king}
  911. # run KING-robust
  912. king <- snpgdsIBDKING(gds, snp.id=pruned)
  913. ```
  914.  
  915. ```{r}
  916. names(king)
  917. dim(king$kinship)
  918. ```
  919.  
  920. ```{r}
  921. kingMat <- king$kinship
  922. colnames(kingMat) <- rownames(kingMat) <- king$sample.id
  923.  
  924. # look at the top corner of the matrix
  925. kingMat[1:5,1:5]
  926.  
  927. save(kingMat, file = "OutputFiles/king_matrix.RData")
  928. ```
  929.  
  930. We extract pairwise kinship estimates and IBS0 values (the proportion of variants for which the pair of indivdiuals share 0 alleles identical by state) to plot.
  931.  
  932. ```{r king_plot}
  933. kinship <- snpgdsIBDSelection(king)
  934. head(kinship)
  935. ```
  936.  
  937. We use a hexbin plot to visualize the relatedness for all pairs of samples.
  938.  
  939. ```{r}
  940. library(ggplot2)
  941. ggplot(kinship, aes(IBS0, kinship)) +
  942. geom_hline(yintercept=2^(-seq(3,9,2)/2), linetype="dashed", color="grey") +
  943. geom_hex(bins = 100) +
  944. ylab("kinship estimate") +
  945. theme_bw()
  946. ```
  947.  
  948. We see a few parent-offspring, full sibling, 2nd degree, and 3rd degree relative pairs. The abundance of negative estimates represent pairs of individuals who have ancestry from different populations -- the magnitude of the negative relationship is informative of how different their ancestries are; more on this below.
  949.  
  950. ### PC-AiR
  951.  
  952. The next step is [PC-AiR](http://www.ncbi.nlm.nih.gov/pubmed/25810074), which provides robust population structure inference in samples with kinship and pedigree structure. PC-AiR is available in the GENESIS package via the function `pcair`.
  953.  
  954. First, PC-AiR partitions the full sample set into a set of mutually unrelated samples that is maximally informative about all ancestries in the sample (i.e. the unrelated set) and their relatives (i.e. the related set). We use a 3rd degree kinship threshold (`kin.thresh = 2^(-9/2)`), which corresponds to first cousins -- this defines anyone less related than first cousins as "unrelated". We use the negative KING-robust estimates as "ancestry divergence" measures (`divMat`) to identify pairs of samples with different ancestry -- we preferentially select individuals with many negative estimates for the unrelated set to ensure ancestry representation. For now, we also use the KING-robust estimates as our kinship measures (`kinMat`); more on this below.
  955.  
  956. Once the unrelated and related sets are identified, PC-AiR performs a standard Principal Component Analysis (PCA) on the unrelated set of individuals and then projects the relatives onto the PCs. Under the hood, PC-AiR uses the SNPRelate package for efficient PC computation and projection.
  957.  
  958. ```{r pcair1}
  959. # run PC-AiR
  960. library(GENESIS)
  961. pca <- pcair(gds,
  962. kinobj = kingMat,
  963. kin.thresh=2^(-9/2),
  964. divobj = kingMat,
  965. div.thresh=-2^(-9/2))
  966. ```
  967.  
  968. ```{r}
  969. names(pca)
  970. ```
  971.  
  972. ```{r}
  973. # the unrelated set of samples
  974. length(pca$unrels)
  975. head(pca$unrels)
  976.  
  977. # the related set of samples
  978. length(pca$rels)
  979. head(pca$rels)
  980. ```
  981.  
  982. ```{r}
  983. # extract the top 10 PCs and make a data.frame
  984. pcs <- data.frame(pca$vectors[,1:10])
  985. colnames(pcs) <- paste0('PC', 1:10)
  986. pcs$sample.id <- pca$sample.id
  987. dim(pcs)
  988. head(pcs)
  989. ```
  990.  
  991. We'd like to determine which PCs are ancestry informative. To do this we look at the PCs in conjunction with population information for the 1000 Genomes samples. This information is stored in an `AnnotatedDataFrame`. We make a parallel coordinates plot, color-coding by 1000 Genomes population.
  992.  
  993. ```{r pcair1_parcoord, message = FALSE}
  994. library(Biobase)
  995. sampfile <- "AnalysisFiles/sample_annotation.RData"
  996. annot <- get(load(sampfile))
  997.  
  998. library(dplyr)
  999. annot <- pData(annot) %>%
  1000. dplyr::select(sample.id, Population)
  1001. pc.df <- left_join(pcs, annot, by="sample.id")
  1002.  
  1003. library(GGally)
  1004. library(RColorBrewer)
  1005. pop.cols <- setNames(brewer.pal(12, "Paired"),
  1006. c("ACB", "ASW", "CEU", "GBR", "CHB", "JPT", "CLM", "MXL", "LWK", "YRI", "GIH", "PUR"))
  1007. ggparcoord(pc.df, columns=1:10, groupColumn="Population", scale="uniminmax") +
  1008. scale_color_manual(values=pop.cols) +
  1009. xlab("PC") + ylab("")
  1010. ```
  1011.  
  1012. ### PC-Relate
  1013.  
  1014. The next step is [PC-Relate](http://www.ncbi.nlm.nih.gov/pubmed/26748516), which provides accurate kinship inference, even in the presence of population structure and ancestry admixture, by conditioning on ancestry informative PCs. As we saw above, the first 4 PCs separate populations in our study, so we condition on PCs 1-4 in our PC-Relate analysis. PC-Relate can be performed using the `pcrelate` function in GENESIS, which expects a `SeqVarIterator` object for the genotype data. The `training.set` argument allows for specification of which samples to use to "learn" the ancestry adjustment -- we recommend the unrelated set from the PC-AiR analysis.
  1015.  
  1016. (NOTE: this will take a few minutes to run).
  1017.  
  1018. ```{r pcrelate1}
  1019. seqResetFilter(gds, verbose=FALSE)
  1020. library(SeqVarTools)
  1021. seqData <- SeqVarData(gds)
  1022.  
  1023. # filter the GDS object to our LD-pruned variants
  1024. seqSetFilter(seqData, variant.id=pruned)
  1025. iterator <- SeqVarBlockIterator(seqData, verbose=FALSE)
  1026.  
  1027. pcrel <- pcrelate(iterator,
  1028. pcs=pca$vectors[,1:4],
  1029. training.set=pca$unrels)
  1030. ```
  1031.  
  1032. ```{r}
  1033. names(pcrel)
  1034. ```
  1035.  
  1036. ```{r}
  1037. # relatedness between pairs of individuals
  1038. dim(pcrel$kinBtwn)
  1039. ```
  1040.  
  1041. ```{r}
  1042. head(pcrel$kinBtwn)
  1043. ```
  1044.  
  1045. ```{r}
  1046. # self-kinship estimates
  1047. dim(pcrel$kinSelf)
  1048. ```
  1049.  
  1050. ```{r}
  1051. head(pcrel$kinSelf)
  1052. ```
  1053.  
  1054. We plot the pairwise kinship estimates againts the IBD0 (`k0`) estimates (the proportion of variants for which the pair of individuals share 0 alleles identical by descent). We use a hexbin plot to visualize the relatedness for all pairs of samples.
  1055.  
  1056. ```{r pcrelate1_plot}
  1057. ggplot(pcrel$kinBtwn, aes(k0, kin)) +
  1058. geom_hline(yintercept=2^(-seq(3,9,2)/2), linetype="dashed", color="grey") +
  1059. geom_hex(bins = 100) +
  1060. geom_abline(intercept = 0.25, slope = -0.25) +
  1061. ylab("kinship estimate") +
  1062. theme_bw()
  1063. ```
  1064.  
  1065. We see that the PC-Relate relatedness estimates for unrelated pairs (i.e. kin ~ 0 and k0 ~ 1) are much closer to expectation than those from KING-robust.
  1066.  
  1067. We can use the `pcrelateToMatrix` function to transform the output into an (n x n) kinship matrix (KM).
  1068.  
  1069. ```{r pcrelate1_km}
  1070. pcrelMat <- pcrelateToMatrix(pcrel, scaleKin=1, verbose=FALSE)
  1071.  
  1072. # look at the top corner of the matrix
  1073. pcrelMat[1:5,1:5]
  1074.  
  1075. save(pcrelMat, file = "OutputFiles/pcrelate_matrix_round1.RData")
  1076. ```
  1077.  
  1078. ```{r}
  1079. seqClose(seqData)
  1080. ```
  1081.  
  1082. ## Exercises
  1083.  
  1084. In small samples (such as this one), we recommend performing a second iteration of PC-AiR and PC-Relate. Now that we have the PC-Relate ancestry-adjusted kinship estimates, we can better partition our sample into unrelated and related sets. This can lead to better ancestry PCs from PC-AiR and better relatedness estimates from PC-Relate.
  1085.  
  1086. 1. Perform a second PC-AiR analysis, this time using the PC-Relate kinship matrix as the kinship estimates (you should still use the KING-robust matrix for the ancestry divergence estimates). How does the unrelated set compare to the first PC-AiR analysis?
  1087.  
  1088. 2. Make a parallel coordinates plot of the top 10 PC-AiR PCs. How does this compare to the plot from the first iteration? How many PCs seem to reflect ancestry?
  1089.  
  1090. 3. Perform a second PC-Relate analysis, this time using the new PC-AiR PCs to adjust for ancestry. Make a hexbin plot of estimated kinship vs IBD0.
  1091.  
  1092. [Solutions](.\Solutions.html#ancestry-and-relatedness-inference)
  1093.  
  1094.  
  1095. # Mixed models
  1096.  
  1097. These exercises extend what was previously introduced in the association tests from regression models to mixed models that account for genetic relatedness among samples.
  1098.  
  1099. ## Null model
  1100.  
  1101. Recall that the first step in our association testing procedure is to fit the null model. In addition to the `AnnotatedDataFrame` with phenotype data that we used previously, we will also use the ancestry PCs and pairwise kinship estimates we created in the previous session. We will use the first 4 PCs to adjust for ancestry.
  1102.  
  1103. ```{r null_model_mm, message = FALSE}
  1104. # sample annotation
  1105. sampfile <- "AnalysisFiles/sample_phenotype_annotation.RData"
  1106. annot <- get(load(sampfile))
  1107. library(Biobase)
  1108. head(pData(annot))
  1109. ```
  1110.  
  1111. ```{r}
  1112. # load the ancestry PCs
  1113. pcfile <- "AnalysisFiles/pcs.RData"
  1114. pcs <- get(load(pcfile))
  1115. pcs <- pcs[,c("sample.id", "PC1", "PC2", "PC3", "PC4")]
  1116. head(pcs)
  1117. ```
  1118.  
  1119. ```{r}
  1120. # merge PCs with the sample annotation
  1121. library(dplyr)
  1122. dat <- left_join(pData(annot), pcs, by="sample.id")
  1123. # update the AnnotatedDataFrame
  1124. pData(annot) <- dat
  1125. save(annot, file="OutputFiles/sample_phenotype_pcs.RData")
  1126. ```
  1127.  
  1128. We can create a kinship matrix from the output of `pcrelate`. We multiply the kinship values by 2 to get values on the same scale as the standard GRM. This matrix is represented in R as a symmetric matrix object from the Matrix package.
  1129.  
  1130. ```{r load_kinship}
  1131. kinfile <- "AnalysisFiles/pcrelate_kinship.RData"
  1132. pcrel <- get(load(kinfile))
  1133. library(GENESIS)
  1134. kinship <- pcrelateToMatrix(pcrel, scaleKin=2, verbose=FALSE)
  1135. kinship[1:5,1:5]
  1136. ```
  1137.  
  1138. When running a mixed model analysis, we still fit the null model using the `fitNullModel` function in GENESIS. Now, we include the kinship matrix in the model with the `cov.mat` argument, which is used to specify the random effect(s) in the model with covariance structure(s) proportional to the supplied matrix(s). The inclusion of these random effects is what makes this a mixed model, rather than a simple regression model. We also add the ancestry PCs to the list of covariates and allow for heterogeneous residual variance by `study` with the `group.var` argument, as before.
  1139.  
  1140. ```{r null_model_fit_mm}
  1141. nullmod <- fitNullModel(annot,
  1142. outcome="height",
  1143. covars=c("sex", "age", "study", paste0("PC", 1:4)),
  1144. cov.mat=kinship,
  1145. group.var="study",
  1146. verbose=FALSE)
  1147. save(nullmod, file="OutputFiles/null_mixed_model.RData")
  1148. ```
  1149.  
  1150. We can investigate the output from `fitNullModel`.
  1151. ```{r}
  1152. # description of the model we fit
  1153. nullmod$model
  1154. ```
  1155.  
  1156. ```{r}
  1157. # fixed effect regression estimates
  1158. nullmod$fixef
  1159. ```
  1160.  
  1161. ```{r}
  1162. # variance component estimates by group.var
  1163. nullmod$varComp
  1164. ```
  1165.  
  1166. ```{r}
  1167. # model fit: fitted values, residuals
  1168. head(nullmod$fit)
  1169. ```
  1170.  
  1171. ```{r}
  1172. library(ggplot2)
  1173. ggplot(nullmod$fit, aes(x = fitted.values, y = resid.marginal)) +
  1174. geom_point(alpha = 0.5) +
  1175. geom_hline(yintercept = 0) +
  1176. geom_smooth(method = 'lm')
  1177. ```
  1178.  
  1179.  
  1180. ## Single-variant tests
  1181.  
  1182. Now we can run a single-variant test, accounting for genetic ancestry and genetic relatedness among the subjects. We use the same `assocTestSingle` function as before; the only difference is that we pass in our new null model.
  1183.  
  1184. ```{r assoc_single_mm, message = FALSE}
  1185. library(SeqVarTools)
  1186. gdsfile <- "AnalysisFiles/1KG_phase3_subset_chr1.gds"
  1187. gdsfmt::showfile.gds(closeall=TRUE) # make sure file is not already open
  1188. gds <- seqOpen(gdsfile)
  1189.  
  1190. # make the seqVarData object
  1191. seqData <- SeqVarData(gds, sampleData=annot)
  1192.  
  1193. # make the iterator object
  1194. iterator <- SeqVarBlockIterator(seqData, verbose=FALSE)
  1195. ```
  1196.  
  1197. ```{r}
  1198. # run the single-variant association test
  1199. assoc <- assocTestSingle(iterator, nullmod)
  1200. dim(assoc)
  1201. ```
  1202.  
  1203. ```{r}
  1204. head(assoc)
  1205. ```
  1206.  
  1207. We make the usual QQ plot, filtering to variants with minor allele count (MAC) >= 5.
  1208.  
  1209. ```{r assoc_single_qq_mm}
  1210. library(ggplot2)
  1211. qqPlot <- function(pval) {
  1212. pval <- pval[!is.na(pval)]
  1213. n <- length(pval)
  1214. x <- 1:n
  1215. dat <- data.frame(obs=sort(pval),
  1216. exp=x/n,
  1217. upper=qbeta(0.025, x, rev(x)),
  1218. lower=qbeta(0.975, x, rev(x)))
  1219.  
  1220. ggplot(dat, aes(-log10(exp), -log10(obs))) +
  1221. geom_line(aes(-log10(exp), -log10(upper)), color="gray") +
  1222. geom_line(aes(-log10(exp), -log10(lower)), color="gray") +
  1223. geom_point() +
  1224. geom_abline(intercept=0, slope=1, color="red") +
  1225. xlab(expression(paste(-log[10], "(expected P)"))) +
  1226. ylab(expression(paste(-log[10], "(observed P)"))) +
  1227. theme_bw()
  1228. }
  1229.  
  1230. qqPlot(assoc$Score.pval[assoc$MAC >= 5])
  1231. ```
  1232.  
  1233. Notice that we observe much less inflation than before, when we did not adjust for ancestry and relatedness.
  1234.  
  1235. ```{r assoc_mm_close}
  1236. seqClose(seqData)
  1237. ```
  1238.  
  1239. ## Exercise
  1240.  
  1241. 1. Perform a single-variant association test for `status`. Adjust for sex, age, study, ancestry, and kinship in the model. Don't forget to consider the `family` parameter. Make a QQ plot of the p-values for all variants with MAC >= 5.
  1242.  
  1243. [Solutions](.\Solutions.html#mixed-models)
  1244.  
  1245.  
  1246. # Variant annotation
  1247.  
  1248. In this session, we will learn how to use [Annotation Explorer](https://platform.sb.biodatacatalyst.nhlbi.nih.gov/u/biodatacatalyst/annotation-explorer/), an open tool available on NHLBI BioData Catalyst cloud platform that eliminates the challenges of working with very large variant-level annotated datasets. Using Annotation Explorer, we will learn how to explore and interactively query variant annotations and integrate them in GWAS analyses. Annotation Explorer can be used pre-association testing -- for example, to generate annotation informed variant filtering and grouping files for aggregate testing -- as well as for post-association testing -- for example, to explore annotations of variants in a novel GWAS signal. We will execute three representative use cases to demonstrate both pre- and post-GWAS applications. For all the use cases, we will be using the open-access dataset `TOPMed_freeze5_open`, which everyone has access to.
  1249.  
  1250. Annotation explorer has an interactive graphical user interface built on high performance databases and does not require any programming experience. It currently caps the number of users at a given time, so we will not all be able to use it live at the same time during the workshop. We request that everyone perform the hands-on exercises involving Annotation explorer after the workshop is over, at their own convenience. We have provided a [detailed tutorial]( https://docs.google.com/document/d/1_yXemTTYnBzL6Dv4fngojE0T5CAH3Z-CSxj1X5Qq8kI/edit?usp=sharing) and will provide a video recording of this demo for how to perform the following exercises using Annotation Explorer.
  1251.  
  1252. ## Use cases
  1253.  
  1254. ### Use case 1
  1255.  
  1256. User wants to generate aggregation units for rare variant association testing such that only missense variants which have `CADD phred score >20` are grouped by Ensemble gene definitions.
  1257.  
  1258. ### Use case 2
  1259.  
  1260. User wants to generate aggregation units for rare variant association testing such that they retain only variants with `fathmm_MKL_non_coding_score > 0.5` grouped by user-defined genomic coordinates (for example, using ATAC-Seq peaks from the tissue of your choice).
  1261.  
  1262. ### Use case 3
  1263.  
  1264. User wants to explore the annotations for a variant of their interest.
  1265.  
  1266. ## Exercise
  1267.  
  1268. 1. Using [Annotation Explorer](https://platform.sb.biodatacatalyst.nhlbi.nih.gov/u/biodatacatalyst/annotation-explorer/), generate a new set of aggregation units by setting up the same filtering criteria as in use case 1, but this time use a different CADD phred score cut-off (example: 40, 10) and study how that changes plots under the `interactive plots` tab of Annotation Explorer. For example, how does changing the filtering criteria change the number of aggregation units with no variants? How does the distribution and number of aggregation units in each bin change in the histogram?
  1269.  
  1270. [Solutions](.\Solutions.html#variant-annotation)
  1271.  
  1272.  
  1273. # Annotation informed aggregate association tests
  1274.  
  1275. ## Aggregate unit for association testing exercise
  1276.  
  1277. Now that we know how to make genome annotation informed aggregation units using Annotation Explorer, such as the gene-based variant aggregation units, we can proceed to an association testing exercise. *NOTE : The exercises in this workshop are based on the 1000 genomes dataset mapped to genome build GRCh37/hg19. Because the aggregation units we generated using the Annotation Explorer in the previous section are mapped to GRCh38 and are not based on 1000 genomes data, we will NOT be using them in this section*. Instead, in this exercise we will be using pre-computed aggregation units based on 1000 genomes mapped to GRCh37 so that the annotation positions are consistent with the build used for genotyping data in the workshop. These gene-based units include SNVs from all chromosomes (no indels, and not just chromosome 1 as before). Each genic unit was specified to include the set of SNVs falling within a GENCODE-defined gene boundaries and the 20 kb flanking regions upstream and downstream of that range. This set of aggregation units is not filtered by CADD score or consequence.
  1278.  
  1279. The aggregation units are defined in an R dataframe in the format consistent with the output from Annotation Explorer and compatible with the GENESIS association testing workflows. Each row of the dataframe specifies a variant (chr, pos, ref, alt) and the group identifier (group_id) it is a part of. Multiple rows with different group identifiers can be specified to assign a variant to different groups (i.e., a variant can be assigned to multiple genes).
  1280.  
  1281. Begin by loading the aggregation units:
  1282.  
  1283. ```{r agg_unit}
  1284. library(dplyr)
  1285. aggfile <- "AnalysisFiles/variants_by_gene.RData"
  1286. aggunit <- get(load(aggfile))
  1287. head(aggunit)
  1288. ```
  1289.  
  1290. ```{r}
  1291. # an example of a variant that is present in multiple groups
  1292. mult <- aggunit %>%
  1293. group_by(chr, pos) %>%
  1294. summarise(n=n()) %>%
  1295. filter(n > 1)
  1296. inner_join(aggunit, mult[2,1:2])
  1297. ```
  1298.  
  1299. ## Association testing with aggregate units
  1300.  
  1301. We can run burden and SKAT tests on each of these units using the same `assocTestAggregate` function we used previously. We define a `SeqVarListIterator` object where each list element is an aggregate unit. The constructor expects a `GRangesList`, so we use the TopmedPipeline function `aggregateGRangesList` to quickly convert our single dataframe to the required format. This function can account for multiallelic variants (the same chromosome, position, and ref, but different alt alleles).
  1302.  
  1303. ```{r aggVarList}
  1304. # open the GDS file
  1305. library(SeqVarTools)
  1306. gdsfile <- "AnalysisFiles/1KG_phase3_subset_chr1.gds"
  1307. gdsfmt::showfile.gds(closeall=TRUE) # make sure file is not already open
  1308. gds <- seqOpen(gdsfile)
  1309.  
  1310. # sample annotation file
  1311. annotfile <- "AnalysisFiles/sample_phenotype_pcs.RData"
  1312. annot <- get(load(annotfile))
  1313.  
  1314. # make the seqVarData object
  1315. seqData <- SeqVarData(gds, sampleData=annot)
  1316. ```
  1317.  
  1318. ```{r}
  1319. # subset to chromosome 1
  1320. aggunit1 <- filter(aggunit, chr == 1)
  1321.  
  1322. # create the GRangesList object
  1323. library(TopmedPipeline)
  1324. aggVarList <- aggregateGRangesList(aggunit1)
  1325. length(aggVarList)
  1326. head(names(aggVarList))
  1327. aggVarList[[1]]
  1328. ```
  1329.  
  1330. ```{r}
  1331. # construct the iterator using the SeqVarListIterator function
  1332. iterator <- SeqVarListIterator(seqData, variantRanges=aggVarList, verbose=FALSE)
  1333. ```
  1334.  
  1335. As in the previous section, we must load the null model we fit earlier before running the association test.
  1336.  
  1337. ```{r assoc_aggregate}
  1338. # load the null model
  1339. nullmodfile <- "AnalysisFiles/null_mixed_model.RData"
  1340. nullmod <- get(load(nullmodfile))
  1341.  
  1342. # run the burden test
  1343. library(GENESIS)
  1344. assoc <- assocTestAggregate(iterator,
  1345. nullmod,
  1346. test="Burden",
  1347. AF.max=0.1,
  1348. weight.beta=c(1,1),
  1349. verbose = FALSE)
  1350. ```
  1351.  
  1352. ```{r}
  1353. names(assoc)
  1354. ```
  1355.  
  1356. ```{r}
  1357. head(assoc$results)
  1358. ```
  1359.  
  1360. ```{r}
  1361. head(names(assoc$variantInfo))
  1362. ```
  1363.  
  1364. ```{r}
  1365. assoc$variantInfo[[3]]
  1366. ```
  1367.  
  1368. We can make our usual QQ plot
  1369.  
  1370. ```{r}
  1371. library(ggplot2)
  1372. qqPlot <- function(pval) {
  1373. pval <- pval[!is.na(pval)]
  1374. n <- length(pval)
  1375. x <- 1:n
  1376. dat <- data.frame(obs=sort(pval),
  1377. exp=x/n,
  1378. upper=qbeta(0.025, x, rev(x)),
  1379. lower=qbeta(0.975, x, rev(x)))
  1380.  
  1381. ggplot(dat, aes(-log10(exp), -log10(obs))) +
  1382. geom_line(aes(-log10(exp), -log10(upper)), color="gray") +
  1383. geom_line(aes(-log10(exp), -log10(lower)), color="gray") +
  1384. geom_point() +
  1385. geom_abline(intercept=0, slope=1, color="red") +
  1386. xlab(expression(paste(-log[10], "(expected P)"))) +
  1387. ylab(expression(paste(-log[10], "(observed P)"))) +
  1388. theme_bw()
  1389. }
  1390.  
  1391. qqPlot(assoc$results$Score.pval)
  1392. ```
  1393.  
  1394. ```{r}
  1395. seqClose(seqData)
  1396. ```
  1397.  
  1398. ## Exercise
  1399.  
  1400. 1. Since we are working with a subset of the data, many of the genes listed in `group_id` have a very small number of variants. Create a new set of aggregation units based on position, rather than gene name -- create 10 units that are 1MB long and span all of the chr1 variants by using the TopmedPipeline function `aggregateGRanges`. Run a SKAT test using those units and a `SeqVarRangeIterator` object.
  1401.  
  1402. [Solutions](.\Solutions.html#annotation-informed-aggregate-association-tests)
  1403.  
  1404.  
  1405.  
  1406. # Links to Exercises and Solutions
  1407.  
  1408. #### [Link to exercises](.\Exercises.html)
  1409. #### [Link to solutions](.\Solutions.html)
  1410.  
  1411. # R API implementation
  1412.  
  1413. ## Installation
  1414.  
  1415. The [*sevenbridges*](http://bioconductor.org/packages/devel/bioc/vignettes/sevenbridges/inst/doc/api.html#1_introduction) package is available on both the release and devel branch from Bioconductor.
  1416. ```{r, message=FALSE, error=FALSE, results=FALSE}
  1417. # To install it from the devel branch, use:
  1418. #install.packages("BiocManager")
  1419. #BiocManager::install("sevenbridges", version = "devel")
  1420. ```
  1421. ```{r}
  1422. #remotes::install_github("sbg/sevenbridges-r")
  1423. ```
  1424. Before you can access your account via the API, you have to provide your credentials. You can obtain your credentials in the form of an “authentication token” from the Developer Tab on the visual interface. Once you’ve obtained this, create an Auth object, so it remembers your authentication token and the path for the API. All subsequent requests will draw upon these two pieces of information.
  1425.  
  1426. ```{r, message=FALSE}
  1427. #library("sevenbridges")
  1428.  
  1429. # Authentication
  1430. # Set environment
  1431. #sbg_set_env("https://api.sb.biodatacatalyst.nhlbi.nih.gov/v2", "<API-token>")
  1432.  
  1433. # Create an Auth object:
  1434. #api <- Auth(from = "env")
  1435. ```
  1436.  
  1437. To draft a new task, you need to specify the following:<br />
  1438.  
  1439. + The name of the task <br />
  1440. + An optional description <br />
  1441. + The app id of the workflow you are executing <br />
  1442. + The inputs for your workflow. In this case, the CWL app accepts four parameters: number, min, max, and seed. <br />
  1443.  
  1444. You can always check the App details on the visual interface for task input requirements. To find the required inputs with R, you need to get an App object first.
  1445.  
  1446. ## VCF to GDS
  1447.  
  1448. Please copy the public GENESIS VCF to GDS workflow to your project prior to running the API script.
  1449.  
  1450. Set up project and application you want to run:
  1451. ```{r}
  1452. # Set project where you want to run the task.
  1453. # You can get this from the project URL - username/project_id
  1454. #project <- api$project(id = "biodatacatalyst/genesis-tutorial")
  1455.  
  1456. # Set application using the app ID
  1457. # You should change this to reflect your project, same as above.
  1458. #app <- api$app(id = "biodatacatalyst/genesis-tutorial/vcf-to-gds")
  1459.  
  1460. # get input matrix
  1461. #app$input_matrix()
  1462. #app$input_matrix(c("id", "label", "type"))
  1463. ```
  1464.  
  1465. Get input files and create task draft:
  1466. ```{r, message=FALSE}
  1467. # get the input vcf file
  1468. #vcf_input <- project$file(name = c('1KG_phase3_subset_chr1.vcf.gz'), exact = TRUE)
  1469.  
  1470. # add new tasks
  1471. #taskName <- paste0("VCF to GDS - R API run ")
  1472.  
  1473. # draft your task
  1474. #you should change the project ID here too
  1475. #task <- project$task_add(
  1476. # name = taskName,
  1477. # description = "VCF to GDS api run",
  1478. # app = "biodatacatalyst/genesis-tutorial/vcf-to-gds",
  1479. # inputs = list(vcf_file = vcf_input)
  1480. #)
  1481.  
  1482. ```
  1483.  
  1484. ```{r}
  1485. #task$run()
  1486. ```
  1487.  
  1488. # References
  1489.  
  1490. Matthew P. Conomos, Stephanie M. Gogarten, Lisa Brown, Han Chen, Thomas Lumley, Ken Rice, Tamar Sofer, Adrienne Stilp, Timothy Thornton, Chaoyu Yu. *GENetic EStimation and Inference in Structured samples (GENESIS)*. https://bioconductor.org/packages/devel/bioc/manuals/GENESIS/man/GENESIS.pdf.
  1491.  
  1492. Xiuwen Zheng, Stephanie Gogarten, David Levine, Cathy Laurie. *SeqArray*. https://www.bioconductor.org/packages/release/bioc/manuals/SeqArray/man/SeqArray.pdf.
  1493.  
  1494. Stephanie M. Gogarten, Xiuwen Zheng, Adrienne Stilp. *SeqVarTools*. https://bioconductor.org/packages/devel/bioc/manuals/SeqVarTools/man/SeqVarTools.pdf.
  1495.  
  1496. Xiuwen Zheng, Stephanie Gogarten, Cathy Laurie, Bruce Weir. *SNPRelate*. https://www.bioconductor.org/packages/release/bioc/manuals/SNPRelate/man/SNPRelate.pdf.
  1497.  
  1498. R. Gentleman, V. Carey, M. Morgan, S. Falcon. *Biobase: Base functions for Bioconductor*.
  1499. https://www.bioconductor.org/packages/release/bioc/manuals/Biobase/man/Biobase.pdf.
  1500.  
  1501. P. Aboyoun, H. Pagès, and M. Lawrence. *Representation and manipulation of genomic intervals*. https://bioconductor.org/packages/release/bioc/manuals/GenomicRanges/man/GenomicRanges.pdf.
  1502.  
  1503. *SBG R-API*. http://bioconductor.org/packages/devel/bioc/vignettes/sevenbridges/inst/doc/api.html#1_introduction.
  1504.  
Add Comment
Please, Sign In to add comment