agentsclimarketplace

Plink2 gwas analysis

Skill jaechang-hits/SciAgent-Skills/skills/genomics-bioinformatics/variant/plink2-gwas-analysis

197 bioinformatics & life science skills for Claude Code and AI agents — BixBench 92.0% accuracy. RNA-seq, single-cell, drug discovery, proteomics, and more. Powers OmicsHorizon.

Install
npx -y skills add jaechang-hits/SciAgent-Skills --skill plink2-gwas-analysis

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

One thing to look at

  • no licenseNo license file was found in the repository. Code published without one is not open source by default, so using it at work is a question for whoever answers licensing questions where you are.

What its author says it does

Copied from the file, not written here

GWAS and population genetics tool. Processes PLINK (.bed/.bim/.fam), VCF, and BGEN; runs QC (MAF, HWE, missingness), IBD estimation, PCA, and linear/logistic regression GWAS. Outputs Manhattan-ready summary stats. Use regenie or SAIGE for biobanks (>100k samples) needing mixed models.

The file declares its own license as GPL-3.0. That is the author’s claim about this one file, and it is not the same thing as the license GitHub reports for the repository, which is listed with the other numbers below.

SKILL.md

12.5 KB, ~3.6k tokens by cl100k_base, as published. Nobody here has run it

PLINK2 — GWAS and Population Genetics

Overview

PLINK2 is the high-performance successor to PLINK 1.9, designed for genome-wide association studies (GWAS) and population genetics analysis on large cohorts. It processes genotype data in PLINK binary format (.bed/.bim/.fam), VCF, and BGEN formats — performing sample and variant quality control (QC), kinship estimation, principal component analysis (PCA), and linear/logistic regression association testing. PLINK2 is 10–100× faster than PLINK 1.9 on most tasks due to multithreading and optimized I/O. Output files are compatible with downstream visualization (Manhattan/QQ plots) and meta-analysis tools.

When to Use

  • Running GWAS on a case-control or quantitative trait cohort after genotyping array QC
  • Performing sample QC: missingness, heterozygosity outliers, sex check, cryptic relatedness
  • Computing genome-wide LD pruning for PCA or relatedness estimation
  • Running PCA on genotype data to identify population stratification
  • Converting between PLINK binary, VCF, and BGEN formats
  • Filtering variants by MAF, HWE, missingness, or INFO score in VCF/imputed data
  • Use regenie or SAIGE instead for biobank-scale GWAS (>100k samples) requiring mixed model association to control for population structure
  • Use VCFtools as an alternative for VCF-specific population genetics statistics

Prerequisites

  • Software: PLINK2 (pre-compiled binary; no pip/conda package)
  • Input: PLINK binary files (.bed/.bim/.fam) or VCF/BGEN from array genotyping or imputation

Check before installing: The tool may already be available (e.g., inside a pixi / conda env). Always run command -v plink2 first and skip the install block if it returns a path. When executing tools inside a pixi project, prefer pixi run <tool> over plain <tool>.

# Skip install if already present
if command -v plink2 >/dev/null 2>&1; then
    echo "plink2 already installed: $(plink2 --version)"
else
    # Download PLINK2 pre-compiled binary (Linux)
    wget https://s3.amazonaws.com/plink2-assets/alpha6/plink2_linux_avx2_20241112.zip
    unzip plink2_linux_avx2_20241112.zip
    chmod +x plink2
    export PATH="$PWD:$PATH"

    # macOS
    # wget https://s3.amazonaws.com/plink2-assets/alpha6/plink2_mac_20241112.zip
    # unzip plink2_mac_20241112.zip

    plink2 --version
    # PLINK v2.00a6LM
fi

# Python for downstream analysis
pip install pandas numpy matplotlib scipy

Quick Start

# Run GWAS: linear regression for quantitative trait
plink2 \
    --bfile cohort_qc \
    --pheno phenotypes.txt \
    --covar covariates.txt \
    --linear hide-covar \
    --out results/gwas_result \
    --threads 8

# View top hits
head results/gwas_result.*.glm.linear | sort -k12,12g | head -20

Workflow

Step 1: Convert VCF/Imputed Data to PLINK Binary Format

Convert input genotype data to PLINK binary format for fast processing.

# Convert VCF to PLINK binary
plink2 \
    --vcf cohort_genotyped.vcf.gz \
    --make-bed \
    --out cohort_plink \
    --threads 8

# Convert BGEN (imputed data from Michigan/TopMed imputation server)
plink2 \
    --bgen cohort_imputed.bgen ref-first \
    --sample cohort_imputed.sample \
    --make-bed \
    --out cohort_imputed_plink \
    --threads 8

