---
title: "Running qpAdm in R with ADMIXTOOLS 2: a working tutorial"
description: "From genotype files to a tested model: f2 extraction, qpadm() and its output tables, the arguments that silently change results (allsnps, fudge_twice, constrained), and the protocol discipline the code will not enforce for you."
canonical: https://www.ancestrify.io/blog/run-qpadm-in-r-admixtools2
date: 2026-08-31T10:32:00+00:00
updated: 2026-09-24
author: "Andi Thomaj"
---

# Running qpAdm in R with ADMIXTOOLS 2: a working tutorial

Everything qpAdm, the method behind [a decade of ancient-DNA papers](/blog/how-qpadm-changed-ancient-dna)
and [our paid analysis](/qpadm), is free software: the `qpadm()` function of the ADMIXTOOLS 2 R
package (Maier et al. 2023). This is a working tutorial for running it yourself: the setup, the
calls, the output tables, and the part no README covers, the arguments and protocol choices that
silently change your results.

If you want the modelling without the pipeline, that exists too: the free
[AdmixTools 2 Lab](/lab/admixtools) runs f-statistics, [qpWave](/blog/qpwave-explained) and qpAdm
against a curated ancient panel in the browser, and the
[Ancestry Workbench](/blog/run-your-own-qpadm-model-lab) does it on your own genome merged into AADR. This
post is for the readers who want the R console.

## Setup

