๐ง GWAS Fundamentals
๐ฏ What is GWAS?
GWAS (Genome-Wide Association Study) is a method to identify genetic variants (usually SNPs) associated with a trait or disease. It tests millions of SNPs across the genome for statistical association with a phenotype, using large cohorts of individuals. GWAS has revolutionized our understanding of the genetic basis of complex traits and diseases, from height and BMI to diabetes and schizophrenia.
Why GWAS Matters
Discovery: Find risk variants. Biology: Understand mechanisms. Prediction: Polygenic scores. Drugs: Target identification. Personalized: Precision medicine. Populations: Evolutionary insights.
# GWAS & Population Genetics โ the big picture
# Finding genetic variants associated with traits.
print("""
=== GWAS & Population Genetics ===
What it is:
- Genome-wide association study
- Millions of SNPs
- Trait / disease
- Statistical association
Why it matters:
- Discover risk variants
- Understand biology
- Polygenic scores
- Drug targets
- Precision medicine
- Evolutionary insights
Key concepts:
- SNP (single nucleotide polymorphism)
- Linkage disequilibrium (LD)
- Hardy-Weinberg equilibrium (HWE)
- Population structure
- Association testing
- Mixed models
- Manhattan plot
- QQ plot
- Fine-mapping
- Imputation
Applications:
- Disease genetics
- Complex traits
- Agriculture
- Evolutionary biology
- Pharmacogenomics
- Personalized medicine
""")
print("=== Historical Context ===")
print("""
2005: First GWAS (age-related macular degeneration)
2007: Wellcome Trust Case Control Consortium
2010: Thousands of GWAS
2015: Biobanks (UK Biobank)
2020: Millions of individuals
2025: Multi-omics, diverse populations, PRS
""")
print("=== Why It's Hard ===")
print("""
1. MULTIPLE TESTING
- Millions of SNPs
- Bonferroni: 5e-8
- False positives
2. POPULATION STRUCTURE
- Ancestry differences
- Spurious associations
- Correction needed
3. LINKAGE DISEQUILIBRIUM
- Correlated SNPs
- Fine-mapping hard
- Causal variants
4. EFFECT SIZE
- Small effects
- Large samples needed
- Missing heritability
5. RARE VARIANTS
- Not on arrays
- Sequencing needed
- Statistical power
""")๐ฏ Why GWAS Matters
๐ฏ The Value of GWAS
GWAS has identified thousands of genetic variants associated with hundreds of traits and diseases, transforming our understanding of complex genetics.
Key Benefits
Discovery: Risk variants. Biology: Mechanisms. Prediction: PRS. Drugs: Targets. Precision: Medicine. Evolution: Selection.
# Why GWAS Matters
print("=== 1. Discovery of Risk Variants ===")
print("""
Discovery:
- Thousands of associations
- Many traits
- Many diseases
Example:
Height: ~10,000 SNPs
BMI: ~1,000 SNPs
Schizophrenia: ~300 loci
Diabetes: ~400 loci
""")
print("\n=== 2. Understanding Biology ===")
print("""
Biology:
- Mechanisms
- Pathways
- Drug targets
Example:
PCSK9 โ cholesterol
โ Drug (evolocumab)
โ Approved
""")
print("\n=== 3. Polygenic Scores ===")
print("""
PRS:
- Polygenic risk score
- Sum of effect sizes
- Prediction
Example:
Height PRS
โ Predict height
โ ~80% accuracy
""")
print("\n=== 4. Drug Discovery ===")
print("""
Drug:
- Target identification
- Validation
- Repurposing
Example:
GWAS โ target
โ Drug
โ Clinical trial
""")
print("\n=== 5. Precision Medicine ===")
print("""
Precision:
- Personalized risk
- Treatment
- Prevention
Example:
Breast cancer
โ BRCA1/2
โ Screening
""")
print("\n=== 6. Evolutionary Insights ===")
print("""
Evolution:
- Selection
- Adaptation
- Migration
Example:
Lactase persistence
โ Selection
โ Europeans
""")
print("\n=== 7. Agriculture ===")
print("""
Agriculture:
- Crop traits
- Breeding
- Food security
Example:
Rice yield
โ GWAS
โ Breeding
""")
print("\n=== 8. Pharmacogenomics ===")
print("""
Pharmacogenomics:
- Drug response
- Adverse effects
- Dosing
Example:
CYP2C19
โ Clopidogrel
โ Dosing
""")
print("\n=== 9. Missing Heritability ===")
print("""
Missing heritability:
- GWAS explains some
- Rare variants
- Epistasis
- Epigenetics
Example:
Height: ~80% heritable
GWAS: ~50% explained
Missing: ~30%
""")
print("\n=== 10. Multi-Omics ===")
print("""
Multi-omics:
- GWAS + transcriptomics
- GWAS + proteomics
- GWAS + metabolomics
Example:
eQTL
โ Gene expression
โ Mechanism
""")๐ฌ SNPs & Genotyping
๐ฏ Single Nucleotide Polymorphisms
SNPs (Single Nucleotide Polymorphisms) are the most common genetic variants, occurring at ~1 in 1,000 bases. They are the primary markers used in GWAS.
SNPs
Definition: Single base change. Frequency: >1%. Genotyping: Arrays. Imputation: Fill missing. MAF: Minor allele. HWE: Equilibrium.
# SNPs & Genotyping
print("=== What is a SNP? ===")
print("""
SNP:
- Single base change
- A โ T, C โ G, etc.
- > 1% frequency
- Most common variant
Example:
Individual 1: ...ACGT...
Individual 2: ...ATGT...
โ SNP at position 2 (C/T)
""")
print("\n=== SNP Types ===")
print("""
Types:
- Transition: AโG, CโT
- Transversion: AโC, AโT, GโC, GโT
- Synonymous: no amino acid change
- Non-synonymous: amino acid change
Example:
rs123456
โ A/G
""")
print("\n=== Genotyping ===")
print("""
Genotyping:
- Microarray
- Sequencing
- TaqMan
- MassArray
Arrays:
- Illumina
- Affymetrix
- ~500K-2M SNPs
Example:
Illumina Global Screening Array
โ 700K SNPs
""")
print("\n=== Genotype Calls ===")
print("""
Genotype:
- 0/0: homozygous reference
- 0/1: heterozygous
- 1/1: homozygous alternate
Example:
rs123456
Individual 1: 0/0 (AA)
Individual 2: 0/1 (AG)
Individual 3: 1/1 (GG)
""")
print("\n=== MAF ===")
print("""
MAF:
- Minor allele frequency
- Frequency of minor allele
- 0 to 0.5
Example:
Allele A: 70%
Allele G: 30%
MAF = 0.3
""")
print("\n=== HWE ===")
print("""
HWE:
- Hardy-Weinberg equilibrium
- Expected genotype frequencies
- pยฒ + 2pq + qยฒ = 1
Example:
p = 0.7 (A)
q = 0.3 (G)
AA: 0.49
AG: 0.42
GG: 0.09
""")
print("\n=== QC ===")
print("""
QC:
- Call rate
- MAF
- HWE
- Heterozygosity
- Relatedness
Example:
Call rate > 95%
MAF > 1%
HWE p > 1e-6
""")
print("\n=== Imputation ===")
print("""
Imputation:
- Fill missing genotypes
- Reference panel
- Increases SNPs
Example:
Array: 700K SNPs
Imputed: 10M SNPs
""")
print("\n=== Python Genotype ===")
print("""
import numpy as np
def genotype_stats(genotypes):
\"\"\"Compute genotype statistics.\"\"\"
n = len(genotypes)
ref = genotypes.count(0) + genotypes.count('0/0')
het = genotypes.count(1) + genotypes.count('0/1')
alt = genotypes.count(2) + genotypes.count('1/1')
maf = (2 * alt + het) / (2 * n)
return {
'ref': ref,
'het': het,
'alt': alt,
'maf': maf
}
# Example
genotypes = [0, 0, 1, 1, 2, 1, 0, 1]
stats = genotype_stats(genotypes)
print(stats)
""")
print("\n=== Use Cases ===")
print("""
Use cases:
- GWAS
- Population genetics
- Forensics
- Ancestry
- Pharmacogenomics
- Breeding
""")๐ Linkage Disequilibrium
๐ฏ Non-Random Association
Linkage disequilibrium (LD) is the non-random association of alleles at different loci. High LD means alleles are inherited together more often than expected by chance.
LD
Definition: Non-random. D: Disequilibrium. D': Normalized. rยฒ: Correlation. Blocks: Haplotypes. Fine-mapping: Use LD.
# Linkage Disequilibrium
print("=== What is LD? ===")
print("""
LD:
- Non-random association
- Alleles at different loci
- Inherited together
- More than expected
Example:
Locus A: A/G
Locus B: C/T
If A always with C:
โ LD
""")
print("\n=== Measures ===")
print("""
D:
- Disequilibrium coefficient
- D = P(AB) - P(A)P(B)
- Range: -0.25 to 0.25
D':
- Normalized D
- Range: 0 to 1
- D' = 1: complete LD
rยฒ:
- Correlation
- Range: 0 to 1
- rยฒ = 1: perfect LD
- Most common
""")
print("\n=== LD Example ===")
print("""
Example:
Locus A: A (p=0.7), G (q=0.3)
Locus B: C (p=0.6), T (q=0.4)
Observed:
AC: 0.5
AT: 0.2
GC: 0.1
GT: 0.2
Expected (no LD):
AC: 0.7*0.6 = 0.42
AT: 0.7*0.4 = 0.28
GC: 0.3*0.6 = 0.18
GT: 0.3*0.4 = 0.12
D = 0.5 - 0.42 = 0.08
โ LD
""")
print("\n=== LD Blocks ===")
print("""
LD blocks:
- Regions of high LD
- Haplotype blocks
- Fewer tag SNPs
Example:
Block 1: SNPs 1-10
Block 2: SNPs 11-20
Block 3: SNPs 21-30
""")
print("\n=== Tag SNPs ===")
print("""
Tag SNPs:
- Representative SNPs
- Capture LD
- Reduce genotyping
Example:
Block with 10 SNPs
โ 2 tag SNPs
โ Capture all
""")
print("\n=== LD and GWAS ===")
print("""
LD in GWAS:
- Association signal
- Tag SNP
- Causal variant
Example:
Causal variant not genotyped
โ Tag SNP in LD
โ Association
""")
print("\n=== LD and Fine-Mapping ===")
print("""
Fine-mapping:
- Identify causal variant
- LD structure
- Credible set
Example:
Association at rs123
โ LD with rs456
โ Causal?
""")
print("\n=== LD Calculation ===")
print("""
rยฒ:
rยฒ = Dยฒ / (p_A * q_A * p_B * q_B)
Example:
D = 0.08
p_A = 0.7, q_A = 0.3
p_B = 0.6, q_B = 0.4
rยฒ = 0.08ยฒ / (0.7 * 0.3 * 0.6 * 0.4)
= 0.0064 / 0.0504
= 0.127
""")
print("\n=== Python LD ===")
print("""
import numpy as np
def ld_r2(geno_a, geno_b):
\"\"\"Compute rยฒ between two SNPs.\"\"\"
a = np.array(geno_a)
b = np.array(geno_b)
# Allele frequencies
p_a = a.mean() / 2
p_b = b.mean() / 2
# D
d = np.mean((a - 2*p_a) * (b - 2*p_b)) / 2
# rยฒ
r2 = d**2 / (p_a * (1-p_a) * p_b * (1-p_b))
return r2
# Example
a = [0, 0, 1, 1, 2, 1, 0, 1]
b = [0, 1, 1, 0, 2, 1, 0, 0]
print(f"rยฒ = {ld_r2(a, b):.3f}")
""")
print("\n=== Use Cases ===")
print("""
Use cases:
- GWAS
- Fine-mapping
- Imputation
- Tag SNPs
- Haplotypes
- Selection
""")โ๏ธ Hardy-Weinberg Equilibrium
๐ฏ HWE
Hardy-Weinberg Equilibrium (HWE) states that allele and genotype frequencies in a population remain constant from generation to generation in the absence of evolutionary forces.
HWE
Equation: pยฒ + 2pq + qยฒ = 1. p: Allele 1. q: Allele 2. Equilibrium: No evolution. Deviation: QC. Test: Chi-square.
# Hardy-Weinberg Equilibrium
print("=== HWE Equation ===")
print("""
HWE:
- pยฒ + 2pq + qยฒ = 1
- p: frequency of allele A
- q: frequency of allele a
- p + q = 1
Genotypes:
AA: pยฒ
Aa: 2pq
aa: qยฒ
""")
print("\n=== Example ===")
print("""
Example:
p = 0.7 (A)
q = 0.3 (a)
AA: 0.7ยฒ = 0.49
Aa: 2*0.7*0.3 = 0.42
aa: 0.3ยฒ = 0.09
Total: 1.00
""")
print("\n=== HWE Test ===")
print("""
Test:
- Chi-square
- Observed vs expected
- p-value
Example:
Observed:
AA: 50
Aa: 40
aa: 10
Expected (p=0.7):
AA: 49
Aa: 42
aa: 9
Chi-square = 0.20
p = 0.90
โ HWE
""")
print("\n=== HWE in GWAS ===")
print("""
HWE in GWAS:
- QC filter
- p > 1e-6
- Remove deviant SNPs
Reasons for deviation:
- Genotyping error
- Population structure
- Selection
- Copy number
""")
print("\n=== HWE Python ===")
print("""
from scipy.stats import chisquare
import numpy as np
def hwe_test(obs_aa, obs_ab, obs_bb):
\"\"\"Test HWE.\"\"\"
n = obs_aa + obs_ab + obs_bb
p = (2 * obs_aa + obs_ab) / (2 * n)
q = 1 - p
exp_aa = p**2 * n
exp_ab = 2 * p * q * n
exp_bb = q**2 * n
chi2, pval = chisquare([obs_aa, obs_ab, obs_bb],
[exp_aa, exp_ab, exp_bb])
return chi2, pval
# Example
chi2, p = hwe_test(50, 40, 10)
print(f"Chi2: {chi2:.3f}, p: {p:.3f}")
""")
print("\n=== Deviation ===")
print("""
Deviation:
- Excess heterozygotes
- Deficiency
- Reasons
Excess het:
- Genotyping error
- Population structure
Deficiency:
- Inbreeding
- Selection
- Null alleles
""")
print("\n=== QC Filter ===")
print("""
Filter:
- HWE p > 1e-6
- Remove deviant
- Per SNP
Example:
PLINK:
--hwe 1e-6
""")
print("\n=== Use Cases ===")
print("""
Use cases:
- QC
- Population genetics
- Selection
- Genotyping error
- Association
""")๐บ๏ธ Population Structure
๐ฏ Population Structure
Population structure refers to systematic differences in allele frequencies between subpopulations. It can cause spurious associations in GWAS if not corrected.
Structure
PCA: Principal components. ADMIXTURE: Ancestry. LMM: Mixed models. Correction: Essential. Spurious: False positives. Ancestry: Differences.
# Population Structure
print("=== What is Population Structure? ===")
print("""
Structure:
- Allele frequency differences
- Subpopulations
- Ancestry
Example:
European: A allele 70%
African: A allele 30%
Asian: A allele 50%
""")
print("\n=== PCA ===")
print("""
PCA:
- Principal Component Analysis
- Dimensionality reduction
- Population structure
Example:
PC1: African vs European
PC2: Asian vs European
PC3: ...
""")
print("\n=== ADMIXTURE ===")
print("""
ADMIXTURE:
- Ancestry estimation
- Admixture proportions
- K populations
Example:
Individual 1: 80% EUR, 20% AFR
Individual 2: 50% EUR, 50% AFR
""")
print("\n=== Correction Methods ===")
print("""
Methods:
1. PCA correction
- Include PCs as covariates
2. Linear mixed models (LMM)
- Random effect
- Kinship matrix
3. Genomic control
- Inflation factor
4. Structured association
- ADMIXTURE
""")
print("\n=== PCA in PLINK ===")
print("""
PLINK:
--pca 10
โ 10 PCs
Example:
plink --bfile data --pca 10 --out pca
โ pca.eigenvec
โ pca.eigenval
""")
print("\n=== LMM ===")
print("""
LMM:
- Linear mixed model
- Fixed effects: SNPs, covariates
- Random effects: kinship
- Corrects structure
Example:
y = Xฮฒ + Zu + e
u ~ N(0, ฯยฒK)
e ~ N(0, ฯยฒI)
""")
print("\n=== Kinship Matrix ===")
print("""
Kinship:
- Relatedness matrix
- Genetic similarity
- Random effect
Example:
K = (1/n) * ฮฃ (G_i - 2p_i)(G_i - 2p_i)^T
""")
print("\n=== Genomic Control ===")
print("""
GC:
- Inflation factor ฮป
- ฮป = median(ฯยฒ) / 0.456
- Correct p-values
Example:
ฮป = 1.0: no inflation
ฮป = 1.1: mild inflation
ฮป = 1.5: severe inflation
""")
print("\n=== Python PCA ===")
print("""
import numpy as np
from sklearn.decomposition import PCA
def population_pca(genotypes):
\"\"\"PCA on genotypes.\"\"\"
pca = PCA(n_components=10)
pcs = pca.fit_transform(genotypes)
return pcs, pca.explained_variance_ratio_
# Example
# genotypes = load_genotypes()
# pcs, var = population_pca(genotypes)
# print(f"PC1: {var[0]:.2%}")
""")
print("\n=== Use Cases ===")
print("""
Use cases:
- GWAS correction
- Ancestry inference
- Admixture mapping
- Population genetics
- Evolutionary studies
""")๐ Association Testing
๐ฏ Testing SNP-Trait Associations
Association testing tests each SNP for statistical association with a trait. It's the core of GWAS.
Association
Linear: Quantitative. Logistic: Binary. Covariates: Age, sex, PCs. Test: t-test, chi-square. Threshold: 5e-8. Effect: Beta, OR.
# Association Testing
print("=== Types of Traits ===")
print("""
Quantitative:
- Height, BMI, blood pressure
- Linear regression
Binary:
- Disease (case/control)
- Logistic regression
""")
print("\n=== Linear Regression ===")
print("""
Linear:
y = ฮผ + ฮฒ*SNP + covariates + e
Test:
- H0: ฮฒ = 0
- H1: ฮฒ โ 0
- t-test
- p-value
Example:
y = height
SNP = 0, 1, 2
ฮฒ = effect size
""")
print("\n=== Logistic Regression ===")
print("""
Logistic:
logit(P(y=1)) = ฮผ + ฮฒ*SNP + covariates
Test:
- H0: ฮฒ = 0
- H1: ฮฒ โ 0
- Wald test
- p-value
Example:
y = disease (0/1)
SNP = 0, 1, 2
OR = exp(ฮฒ)
""")
print("\n=== Covariates ===")
print("""
Covariates:
- Age
- Sex
- PCs (population structure)
- Batch
- BMI
Example:
y ~ SNP + age + sex + PC1 + PC2 + ...
""")
print("\n=== Multiple Testing ===")
print("""
Multiple testing:
- Millions of SNPs
- Bonferroni: 0.05 / 1M = 5e-8
- FDR
Threshold:
- Genome-wide: 5e-8
- Suggestive: 1e-5
Example:
p < 5e-8: significant
p < 1e-5: suggestive
""")
print("\n=== PLINK Association ===")
print("""
PLINK:
--linear (quantitative)
--logistic (binary)
Example:
plink --bfile data --linear --pheno pheno.txt --covar covar.txt --out assoc
plink --bfile data --logistic --pheno pheno.txt --covar covar.txt --out assoc
""")
print("\n=== Output ===")
print("""
Output:
CHR: chromosome
SNP: SNP ID
BP: position
A1: effect allele
TEST: test
NMISS: samples
BETA: effect size
STAT: statistic
P: p-value
OR: odds ratio (logistic)
Example:
1 rs123 100 A ADD 1000 0.05 3.2 0.001
""")
print("\n=== Python Association ===")
print("""
import numpy as np
from scipy import stats
import statsmodels.api as sm
def association_test(genotype, phenotype, covariates=None):
\"\"\"Test SNP-phenotype association.\"\"\"
# Design matrix
X = genotype.reshape(-1, 1)
if covariates is not None:
X = np.hstack([X, covariates])
X = sm.add_constant(X)
# Linear regression
model = sm.OLS(phenotype, X).fit()
beta = model.params[1]
pval = model.pvalues[1]
return beta, pval
# Example
# genotype = np.array([0, 0, 1, 1, 2, 1, 0, 1])
# phenotype = np.array([1.2, 1.5, 1.8, 1.9, 2.1, 1.7, 1.3, 1.6])
# beta, pval = association_test(genotype, phenotype)
# print(f"Beta: {beta:.3f}, p: {pval:.3e}")
""")
print("\n=== Use Cases ===")
print("""
Use cases:
- GWAS
- Candidate gene
- QTL mapping
- PheWAS
- Rare variant
""")๐งฎ Mixed Models (LMM)
๐ฏ Linear Mixed Models
Linear mixed models (LMM) account for population structure and relatedness by including a random effect based on a kinship matrix. They are the gold standard for GWAS.
LMM
Fixed: SNPs, covariates. Random: Kinship. Corrects: Structure. Power: High. Tools: GCTA, BOLT-LMM, GEMMA. Standard: GWAS.
# Mixed Models
print("=== LMM Equation ===")
print("""
y = Xฮฒ + Zu + e
y: phenotype
X: fixed effects (SNP, covariates)
ฮฒ: fixed effects
Z: random effects design
u: random effects (kinship)
e: residuals
u ~ N(0, ฯยฒ_g K)
e ~ N(0, ฯยฒ_e I)
""")
print("\n=== Kinship Matrix ===")
print("""
Kinship:
- Relatedness
- Genetic similarity
- Random effect
K = (1/n) * ฮฃ (G_i - 2p_i)(G_i - 2p_i)^T
Example:
K[i,j] = relatedness between i and j
""")
print("\n=== Variance Components ===")
print("""
Variance:
- ฯยฒ_g: genetic variance
- ฯยฒ_e: environmental variance
- hยฒ = ฯยฒ_g / (ฯยฒ_g + ฯยฒ_e)
Example:
hยฒ = 0.5
โ 50% heritability
""")
print("\n=== Tools ===")
print("""
1. GCTA
- Mixed model
- Heritability
- Fast
2. BOLT-LMM
- Large-scale
- Fast
- UK Biobank
3. GEMMA
- Mixed model
- Multiple phenotypes
- Bayesian
4. SAIGE
- Binary traits
- Related individuals
- Scalable
5. REGENIE
- Whole-genome regression
- Fast
- Large-scale
""")
print("\n=== GCTA ===")
print("""
GCTA:
- Genome-wide Complex Trait Analysis
- Heritability
- Mixed model
Commands:
gcta --bfile data --make-grm --out grm
gcta --grm grm --pheno pheno.txt --reml --out h2
gcta --grm grm --mlma --bfile data --out assoc
""")
print("\n=== BOLT-LMM ===")
print("""
BOLT-LMM:
- Large-scale
- Fast
- UK Biobank
Command:
bolt --bfile=data --phenoFile=pheno.txt --lmm --LDscoresFile=LD.txt
""")
print("\n=== GEMMA ===")
print("""
GEMMA:
- Mixed model
- Multiple phenotypes
Commands:
gemma -bfile data -gk 1 -o kinship
gemma -bfile data -k kinship.cXX.txt -lmm 1 -o assoc
""")
print("\n=== SAIGE ===")
print("""
SAIGE:
- Binary traits
- Related individuals
- Scalable
Command:
saige --bfile=data --phenoFile=pheno.txt --phenoCol=disease
""")
print("\n=== REGENIE ===")
print("""
REGENIE:
- Whole-genome regression
- Fast
- Large-scale
Commands:
regenie --step 1 --bfile data --phenoFile pheno.txt
regenie --step 2 --bfile data --phenoFile pheno.txt --pred pred.list
""")
print("\n=== Heritability ===")
print("""
Heritability:
- SNP heritability
- hยฒ_SNP
- Variance explained
Example:
hยฒ = 0.5
โ 50% variance explained
""")
print("\n=== Use Cases ===")
print("""
Use cases:
- GWAS
- Heritability
- Prediction
- Related individuals
- Biobanks
""")๐ Manhattan & QQ Plots
๐ฏ Visualizing GWAS Results
Manhattan plots and QQ plots are the standard visualizations for GWAS results. They reveal significant associations and assess model quality.
Plots
Manhattan: Genome-wide. QQ: p-value distribution. ฮป: Inflation. Peaks: Associations. Threshold: 5e-8. Colors: Chromosomes.
# Manhattan & QQ Plots
print("=== Manhattan Plot ===")
print("""
Manhattan:
- X: chromosome
- Y: -log10(p)
- Points: SNPs
- Colors: alternating
- Threshold: 5e-8
Example:
Peak at chr6
โ HLA region
โ Association
""")
print("\n=== QQ Plot ===")
print("""
QQ:
- X: expected -log10(p)
- Y: observed -log10(p)
- Diagonal: null
- Deviation: signal
Example:
Points on diagonal
โ Well-calibrated
""")
print("\n=== Inflation Factor ===")
print("""
ฮป:
- Genomic inflation
- ฮป = median(ฯยฒ) / 0.456
- ฮป = 1: no inflation
- ฮป > 1: inflation
Example:
ฮป = 1.05
โ Mild inflation
""")
print("\n=== Python Manhattan ===")
print("""
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
def manhattan(df, output="manhattan.png"):
\"\"\"Manhattan plot.\"\"\"
df = df.sort_values(['CHR', 'BP'])
df['ind'] = range(len(df))
df['logp'] = -np.log10(df['P'])
fig, ax = plt.subplots(figsize=(15, 5))
colors = ['#a855f7', '#6366f1']
x_labels = []
x_pos = []
for i, (chrom, group) in enumerate(df.groupby('CHR')):
ax.scatter(group['ind'], group['logp'],
c=colors[i % 2], s=5, alpha=0.7)
x_labels.append(str(chrom))
x_pos.append(group['ind'].mean())
ax.axhline(-np.log10(5e-8), color='red', linestyle='--')
ax.set_xticks(x_pos)
ax.set_xticklabels(x_labels)
ax.set_xlabel("Chromosome")
ax.set_ylabel("-log10(p)")
ax.set_title("Manhattan Plot")
plt.tight_layout()
plt.savefig(output)
plt.show()
# Use
# manhattan(df)
""")
print("\n=== Python QQ ===")
print("""
from scipy import stats
def qq_plot(pvals, output="qq.png"):
\"\"\"QQ plot.\"\"\"
n = len(pvals)
expected = -np.log10(np.arange(1, n+1) / (n+1))
observed = -np.log10(np.sort(pvals))
plt.figure(figsize=(6, 6))
plt.scatter(expected, observed, s=5, alpha=0.7, color='purple')
plt.plot([0, max(expected)], [0, max(expected)],
'r--', label='Null')
plt.xlabel("Expected -log10(p)")
plt.ylabel("Observed -log10(p)")
plt.title("QQ Plot")
plt.legend()
plt.tight_layout()
plt.savefig(output)
plt.show()
# Use
# qq_plot(df['P'])
""")
print("\n=== R qqman ===")
print("""
library(qqman)
# Manhattan
manhattan(gwasResults, chr="CHR", bp="BP",
snp="SNP", p="P", suggestiveline=FALSE,
genomewideline=-log10(5e-8))
# QQ
qq(gwasResults$P)
""")
print("\n=== Tools ===")
print("""
Tools:
- qqman (R)
- ggplot2 (R)
- matplotlib (Python)
- LocusZoom
- FUMA
- Manhattan plot web tools
""")
print("\n=== Interpretation ===")
print("""
Interpretation:
- Peaks: associations
- Height: significance
- Width: LD
- ฮป: inflation
Example:
Peak at chr6
โ HLA
โ Immune
""")
print("\n=== Use Cases ===")
print("""
Use cases:
- GWAS results
- Visualization
- Quality control
- Publication
- Communication
""")๐ Fine-Mapping & Imputation
๐ฏ From Association to Causality
Fine-mapping identifies the causal variant(s) within an associated locus. Imputation fills in missing genotypes to increase resolution.
Fine-Mapping
Goal: Causal variant. LD: Correlated SNPs. Methods: Bayesian. Credible set: 95%. Imputation: Fill missing. Tools: FINEMAP, SuSiE, CAVIAR.
# Fine-Mapping & Imputation
print("=== Fine-Mapping ===")
print("""
Fine-mapping:
- Identify causal variant
- Within associated locus
- LD structure
- Bayesian methods
Goal:
- Credible set
- 95% probability
- Causal variant
""")
print("\n=== Methods ===")
print("""
1. FINEMAP
- Bayesian
- Shotgun stochastic search
- Fast
2. SuSiE
- Sum of single effects
- Bayesian
- Multiple causal variants
3. CAVIAR
- Bayesian
- Credible set
- Fast
4. PAINTOR
- Functional annotation
- Bayesian
- Integration
""")
print("\n=== Credible Set ===")
print("""
Credible set:
- 95% probability
- Contains causal variant
- Smallest set
Example:
Credible set: 5 SNPs
โ 95% chance causal is here
""")
print("\n=== Imputation ===")
print("""
Imputation:
- Fill missing genotypes
- Reference panel
- Increase SNPs
Reference panels:
- 1000 Genomes
- HRC
- TOPMed
- UK Biobank
Tools:
- IMPUTE2
- IMPUTE5
- Beagle
- Minimac4
- Michigan Imputation Server
""")
print("\n=== IMPUTE5 ===")
print("""
IMPUTE5:
- Fast
- Reference panel
- Phasing
Command:
impute5 --haps ref.hap --legend ref.legend --g ref.gen \
--h target.hap --l target.legend --g target.gen \
--o output --r chr --m map.txt
""")
print("\n=== Beagle ===")
print("""
Beagle:
- Phasing
- Imputation
- Fast
Command:
java -jar beagle.jar gt=data.vcf ref=ref.vcf out=imputed
""")
print("\n=== Minimac4 ===")
print("""
Minimac4:
- Fast
- Reference panel
- Michigan Imputation Server
Command:
minimac4 --refHaps ref.m3vcf --haps target.vcf --prefix output
""")
print("\n=== Quality ===")
print("""
Quality:
- INFO score
- > 0.8: good
- > 0.9: excellent
- DR2 (mach)
- Rยฒ (beagle)
Example:
INFO = 0.95
โ High quality
""")
print("\n=== Fine-Mapping Python ===")
print("""
# Use FINEMAP or SuSiE via R
# Python: use subprocess
import subprocess
def run_finemap(z_file, ld_file, n, output="finemap"):
\"\"\"Run FINEMAP.\"\"\"
with open(f"{output}.master", "w") as f:
f.write(f"z;{z_file}\\n")
f.write(f"ld;{ld_file}\\n")
f.write(f"n;{n}\\n")
f.write(f"ss;1\\n")
f.write(f"config;1\\n")
subprocess.run(["finemap", "--master", f"{output}.master"])
return output
# Use
# run_finemap("z.txt", "ld.txt", 10000)
""")
print("\n=== Use Cases ===")
print("""
Use cases:
- Causal variant
- Functional follow-up
- Drug targets
- Mechanism
- Precision medicine
""")๐ ๏ธ Tools (PLINK, GCTA, BOLT-LMM, GEMMA)
๐ฏ GWAS Tools
Many tools exist for GWAS analysis. Each has strengths and is suited to different use cases.
Tools
PLINK: QC, association. GCTA: Heritability. BOLT-LMM: Large-scale. GEMMA: Mixed models. SAIGE: Binary. REGENIE: Whole-genome.
# Tools
print("=== PLINK ===")
print("""
PLINK:
- QC
- Association
- Standard
- Free
Commands:
plink --bfile data --maf 0.01 --hwe 1e-6 --geno 0.05 --make-bed --out clean
plink --bfile clean --linear --pheno pheno.txt --covar covar.txt --out assoc
plink --bfile clean --logistic --pheno pheno.txt --covar covar.txt --out assoc
""")
print("\n=== GCTA ===")
print("""
GCTA:
- Heritability
- Mixed model
- Fast
Commands:
gcta --bfile data --make-grm --out grm
gcta --grm grm --pheno pheno.txt --reml --out h2
gcta --grm grm --mlma --bfile data --out assoc
""")
print("\n=== BOLT-LMM ===")
print("""
BOLT-LMM:
- Large-scale
- Fast
- UK Biobank
Command:
bolt --bfile=data --phenoFile=pheno.txt --lmm --LDscoresFile=LD.txt
""")
print("\n=== GEMMA ===")
print("""
GEMMA:
- Mixed model
- Multiple phenotypes
Commands:
gemma -bfile data -gk 1 -o kinship
gemma -bfile data -k kinship.cXX.txt -lmm 1 -o assoc
""")
print("\n=== SAIGE ===")
print("""
SAIGE:
- Binary traits
- Related individuals
- Scalable
Command:
saige --bfile=data --phenoFile=pheno.txt --phenoCol=disease
""")
print("\n=== REGENIE ===")
print("""
REGENIE:
- Whole-genome regression
- Fast
- Large-scale
Commands:
regenie --step 1 --bfile data --phenoFile pheno.txt
regenie --step 2 --bfile data --phenoFile pheno.txt --pred pred.list
""")
print("\n=== Other Tools ===")
print("""
1. PLINK2
- Faster
- More features
2. METAL
- Meta-analysis
3. MR-Base
- Mendelian randomization
4. FUMA
- Functional annotation
5. LocusZoom
- Visualization
6. PRSice
- Polygenic scores
7. LDSC
- LD score regression
8. Hail
- Cloud-scale
""")
print("\n=== Tool Comparison ===")
print(f"{'Tool':<20} {'Type':<20} {'Speed':<15} {'Best For':<20}")
print("-" * 75)
print(f"{'PLINK':<20} {'QC, assoc':<20} {'Fast':<15} {'General':<20}")
print(f"{'GCTA':<20} {'Mixed model':<20} {'Fast':<15} {'Heritability':<20}")
print(f"{'BOLT-LMM':<20} {'Mixed model':<20} {'Very fast':<15} {'Biobank':<20}")
print(f"{'GEMMA':<20} {'Mixed model':<20} {'Fast':<15} {'Multiple':<20}")
print(f"{'SAIGE':<20} {'Binary':<20} {'Fast':<15} {'Related':<20}")
print(f"{'REGENIE':<20} {'Whole-genome':<20} {'Fast':<15} {'Large-scale':<20}")
print("\n=== When to Use Which ===")
print("""
USE PLINK:
โ QC
โ Basic association
โ General
USE GCTA:
โ Heritability
โ Mixed model
โ Relatedness
USE BOLT-LMM:
โ Large-scale
โ Biobank
โ Fast
USE GEMMA:
โ Multiple phenotypes
โ Mixed model
โ Bayesian
USE SAIGE:
โ Binary traits
โ Related individuals
โ Scalable
USE REGENIE:
โ Whole-genome
โ Large-scale
โ Fast
""")๐ Python Integration
๐ฏ GWAS with Python
Python is a powerful language for GWAS analysis, visualization, and automation. Here's how.
Python
Pandas: Data. NumPy: Arrays. Statsmodels: Regression. Matplotlib: Plots. Scikit-learn: PCA. Subprocess: Tools.
# Python Integration
print("=== 1. Load GWAS Data ===")
print("""
import pandas as pd
# Load
df = pd.read_csv("gwas_results.txt", sep="\\t")
print(df.head())
print(df.columns)
""")
print("\n=== 2. Association Testing ===")
print("""
import statsmodels.api as sm
import numpy as np
def association(genotype, phenotype, covariates=None):
\"\"\"Association test.\"\"\"
X = genotype.reshape(-1, 1)
if covariates is not None:
X = np.hstack([X, covariates])
X = sm.add_constant(X)
model = sm.OLS(phenotype, X).fit()
return model.params[1], model.pvalues[1]
# Example
# beta, pval = association(genotype, phenotype)
""")
print("\n=== 3. PCA ===")
print("""
from sklearn.decomposition import PCA
def compute_pca(genotypes, n_components=10):
\"\"\"PCA.\"\"\"
pca = PCA(n_components=n_components)
pcs = pca.fit_transform(genotypes)
return pcs, pca.explained_variance_ratio_
# Use
# pcs, var = compute_pca(genotypes)
""")
print("\n=== 4. Manhattan Plot ===")
print("""
import matplotlib.pyplot as plt
import numpy as np
def manhattan(df, output="manhattan.png"):
\"\"\"Manhattan plot.\"\"\"
df = df.sort_values(['CHR', 'BP'])
df['ind'] = range(len(df))
df['logp'] = -np.log10(df['P'])
fig, ax = plt.subplots(figsize=(15, 5))
colors = ['#a855f7', '#6366f1']
for i, (chrom, group) in enumerate(df.groupby('CHR')):
ax.scatter(group['ind'], group['logp'],
c=colors[i % 2], s=5, alpha=0.7)
ax.axhline(-np.log10(5e-8), color='red', linestyle='--')
ax.set_xlabel("Chromosome")
ax.set_ylabel("-log10(p)")
ax.set_title("Manhattan Plot")
plt.tight_layout()
plt.savefig(output)
plt.show()
# Use
# manhattan(df)
""")
print("\n=== 5. QQ Plot ===")
print("""
def qq_plot(pvals, output="qq.png"):
\"\"\"QQ plot.\"\"\"
n = len(pvals)
expected = -np.log10(np.arange(1, n+1) / (n+1))
observed = -np.log10(np.sort(pvals))
plt.figure(figsize=(6, 6))
plt.scatter(expected, observed, s=5, alpha=0.7, color='purple')
plt.plot([0, max(expected)], [0, max(expected)], 'r--')
plt.xlabel("Expected -log10(p)")
plt.ylabel("Observed -log10(p)")
plt.title("QQ Plot")
plt.tight_layout()
plt.savefig(output)
plt.show()
# Use
# qq_plot(df['P'])
""")
print("\n=== 6. Run PLINK ===")
print("""
import subprocess
def run_plink(bfile, output, command="--linear"):
\"\"\"Run PLINK.\"\"\"
cmd = ["plink", "--bfile", bfile, command,
"--out", output]
subprocess.run(cmd, check=True)
return f"{output}.assoc.linear"
# Use
# run_plink("data", "assoc", "--linear")
""")
print("\n=== 7. Parse PLINK Output ===")
print("""
def parse_plink(filepath):
\"\"\"Parse PLINK association output.\"\"\"
df = pd.read_csv(filepath, sep="\\s+")
return df
# Use
# df = parse_plink("assoc.assoc.linear")
""")
print("\n=== 8. Heritability ===")
print("""
import numpy as np
def estimate_heritability(genotypes, phenotype):
\"\"\"Estimate SNP heritability.\"\"\"
# GRM
n = genotypes.shape[0]
p = genotypes.mean(axis=0) / 2
Z = genotypes - 2 * p
K = Z @ Z.T / genotypes.shape[1]
# REML (simplified)
# ...
return None
# Use
# h2 = estimate_heritability(genotypes, phenotype)
""")
print("\n=== 9. Polygenic Score ===")
print("""
def compute_prs(genotypes, betas):
\"\"\"Compute polygenic score.\"\"\"
return genotypes @ betas
# Use
# prs = compute_prs(genotypes, betas)
""")
print("\n=== 10. Complete Pipeline ===")
print("""
import pandas as pd
import numpy as np
import subprocess
import matplotlib.pyplot as plt
def gwas_pipeline(bfile, pheno, covar, output="gwas"):
\"\"\"Complete GWAS pipeline.\"\"\"
# QC
subprocess.run([
"plink", "--bfile", bfile,
"--maf", "0.01", "--hwe", "1e-6",
"--geno", "0.05", "--make-bed",
"--out", f"{output}_clean"
], check=True)
# Association
subprocess.run([
"plink", "--bfile", f"{output}_clean",
"--linear", "--pheno", pheno,
"--covar", covar,
"--out", output
], check=True)
# Parse
df = pd.read_csv(f"{output}.assoc.linear", sep="\\s+")
# Plots
# manhattan(df)
# qq_plot(df['P'])
return df
# Use
# df = gwas_pipeline("data", "pheno.txt", "covar.txt")
""")โ๏ธ Tool Comparison
๐ฏ Which Tool?
Choosing the right GWAS tool depends on your data size, trait type, and computational resources.
Comparison
PLINK: Standard. GCTA: Heritability. BOLT-LMM: Biobank. GEMMA: Multiple. SAIGE: Binary. REGENIE: Whole-genome.
| Tool | Type | Speed | Trait | Best For |
|---|---|---|---|---|
| PLINK | QC, assoc | Fast | Both | General |
| GCTA | Mixed model | Fast | Both | Heritability |
| BOLT-LMM | Mixed model | Very fast | Quantitative | Biobank |
| GEMMA | Mixed model | Fast | Both | Multiple |
| SAIGE | Mixed model | Fast | Binary | Related |
| REGENIE | Whole-genome | Fast | Both | Large-scale |
| PLINK2 | QC, assoc | Very fast | Both | Modern |
| Hail | Cloud | Scalable | Both | Cloud |
# Decision Guide
print("=== Choose Tool by Data Size ===")
print("""
Small (< 10k):
โ PLINK
โ GEMMA
Medium (10k-100k):
โ PLINK
โ GCTA
โ GEMMA
Large (100k-500k):
โ BOLT-LMM
โ SAIGE
โ REGENIE
Very large (> 500k):
โ BOLT-LMM
โ REGENIE
โ Hail
""")
print("\n=== Choose Tool by Trait ===")
print("""
Quantitative:
โ BOLT-LMM
โ GCTA
โ GEMMA
Binary:
โ SAIGE
โ PLINK
โ REGENIE
Both:
โ PLINK
โ GEMMA
โ REGENIE
""")
print("\n=== Speed Comparison ===")
print(f"{'Tool':<20} {'100k':<15} {'500k':<15} {'1M':<15}")
print("-" * 65)
print(f"{'PLINK':<20} {'1 hour':<15} {'5 hours':<15} {'10 hours':<15}")
print(f"{'BOLT-LMM':<20} {'10 min':<15} {'1 hour':<15} {'2 hours':<15}")
print(f"{'SAIGE':<20} {'30 min':<15} {'2 hours':<15} {'5 hours':<15}")
print(f"{'REGENIE':<20} {'20 min':<15} {'1 hour':<15} {'3 hours':<15}")
print("\n=== When to Use Which ===")
print("""
USE PLINK:
โ QC
โ Basic association
โ Standard
USE GCTA:
โ Heritability
โ Relatedness
โ Mixed model
USE BOLT-LMM:
โ Biobank
โ Large-scale
โ Fast
USE GEMMA:
โ Multiple phenotypes
โ Bayesian
โ Mixed model
USE SAIGE:
โ Binary traits
โ Related individuals
โ Scalable
USE REGENIE:
โ Whole-genome
โ Large-scale
โ Fast
""")โ FAQ
๐ฏ Frequently Asked Questions
Answers to common questions about GWAS and population genetics.
What is GWAS?
GWAS (Genome-Wide Association Study) is a method to identify genetic variants (usually SNPs) associated with a trait or disease. It tests millions of SNPs across the genome for statistical association with a phenotype, using large cohorts of individuals.
What is a SNP?
A SNP (Single Nucleotide Polymorphism) is a variation at a single position in DNA where different individuals have different nucleotides (e.g., A, T, C, or G). SNPs are the most common genetic variants and are the primary markers used in GWAS.
What is population structure and why does it matter in GWAS?
Population structure refers to systematic differences in allele frequencies between subpopulations. It matters in GWAS because it can cause spurious associations (false positives) if cases and controls have different ancestry. Methods like PCA, ADMIXTURE, and linear mixed models correct for structure.
What is linkage disequilibrium (LD)?
Linkage disequilibrium (LD) is the non-random association of alleles at different loci. High LD means alleles are inherited together more often than expected by chance. LD is used to identify haplotype blocks and to perform imputation and fine-mapping in GWAS.
Which tools are used for GWAS?
Common GWAS tools include PLINK (association testing, QC), GCTA (mixed models, heritability), BOLT-LMM (large-scale LMM), GEMMA (mixed models), SAIGE (binary traits, related individuals), and REGENIE (whole-genome regression). Visualization is done with QQ plots and Manhattan plots.
What is the genome-wide significance threshold?
The standard genome-wide significance threshold is p < 5 ร 10โปโธ. This is based on Bonferroni correction for ~1 million independent tests (0.05 / 1,000,000). It is used to control for multiple testing across the genome.
What is a Manhattan plot?
A Manhattan plot displays GWAS results with chromosomes on the x-axis and -log10(p-value) on the y-axis. Each point is a SNP. Significant associations appear as peaks above the genome-wide significance line.
What is a QQ plot?
A QQ plot compares observed p-values to expected p-values under the null hypothesis. Points on the diagonal indicate good calibration. Deviation at the top indicates true associations. The inflation factor ฮป measures overall deviation.
What is fine-mapping?
Fine-mapping identifies the causal variant(s) within an associated locus. Because of LD, many SNPs may be associated. Fine-mapping uses Bayesian methods (FINEMAP, SuSiE) to define a credible set of variants that likely contains the causal variant.
What is imputation?
Imputation fills in missing genotypes using a reference panel (e.g., 1000 Genomes, HRC, TOPMed). It increases the number of SNPs tested in GWAS and improves fine-mapping resolution. Tools include IMPUTE5, Beagle, and Minimac4.
# Quick Reference
print("=== Quick Answers ===")
print("""
Q: What is GWAS?
A: Genome-wide association study.
Q: What is a SNP?
A: Single nucleotide polymorphism.
Q: Significance threshold?
A: p < 5e-8.
Q: What is LD?
A: Linkage disequilibrium.
Q: What is HWE?
A: Hardy-Weinberg equilibrium.
Q: Population structure?
A: Ancestry differences; correct with PCA/LMM.
Q: Tools?
A: PLINK, GCTA, BOLT-LMM, GEMMA, SAIGE, REGENIE.
Q: Manhattan plot?
A: GWAS visualization.
Q: QQ plot?
A: P-value calibration.
Q: Fine-mapping?
A: Identify causal variant.
Q: Imputation?
A: Fill missing genotypes.
Q: Free?
A: Yes โ all major tools are free.
""")๐ Future
๐ฏ Where GWAS Is Going
The future is diverse populations, multi-omics, AI, rare variants, single-cell, and clinical translation.
Future Directions
Diversity: Global. Multi-omics: Integrated. AI: Prediction. Rare: Sequencing. Single-cell: Resolution. Clinical: Translation.
# Future of GWAS
print("=== 1. Diverse Populations ===")
print("""
Trend: Global diversity
Current:
- Mostly European
- ~80% European
Future:
- Global
- Multi-ancestry
- Equity
Examples:
- PAGE
- H3Africa
- GBMI
Future:
- Diverse
- Equitable
- Generalizable
""")
print("\n=== 2. Multi-Omics ===")
print("""
Trend: Multi-omics
Current:
- Genomics only
Future:
- Genomics + transcriptomics
- Genomics + proteomics
- Genomics + metabolomics
- Genomics + epigenomics
Examples:
- GTEx
- UK Biobank
- TOPMed
Future:
- Integrated
- Mechanistic
- Systems
""")
print("\n=== 3. AI ===")
print("""
Trend: AI + GWAS
Ideas:
- Deep learning
- Polygenic scores
- Fine-mapping
- Prediction
Examples:
- DeepWAS
- Graph neural networks
- Transformer
Future:
- Better prediction
- Better fine-mapping
- Better understanding
""")
print("\n=== 4. Rare Variants ===")
print("""
Trend: Rare variants
Current:
- Common variants
- Arrays
Future:
- Rare variants
- Whole-genome sequencing
- Gene-based tests
Examples:
- UK Biobank WGS
- All of Us
- gnomAD
Future:
- Rare variant associations
- Missing heritability
- Clinical
""")
print("\n=== 5. Single-Cell ===")
print("""
Trend: Single-cell
Current:
- Bulk
Future:
- Single-cell
- Cell-type specific
- eQTL
Examples:
- scRNA-seq
- scATAC-seq
- Cell atlas
Future:
- Cell-type resolution
- Mechanisms
- Precision
""")
print("\n=== 6. Clinical Translation ===")
print("""
Trend: Clinical
Current:
- Research
Future:
- Clinical
- Polygenic scores
- Pharmacogenomics
- Screening
Examples:
- Breast cancer
- Cardiovascular
- Pharmacogenomics
Future:
- Clinical implementation
- Precision medicine
- Equity
""")
# TIMELINE
print("\n=== Timeline ===")
print("""
Past:
2005: First GWAS
2007: WTCCC
2010: Thousands
2015: Biobanks
2020: Millions
2025: Diverse populations
Present:
2025: Multi-omics
2026: AI
Future:
2030: Global
2035: Clinical
2040: Precision?
""")
print("\n=== Final Thoughts ===")
print("""
GWAS is:
- Transforming genetics
- Finding variants
- Understanding biology
- Evolving
The future is:
- Diverse populations
- Multi-omics
- AI
- Rare variants
- Single-cell
- Clinical translation
"GWAS is the key to complex genetics."
Keep exploring. Keep building.
""")