
fbnet computes likelihood ratios for kinship testing and human identification by representing a pedigree as a Bayesian network. Every allele of every individual becomes a node, edges encode Mendelian transmission, and queries are answered by variable elimination rather than by peeling the pedigree directly. That representation is what makes the package suited to large-size queries against databases of missing individuals: the elimination order is computed once and reused across queries, and unobserved parts of the network can be pruned before the expensive step.
The package implements the core kinship engine of GENis, the open-source forensic DNA database system developed by Fundación Sadosky (https://github.com/fundacion-sadosky/genis).
Maintenance status: this repository is kept for the CRAN release. Active development of new releases happens at https://github.com/chernolabs/fbnet-cran.
install.packages("fbnet")
The development version:
# install.packages("remotes")
# remotes::install_github("MarsicoFL/fbnet")
install.packages("fbnet", repos = c('https://marsicofl.r-universe.dev', 'https://cloud.r-project.org'))
Pedigrees are built with pedtools, and forrel is useful for simulating
profiles:
install.packages(c("pedtools", "forrel"))
A mother and a child, two markers with four alleles each. The mother is
genotyped 3/4 at both markers; the child is the query person, and we want the
probability distribution of the alleles they carry.
library(fbnet)
library(pedtools)
ped <- nuclearPed(1)
m1 <- marker(ped, afreq = c("1" = 0.1, "2" = 0.2, "3" = 0.3, "4" = 0.4), name = "M1")
m2 <- marker(ped, afreq = c("1" = 0.1, "2" = 0.2, "3" = 0.3, "4" = 0.4), name = "M2")
ped <- setMarkers(ped, list(m1, m2))
ped <- setGenotype(ped, id = "2", marker = 1, geno = "3/4")
ped <- setGenotype(ped, id = "2", marker = 2, geno = "3/4")
pbn <- from_pedtools(ped, QP = "3")
bnet <- buildBN(pbn, QP = "3")
bn1 <- buildCPTs(bnet)
resQ <- velim.bn(bn1, ordMethod = "min_fill", verbose = FALSE)
resQ holds one table per queried allele, named <individual>_<marker>_<p|m>,
where p is the paternal and m the maternal allele. The maternal allele of the
child is constrained by the evidence, since the mother can only have transmitted
a 3 or a 4:
resQ[["3_M1_m"]]
#> 3_M1_m prob
#> 1 3 0.12
#> 2 4 0.12
The paternal allele has no evidence bearing on it, so its distribution is the population one:
resQ[["3_M1_p"]]
#> 3_M1_p prob
#> 1 1 0.1
#> 2 2 0.2
#> 3 3 0.3
#> 4 4 0.4
Reading those two tables side by side is the point of the method: the network reports where the evidence acts and where it does not, one allele at a time, instead of collapsing everything into a single number.
Given an observed genotype for the query person, reportLR returns the LR per
marker and for the whole profile. The genotypes go in as a character matrix,
with individuals in rows and markers in columns.
geno <- matrix(c("3/4", "3/4"), nrow = 1,
dimnames = list("3", c("M1", "M2")))
reportLR(bnet, resQ, geno = geno)
#> $systemLR
#> L0 L1 LR
#> M1 0.0576 0.084 1.458333
#> M2 0.0576 0.084 1.458333
#>
#> $LR
#> [1] 2.126736
The per-marker value can be checked by hand. Under the pedigree hypothesis the child inherits a 3 or a 4 from the mother with probability 1/2 each and the second allele from the population, so P(3/4) = 0.5 x 0.4 + 0.5 x 0.3 = 0.35. Under the alternative the child is an unrelated individual, so P(3/4) = 2 x 0.3 x 0.4 = 0.24. The ratio is 0.35 / 0.24 = 1.4583, and the profile LR is that value squared, the two markers being independent.
The denominator on its own, the random match probability of the queried genotype, is available directly:
getQSetRMP(bnet, list("3_M1" = c("3", "4"), "3_M2" = c("3", "4")))
#> [1] 0.0576
from_pedtools() accepts any pedtools::ped object, which means the whole
pedsuite is available for building the case: linearPed, cousinPed,
avuncularPed, halfSibPed, and profile simulation through forrel.
library(fbnet)
library(pedtools)
library(forrel)
ped <- linearPed(2) # grandparent to grandchild
ped <- setMarkers(ped, locusAttributes = NorwegianFrequencies[1:2])
ped <- profileSim(ped, N = 1, ids = 2, seed = 1) # genotype the middle generation
pbn <- from_pedtools(ped, QP = "5")
bn1 <- buildCPTs(buildBN(pbn, QP = "5"))
resQ <- velim.bn(bn1, ordMethod = "min_fill", verbose = FALSE)
names(resQ)
#> [1] "5_D3S1358_p" "5_TH01_p" "5_D3S1358_m" "5_TH01_m"
When available is not supplied, the typed individuals are inferred from the
pedigree: anyone carrying at least one non-missing genotype is treated as
evidence.
| Step | Function | What it does |
|---|---|---|
| 1 | from_pedtools() |
Reads a pedtools::ped into the internal structure: topology, evidence, allele frequencies |
| 2 | buildBN() |
Builds the network, one node per allele, and marks the query person |
| 3 | buildCPTs() |
Fills the conditional probability tables. This is the expensive step |
| 4 | velim.bn() |
Variable elimination, returns the conditioned allele distributions |
| 5 |
reportLR(), getQSetRMP()
|
Likelihood ratio and random match probability for an observed genotype |
pruneNodes(), stateRemoval() and evidencePrunning() reduce the network
before step 3 and are what keep large pedigrees tractable. minOrdering() and
setOrdering() control the elimination order used in step 4.
initBN(), initBN.fromPed(), initBN.fromVars(), convertPedformat() and
preparePed() take pedigrees in the paramlink representation. paramlink was
archived from CRAN in 2022, so those functions now stop with a message pointing
to from_pedtools() unless the package is installed from the CRAN archive.
Existing scripts written against initBN() keep working if paramlink is
present; new work should use from_pedtools().
Per-marker LRs obtained from the network can be passed to mispitools
(version 1.4.0 and above) to see how the support is distributed across markers,
and how much of it survives dropping any single one. A case whose LR rests
almost entirely on one marker warrants a different reading from one where the
support is spread out, even when the two report the same total. The package
vignette shows the full workflow.
Chernomoretz, A. et al. (2021). Forensic Science International: Reports. https://doi.org/10.1016/j.fsir.2020.100132
Darwiche, A. (2009). Modeling and Reasoning with Bayesian Networks. Cambridge University Press.
Vigeland, M. D. (2021). Pedigree Analysis in R. Academic Press.
Egeland, T., Kling, D. & Mostad, P. (2015). Relationship Inference with Familias and R. Academic Press.
fbnet and GENis are supported by Fundación Sadosky (https://www.fundacionsadosky.org.ar/) and the Ministerio de Ciencia, Tecnología e Innovación of Argentina (https://www.argentina.gob.ar/ciencia).