ADMIXTOOLS 2 installs from GitHub (`uqrmaie1/admixtools`); on most systems
`devtools::install_github("uqrmaie1/admixtools")` plus a compiler is the whole story. Data must
be EIGENSTRAT or PACKEDANCESTRYMAP genotypes; in practice that means the
[AADR](/blog/aadr-allen-ancient-dna-resource-explained) download, with your own genotypes
[merged in](/blog/qpadm-from-23andme-raw-data) if you intend to model yourself. The merge is its
own craft (position intersection, strand hygiene, [build discipline](/blog/how-to-get-global25-coordinates#whole-genome-sequencing));
sanity-check any consumer file first with the free [file check](/lab/file-check).

## The two-step workflow

ADMIXTOOLS 2's speed comes from precomputing **f2-statistics** once, then reusing them across
thousands of models:

```r
library(admixtools)

extract_f2("path/to/geno_prefix", "f2_dir",
           pops = my_pops, maxmiss = 0)   # once, slow
f2 <- f2_from_precomp("f2_dir")           # fast forever after

left  <- c("Anatolia_N", "Yamnaya_Samara", "WHG")
right <- c("Mbuti.DG", "Ust_Ishim.DG", "Kostenki14", "MA1",
           "Han.DG", "Papuan.DG", "Onge.DG", "Karitiana.DG",
           "EHG", "Iran_N", "Levant_N")
qpadm(f2, left, right, target = "MyKit")
```

The right set above is the [standard spine plus era contrasts](/blog/how-to-choose-qpadm-sources-and-outgroups#the-standard-right-sets):
not a decoration to copy blindly but the half of the model
[that decides whether it can be tested at all](/blog/how-to-choose-qpadm-sources-and-outgroups).

**The sparse-data exception, immediately:** precomputed f2 blocks restrict every statistic to
sites present across all populations. With low-coverage ancients that discards most of the data,
and the measured penalty reaches
[SE 9.994 versus 0.035 at 90% missingness](/blog/how-many-snps-does-qpadm-need). The fix is
classic qpAdm's `allsnps` behaviour, which in ADMIXTOOLS 2 requires skipping the f2 directory and
passing the genotype prefix directly:

```r
qpadm("path/to/geno_prefix", left, right, target = "MyKit",
      allsnps = TRUE)
```

Slower, and usually right for real ancient panels. Whichever you choose, hold it constant across
every model you intend to compare.

## Reading the output

`qpadm()` returns the tables a [model record](/blog/qpadm-model-record-explained) is built from:

- **`weights`**: `weight`, `se` and `z` for each source. The
  [three-number reading](/blog/how-to-read-qpadm-p-value-z-score-standard-error): a weight whose
  2-SE interval covers 0 is a source the data cannot tell apart from absent; negative weights are
  [a diagnosis, not a nuisance](/blog/why-qpadm-models-get-rejected#negative-weights).
- **`rankdrop`**: the [rank test](/blog/qpwave-explained), with chi-square, dof (outgroups minus
  sources for the full model) and `p` for each tested rank, plus `p_nested` comparing adjacent
  ranks.
- **`popdrop`**: every sub-model obtained by dropping sources, with its p, weights and a `feasible`
  flag. The discipline it encodes: **if a simpler model survives, prefer the simpler model**, as
  parsimony relative to your right set rather than as a count of historical sources.

## The arguments that silently change results

- **`allsnps`**: see above; the largest single lever on sparse data.
- **`fudge_twice = TRUE`**: applies the numerical ridge a second time, matching classic qpAdm's
  p-values. Set it when comparing against published numbers; either way, consistently.
- **`constrained = TRUE`**: forces weights non-negative. Never for screening: an unconstrained
  negative weight is information about a wrong or missing source, and constraining hides it.
- **`blgsize`**: jackknife block size, default 0.05 Morgans (5 cM). The convention behind every
  published SE; change it only knowingly.
- **`boot`**: bootstrap instead of jackknife resampling. Jackknife SEs are the ones comparable
  to the published literature.

Every knob above has a fuller treatment (what it does, the measured stakes, when to deviate) in
[the parameters reference](/blog/admixtools2-f2-extraction-parameters), with
[its section on matching classic ADMIXTOOLS](/blog/admixtools2-f2-extraction-parameters#matching-classic-admixtools) covering the settings that
reproduce published numbers from the original software. And if your own file is not yet merged
into the AADR, [the data-preparation guide](/blog/qpadm-data-preparation-merge-aadr) is the
missing chapter before this one.

Batch helpers exist (`qpadm_rotate()`, `qpadm_multi()`), and they make it trivial to test thousands
of models, which is where the false-discovery hazard lives: the more implausible models a search
contains, the more of its passes are wrong, whatever the protocol
([the pre-study odds arithmetic](/blog/qpadm-rotation-explained)). On simulated admixture-graph
histories, unstratified rotating screens ran false-discovery rates of 72.5 to 100%; on simulated
stepping-stone landscapes the protocols did not differ significantly, and the odds decided.
Enumerate to map the space; never let the enumeration pick the winner.

## The discipline the code will not enforce

`qpadm()` will happily run models that violate every assumption behind the statistics. The
protocol, from the audit literature, is the difference between output and results:

1. **[Constrain the hypotheses first](/blog/qpadm-rotation-explained):** choose a few candidate
   sources for reasons outside qpAdm (archaeology, chronology, geography, earlier genetic work). A
   search's false-discovery rate depends on how plausible its candidates were before anything ran.
2. **[Temporal stratification](/blog/qpadm-distal-vs-proximal):** no source younger than the
   target. A modern kit against ancient sources satisfies this by construction, which protects
   against one class of errors on graph-like histories and is not a guarantee on landscape-like
   ones.
3. **[Simplest adequate model first](/blog/why-qpadm-models-get-rejected):** one-source
   explanations before two, two before three, with `p_nested` as referee. That is parsimony
   relative to your right set, not a count of the migrations in the target's history.
4. **Never rank surviving models by p:** the best p identifies the true model 48% of the time.
   Margins, feasibility and stability across right-set variations do the choosing.
5. **Composite feasibility:** p above threshold *and* weights bounded inside (0,1) within error
   *and* trailing simpler models rejected. These criteria cut false positives more than fourfold
   in simulations; on their own they do not rescue a search with poor pre-study odds.
6. **Report the family, not the winner:** state which models also passed and what they agree on;
   single-winner claims are the overfit the auditors keep finding.
7. **Read a fit as a position, not an event:** on landscape-like histories, a fitting model can
   summarise where the target sits between sampled groups without those groups ever having mixed.

That protocol is what [the paid analysis](/qpadm) applies to your file, plus the merge, the curated
panels and a [model record](/blog/qpadm-model-record-explained) at the end. Candidate sources are
chosen for your declared origins before any model runs, our analyst tests models from that pool,
and a person reviews the results and approves the one that is published, measured against one
house bar (p > 0.05, every |Z| > 3, every SE < 0.10) that is our own convention. Run it yourself,
buy it run, or both: the method is the same, which is exactly the point.

Terms used here are defined in the [glossary](/glossary).

## References

- Maier, R. et al. (2023). On the limits of fitting complex models of population history to
  f-statistics. *eLife*, 12, e85492. (ADMIXTOOLS 2.)
- Harney, É., Patterson, N., Reich, D. & Wakeley, J. (2021). Assessing the performance of qpAdm.
  *Genetics*, 217(4), iyaa045.
- Williams, M. P. et al. (2024). Testing times: disentangling admixture histories in recent and
  complex demographies using ancient DNA. *Genetics*, 228(1), iyae110.
- Flegontova, O. et al. (2025). [Performance of qpAdm-based screens for genetic admixture on graph-shaped histories and stepping stone landscapes](https://doi.org/10.1093/genetics/iyaf047). *Genetics*, 230(1), iyaf047.
- ADMIXTOOLS 2 documentation and source: uqrmaie1.github.io/admixtools.

*Editorial note: revised on 24 September 2026. An earlier version quoted the admixture-graph false-discovery rates without their regime, overstated what composite criteria do to the false-discovery rate (they cut false positives; the false-discovery rate moves far less), and described our own analysis in terms that no longer matched how it runs.*