echo "Files created:"
ls -lh cohort_plink.{bed,bim,fam}
echo "Samples: $(wc -l < cohort_plink.fam)"
echo "Variants: $(wc -l < cohort_plink.bim)"

Step 2: Sample QC — Missingness and Heterozygosity

Remove samples with high missingness or heterozygosity outliers.

# Compute per-sample and per-variant missingness
plink2 \
    --bfile cohort_plink \
    --missing \
    --out qc/sample_missingness \
    --threads 8

# Remove samples with > 2% missingness and variants with > 5% missing
plink2 \
    --bfile cohort_plink \
    --mind 0.02 \
    --geno 0.05 \
    --make-bed \
    --out cohort_sample_qc \
    --threads 8

echo "After sample QC:"
echo "Samples: $(wc -l < cohort_sample_qc.fam)"
echo "Variants: $(wc -l < cohort_sample_qc.bim)"

Step 3: Variant QC — MAF, HWE, and INFO Score Filtering

Filter variants by minor allele frequency, Hardy-Weinberg equilibrium, and imputation quality.

# Variant QC: MAF, HWE, missingness
plink2 \
    --bfile cohort_sample_qc \
    --maf 0.01 \
    --hwe 1e-6 \
    --geno 0.05 \
    --make-bed \
    --out cohort_variantqc \
    --threads 8

echo "After variant QC:"
echo "Variants remaining: $(wc -l < cohort_variantqc.bim)"

# For imputed data: also filter by INFO score (using VCF INFO field)
# plink2 --bfile cohort_variantqc --var-min-qual 0.8 --make-bed --out cohort_info_qc

Step 4: LD Pruning and PCA for Population Stratification

Compute principal components from LD-pruned variants.

# LD pruning: remove one variant from each pair with r² > 0.2
plink2 \
    --bfile cohort_variantqc \
    --indep-pairwise 50 5 0.2 \
    --out qc/ld_pruned \
    --threads 8

# Compute PCA on LD-pruned variants (top 20 PCs)
plink2 \
    --bfile cohort_variantqc \
    --extract qc/ld_pruned.prune.in \
    --pca 20 \
    --out pca/cohort_pca \
    --threads 8

echo "PCA files: pca/cohort_pca.eigenvec (PCs) and pca/cohort_pca.eigenval (variance)"
head pca/cohort_pca.eigenvec

Step 5: Run GWAS Association Analysis

Perform logistic regression (case-control) or linear regression (quantitative trait).

# Case-control GWAS: logistic regression with covariates
plink2 \
    --bfile cohort_variantqc \
    --pheno phenotypes.txt \
    --1 \
    --covar covariates.txt \
    --covar-variance-standardize \
    --logistic hide-covar \
    --ci 0.95 \
    --out results/gwas_cc \
    --threads 8

# Quantitative trait GWAS: linear regression
plink2 \
    --bfile cohort_variantqc \
    --pheno phenotypes.txt \
    --covar covariates.txt \
    --covar-variance-standardize \
    --linear hide-covar \
    --ci 0.95 \
    --out results/gwas_qt \
    --threads 8

echo "GWAS complete. Files: results/gwas_*.glm.*"
wc -l results/gwas_qt.*.glm.linear

Step 6: Plot Manhattan and QQ Plots

Visualize GWAS results with Python.

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from scipy.stats import chi2

# Load GWAS results (PLINK2 linear output)
df = pd.read_csv("results/gwas_qt.PHENO1.glm.linear", sep="\t",
                 usecols=["#CHROM", "POS", "ID", "P"])
df.columns = ["CHR", "POS", "SNP", "P"]
df = df.dropna(subset=["P"])
df["P"] = pd.to_numeric(df["P"], errors="coerce")
df = df[df["P"] > 0].copy()
df["-log10P"] = -np.log10(df["P"])

print(f"Variants: {len(df)}")
print(f"Genome-wide significant (p<5e-8): {(df['P'] < 5e-8).sum()}")

# Manhattan plot
fig, ax = plt.subplots(figsize=(14, 4))
colors = plt.cm.Set1.colors
chrom_pos = 0
ticks = []
for chrom, group in df.groupby("CHR", sort=False):
    col = colors[int(chrom) % 2] if str(chrom).isdigit() else "gray"
    ax.scatter(group["POS"] + chrom_pos, group["-log10P"],
               c=[col], s=2, alpha=0.7)
    ticks.append((chrom_pos + group["POS"].mean(), str(chrom)))
    chrom_pos += group["POS"].max() + 1e7

