Skip to contents

Why this article exists

The kinship2 R package is the field’s standard reference implementation for pedigree kinship computation and pedigree drawing (Sinnwell, Therneau & Schaid 2014). Its own supplementary material (Sinnwell, Therneau & Schaid, “The kinship2 R Package for Pedigree Data: Supplementary Material”) works a small, fully-specified 10-subject example pedigree through kinship2’s own kinship matrix, X-chromosome kinship matrix, and pedigree-trimming (“shrink”) functions.

The ratified kinship2 supplement full-reproduction plan closed 3 tracks against that supplement:

  • Track Akinship() gained chrtype = c("autosome", "x") and sex arguments, reproducing the supplement’s X-chromosome kinship matrix (Table S2).
  • Track B – new shrinkPedigree(), a kinship2::pedigree.shrink() equivalent over this package’s own id/sire/dam data-frame pedigree representation.
  • Track CmakePedigreeMatingLayout()’s consanguineous-mating visual marker (a distinct color/width on a blood-related couple’s mate-line edges) now propagates correctly onto edgeStyle = "rectilinear"’s dogleg-rerouted projection edges, not just the direct-style case.

Each track’s own implementing session (Track C: S563, Track A: S564, Track B: S565) verified its own fixtures against a live, installed kinship2 1.9.6.2 and recorded the results as hardcoded expected values in tests/testthat/test_kinship.R, test_shrinkPedigree.R, and test_makePedigreeMatingLayout.R. This article is the recorded, side-by-side evidence for a reader who was not in those sessions: for each track, the exact same fixture is run through both packages, live, and the numeric and graphic output is shown together, not just asserted equal in a test file. It is the same “validate before expose” discipline as fg-se-validation.qmd, applied to a reference package instead of a reference paper’s worked numbers.

kinship2 is not a dependency of nprcgenekeepr. Every comparison below was generated once, offline, by data-raw/kinship2FidelityValidation.R (kinship2 installed locally, used interactively – matching the same evidence standard and the same “no new Suggests dependency” choice the 3 tracks’ own implementing sessions already made) and the resulting numbers/images are embedded below, exactly as fg-se-validation.qmd embeds its own offline validation study’s results rather than recomputing them at render time.

# Reproduce (build-ignored; not run on render). Requires kinship2 installed
# locally (install.packages("kinship2")) and Chrome (chromote, an indirect
# Suggests dependency via shinytest2, for the nprcgenekeepr diagram
# screenshots):
#   Rscript data-raw/kinship2FidelityValidation.R
# Writes PNGs to vignettes/articles/kinship2-fidelity-validation-img/ and
# prints the numeric and structural-comparison summary below to the
# console.

Track A – X-chromosome kinship (Table S2)

Fixture

The kinship2 supplement’s own Figure S1 subset (reconstructed from Table S1’s printed kinship values, per KINSHIP2_SUPPLEMENT_REPRODUCIBILITY_AUDIT_2026-08-13.md): a 10-subject pedigree in which subjects 8 and 9 are full siblings declared as a monozygotic (MZ) twin pair, and subject 10 is a child of twin 8 – the load-bearing case for confirming a twin correction propagates to a non-twin descendant, not just the declared pair.

Numeric fidelity

Both packages compute the full 10x10 autosomal matrix and the full 10x10 X-linked matrix on the identical fixture (nprcgenekeepr’s own MZ-twin correction supplies the same relation/twinRelations declaration to each package in its own idiom):

Comparison max|nprcgenekeepr − kinship2| Identical?
Autosomal kinship matrix (100 cells) 0 Yes
X-linked kinship matrix (100 cells) 0 Yes

A few named cells, reproducing the supplement’s own Table S2 (self-kinship differs by sex on the X chromosome – a male’s self-kinship is 1.0, not 0.5, since he carries a single X copy):

