
Bio Population Genetics Selection Statistics
- 3 installs
- 1.1k repo stars
- Updated July 25, 2026
- gptomics/bioskills
Compute selection statistics like Fst, Tajima's D, and iHS to detect selective sweeps using scikit-allel and vcftools.
About
Detects signatures of natural selection using Fst, Tajima's D, iHS, XP-EHH and other statistics with scikit-allel and vcftools. Developers use it to measure population differentiation, test neutrality, and find selective sweeps.
- Fst, Tajima's D, iHS, XP-EHH selection statistics
- Detect selective sweeps with scikit-allel and vcftools
Bio Population Genetics Selection Statistics by the numbers
- 3 all-time installs (skills.sh)
- Ranked #1,661 of 2,064 Data Science & ML skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/gptomics/bioskills --skill bio-population-genetics-selection-statisticsAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 3 |
|---|---|
| repo stars | ★ 1.1k |
| Last updated | July 25, 2026 |
| Repository | gptomics/bioskills ↗ |
What it does
Compute selection statistics like Fst, Tajima's D, and iHS to detect selective sweeps using scikit-allel and vcftools.
Files
Selection Statistics
Detect natural selection signatures using diversity statistics and extended haplotype homozygosity.
Fst - Population Differentiation
scikit-allel
import allel
import numpy as np
callset = allel.read_vcf('data.vcf.gz')
gt = allel.GenotypeArray(callset['calldata/GT'])
pos = callset['variants/POS']
subpops = {'pop1': [0, 1, 2, 3, 4], 'pop2': [5, 6, 7, 8, 9]}
ac_subpops = gt.count_alleles_subpops(subpops)
num, den = allel.hudson_fst(ac_subpops['pop1'], ac_subpops['pop2'])
fst_per_snp = num / den
print(f'Mean Fst: {np.nanmean(fst_per_snp):.4f}')Windowed Fst
fst_windowed, windows, n_snps = allel.windowed_hudson_fst(
pos, ac_subpops['pop1'], ac_subpops['pop2'],
size=100000, step=50000)
import matplotlib.pyplot as plt
plt.figure(figsize=(14, 4))
plt.plot(windows[:, 0], fst_windowed)
plt.xlabel('Position')
plt.ylabel('Fst')
plt.savefig('fst_windows.png')vcftools
# Calculate Fst between populations
vcftools --vcf data.vcf --weir-fst-pop pop1.txt --weir-fst-pop pop2.txt --out fst_result
# With window
vcftools --vcf data.vcf --weir-fst-pop pop1.txt --weir-fst-pop pop2.txt \
--fst-window-size 100000 --fst-window-step 50000 --out fst_windowedTajima's D - Departures from Neutrality
scikit-allel
import allel
import numpy as np
callset = allel.read_vcf('data.vcf.gz')
gt = allel.GenotypeArray(callset['calldata/GT'])
pos = callset['variants/POS']
ac = gt.count_alleles()
D, windows, counts = allel.windowed_tajima_d(pos, ac, size=100000, step=50000)
plt.figure(figsize=(14, 4))
plt.plot(windows[:, 0], D)
plt.axhline(y=0, color='r', linestyle='--')
plt.xlabel('Position')
plt.ylabel("Tajima's D")
plt.savefig('tajima_d.png')Interpretation
| D Value | Interpretation |
|---|---|
| D < -2 | Recent selective sweep or population expansion |
| D ≈ 0 | Neutral evolution |
| D > 2 | Balancing selection or population bottleneck |
vcftools
vcftools --vcf data.vcf --TajimaD 100000 --out tajima
# Output: tajima.Tajima.D (CHROM, BIN_START, N_SNPS, TajimaD)iHS - Integrated Haplotype Score
Detects ongoing selective sweeps.
import allel
import numpy as np
callset = allel.read_vcf('data.vcf.gz')
gt = allel.GenotypeArray(callset['calldata/GT'])
pos = callset['variants/POS']
h = gt.to_haplotypes()
ac = h.count_alleles()
flt = (ac[:, 0] > 1) & (ac[:, 1] > 1)
h_flt = h.compress(flt, axis=0)
pos_flt = pos[flt]
ac_flt = ac.compress(flt, axis=0)
ihs = allel.ihs(h_flt, pos_flt, include_edges=True)
ihs_std = allel.standardize_by_allele_count(ihs, ac_flt[:, 1])
significant_ihs = np.abs(ihs_std[0]) > 2
print(f'Significant iHS hits: {significant_ihs.sum()}')Plot iHS
import matplotlib.pyplot as plt
plt.figure(figsize=(14, 4))
plt.scatter(pos_flt, ihs_std[0], s=1)
plt.axhline(y=2, color='r', linestyle='--')
plt.axhline(y=-2, color='r', linestyle='--')
plt.xlabel('Position')
plt.ylabel('Standardized iHS')
plt.savefig('ihs.png')XP-EHH - Cross-Population Extended Haplotype Homozygosity
Detects completed sweeps by comparing populations.
import allel
import numpy as np
h = gt.to_haplotypes()
h_pop1 = h.take(pop1_hap_idx, axis=1)
h_pop2 = h.take(pop2_hap_idx, axis=1)
xpehh = allel.xpehh(h_pop1, h_pop2, pos, include_edges=True)
significant = np.abs(xpehh) > 2
print(f'Significant XP-EHH hits: {significant.sum()}')NSL - Number of Segregating Sites by Length
Alternative to iHS, less sensitive to recombination rate variation.
nsl = allel.nsl(h_flt)
nsl_std = allel.standardize_by_allele_count(nsl, ac_flt[:, 1])Garud's H Statistics
Detect soft sweeps.
h1, h12, h123, h2_h1 = allel.garud_h(h)
h12_windowed = allel.moving_garud_h(h, size=100)Composite Selection Score
Combine multiple statistics:
import numpy as np
from scipy import stats
def composite_score(fst, tajD, ihs_abs):
fst_rank = stats.rankdata(fst) / len(fst)
tajD_rank = stats.rankdata(-tajD) / len(tajD) # Low Tajima's D
ihs_rank = stats.rankdata(ihs_abs) / len(ihs_abs)
return (fst_rank + tajD_rank + ihs_rank) / 3
css = composite_score(fst_per_snp, tajD_values, np.abs(ihs_values))Complete Selection Scan
import allel
import numpy as np
import matplotlib.pyplot as plt
callset = allel.read_vcf('data.vcf.gz')
gt = allel.GenotypeArray(callset['calldata/GT'])
pos = callset['variants/POS']
ac = gt.count_alleles()
flt = ac.is_segregating() & (ac.max_allele() == 1)
gt = gt.compress(flt, axis=0)
pos = pos[flt]
ac = ac.compress(flt, axis=0)
window_size = 100000
window_step = 50000
tajD, tajD_windows, _ = allel.windowed_tajima_d(pos, ac, size=window_size, step=window_step)
pi, pi_windows, _, _ = allel.windowed_diversity(pos, ac, size=window_size, step=window_step)
fig, axes = plt.subplots(2, 1, figsize=(14, 8), sharex=True)
axes[0].plot(tajD_windows[:, 0], tajD)
axes[0].axhline(0, color='r', linestyle='--')
axes[0].set_ylabel("Tajima's D")
axes[1].plot(pi_windows[:, 0], pi)
axes[1].set_ylabel('Pi')
axes[1].set_xlabel('Position')
plt.tight_layout()
plt.savefig('selection_scan.png', dpi=150)Related Skills
- scikit-allel-analysis - Data loading and basic statistics
- population-structure - Population assignment for Fst
- linkage-disequilibrium - EHH depends on LD patterns
#!/usr/bin/env python3
'''Genome-wide selection scan with Fst and Tajima's D.'''
import allel
import numpy as np
import matplotlib.pyplot as plt
vcf_file = 'data.vcf.gz'
pop1_samples = ['sample1', 'sample2', 'sample3', 'sample4', 'sample5']
pop2_samples = ['sample6', 'sample7', 'sample8', 'sample9', 'sample10']
print('=== Loading Data ===')
callset = allel.read_vcf(vcf_file)
gt = allel.GenotypeArray(callset['calldata/GT'])
pos = callset['variants/POS']
samples = list(callset['samples'])
pop1_idx = [samples.index(s) for s in pop1_samples if s in samples]
pop2_idx = [samples.index(s) for s in pop2_samples if s in samples]
print(f'Pop1 samples: {len(pop1_idx)}')
print(f'Pop2 samples: {len(pop2_idx)}')
print('\n=== Filtering ===')
ac = gt.count_alleles()
flt = ac.is_segregating() & (ac.max_allele() == 1)
gt = gt.compress(flt, axis=0)
pos = pos[flt]
print(f'Variants after filtering: {gt.n_variants}')
print('\n=== Calculating Fst ===')
subpops = {'pop1': pop1_idx, 'pop2': pop2_idx}
ac_subpops = gt.count_alleles_subpops(subpops)
fst, fst_windows, _ = allel.windowed_hudson_fst(
pos, ac_subpops['pop1'], ac_subpops['pop2'],
size=100000, step=50000)
print(f'Mean genome-wide Fst: {np.nanmean(fst):.4f}')
print("\n=== Calculating Tajima's D ===")
ac_all = gt.count_alleles()
tajD, tajD_windows, _ = allel.windowed_tajima_d(pos, ac_all, size=100000, step=50000)
print(f"Mean Tajima's D: {np.nanmean(tajD):.4f}")
print('\n=== Plotting ===')
fig, axes = plt.subplots(2, 1, figsize=(14, 8), sharex=True)
axes[0].plot(fst_windows[:, 0] / 1e6, fst, 'b-', linewidth=0.5)
axes[0].axhline(np.nanmean(fst), color='r', linestyle='--', alpha=0.5)
axes[0].set_ylabel('Fst')
axes[0].set_title('Population Differentiation')
axes[1].plot(tajD_windows[:, 0] / 1e6, tajD, 'g-', linewidth=0.5)
axes[1].axhline(0, color='r', linestyle='--', alpha=0.5)
axes[1].set_ylabel("Tajima's D")
axes[1].set_xlabel('Position (Mb)')
axes[1].set_title('Departures from Neutrality')
plt.tight_layout()
plt.savefig('selection_scan.png', dpi=150)
print('Plot saved: selection_scan.png')
print('\n=== Top Fst Windows ===')
top_fst_idx = np.argsort(fst)[-10:][::-1]
for i in top_fst_idx:
if not np.isnan(fst[i]):
print(f'{fst_windows[i, 0]:,}-{fst_windows[i, 1]:,}: Fst={fst[i]:.4f}')
print("\n=== Extreme Tajima's D Windows ===")
low_tajD_idx = np.argsort(tajD)[:5]
for i in low_tajD_idx:
if not np.isnan(tajD[i]):
print(f'{tajD_windows[i, 0]:,}-{tajD_windows[i, 1]:,}: D={tajD[i]:.4f}')
print('\n=== Done ===')
Selection Statistics - Usage Guide
Overview
Selection statistics detect signatures of natural selection in genomic data. Different methods detect different selection types and timescales, from recent sweeps (iHS) to ancient differentiation (Fst).
Prerequisites
pip install scikit-allel
conda install -c bioconda vcftoolsQuick Start
Tell your AI agent what you want to do:
- "Calculate Fst between my two populations"
- "Scan for selection signatures using Tajima's D"
- "Compute iHS to detect ongoing selective sweeps"
- "Find regions under balancing selection"
- "Compare selection pressures between populations"
Example Prompts
Diversity Statistics
"Calculate Tajima's D in 50kb windows across the genome"
"Compute nucleotide diversity (pi) for each population"
"Find regions with unusually low diversity suggesting sweeps"
Population Differentiation
"Calculate Fst between European and African samples"
"Find highly differentiated SNPs between cases and controls"
"Generate a Manhattan plot of Fst values"
Haplotype-Based Tests
"Compute iHS scores to detect ongoing selection"
"Run XP-EHH between my populations to find completed sweeps"
"Identify haplotypes under positive selection"
Multi-Statistic Analysis
"Scan for selection using Tajima's D, Fst, and iHS together"
"Find regions significant in multiple selection tests"
"Compare selection signatures across chromosomes"
What the Agent Will Do
1. Assess data format and phase status 2. Calculate requested statistics genome-wide or in windows 3. Standardize/normalize scores where appropriate 4. Identify outlier regions exceeding thresholds 5. Generate visualizations (Manhattan plots, histograms) 6. Report candidate regions with coordinates
Tips
- Haplotype-based tests (iHS, XP-EHH) require phased data
- Demographic history can mimic selection signals
- Use multiple statistics to reduce false positives
- Always adjust for recombination rate variation
- Empirical outlier cutoffs (top 1%) are often more reliable than p-values
Selection Signatures Reference
| Statistic | Type Detected | Timescale |
|---|---|---|
| Fst | Population differentiation | Any |
| Tajima's D | Neutral departures | Recent |
| iHS | Ongoing sweep | Very recent |
| XP-EHH | Completed sweep | Recent |
| H12/H2H1 | Soft sweeps | Recent |
Positive Selection (Hard Sweep)
Signs:
- Low Tajima's D (< -2)
- High |iHS| (> 2)
- High Fst between populations
- Reduced diversity (Pi)
Balancing Selection
Signs:
- High Tajima's D (> 2)
- Elevated heterozygosity
- Old alleles maintained
Recent Selection
Use haplotype-based methods:
- iHS for ongoing sweeps
- XP-EHH for completed sweeps
Ancient Selection
Use diversity-based methods:
- Fst for differentiation
- dN/dS for coding regions
Interpretation Caveats
- Demographic history mimics selection
- Recombination rate affects EHH statistics
- Multiple testing correction needed
- Functional validation recommended