This post covers using qpAdm in R to test ancestry models and estimate admixture proportions. qpAdm builds on f4-statistics and provides a framework for evaluating whether proposed source populations can explain a target population’s genetic makeup.
For R and admixtools setup instructions on Debian/Ubuntu, see my previous post: Running f4-Statistics with Admixtools in R. Windows users can find R installation instructions on the R website.
What is qpAdm?
qpAdm is a method for testing ancestry models and estimating admixture proportions. It determines whether a target population can be modeled as a mixture of specified source populations (“left populations”), and if the model fits, calculates the contribution from each source. The method builds on f4-statistics (covered in my previous post) to evaluate these ancestry models.
Unlike many ancestry estimation tools, qpAdm provides a formal statistical framework for model evaluation through a fit test. If the model is feasible (p-value > 0.05), qpAdm estimates the admixture proportions. Like f4-statistics and other admixtools methods, qpAdm handles low-coverage ancient DNA samples well, making it particularly suitable for ancient DNA research.
How to run qpAdm-Models in R?
For this tutorial, I’ll use as usual the AADR dataset, a curated collection of ancient and modern genomic samples in EIGENSTRAT format. Since admixtools works natively with EIGENSTRAT files, the dataset can be used directly. For download instructions, see: Download Ancient & Modern DNA (AADR Tutorial).
First, navigate to the folder containing your EIGENSTRAT dataset and start R:
cd /path/to/aadr/dataset
R
Within R we load the admixtools library (refer to the previous f4-statistics post linked above for set up instructions if you are unsure how to set it up on Debian/Ubuntu) and set the file prefix:
library(admixtools)
# Assuming the EIGENSTRAT dataset is named data.geno/.snp/.ind
data = "data"
When running qpAdm, you specify three components: a target population, source populations (left), and outgroup populations (right). I typically define these as separate variables before passing them to the qpadm() function.
To demonstrate, let’s test a specific admixture model.
Example: Modeling Northern Italians as a Mixture of Neolithic and Bronze Age Populations
First, specify the target population using the exact label from the third column of your .ind file, for this example:
target = "Italian_North.HO"
The qpAdm function will automatically include all samples assigned to this population label.
Next, we will set an initial set of left populations.
For a Neolithic-Bronze Age model we will start with three main populations: Anatolian Neolithic Farmers, Bronze Age Steppe pastoralists and Paleolithic Hunter Gatherers from Italy:
left <- c("Turkey_Marmara_Barcin_N.SG", "Russia_Samara_EBA_Yamnaya.AG", "Italy_Epigravettian.SG")
Next, select the right populations (outgroups). These are crucial for model evaluation, as they provide the statistical contrasts qpAdm uses to resolve ancestry components. There’s no definitive method for choosing right populations, though 8-12 is typically a good range. The key is selecting populations that span different branches of the population tree relevant to your model and are differentially related to your sources (see the f4-statistics post for details). Ideally, right populations should not have received gene flow from your left populations (and target), as this can obscure the statistical signal.
When selecting right populations, consider the temporal depth of your analysis. For instance, when modeling Iron Age samples, including Bronze Age right populations can improve resolution compared to using only deep prehistoric outgroups. Similarly, if you’re trying to distinguish between closely related source populations, choose right populations that are differentially related to those sources. This reveals drift imbalances that help resolve subtle ancestry differences.
For this example, I’ll use a smaller set to demonstrate the iterative process of model refinement. Mbuti serves as a deep outgroup, commonly used in f4-statistics as well. Turkey_Central_Pinarbasi_Epipaleolithic provides an anchor for the Anatolian Neolithic component since it predates Barcin farmers. Russia_MA1_UP serves as an Ancient North Eurasian (ANE) anchor, which is relevant for resolving Yamnaya ancestry. Similarly, Georgia_Satsurblia_LateUP acts as a Caucasus Hunter-Gatherer (CHG) anchor, another component important for Yamnaya. These populations help differentiate the ancestry sources by providing distinct statistical contrasts:
right <- c("Mbuti.DG", "Turkey_Central_Pinarbasi_Epipaleolithic.AG", "Russia_MA1_UP.SG", "Jordan_PPNB.AG", "Georgia_Satsurblia_LateUP.SG")
Now run the model and save it into a variable called (for example) result:
result <- qpadm(data, left, right, target)
After running the model, display the results by entering result:
# A tibble: 3 × 5
target left weight se z
<chr> <chr> <dbl> <dbl> <dbl>
1 Italian_North.HO Turkey_Marmara_Barcin_N.SG 0.587 0.0649 9.04
2 Italian_North.HO Russia_Samara_EBA_Yamnaya.AG 0.381 0.0941 4.04
3 Italian_North.HO Italy_Epigravettian.SG 0.0320 0.153 0.209
This shows the estimated ancestry proportions (weights), their standard errors (se), and z-scores indicating statistical significance. The z-scores test whether each ancestry component is significantly different from zero.
The results show 58.7% (± 6.49%) Neolithic Farmer ancestry, 38.1% (± 9.41%) Bronze Age Steppe ancestry, and 3.2% Epigravettian hunter-gatherer ancestry. However, the Epigravettian component has a z-score of only 0.209, indicating it’s not statistically distinguishable from zero. The extremely high standard error (± 15.3%) relative to the estimate confirms this component is poorly resolved in the current model setup. Even before checking the overall model fit (p-value), this signals a problem with how the model resolves hunter-gatherer ancestry.
The rankdrop section shows the model fit statistics:
# A tibble: 3 × 7
f4rank dof chisq p dofdiff chisqdiff p_nested
<int> <int> <dbl> <dbl> <int> <dbl> <dbl>
1 2 2 7.67 2.16e- 2 4 34.9 4.94e- 7
2 1 6 42.5 1.44e- 7 6 627. 3.16e-132
3 0 12 670. 1.32e-135 NA NA NA
The first row shows our three-source model (f4rank = 2). The p-value of 0.0216 falls below the 0.05 threshold, meaning the model is rejected. The proposed sources cannot explain the target’s ancestry with the current right population set. This combination of a failed p-value and poorly resolved Epigravettian component suggests we need to refine our approach by adding more right populations that can better resolve hunter-gatherer ancestry:
# We include a sixth right pop to our right populations vector
right[6] = "Switzerland_Bichon_Epipaleolithic.SG"
After adding the Bichon Epipaleolithic population to our right populations (which should serve as anchor for the WHG ancestry), we re-run the model and see:
# A tibble: 3 × 5
target left weight se z
<chr> <chr> <dbl> <dbl> <dbl>
1 Italian_North.HO Turkey_Marmara_Barcin_N.SG 0.579 0.0203 28.5
2 Italian_North.HO Russia_Samara_EBA_Yamnaya.AG 0.368 0.0226 16.3
3 Italian_North.HO Italy_Epigravettian.SG 0.0525 0.00910 5.77
$rankdrop
# A tibble: 3 × 7
f4rank dof chisq p dofdiff chisqdiff p_nested
<int> <int> <dbl> <dbl> <int> <dbl> <dbl>
1 2 3 7.78 5.08e- 2 5 593. 6.31e-126
2 1 8 601. 1.53e-124 7 1750. 0
3 0 15 2351. 0 NA NA NA
The Epigravettian component is now well-resolved with a z-score of 5.77 and a much smaller standard error (± 0.91%). The model now passes with p = 0.0508, just above the 0.05 threshold. However, this marginal p-value suggests the model is still under strain. To improve the fit, we’ll test whether adding a fourth source, a Levantine population, improves the model:
left[4] = "Lebanon_Hellenistic.SG"
After adding Hellenistic-era samples from Lebanon as a fourth source, we re-run qpAdm and see:
# A tibble: 4 × 5
target left weight se z
<chr> <chr> <dbl> <dbl> <dbl>
1 Italian_North.HO Turkey_Marmara_Barcin_N.SG 0.450 0.0524 8.59
2 Italian_North.HO Russia_Samara_EBA_Yamnaya.AG 0.325 0.0328 9.89
3 Italian_North.HO Italy_Epigravettian.SG 0.0615 0.0112 5.49
4 Italian_North.HO Lebanon_Hellenistic.SG 0.164 0.0654 2.50
$rankdrop
# A tibble: 4 × 7
f4rank dof chisq p dofdiff chisqdiff p_nested
<int> <int> <dbl> <dbl> <int> <dbl> <dbl>
1 3 2 2.17 3.37e- 1 4 32.4 1.58e- 6
2 2 6 34.6 5.20e- 6 6 482. 5.04e-101
3 1 12 517. 5.23e-103 8 1485. 1.86e-315
4 0 20 2003. 0 NA NA NA
The Levantine source contributes a significant 16.4% (± 6.54%), and the model fit improves substantially. The p-value rises to 0.337, well above the acceptance threshold, and all four ancestry components show strong statistical significance (z > 2.5). This four-way model successfully captures Northern Italian ancestry as a mixture of Anatolian Neolithic farmers, Bronze Age steppe populations, Western hunter-gatherers, and Eastern Mediterranean ancestry.
To better constrain the timing of the Eastern Mediterranean ancestry, I’ll test whether Bronze Age Levantine samples provide a better fit than Hellenistic-era samples. We can simply replace the Hellenistic source in our left populations vector:
# Replacing Hellenistic Lebanon with Lebanon_MBA
left[4] = "Lebanon_MBA.SG"
After re-running qpAdm we see:
# A tibble: 4 × 5
target left weight se z
<chr> <chr> <dbl> <dbl> <dbl>
1 Italian_North.HO Turkey_Marmara_Barcin_N.SG 0.402 0.0559 7.19
2 Italian_North.HO Russia_Samara_EBA_Yamnaya.AG 0.301 0.0266 11.3
3 Italian_North.HO Italy_Epigravettian.SG 0.0747 0.00981 7.61
4 Italian_North.HO Lebanon_MBA.SG 0.222 0.0666 3.34
$rankdrop
# A tibble: 4 × 7
f4rank dof chisq p dofdiff chisqdiff p_nested
<int> <int> <dbl> <dbl> <int> <dbl> <dbl>
1 3 2 0.262 8.77e- 1 4 46.0 2.43e- 9
2 2 6 46.3 2.60e- 8 6 598. 5.37e-126
3 1 12 645. 3.12e-130 8 1840. 0
4 0 20 2485. 0 NA NA NA
The Bronze Age Levantine model shows excellent fit with p = 0.877, much higher than the Hellenistic model. The MBA source contributes 22.2% (± 6.66%), slightly more than the Hellenistic source, and all components remain highly significant. This suggests the Eastern Mediterranean ancestry in Northern Italians is better modeled by Bronze Age Levantine populations, consistent with earlier gene flow events rather than later historical contacts.
This model provides a solid fit for Northern Italian ancestry. However, qpAdm modeling is inherently iterative. You could continue refining by adding more informative right populations, testing alternative sources (as we did with the Levantine component), or exploring different right population combinations to see which provides the strongest resolution. The goal is finding a model that both passes statistically and makes historical sense.
One common issue you might encounter is negative ancestry proportions for a source population. This typically indicates that source isn’t contributing ancestry to your target. The intuitive response is to remove or replace that source and re-run the model. However, negative values can sometimes result from missing sources in your left populations or insufficient resolution in your right populations. Adding the right populations or an additional source can sometimes turn negative contributions positive.
When running qpAdm models, it’s generally recommended to use the allsnps = TRUE parameter. This setting selects different SNPs for each f4-statistic that qpAdm computes, which was the standard behavior in the original ADMIXTOOLS qpAdm implementation. This approach is particularly useful when working with sparse genotype data, as it maximizes the available genetic information for each individual statistic rather than restricting all statistics to a common SNP set. Using this setting typically provides more stable estimates and better model resolution.
To run the same model with allsnps = TRUE, simply add this parameter to the qpadm() function:
result <- qpadm(data, left, right, target, allsnps = TRUE)
Using our final Northern Italian model with Bronze Age Levantine ancestry, here’s the comparison:
With allsnps = TRUE:
# A tibble: 4 × 5
target left weight se z
1 Italian_North.HO Turkey_Marmara_Barcin_N.SG 0.370 0.0434 8.53
2 Italian_North.HO Russia_Samara_EBA_Yamnaya.AG 0.361 0.0294 12.3
3 Italian_North.HO Italy_Epigravettian.SG 0.0678 0.00910 7.45
4 Italian_North.HO Lebanon_MBA.SG 0.202 0.0581 3.48
$rankdrop
# A tibble: 4 × 7
f4rank dof chisq p dofdiff chisqdiff p_nested
1 3 2 1.54 4.64e- 1 4 69.8 2.48e- 14
2 2 6 71.4 2.15e- 13 6 697. 3.04e-147
3 1 12 768. 1.12e-156 8 2583. 0
4 0 20 3351. 0 NA NA NA
Notice the differences compared to the default settings shown earlier: the ancestry proportions shift (Anatolian Neolithic decreases from 40.2% to 37.0%, while Steppe increases from 30.1% to 36.1%), standard errors generally decrease, and the p-value drops from 0.877 to 0.464 but still passes comfortably. The model remains valid with good fit, and the reduced standard errors indicate more precise estimates when using the full SNP set.
The allsnps = TRUE setting is particularly valuable when working with ancient DNA samples that have varying degrees of SNP coverage. By allowing each f4-statistic to use its own optimal SNP set rather than forcing all statistics to share the same SNPs, this approach maximizes the genetic information available for model testing while maintaining statistical rigor. Additionally, without allsnps = TRUE, similar but clearly different models can pass with relatively similar fit statistics, making the test less stringent. Using allsnps = TRUE makes models “harder” to pass, which helps avoid accepting models that shouldn’t fit. This makes the parameter useful even when working with modern populations with complete SNP coverage, as it provides more rigorous model evaluation.
Summary
A successful qpAdm model should have: a low chi-square value (indicating minimal deviation between observed and expected f4-statistics) and a p-value above 0.05 (model passes), low standard errors on ancestry proportions, ideally highly statistically significant contributions from each source, and all components making historical sense. However, remember that passing models don’t necessarily mean these are the actual ancestral populations, in some cases it can be proxies for similar populations.
qpAdm is a powerful tool for testing ancestry models and estimating admixture proportions, but the results require careful interpretation. Multiple models may fit equally well, which is why comparing alternative source combinations and evaluating them against historical and archaeological context is essential.
The choice of right populations critically affects model outcomes, as demonstrated by how adding the Epipaleolithic Bichon population resolved the Epigravettian component. Start with a diverse set of outgroups that span relevant ancestral divergences, and iterate from there.