Pair Relationship nprcgenekeepr kinship2
(1, 1) male self 1.0000 1.0000
(2, 2) female self 0.5000 0.5000
(1, 3) father-son 0.0000 0.0000
(1, 4) father-daughter 0.5000 0.5000
(8, 9) declared MZ twins, X-linked 1.0000 1.0000
(9, 10) twin correction propagated to a child 0.5625 0.5625

Graphic fidelity

Four heatmap panels arranged 2 by 2: nprcgenekeepr autosomal, kinship2 autosomal, nprcgenekeepr X-linked, kinship2 X-linked kinship matrices for the same 10-subject pedigree, each pair visually identical to the other.

Kinship matrix heatmaps for the 10-subject fixture: nprcgenekeepr’s autosomal matrix (top left) against kinship2’s autosomal matrix (top right), and nprcgenekeepr’s X-linked matrix (bottom left) against kinship2’s X-linked matrix (bottom right). All 4 panels use the same color scale; the two autosomal panels are pixel-for-pixel identical, as are the two X-linked panels.

Track B – shrinkPedigree() vs. pedigree.shrink()

Fixture

The composite 16-subject fixture from test_shrinkPedigree.R, constructed to exercise every removal phase of kinship2’s own algorithm in one pedigree: an unavailable (ungenotyped) terminal leaf, an unavailable founder couple with a single child, a childless “stray marry-in” founder, a genotyped but non-informative individual, and a priority-ordered affected-status reduction down to maxBits = 1.

Numeric fidelity

Comparison nprcgenekeepr kinship2 Match?
Surviving subject set (8 of 16) C4, C4a, G3, L3, M1, P1, P2, P6 C4, C4a, G3, L3, M1, P1, P2, P6 Yes
bitSize trajectory 11 → 7 → 5 → 3 → 1 11 → 7 → 5 → 3 → 1 Yes

Graphic fidelity

The same fixture, before and after shrinking, rendered by both packages. Both kinship2’s own plot.pedigree() and (as of Phase 1 of the P5-suppression fix, S644, 2026-08-27) nprcgenekeepr’s own makePedigreeMatingLayout() omit subject P5 (an isolated, mate-less, child-less founder) from the full-fixture diagram below – expected behavior for a disconnected singleton, not an error. See Structural verification below.

kinship2 pedigree diagram of the full 16-subject fixture (15 of 16 declared subjects placed; the isolated founder P5 is omitted), standard square/circle symbols connected by a strict-hierarchy layout.

kinship2’s own plot.pedigree() on the full 16-subject fixture. P5, a fully isolated founder, is declared but not placed on the plot grid.

nprcgenekeepr pedigree diagram of the full 16-subject fixture, showing 15 of 16 declared subjects; the isolated founder P5 is suppressed, matching kinship2's own convention of omitting it.

makePedigreeMatingLayout() + visNetwork on the same full 16-subject fixture. As of Phase 1 of the P5-suppression fix (S644, 2026-08-27), this rendering now also omits P5, matching kinship2’s own convention of not placing a fully isolated individual on the diagram.

kinship2 pedigree diagram of the shrunk 8-subject pedigree, with unavailable individuals shown as unfilled nodes marked with a question mark.

kinship2’s pedigree.shrink()$pedObj – shrunk to 8 subjects. Unavailable (‘?’) individuals still shown, per kinship2’s own convention.

nprcgenekeepr pedigree diagram of the shrunk 8-subject pedigree, showing the same 2 family groups as kinship2's own shrunk diagram, drawn side by side as separate blocks.

shrinkPedigree()’s own surviving pedigree, same 8 subjects, rendered the same way. As of the disconnected-component separation fix (S667, 2026-09-02), the two unrelated surviving families are drawn as separate side-by-side blocks with the same one-unit gap kinship2 uses – every individual’s horizontal position matches kinship2’s own align.pedigree() exactly.

