The hardware and bandwidth for this mirror is donated by METANET, the Webhosting and Full Service-Cloud Provider.
If you wish to report a bug, or if you are interested in having us mirror your free-software or open-source project, please feel free to contact us at mirror[@]metanet.ch.
Please refer to the dna_vignette
(“STR markers in
forensic genetics”) for an explanation of forensic DNA profiles.
When more than one individual contributes biological material to a forensic stain, the resulting DNA type is termed a DNA mixture. DNA mixtures occur frequently in forensic genetic casework, and in recent years much research has been devoted to this subject.
This vignette shows how to compute the exact distribution of the
number of alleles for any number of profiles and investigated loci using
the DNAtools
package in R.
library(DNAtools)
The per locus number of observed alleles is of interest as it indicates the plausible range of the number of contributors. If a DNA mixture has \(m\) contributors, it is possible to observe anything between \(1\) and \(2m\) alleles at a given marker. Of particular interest is the probability at which an \(m+1\) person DNA mixture is percieved as an \(m\) person DNA mixture. That is, what is the probability that \(m\) persons have at most \(2(m-1)\) distinct alleles among them?
In forensic genetics, a DNA mixture refers to the situation where more than one individual’s DNA is present in an analysed trace. DNA mixtures are regularly encountered in forensic genetics.
However, an exact expression of the number of alleles has not been presented up to now. We show how to derive the probability that \(N\) alleles are observed given \(m\) contributors and \(L\) typed loci.
The prevailing technology for identity assessment and DNA mixture analysis in forensic genetics uses so-called Short Tandem Repeat (STR) markers or loci. The number of alleles observed at these markers are typically in the range of 10 to 30 alleles.
In the DNAtools
there is a database with 1,000 simulated
DNA profiles on 10 autosomal STR markers, which we will use to compute
the allele frequencies.
data(dbExample, package = "DNAtools")
kable(head(dbExample)[,2:7])
D16S539.1 | D16S539.2 | D18S51.1 | D18S51.2 | D19S433.1 | D19S433.2 |
---|---|---|---|---|---|
11 | 11 | 15 | 21 | 14 | 14 |
13 | 12 | 15 | 14 | 16 | 16 |
9 | 9 | 13 | 17 | 14 | 14 |
11 | 12 | 14 | 15 | 15 | 13 |
12 | 12 | 17 | 12 | 15.2 | 13 |
9 | 13 | 17 | 14 | 13 | 14 |
We extract the relevant columns and compute the allele frequencies by the allele count and total number of alleles in the data.
<- lapply(1:10, function(x){
allele_freqs <- table(c(dbExample[[x*2]], dbExample[[1+x*2]]))/(2*nrow(dbExample))
al_freq sort.list(as.numeric(names(al_freq)))]
al_freq[
})names(allele_freqs) <- sub("\\.1", "", names(dbExample)[(1:10)*2])
D16S539: | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
---|---|---|---|---|---|---|---|
5e-04 | 0.191 | 0.0195 | 0.2755 | 0.286 | 0.2255 | 0.002 |
D18S51: | 11 | 12 | 13 | 14 | 15 | 16 | 16.2 | 17 | 18 | 19 | 20 | 21 |
---|---|---|---|---|---|---|---|---|---|---|---|---|
0.0175 | 0.0955 | 0.0825 | 0.1705 | 0.137 | 0.239 | 0.0045 | 0.1535 | 0.0695 | 0.0035 | 0.001 | 0.026 |
D19S433: | 11 | 12 | 12.2 | 13 | 13.2 | 14 | 14.2 | 15 | 15.2 | 16 | 16.2 |
---|---|---|---|---|---|---|---|---|---|---|---|
0.003 | 0.054 | 0.001 | 0.2015 | 0.0065 | 0.3895 | 0.0505 | 0.174 | 0.0635 | 0.0555 | 0.001 |
D21S11: | 27 | 28 | 29 | 30 | 30.2 | 31 | 31.2 | 32 | 32.2 | 33.2 | 34.2 |
---|---|---|---|---|---|---|---|---|---|---|---|
0.028 | 0.394 | 0.2195 | 0.162 | 0.0465 | 0.0695 | 0.023 | 0.004 | 0.0415 | 0.0075 | 0.0045 |
D2S1338: | 14 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 |
---|---|---|---|---|---|---|---|---|---|---|---|---|
5e-04 | 0.042 | 0.1235 | 0.1335 | 0.135 | 0.092 | 0.069 | 0.007 | 0.1675 | 0.1015 | 0.126 | 0.0025 |
D3S1358: | 11 | 14 | 15 | 16 | 17 | 18 | 19 |
---|---|---|---|---|---|---|---|
0.0045 | 0.1365 | 0.327 | 0.2675 | 0.1355 | 0.115 | 0.014 |
The total number of alleles observed for \(m\) contributors depends on the number of loci, \(L\), through the locus specific allele counts, \(N_l\), by \(N(m) = \sum_{l=1}^L N_l(m)\). Hence, we can focus on computing the distribution of \(N_l(m)\) below.
The number of alleles at locus \(l\), \(N_l(m)\), follows a distribution, where the
number of alleles range from 1 (all \(m\) profiles homozygous with the same
allele) to \(2m\) (all \(m\) profiles heterozygous sharing no
allele). For each value that \(N_l(m)\)
can attain there will typically be several ways \(m\) profiles can produce \(N_l(m)\) distinct alleles. For a given
locus \(l\), we use the function
Pnm_locus()
to compute \(P(N_l(m)
= n)\), for \(n = 1,\dots,2m\),
where we need to specify \(m\) together
with the allele frequencies.
<- 3
m <- Pnm_locus(m = m, theta = 0, alleleProbs = allele_freqs$D16S539) P3_D16
cat(kable(t(setNames(P3_D16, paste0("P(n=",1:(2*m), "|m=3)")))), sep = "\n")
P(n=1|m=3) | P(n=2|m=3) | P(n=3|m=3) | P(n=4|m=3) | P(n=5|m=3) | P(n=6|m=3) |
---|---|---|---|---|---|
0.0011646 | 0.0895515 | 0.4920981 | 0.3895294 | 0.027534 | 0.0001224 |
The theta
argument refers to the \(\theta\) coefficient used to account for
genetic dependence of alleles in the Balding-Nicholls population genetic
model. Typical values ranges from \(0\)
(independent alleles, no subpopulation structure) to \(0.03\) (mild correlation, some
subpopulation structure).
We can also compute the probabilities for several \(m\). Here we let \(m\) range between 1 and 6 contributors.
<- 1:6
ms <- matrix(NA, ncol = length(ms), nrow = max(ms)*2,
Ps_D16 dimnames = list(alleles = 1:(max(ms)*2), noContrib = ms))
for(m in seq_along(ms)){
1:(ms[m]*2), m] <- Pnm_locus(m = ms[m], theta = 0, alleleProbs = allele_freqs$D16S539)
Ps_D16[ }
dimnames(Ps_D16) <- list(paste0("n=", rownames(Ps_D16),""),
paste0("P(n|m=", colnames(Ps_D16),")"))
kable(Ps_D16, row.names = TRUE)
P(n|m=1) | P(n|m=2) | P(n|m=3) | P(n|m=4) | P(n|m=5) | P(n|m=6) | |
---|---|---|---|---|---|---|
n=1 | 0.245412 | 0.0163682 | 0.0011646 | 0.0000864 | 0.0000066 | 0.0000005 |
n=2 | 0.754588 | 0.3170886 | 0.0895515 | 0.0241752 | 0.0065797 | 0.0018244 |
n=3 | 0.5545736 | 0.4920981 | 0.3225956 | 0.1938807 | 0.1133775 | |
n=4 | 0.1119696 | 0.3895294 | 0.5736285 | 0.6633572 | 0.6956363 | |
n=5 | 0.0275340 | 0.0785909 | 0.1337656 | 0.1847718 | ||
n=6 | 0.0001224 | 0.0009221 | 0.0024039 | 0.0043736 | ||
n=7 | 0.0000013 | 0.0000062 | 0.0000157 | |||
n=8 | 0.0000000 | 0.0000000 | 0.0000000 | |||
n=9 | 0.0000000 | 0.0000000 | ||||
n=10 | 0.0000000 | 0.0000000 | ||||
n=11 | 0.0000000 | |||||
n=12 | 0.0000000 |
If we plot the outcome we see that for \(m \in \{4, 5, 6\}\) the most probable allele count is 4 indicating that D16S539 is not very polymorphic in this population.
par(mar = c(4,5,0,0))
plot(c(1,max(ms)*2), range(Ps_D16, na.rm = TRUE), type = "n",
xlab = "no. of allele", ylab = "Probability")
for (m in seq_along(ms)) {
lines(1:(2*ms[m]), Ps_D16[1:(ms[m]*2),m], col = m)
}legend("topright", bty = "n", title = "m", legend = ms, col = seq_along(ms), lty = 1)
The probability of masking can be calculated from the table above. Hence, we need to compute \(P(N_l(m) \le 2(m-1))\), which is done below.
<- apply(Ps_D16, 2, cumsum) Ps_D16_cumsum
P(n≤n’|m=1) | P(n≤n’|m=2) | P(n≤n’|m=3) | P(n≤n’|m=4) | P(n≤n’|m=5) | P(n≤n’|m=6) | |
---|---|---|---|---|---|---|
n’=1 | 0.245412 | 0.0163682 | 0.0011646 | 0.0000864 | 0.0000066 | 0.0000005 |
n’=2 | 1.000000 | 0.3334568 | 0.0907160 | 0.0242616 | 0.0065863 | 0.0018249 |
n’=3 | 0.8880304 | 0.5828141 | 0.3468572 | 0.2004671 | 0.1152024 | |
n’=4 | 1.0000000 | 0.9723436 | 0.9204856 | 0.8638243 | 0.8108388 | |
n’=5 | 0.9998776 | 0.9990765 | 0.9975899 | 0.9956106 | ||
n’=6 | 1.0000000 | 0.9999987 | 0.9999938 | 0.9999843 | ||
n’=7 | 1.0000000 | 1.0000000 | 1.0000000 | |||
n’=8 | 1.0000000 | 1.0000000 | 1.0000000 | |||
n’=9 | 1.0000000 | 1.0000000 | ||||
n’=10 | 1.0000000 | 1.0000000 | ||||
n’=11 | 1.0000000 | |||||
n’=12 | 1.0000000 |
We see that the probability that an \(m\) person mixture is misconceived as an \(m-1\) person mixture is
P(n≤2|m=2) | P(n≤4|m=3) | P(n≤6|m=4) | P(n≤8|m=5) | P(n≤10|m=6) |
---|---|---|---|---|
0.3334568 | 0.9723436 | 0.9999987 | 1 | 1 |
Hence, there is a probability of 0.9723436 that a three-person mixture has at most four alleles, which implies it can be interpreted as a two-person mixture.
If we are interested in computing the locuswise probabilities for all
loci in our dataset, we simply use the Pnm_all
function
with locuswise = TRUE
.
<- 3
no_contrib <- Pnm_all(m = no_contrib, theta = 0, probs = allele_freqs, locuswise = TRUE) Pnm_all_tab
P(n=1|m=3) | P(n=2|m=3) | P(n=3|m=3) | P(n=4|m=3) | P(n=5|m=3) | P(n=6|m=3) | |
---|---|---|---|---|---|---|
D16S539 | 0.0011646 | 0.0895515 | 0.4920981 | 0.3895294 | 0.0275340 | 0.0001224 |
D18S51 | 0.0002318 | 0.0179598 | 0.1779391 | 0.4378291 | 0.3115324 | 0.0545077 |
D19S433 | 0.0035866 | 0.0896320 | 0.3625087 | 0.3976107 | 0.1351805 | 0.0114815 |
D21S11 | 0.0038710 | 0.0968946 | 0.3687696 | 0.3853717 | 0.1323391 | 0.0127541 |
D2S1338 | 0.0000432 | 0.0067469 | 0.1068460 | 0.3899646 | 0.3981273 | 0.0982720 |
D3S1358 | 0.0016040 | 0.0781996 | 0.3939623 | 0.4258141 | 0.0976869 | 0.0027331 |
D8S1179 | 0.0007349 | 0.0399056 | 0.2705804 | 0.4539819 | 0.2127581 | 0.0220390 |
FGA | 0.0000742 | 0.0109556 | 0.1455096 | 0.4287449 | 0.3469833 | 0.0677323 |
TH01 | 0.0025374 | 0.1119023 | 0.4515490 | 0.3761236 | 0.0578306 | 0.0000571 |
vWA | 0.0008047 | 0.0542080 | 0.3452015 | 0.4542519 | 0.1385287 | 0.0070051 |
Since the loci are assumed independent, we can also compute the probability that we see at most \(n_0\) alleles at all markers: \[ P(N_1(m) \le n_0, \dots, N_L(m)\le n_0) = \prod_{l=1}^L P(N_l(m) \le n_0) \]
For instance, we may compute for \(m\in\{1,\dots,6\}\) the probability of
seeing at most \(n_0
\in\{1,\dots,2m\}\) alleles for the ten STR markers in
allele_freqs
.
<- lapply(ms, Pnm_all, theta = 0, probs = allele_freqs, locuswise = TRUE)
locus_Ps <- lapply(locus_Ps, apply, 1, cumsum)
locus_Ps_cumsum <- lapply(locus_Ps_cumsum, apply, 1, prod)
all_Ps_cumsum <- matrix(NA, ncol = length(ms), nrow = max(ms)*2,
all_Ps_table dimnames = list(n0 = 1:(max(ms)*2), m = ms))
for(m in seq_along(ms)) all_Ps_table[1:(ms[m]*2), m] <- all_Ps_cumsum[[m]]
P(n≤n0|m=1) | P(n≤n0|m=2) | P(n≤n0|m=3) | P(n≤n0|m=4) | P(n≤n0|m=5) | P(n≤n0|m=6) | |
---|---|---|---|---|---|---|
n0=1 | 1e-07 | 0.0000000 | 0.0000000 | 0.0000000 | 0.0000000 | 0.0000000 |
n0=2 | 1e+00 | 0.0000003 | 0.0000000 | 0.0000000 | 0.0000000 | 0.0000000 |
n0=3 | 0.0584704 | 0.0000145 | 0.0000000 | 0.0000000 | 0.0000000 | |
n0=4 | 1.0000000 | 0.0734495 | 0.0009343 | 0.0000060 | 0.0000000 | |
n0=5 | 0.7510803 | 0.1806604 | 0.0206331 | 0.0015739 | ||
n0=6 | 1.0000000 | 0.7795583 | 0.3948643 | 0.1430100 | ||
n0=7 | 0.9870927 | 0.8781034 | 0.6711846 | |||
n0=8 | 1.0000000 | 0.9925871 | 0.9578855 | |||
n0=9 | 0.9999052 | 0.9984342 | ||||
n0=10 | 1.0000000 | 0.9999851 | ||||
n0=11 | 1.0000000 | |||||
n0=12 | 1.0000000 |
Again we may investigate the risk the a \(m\)-person DNA mixture is misconceived as a \((m-1)\)-person DNA mixture, when considering all loci:
<- setNames(rep(NA, max(ms)-1), paste0("P(n≤",(1:(max(ms)-1))*2,"|m=",2:max(ms),")"))
Pm_all for(m in 2:max(ms)) Pm_all[m-1] <- all_Ps_table[2*(m-1),m]
P(n≤2|m=2) | P(n≤4|m=3) | P(n≤6|m=4) | P(n≤8|m=5) | P(n≤10|m=6) |
---|---|---|---|---|
0 | 0.0734 | 0.7796 | 0.9926 | 1 |
We can also look at the effect of the \(\theta\)-correction on the distribution of observed number of alleles. The stronger the subpopulation effect (measured by increasing \(\theta\)), the fewer alleles will be observed.
par(mar = c(4,5,0,0))
.03 <- Pnm_locus(m = 3, theta = 0.03, alleleProbs = allele_freqs$D16S539)
P3_D16_t0.1 <- Pnm_locus(m = 3, theta = 0.1, alleleProbs = allele_freqs$D16S539)
P3_D16_t0plot(P3_D16_t0.03, type = "o", pch = 16, ylab = "Probability", xlab = "no. of alleles", col = 2)
points(P3_D16, type = "o", lty = 2, col = 1)
points(P3_D16_t0.1, type = "o", lty = 3, pch = 2, col = 3)
legend("topleft", bty = "n", title = expression(theta), col = 1:3,
legend = c("0.00", "0.03", "0.10"), lty = c(2, 1, 3), pch = c(1, 16, 2))
In order to obtain \(n\) alleles for \(m\) contributors and \(L\) loci, the locus counts must sum to \(n\). By convolution, we obtain \[ P(N^{l{+}1}(m) = n) = \sum_{i=1}^I P(N^{l}(m) = n-i)P(N_{l+1}(m)=i), \] where \(I = \min(2m,n{-}1)\) with \(P(N_{l+1}(m)=i)\) denoting the probability that locus \(l{+}1\) has \(i\) observed alleles, and \(P(N^l(m)=n)\) is the probability to observe \(n\) alleles for \(l\) loci and \(m\) profiles.
We compute this by setting locuswise = FALSE
in
Pnm_all
.
par(mar = c(4,5,0,0))
<- Pnm_all(m = 3, theta = 0, probs = allele_freqs, locuswise = FALSE)
P3_all plot(P3_all, xlab = "no. of alleles", ylab = "Probability", type = "o")
Assume that we specify the prior, \(P(m)\), on \(m\) based on some general idea on the number of contributors we typically see, or based on other background information regarding the case. Then \(P(m)\) reflects the belief we have about \(m\) prior to seeing the DNA mixture.
Using Bayes’ theorem, we can express the probability distribution for the number of contributors, \(m\), given the number of observed alleles, \(n_{l}\): \[ P(m\mid n_{l}) = \frac{P(n_{l}\mid m)P(m)}{P(n_{l})} = \frac{P(N_l(m) = n_{l})P(m)}{\sum_m P(N_l(m) = n_{l})P(m)}, \] where \(P(m)\) is a prior distribution on the number of contributors, \(m\). We may also generalise this to including all information, \(\underline{n} = (n_1, \dots, n_L)\), from the loci based on the independent assumption.
\[ P(m\mid \underline{n}) = \frac{P(\underline{n}\mid m)P(m)}{P(\underline{n})} = \frac{P(m)\prod_{l=1}^L P(N_l(m) = n_{l})}{\sum_m\left\{ P(m)\prod_{l=1}^LP(N_l(m) = n_{l})\right\}}, \]
Assume that \(P(m)\) is given by
Let \(n_{vWA} = 4\). Then we can update the belief based on this information using the expression above.
<- 4
n_vWA <- lapply(ms, Pnm_locus, theta = 0, alleleProbs = allele_freqs$vWA)
P_vWA <- sapply(P_vWA, function(p) ifelse(length(p) < n_vWA, 0, p[n_vWA]))
P_vWA_n <- (m_prior * P_vWA_n)/sum(m_prior * P_vWA_n) Pn_vWA
Hence, the change in belief about \(m\) can be seen below, where the first row is the prior \(P(m)\), the second \(P(N_{vWA}(m) = n_{vWA})\) and the last \(P(n \mid N_{vWA}(m) = n_{vWA})\):
m=1 | m=2 | m=3 | m=4 | m=5 | m=6 | |
---|---|---|---|---|---|---|
P(m) | 0.1702128 | 0.2978723 | 0.2553191 | 0.1702128 | 0.0851064 | 0.0212766 |
P(nvWA=4|m) | 0.0000000 | 0.2004809 | 0.4542519 | 0.4549002 | 0.3669437 | 0.2730198 |
P(m|nvWA=4) | 0.0000000 | 0.2058061 | 0.3997010 | 0.2668476 | 0.1076259 | 0.0200194 |
We see that \(m =\) 3 is the most probable after seeing 4 alleles at vWA.
Assuming that we observe \(n_l = 4\) for all loci, \(l=1,\dots,L\), we get
<- function(p, n){
get_pn <- split(as.data.frame(p), n)
pn <- unlist(lapply(names(pn), function(n_l) if(ncol(pn[[n_l]])<n_l) rep(0,nrow(pn[[n_l]])) else pn[[n_l]][,n_l]))
p_n prod(p_n)
}
<- rep(4, length(allele_freqs))
n_loci <- lapply(ms, Pnm_all, theta = 0, probs = allele_freqs, locuswise = TRUE)
P_all <- sapply(P_all, get_pn, n_loci)
P_all_n <- (m_prior * P_all_n)/sum(m_prior * P_all_n) Pn_all
Hence, the posterior probability of \(m\) after observing four alleles at all loci is given by
P(m=1|n=4) | P(m=2|n=4) | P(m=3|n=4) | P(m=4|n=4) | P(m=5|n=4) | P(m=6|n=4) |
---|---|---|---|---|---|
0 | 0.0016757 | 0.8776022 | 0.1207222 | 0 | 0 |
These binaries (installable software) and packages are in development.
They may not be fully stable and should be used with caution. We make no claims about them.