f3-statistics are used to test if populations are admixed or to measure shared genetic drift between two populations relative to an outgroup.
This post explains the theory behind admixture f3-statistics and shows how to run admixture f3 tests with AdmixPy.
If you want to skip the theoretical part, jump to Running an admixture f3-statistic in AdmixPy.
What is an f3-statistic?
For three populations, the statistic is written as:
Here, is in the target position. Populations and are the reference populations. The value is the allele frequency in population at SNP . The expectation is an average across SNPs.
The statistic multiplies two allele-frequency differences. It therefore assesses whether differs from and in the same direction.
At a given SNP, the product is positive when the allele frequency in is higher than in both reference populations or lower than in both. It is negative when the allele frequency in lies between those of and . The f3-statistic averages these products across SNPs.
f3 derived from f2
The f2-statistic is the mean squared allele-frequency difference between two populations:
Expanding the three pairwise f2 distances gives:
Hence, the value is negative when:
This means that the two references are farther from each other than their combined distances to the target.
Example, the three f2 distances are:
Then:
The target occupies an intermediate position between the two references in allele-frequency space. That is the basic geometry behind an admixture f3-statistic.
f3 can also be written as an f4-statistic with a repeated population:
This shows that the f3 admixture test is a special case of an f4-statistic. f4 statistics can also be used to test admixture using four populations, a topic I will possibly cover in another post.
Why a negative f3 statistic detects admixture
Suppose target formed through admixture between and :
where is the ancestry proportion from , and represents drift accumulated after admixture.
Assuming this later drift is independent of the difference between the sources:
where
The admixture term is negative, whereas subsequent drift in the target contributes positively. Therefore, f3 becomes negative when
This also explains why an admixed population does not necessarily produce a negative result. The estimate can stay positive when the ancestry contribution from one source is small, the sources are closely related (resulting in a small distance; hence a smaller negative admixture term), or the target had substantial drift after admixture.
The tree interpretation
Consider a simple population tree with no admixture:
A
|
| a
|
M
/ \
b / \ c
/ \
B C
Here, is the branch point connecting the three populations. The values , , and represent the genetic drift accumulated along each branch.
On an additive tree, the distance between two populations is the sum of the branch lengths along the path connecting them. The path from to contains branches and , so
Likewise,
and
Now substitute these distances into the formula:
Therefore, equals the drift along the branch connecting to . Because a branch length cannot be negative, cannot be negative on a strictly additive population tree. A significantly negative estimate therefore rejects this tree topology, and indicates that has ancestry related to both and .
Significance and Z-scores
The Z-score measures how many standard errors the estimate lies from zero:
For an admixture test, is usually used to indicate a significantly negative f3 estimate.
AdmixPy also reports a two-sided p-value:
A Z-score of means that the f3 estimate is three standard errors below zero. AdmixPy reports a two-sided p-value of approximately for this result. This indicates that such an extreme estimate would be unlikely if the true f3 value were zero. For the directional hypothesis , the one-sided p-value is .
Running an admixture f3-statistic in AdmixPy
For installation instructions, see Introducing AdmixPy. Start Python (or set up a Python file) in the folder containing your genotype files. Then import the package:
import admixpy as ap
Set the dataset prefix:
prefix = "v66_compatibility"
Run one admixture f3-statistic with:
ap.f3(prefix, pop1="Japanese", pop2="Japan_Chiba_HG_Jomon", pop3="China_Shandong_Dinggong_LN")
This returns:
pop1 pop2 pop3 est se z p n
0 Japanese Japan_Chiba_HG_Jomon China_Shandong_Dinggong_LN -0.00926283 0.000606282 -15.28 1.07e-52 980456
The significantly negative result (z < -3) suggests that Japanese are admixed between Jomon Hunter-Gatherer-related ancestry and mainland East Asian Neolithic-farmer-related ancestry.
Another example which produces a significantly negative estimate:
ap.f3(prefix, pop1="Morocco_EN", pop2="Morocco_Iberomaurusian", pop3="Spain_EN")
Result:
pop1 pop2 pop3 est se z p n
0 Morocco_EN Morocco_Iberomaurusian Spain_EN -0.0197945 0.00112779 -17.55 5.79e-69 879916
The significantly negative result suggests that Morocco Neolithic is admixed between Iberomaurusian-related ancestry and ancestry related to ANF-derived groups, here Spain Early Neolithic in particular.
These examples come from simpler Neolithic contexts. Often, even a population with expected admixture does not produce a significantly negative f3-statistic for reasons mentioned above.
Unexpected population pairs can produce significantly negative results. Testing a wide range of combinations rather than only what seems directly plausible is useful. aDNA studies often report admixture f3-statistics as large supplementary tables with many combinations of target and reference populations.
An example:
pop1 pop2 pop3 est se z p n
0 Karelian Norwegian Korean -0.00281868 0.000471994 -5.97 2.35e-09 274624
Here, the negative f3 value is statistically significant (z=-5.97). This implies that Karelians are admixed between ancestries related to northern Europeans and eastern Eurasians. Korean is used here only for demonstration; better sources for the Uralic-associated East Asian ancestry would be available.
AdmixPy returns the following columns:
est: the estimated f3-statistic;se: its block-jackknife standard error;z: the estimate divided by its standard error;p: the two-sided p-value;n: the number of contributing SNPs.
f3 does not quantify ancestry proportions. Methods such as qpAdm are needed to estimate ancestry proportions.
Testing several source pairs
AdmixPy can run many f3 tests at once. To do that, create a pandas data frame with one row for each population triplet and columns named pop1, pop2, and pop3. The same target can then be tested against several source pairs, or different targets can be tested against different references:
import pandas as pd
tests = pd.DataFrame({
"pop1": ["Target", "Target", "Target"],
"pop2": ["Reference_A", "Reference_A", "Reference_B"],
"pop3": ["Reference_B", "Reference_C", "Reference_C"],
})
ap.f3(prefix, tests).sort_values("z")
The call returns one result for each row in tests. Sorting by z places the most negative statistics first.
Unnormalized and normalized f3 statistics in AdmixPy
For direct genotype data, ap.f3(...) returns a normalized statistic by default. It divides the corrected f3 numerator by a sample-size adjusted estimate of the target heterozygosity. Use outgroupmode=True to return the unnormalized f3 numerator. Passing apply_corr=False keeps SNPs with fewer than two allele observations, the resulting estimate is sampling-biased.
Setting apply_corr=False can be neccessary when a pseudohaploid singleton is used as the target (pop1) or as a repeated source because a finite-sample correction cannot be computed in those cases. A singleton used only as a distinct source can still be analyzed with apply_corr=True, even when pseudohaploid.
When ap.f3(...) is given an f2 cache or F2Blocks object, it returns unnormalized f3 values; outgroupmode has no effect in that case. This is equivalent in scale to direct-genotype f3 with outgroupmode=True.
Finite-sample correction
Population allele frequencies are estimated from a finite number of observed alleles. This creates sampling noise.
The target appears in both allele-frequency differences. For three distinct populations, AdmixPy subtracts this correction at each SNP:
where is the observed allele count in population at SNP . The corrected SNP value is:
Because the correction contains , it requires at least two observed alleles in the repeated population. A pseudohaploid singleton provides only one observed allele, so this correction is undefined when that population is repeated. A singleton used only as a distinct source does not need this repeated-population correction.
Block jackknifing for the standard error
Nearby SNPs are correlated through linkage disequilibrium. They cannot be treated as fully independent observations. Doing so would make the standard error too small.
AdmixPy uses a delete-one-block jackknife. It leaves out one SNP block at a time and recalculates the statistic. For equal blocks, the standard error is:
This formula describes the simpler resampling="nominal_blocks" setting, where blocks are weighted by their nominal sizes. AdmixPy’s default resampling="pairwise_counts" uses a generalized count-weighted jackknife based on the number of usable SNPs in each block.
AdmixPy reads genetic distances and physical positions from the .snp or .bim files. By default, blgsize defines blocks using a genetic-map distance of 0.05 cM. If no usable genetic map is available, AdmixPy falls back to 2-Mb physical blocks. Setting blgsize to 100 or more uses physical positions directly, with the value interpreted in base pairs. If neither genetic nor physical positions are available, each chromosome is treated as one block.
Summary
f3-statistics compress three pairwise population relationships into one value.
With the target in the first position, a significantly negative value is evidence of admixture. It shows that the target lies between the two references in allele-frequency space.
With a deep outgroup in the first position, the statistic measures shared drift between the other two populations. Larger values indicate more shared drift relative to that outgroup. Outgroup-f3 will be covered in a separate post in more detail.