What “matches” above means, precisely – and what it does not. The claim is scoped to which individuals appear at all (P5 now omitted by both) and to the structural parent-child/mate-pair relationships among the individuals that do appear (Structural verification below) – not to the two packages’ overall visual layout. Looking at the full-fixture pair directly: kinship2 draws each mated pair spread apart, with the descent line to their children dropping from the midpoint between the two symbols; makePedigreeMatingLayout() draws each pair close together, with the mating-unit marker (the small dot) positioned at the sire’s own symbol rather than centered between sire and dam. This is a real, visible difference in mate-line layout, not a rendering error – it is the established, already-decided rendering convention this package uses everywhere (the marker’s own visibility was weighed and kept as-is in issue #161), and it predates this article: it is unrelated to, and unchanged by, the P5-suppression fix, which only changes which individuals are placed on the diagram, never how a placed pair’s own mate line is drawn.

The two shrunk diagrams show the same 8 surviving subjects in the same 2 family groups{P1, P2, M1, G3, L3} and {C4, P6, C4a} – confirming the numeric surviving-set match above is also structurally faithful, not just a matching id list.

Track C – consanguineous-marker propagation

Fixture

The 9-subject fixture from test_makePedigreeMatingLayout.R (S563): A and Y are full siblings (children of P1 x P2) who mate with each other – the consanguineous union under test – while A also mates with founder X in an unrelated union, and Y also mates with founder W in an unrelated union. Originally (S563), A anchored the A-X union despite the two individuals sitting at different generations, forcing exactly one edgeStyle = "rectilinear" “dogleg” reroute on A’s side – this was the fixture’s whole point, and the name it was given (“the 9-subject dogleg fixture”) in earlier project history. That dogleg no longer occurs, as of Track 4’s gen-aware anchor selection (S573, 2026-08-14, the day after this article was first published): under the gen-first tie-break, X (generation 3) now anchors the A-X union instead of A (generation 1), so A never anchors a union whose generation differs from his own – the consanguineous A-Y union is the only union A anchors, and it already matches his own generation. Track 4’s own structural invariant (genOf[[anchor]] == unitGen, unconditionally) makes this specific dogleg permanently unreachable, not just for this fixture – see tests/testthat/test_makePedigreeMatingLayout.R’s own “Track 4” comment and test (:1297-1368), which documents and asserts exactly this.

Graphic fidelity

kinship2 draws a consanguineous mating as a doubled connecting line between the two mates (visible directly between A and the left-hand Y) and – independently of anything to do with consanguinity – duplicates any individual who appears in more than one union (Y appears twice, joined by a dashed connector). makePedigreeMatingLayout() uses a distinct color and width on the consanguineous union’s own edges and also independently duplicates a multi-union individual with a dashed connector – the two packages converge on the same duplicate-node convention without having copied it from one another.

kinship2 pedigree diagram showing subjects A and Y connected by a doubled mate-line (the consanguinity marker) and Y appearing twice, joined by a dashed arc.

kinship2’s own plot.pedigree(). The doubled line directly between A and the near Y node is kinship2’s consanguinity marker; the dashed arc connects Y’s two appearances (one per union she anchors).

nprcgenekeepr pedigree diagram, direct edge style, showing the A-Y consanguineous union marked in a distinct vermillion color and greater width.

makePedigreeMatingLayout(edgeStyle = "direct") – the A-Y union’s 2 mate edges render in vermillion (#D55E00) at width 4.

nprcgenekeepr pedigree diagram, rectilinear edge style, showing the consanguinity marker carried onto the same 2 direct edges as the direct-style rendering, with no dogleg reroute.

makePedigreeMatingLayout(edgeStyle = "rectilinear") – as of Track 4 (S573), this fixture no longer produces a dogleg on either side, so the marker propagates onto the same 2 direct edges as the direct style above.
Edge style Marked (vermillion) edges Expected
direct 2 2 (one edge per mate, no dogleg)
rectilinear 2 2 (identical to direct – Track 4, S573, eliminated the dogleg this fixture originally forced)

Both packages flag the same union as consanguineous, using their own independent visual conventions – a thickened doubled line in kinship2, a distinct color and width in nprcgenekeepr – and nprcgenekeepr’s marker renders identically under both edge styles for this fixture, since Track 4 (S573, 2026-08-14) eliminated the dogleg reroute this fixture originally exercised (see the corrected Fixture description above).

Structural verification

Tracks B and C’s diagram images above are, on their own, still just two independently-rendered pictures placed side by side – a reader has to trust that they show the same family structure. The kinship2 structural/ topological comparison plan closes that gap: .extractKinship2Structure() and .extractNprcStructure() (both @noRd, zero kinship2 dependency) pull the real-individual-level parent-child edge set, mate-pair set, and rendered-individual set out of kinship2’s own pedigree object and out of makePedigreeMatingLayout()’s output respectively – at the level of who is related to whom, and who is shown at all, not which duplicate copy of a multi-union individual each package’s layout happens to draw (the two packages duplicate different individuals for the same union above: Y in kinship2’s rendering, A in nprcgenekeepr’s). .comparePedigreeStructures() then diffs all three sets directly – “structurally identical” in this article means identical on these three sets, never identical mate-line layout or node placement; see the caveat in Graphic fidelity above and Caveats carried forward below for the specific, visible layout convention the two packages do not share.

A real gap in this comparator was found and fixed 2026-08-26. The original version diffed only the parent-child edge set and mate-pair set – never the set of individuals actually displayed. An individual with zero edges on either side (a fully isolated founder, like P5 in Track B’s full fixture, described above) is invisible to that diff: it contributed no rows to either table regardless of whether either package rendered it, so the comparator reported identical = TRUE on a pair of images that visibly differ (16 rendered nodes vs. 15). The comparator now also diffs the rendered-individual set on each side (kinship2’s side computed from align.pedigree()’s own plot-grid placement, not merely its pedigree() object’s declared id list) – see tests/testthat/test_comparePedigreeStructure.R’s “individuals” test block for the full regression coverage, including this exact Track B fixture.

Run against this article’s own Track B and Track C fixtures, live, with the fixed comparator:

Fixture Structurally identical to kinship2?
Track B, full (16 subjects) Yes – nprcgenekeepr now suppresses P5, matching which individuals kinship2 places on the diagram (see Graphic fidelity above for what this does, and does not, say about the two packages’ mate-line layout)
Track B, shrunk (8 subjects) Yes
Track C (9 subjects, consanguineous dogleg) Yes

All three fixtures are now a clean, no-discrepancy match. P5 does not survive shrinkPedigree()’s trim (an uninformative, unavailable founder with no descendants), so Track B shrunk never exercised this case; Track C’s fixture has no isolated individuals at all. Track B’s full fixture previously showed a genuine discrepancy here: makePedigreeMatingLayout() rendered P5 while kinship2’s own plot omitted it. That was an inconsistency in nprcgenekeepr’s own rendering convention, not a preferable colony-management default – kinship2’s own align.pedigree() never places a fully isolated individual (no recorded parents, mates, or offspring) on the plot grid, and nprcgenekeepr now matches that convention (makePedigreeMatingLayout()’s new isolation pre-filter, Phase 1 of the P5-suppression plan, S644, 2026-08-27, closing issue #164).

Caveats carried forward

  • The two packages’ mate-line layout is not, and was never claimed to be, visually identical. kinship2’s align.pedigree() spreads a mated pair apart and drops the descent line to their children from the midpoint between the two symbols; makePedigreeMatingLayout() draws a mated pair close together, with the mating-unit marker (the small dot) positioned at the sire’s own symbol rather than centered between sire and dam – visible directly in Track B’s full-fixture image pair above. This is nprcgenekeepr’s own established rendering convention, unrelated to and unchanged by any track in this article (the marker’s own visibility was separately weighed and kept as-is in issue #161). Every “match”/ “identical” claim in this article is scoped to numeric values, surviving-subject sets, or the structural parent-child/mate-pair/rendered-individual sets (Structural verification) – never to mate-line spacing or node placement.
  • kinship2 is not a package dependency. Nothing above runs at quarto render time or in R CMD check – the numbers and images are the frozen output of one offline, interactively-run script, matching this package’s established precedent of never calling kinship2:: live from committed test or documentation code.
  • The full 17-subject fam1 pedigree from the kinship2 supplement’s main worked example is not reconstructable from this repository’s materials (its source figure lives in the kinship2 application note, not the supplement PDF this repository ships). Track A’s fixture is the fully-specified 10-subject Figure S1 subset, per the audit’s own scope caveat – not a limitation introduced by this article.
  • Tracks B and C have no PDF-printed worked example at all – the supplement names only which subjects a shrink trims, never their relationships, and says nothing about visual conventions. Both tracks’ ground truth is a live, installed kinship2::pedigree.shrink() / plot.pedigree() run on a fixture purpose-built to exercise the relevant algorithm, not a supplement-sourced value.
  • kinship2::pedigree()’s sex-role validation is stricter than nprcgenekeepr’s own sire/dam columns. Track C’s own committed test fixture lists one individual (Y) as a sire in one row despite her declared sex being female – valid input to makePedigreeMatingLayout(), which does not enforce sex/column-role consistency, but rejected by kinship2::pedigree(), which does. The validation script swaps that one row’s 2 column values (same 2 parents, same family structure) only for the kinship2-side object; every nprcgenekeepr call in this article uses the fixture exactly as committed.

Verdict

PASS. Track A’s autosomal and X-linked kinship matrices are bit-for-bit identical to kinship2’s own output across every one of 200 compared cells (100 autosomal + 100 X-linked), including the MZ-twin correction and its propagation to a descendant. Track B’s shrinkPedigree() reproduces kinship2’s exact surviving subject set and exact bitSize trajectory, and the shrunk pedigrees are the same 2 family groups – confirmed by a real edge-set-and-individual-set diff, not just a visual read (Structural verification). Track B’s full 16-subject fixture previously showed a genuine discrepancy here: nprcgenekeepr rendered the isolated founder P5 while kinship2’s own plot.pedigree() silently omitted it – caught only after the structural comparator itself was fixed to diff the rendered-individual set, not just edges (the gap that let a false identical = TRUE pass unnoticed until an owner-directed image review caught it live, 2026-08-26). That discrepancy is now resolved: makePedigreeMatingLayout() suppresses a fully isolated individual (no recorded parents, mates, or offspring) from the rendered diagram, matching kinship2’s own convention (Phase 1 of the P5-suppression plan, S644, 2026-08-27, closing issue #164). Track C’s consanguineous-mating marker flags the same union kinship2 flags, under both edge styles, using an independently-converged duplicate-node convention for the same underlying multi-union case kinship2 also duplicates – structurally confirmed identical. All three tracks – Track A, both of Track B’s fixtures, and Track C – are now cleared as faithful, structurally identical reproductions of the kinship2 supplement’s own results. “Structurally identical” is precise, not a stand-in for “visually identical”: the two packages’ own mate-line layout conventions remain, and have always been, different (see Caveats carried forward).

References

Sinnwell, J.P., Therneau, T.M., Schaid, D.J. (2014) “The kinship2 R package for pedigree data.” Human Heredity 78(2):91-93.

Sinnwell, J.P., Therneau, T.M., Schaid, D.J. “The kinship2 R Package for Pedigree Data: Supplementary Material.” Mayo Clinic (PMC manuscript NIHMS593658); shipped in this repository at inst/extdata/reference/NIHMS593658-supplement-supplement_1.pdf.

See also kinship(), shrinkPedigree(), makePedigreeMatingLayout(), and kinship2-supplement-full-reproduction-plan.md.