Code
library(gaston)
geno <- read.bed.matrix("data/objQ2")Reading data/objQ2.rds
Reading data/objQ2.bed
Code
pheno <- read.table("data/objQ2.phe", header = TRUE)In this lab we draft a genome-wide association (GWA) study. Genome-wide association is the overall name for a technique that seeks positions in the genome associated with a given phenotype. We carry out this laboratory with the maintained gaston package (Hervé Perdry & Claire Dandine-Roulland), which fits the linear mixed models needed to account for family relatedness.1
By the end of this laboratory you will be able to:
For this session we use real data from the GAIT project (Genetic Analysis of Idiopathic Thrombophilia), kindly provided by Dr. Soria (Unit of Genomics and Complex Diseases, Institut de Recerca de l’Hospital de la Santa Creu i Sant Pau). GAIT is an NIH-funded project studying strong linkage signals for QTLs influencing quantitative risk factors for thrombosis, detected in the 21 extended families that comprise the GAIT sample.
This dataset is not public. The information it contains is privileged, confidential, and protected from disclosure. Unauthorised use, reproduction, or distribution of the dataset — or any portion of it, or any result derived from it — is strictly forbidden, may result in severe civil and criminal penalties, and will be prosecuted to the maximum extent possible under the law. If you received this data in error, notify us immediately (alexandre.perera@upc.edu).
The genotypes are stored in the widely used PLINK bed format, which gaston reads by slices. A bed dataset is three files: .bed (genotypes), .fam (individuals), and .bim (marker map). If you only have the legacy LabGWAS1.Rdata, convert it once with the helper script (Rscript scripts/make-gaston-bed.R), which writes objQ2.bed/.bim/.fam and objQ2.phe into data/.
library(gaston)
geno <- read.bed.matrix("data/objQ2")Reading data/objQ2.rds
Reading data/objQ2.bed
pheno <- read.table("data/objQ2.phe", header = TRUE)geno is a bed.matrix: two annotation tables, @ped (individuals) and @snps (markers), plus a pointer to the packed genotypes. pheno holds the phenotype table.
genoA bed.matrix with 377 individuals and 266966 markers.
snps stats are set
ped stats are set
The @ped table counts, per individual, the genotypes equal to 0/1/2 and missing (N0, N1, N2, NAs), the call rate and heterozygosity; @snps holds the alternate allele frequency p, its expected genotype mu = 2p, and the genotype standard deviation sigma. Under Hardy–Weinberg equilibrium sigma should track \(\sqrt{2p(1-p)}\) (Figure 4.1):
pheno$FAM)?SMOKIN) vs Gender (sex) data.table(pheno$SMOKIN, pheno$sex) gives (row = SMOKIN 1/2, col = sex 0/1) 142/89 and 62/82.The genotypes cast to a numeric 0/1/2 matrix with as.matrix(); the rs codes are the column names. The first few markers:
as.matrix(geno)[1:5, 1:5] rs3934834 rs3737728 rs6687776 rs9651273 rs4970405
1203 0 1 0 1 0
1204 0 0 0 1 0
1208 0 1 0 1 0
1209 0 1 0 1 0
1210 0 1 0 2 0
Look up a marker on the dbSNP website (https://www.ncbi.nlm.nih.gov/snp/) — a public catalogue of Single Nucleotide Polymorphisms. Marker names are rs accessions (Reference SNP records).
On which chromosome and position is the first SNP located (geno@snps[1, ])?
geno@snps[1, ] is rs3934834 on chromosome 1, position 995669 (a known 1p36 SNP you can confirm in dbSNP).
Each individual carries family (FAM) and parent (FA, MO) information. Most individuals are related, and this kinship must be accounted for when computing association statistics — otherwise relatedness inflates the test (recall the genomic-inflation \(\lambda\) from the Genetic Association lab).
We summarise relatedness with the Genetic Relationship Matrix (GRM), the standardised whole-genome analogue of the Identity-By-State idea: for individuals \(i, j\) over \(N\) SNPs with allele frequency \(p_k\),
\[ K_{i,j} = \frac{1}{N} \sum_{k=1}^{N} \frac{(x_{i,k} - 2p_k)(x_{j,k} - 2p_k)}{2 p_k (1 - p_k)} \tag{4.1}\]
K <- GRM(geno)
K[1:5, 1:5] 1203 1204 1208 1209 1210
1203 0.988955021 -0.005105684 0.486438155 -0.0178462826 0.3734436631
1204 -0.005105684 1.000756979 -0.008204872 0.0072001172 -0.0163245704
1208 0.486438155 -0.008204872 0.990921259 -0.0019851427 0.4473317862
1209 -0.017846283 0.007200117 -0.001985143 1.0205782652 -0.0006051082
1210 0.373443663 -0.016324570 0.447331786 -0.0006051082 0.9902690649
A hierarchical clustering of \(1 - K/\max K\) recovers the family blocks (Figure 4.2):
FA/MO missing)?famid together — the GRM recovers the families.The target phenotype XXX is a critical, highly heritable clinical trait related to hemostasis. Note that XXX is a deliberately anonymised label: because the dataset is under confidentiality the real trait name is replaced by a placeholder, so treat it as a generic quantitative phenotype and focus on the method (kinship accounting), not the specific analyte. We first fit the polygenic model — a linear mixed model with a random effect structured by the GRM — to estimate how much of the trait variance is genetic (heritability, \(h^2\)). We restrict to individuals with complete phenotype and covariates:
Y <- pheno$XXX
Xc <- cbind(AGE = pheno$AGE, sex = pheno$sex)
ok <- complete.cases(Y, Xc)
fit <- lmm.aireml(Y[ok], X = cbind(1, Xc[ok, ]), K = K[ok, ok], verbose = FALSE)
h2 <- fit$tau / (fit$tau + fit$sigma2)
round(c(genetic_var = fit$tau, resid_var = fit$sigma2, heritability = h2), 3) genetic_var resid_var heritability
900.390 196.472 0.821
The heritability is high — most of the variance in XXX is genetic, which is why a relatedness-aware model matters. Now the genome-wide mixed-model score test (gaston’s equivalent of GenABEL’s mmscore): each SNP is tested for association with XXX, adjusting for age, sex, and the full kinship:
assoc <- association.test(geno[ok, ], Y = Y[ok], X = Xc[ok, ],
method = "lmm", K = K[ok, ok], test = "score")[Iteration 1] theta = 533.503 537.379
[Iteration 1] log L = -1434.21
[Iteration 1] AI-REML update
[Iteration 1] ||gradient|| = 0.0351275
[Iteration 2] theta = 133.062 887.195
[Iteration 2] log L = -1428.5
[Iteration 2] AI-REML update
[Iteration 2] ||gradient|| = 0.0477282
[Iteration 3] theta = 182.05 911.125
[Iteration 3] log L = -1427.16
[Iteration 3] AI-REML update
[Iteration 3] ||gradient|| = 0.00529741
[Iteration 4] theta = 195.403 901.948
[Iteration 4] log L = -1427.13
[Iteration 4] AI-REML update
[Iteration 4] ||gradient|| = 0.000214138
[Iteration 5] theta = 196.418 900.487
[Iteration 5] log L = -1427.13
[Iteration 5] AI-REML update
[Iteration 5] ||gradient|| = 8.26671e-06
assoc <- assoc[!is.na(assoc$p), ]A Manhattan plot shows \(-\log_{10} p\) along the genome (Figure 4.3):
The scan is well-calibrated because relatedness is modelled (Figure 4.4):
qqplot.pvalues(assoc$p)
The top associated SNPs:
rs id, chromosome, and position. Look it up in dbSNP.association.test(geno[ok,], Y[ok], X = Xc[ok,], method = "lm")). What changes, and why does ignoring relatedness mislead?Genotype data are inherently identifying: a few dozen SNPs can re-identify an individual, and a genome reveals information about relatives who never consented. That is why the GAIT set is access-controlled and why real GWAS data live in managed-access repositories (dbGaP, EGA) under data-use agreements. Share summary statistics — never individual genotypes — and follow the study’s consent and governance terms.
lmm.aireml) implemented here.association.test(..., method = "lmm").Worked solutions for the exercises are in the collapsed callouts below each exercise.
Write a laboratory report following the structure of this text. It should contain an introduction, the goals of each section, and a description of the results of each part. Include any graphics relevant to making your work understandable. The lab should be delivered within two weeks.
Earlier editions of this lab used GenABEL (polygenic + mmscore); GenABEL has since been archived from CRAN. gaston reproduces the same family-aware analysis — lmm.aireml estimates the polygenic variance components and association.test(..., method = "lmm") is the mixed-model score test — and is what we use here.↩︎