Cross-organization linkage (interlocking directorates)
cross-org.Rmdlink_panel() links a person to themselves across years within one organization. This article covers the complementary problem: linking the same person across different organizations — the basis of interlocking directorate studies, where one individual sits on several nonprofit boards.
The two are deliberately separate. Within-org linkage can lean on a tight block (same ein) and rich cross-year signal. Cross-org linkage cannot — it must search the whole population — so it uses a different machine: person profiles plus hash-lookup blocking.
The shape of the pipeline
link_panel() -> EMP_ID (within-org, across years)
|
build_person_profile() -> one identity profile per EMP_ID, with blocking keys
|
blocking_passes() -> hash keys that bring candidate pairs together
|
score_candidate_pairs() -> score cross-org pairs
|
filter_edges_by_role() -> keep the interlocks you care about (e.g. board-board)
|
stamp_xorg() -> a cross-org person id
link_cross_org() wraps the whole chain; the pieces are exported for custom pipelines.
Person profiles
A person may have many within-org records. build_person_profile() reduces each EMP_ID cluster to one canonical profile — the identity you actually match on across orgs — and precomputes the blocking keys used to find candidates:
linked <- link_panel(panel) # must already carry EMP_ID
profiles <- build_person_profile(linked)
# each profile carries last-name blocking keys for the hash join:
str(profiles$last_name_keys[[1]])Blocking passes
Comparing every profile to every other is quadratic and hopeless at population scale. Instead, candidates are generated by blocking passes — each pass emits a hash key, and only profiles sharing a key are ever compared. The available passes are inspectable:
names(blocking_passes())
#> [1] "strict" "geo" "industry" "surname"Multiple passes give multiple chances to co-locate a true pair (e.g. a last-name-based key and a phonetic key), so a single noisy field does not hide an interlock. person_blocking_keys() computes the keys for a profile; candidate_pairs() turns shared keys into the pairs to score.
Scoring and role filtering
Cross-org pairs are scored with the same Fellegi–Sunter machinery as within-org linkage (see How the matching works), using the population-level surname prior (population_surname_weight()) rather than a per-org one. You then keep the edges whose role composition matches your question — most interlock studies want board-to-board ties:
xo <- link_cross_org(linked, state = "state", ntee = "ntee", threshold = 8)
subset(xo$profiles, XORG_N_ORGS > 1) # people who interlock
board_only_edges(xo$edges) # board <-> board ties only
non_board_edges(xo$edges) # staff/officer tiesfilter_edges_by_role() is the general form; board_only_edges() and non_board_edges() are convenience wrappers. Role is assigned by the taxonomy in Title roles.
Stamping the cross-org id
stamp_xorg() closes the accepted cross-org edges into components and stamps a cross-organization person identifier, alongside a count of the distinct organizations each person appears in (XORG_N_ORGS) — the raw material for an interlock network.
See also
-
link_cross_org(),build_person_profile(),blocking_passes(),person_blocking_keys(),candidate_pairs(),score_candidate_pairs(),filter_edges_by_role(),board_only_edges(),non_board_edges(),stamp_xorg()in the reference. - Title roles for how BOARD / OFFICER / STAFF is decided. ```