ax.axhline(-np.log10(5e-8), color="red", linestyle="--", lw=1, label="p=5×10⁻⁸")
ax.set_xticks([t[0] for t in ticks[::2]])
ax.set_xticklabels([t[1] for t in ticks[::2]], fontsize=6)
ax.set_xlabel("Chromosome")
ax.set_ylabel("-log₁₀(p)")
ax.set_title("GWAS Manhattan Plot")
plt.tight_layout()
plt.savefig("manhattan.png", dpi=150)
print("Saved: manhattan.png")

Key Parameters

ParameterDefaultRange/OptionsEffect
--maf0–0.5Minor allele frequency threshold; 0.01 removes singletons
--hwe0–1Hardy-Weinberg equilibrium p-value threshold; 1e-6 typical
--geno0–1Maximum variant missingness; 0.05 = 5%
--mind0–1Maximum sample missingness; 0.02 = 2%
--linear / --logisticflagRegression mode: linear for quantitative, logistic for case-control
--covarfile pathCovariate file (FID IID COV1 COV2...); add PCs here
--pcaintegerNumber of PCs to compute from LD-pruned data
--indep-pairwisewindow step r²LD pruning: window size (variants), step, r² threshold
--threads11–64CPU threads; 8–16 typical for cluster
--ci0–1Confidence interval for effect size output (0.95 for 95% CI)
--1offflagRecode phenotype: 1=control, 2=case → 0=control, 1=case (logistic)

Common Recipes

Recipe 1: Relatedness QC and Remove One from Each Related Pair

# Compute kinship coefficients (King-robust estimator)
plink2 \
    --bfile cohort_variantqc \
    --extract qc/ld_pruned.prune.in \
    --king-cutoff 0.0625 \
    --out qc/kinship \
    --threads 8

# Remove related individuals (king-cutoff auto-generates exclusion list)
plink2 \
    --bfile cohort_variantqc \
    --remove qc/kinship.king.cutoff.out.id \
    --make-bed \
    --out cohort_unrelated \
    --threads 8

echo "After relatedness QC: $(wc -l < cohort_unrelated.fam) samples"

Recipe 2: Parse GWAS Results and Report Top Hits

import pandas as pd
import numpy as np

def load_gwas_results(filepath: str, p_threshold: float = 5e-8) -> pd.DataFrame:
    """Load PLINK2 GWAS linear/logistic output and return genome-wide significant hits."""
    df = pd.read_csv(filepath, sep="\t",
                     usecols=["#CHROM", "POS", "ID", "REF", "ALT", "A1", "BETA", "SE", "P"])
    df.columns = ["CHR", "POS", "SNP", "REF", "ALT", "A1", "BETA", "SE", "P"]
    df = df.dropna(subset=["P"])
    df["P"] = pd.to_numeric(df["P"], errors="coerce")
    sig = df[df["P"] < p_threshold].sort_values("P")
    return sig

# Load results for one phenotype
hits = load_gwas_results("results/gwas_qt.PHENO1.glm.linear")
print(f"Genome-wide significant loci: {len(hits)}")
print(hits.head(10)[["CHR", "POS", "SNP", "BETA", "SE", "P"]])
hits.to_csv("gwas_top_hits.tsv", sep="\t", index=False)

Expected Outputs

OutputFormatDescription
*.glm.linearTSVLinear GWAS results: CHR, POS, ID, BETA, SE, P per variant
*.glm.logistic.hybridTSVLogistic GWAS results: OR, 95% CI, P per variant
*.eigenvecTSVPCA loadings: FID IID PC1 PC2 ... PC20
*.eigenvalTextEigenvalues for PCA variance explained
*.king.cutoff.out.idTSVSample IDs to remove for relatedness QC
*.bed/.bim/.famBinaryPLINK binary format after QC filtering

Troubleshooting

ProblemCauseSolution
Error: No samples left after --mind filterMissingness threshold too strictRelax to --mind 0.05; check input data quality
Logistic regression fails to convergeRare variant, few cases, or collinear covariatesUse --logistic firth for Firth regression; remove correlated covariates
PCA shows clear outlier clusterAdmixed population or sample contaminationRemove outliers using PC cutoffs; check ancestry with 1000 Genomes reference panel
--hwe removes too many variantsPopulation structure inflating HWE testApply HWE filter to controls only: --hwe 1e-6 ctrl-only
BGEN import failsWrong ref-first/ref-last flagCheck imputation server documentation; try --bgen file.bgen ref-last
Very slow association testToo many covariates or large filePre-filter to GWAS-significant window; use --threads 16
Sex mismatch warningsX chromosome heterozygosity outside sex-specific thresholdsRun --check-sex and remove F-statistic outliers
Memory error on large datasetRAM insufficient for 500k+ variantsUse --memory 32000 to cap RAM; split by chromosome

References

Keep looking

Skills are one crate of 328,083. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.