obs <- data.frame(
period = c(1,1, 2,2, 3,3, 4,4,4, 5,5, 6,6, 7, 8,8, 9,9,9, 10,10,
11,11, 12, 13,13, 14,14, 15),
group = c(1,1, 1,1, 1,1, 1,1,2, 1,1, 1,1, 1, 1,1, 1,1,2, 1,1,
1,1, 1, 1,1, 1,1, 1),
id = c("A","B", "A","B", "A","B", "A","B","C", "A","C", "B","C", "A",
"C","D", "A","B","C", "D","E", "D","E", "E", "E","F", "E","F","F")
)
ids <- sort(unique(obs$id))
n <- length(ids)Association indices for social networks
You watch a marked population: a flock, a pod, a troop. Each time you go out you record who was seen together. Out of those sightings you want a network: a weight on every pair of individuals that says how strongly they associate. This first post builds that network from group sightings and shows why the arithmetic you pick to do it is a real decision, not a formality.
The gambit of the group
Field data rarely record “A groomed B” or “A followed B”. They record groups: a set of individuals seen together at one time. The usual move is to treat everyone in a group as associating with everyone else in it. That assumption has a name, the gambit of the group, and the whole network inherits it. It is a strong assumption and we return to it in the checking post; for now we take it and see how far it gets us.
So the raw material is a list of observations, each one an individual seen in a group at a sampling period. A period can hold more than one group (you might see two subgroups on the same morning). Here is a small hand-built example with six individuals across fifteen periods.
Four counts per pair
For any pair of individuals the association index is a ratio, and the ratio needs four counts. Over the sampling periods, count how many times the pair was seen together, how many times both were seen but in different groups, and how many times only one of them was seen at all.
Writing the pair as \(i\) and \(j\):
- \(x\): periods where \(i\) and \(j\) were in the same group.
- \(y_{ab}\): periods where both were seen but in different groups.
- \(y_a\): periods where only \(i\) was seen.
- \(y_b\): periods where only \(j\) was seen.
The following function walks the periods and returns those four numbers for a pair.
counts <- function(i, j) {
x <- yab <- ya <- yb <- 0L
for (p in unique(obs$period)) {
sub <- obs[obs$period == p, ]
gi <- sub$group[sub$id == i]
gj <- sub$group[sub$id == j]
seen_i <- length(gi) > 0
seen_j <- length(gj) > 0
if (seen_i && seen_j) {
if (any(gi %in% gj)) x <- x + 1L else yab <- yab + 1L
} else if (seen_i) ya <- ya + 1L
else if (seen_j) yb <- yb + 1L
}
c(x = x, yab = yab, ya = ya, yb = yb)
}
counts("A", "B") x yab ya yb
5 0 2 1
Two indices, one denominator apart
The simple ratio index (SRI) puts the together-count over every period where at least one of the pair was seen:
\[\text{SRI} = \frac{x}{x + y_{ab} + y_a + y_b}.\]
The half-weight index (HWI) keeps the same numerator but halves the weight on the periods where only one member was identified:
\[\text{HWI} = \frac{x}{x + y_{ab} + \tfrac{1}{2}(y_a + y_b)}.\]
The half-weight was built for a specific worry (Cairns and Schwager 1987): in many protocols, when two animals are together you are less likely to identify both of them than when they are apart. That bias inflates the single-identification counts \(y_a\) and \(y_b\), which drags the SRI down. Halving those counts pushes back against the bias. The two indices therefore diverge exactly where \(y_a + y_b\) is large relative to \(x\): pairs often seen singly.
Let me compute both for every pair and store the two matrices.
sri <- hwi <- matrix(0, n, n, dimnames = list(ids, ids))
tab <- list()
for (a in 1:(n-1)) for (b in (a+1):n) {
cc <- counts(ids[a], ids[b])
s <- cc["x"] / (cc["x"] + cc["yab"] + cc["ya"] + cc["yb"])
h <- cc["x"] / (cc["x"] + cc["yab"] + 0.5*(cc["ya"] + cc["yb"]))
sri[ids[a], ids[b]] <- sri[ids[b], ids[a]] <- s
hwi[ids[a], ids[b]] <- hwi[ids[b], ids[a]] <- h
tab[[length(tab)+1]] <- data.frame(i = ids[a], j = ids[b],
x = cc["x"], yab = cc["yab"], ya = cc["ya"], yb = cc["yb"],
SRI = round(s, 3), HWI = round(h, 3), diff = round(h - s, 3))
}
tab <- do.call(rbind, tab); rownames(tab) <- NULL
tab[order(-tab$SRI), ] i j x yab ya yb SRI HWI diff
1 A B 5 0 2 1 0.625 0.769 0.144
13 D E 2 0 1 3 0.333 0.500 0.167
15 E F 2 0 3 1 0.333 0.500 0.167
10 C D 1 0 4 2 0.143 0.250 0.107
6 B C 1 2 3 2 0.125 0.182 0.057
2 A C 1 2 4 2 0.111 0.167 0.056
3 A D 0 0 7 3 0.000 0.000 0.000
4 A E 0 0 7 5 0.000 0.000 0.000
5 A F 0 0 7 3 0.000 0.000 0.000
7 B D 0 0 6 3 0.000 0.000 0.000
8 B E 0 0 6 5 0.000 0.000 0.000
9 B F 0 0 6 3 0.000 0.000 0.000
11 C E 0 0 5 5 0.000 0.000 0.000
12 C F 0 0 5 3 0.000 0.000 0.000
14 D F 0 0 3 3 0.000 0.000 0.000
Read the table top to bottom. A and B were together five times (\(x = 5\)) and never seen singly enough to pull them apart, giving SRI 0.625 and HWI 0.769. Down among the weaker pairs, D and E sit together only twice but are each seen alone several times, so the two indices split further apart. The C to D edge is a single shared group: a thin bridge between what look like two clusters.
The association matrix makes the clusters visible.
long <- expand.grid(i = ids, j = ids)
long$sri <- mapply(function(a, b) sri[a, b], long$i, long$j)
ggplot(long, aes(i, j, fill = sri)) +
geom_tile(colour = "white", linewidth = 0.6) +
scale_fill_gradient(low = "#f5f5f5", high = "#2b5d34", name = "SRI") +
coord_equal() +
labs(x = NULL, y = NULL,
title = "Pairwise association (simple ratio index)")
Where the choice bites
For the tight pairs the two indices tell the same story; the ranking of edges barely moves. The gap opens for pairs seen singly a lot, and that gap is not tiny.
ggplot(tab, aes(SRI, HWI)) +
geom_abline(slope = 1, intercept = 0, linetype = "dashed", colour = "grey60") +
geom_point(aes(size = ya + yb), colour = "#2b5d34", alpha = 0.8) +
scale_size_continuous(name = "seen singly\n(ya + yb)", range = c(2, 6)) +
coord_equal(xlim = c(0, 0.85), ylim = c(0, 0.85)) +
labs(title = "The two indices diverge for pairs seen apart",
x = "simple ratio index", y = "half-weight index")
c(max_HWI_minus_SRI = round(max(abs(tab$diff)), 3),
edges_present = sum(sri[upper.tri(sri)] > 0),
dyads_total = n * (n - 1) / 2)max_HWI_minus_SRI edges_present dyads_total
0.167 6.000 15.000
The largest gap between the indices for this dataset is 0.167, and it lands on the pairs with the most single sightings. On a scale from zero to one that is enough to reorder a middle-ranked edge or to move a pair above or below whatever threshold you later use to draw the network. The indices agree at the top and disagree in the middle, which is precisely the range where marginal associations get decided.
What this settles and what it does not
The practical rule is short. Use the simple ratio index when you are confident that seeing one animal without its partner really means they were apart, so the single-identification counts are trustworthy. Reach for the half-weight index when your protocol tends to miss the second animal of a pair, so those counts are inflated. Whichever you choose, report it: an SRI network and an HWI network from the same sightings are not interchangeable at the edges.
Two limits are worth stating now. First, both indices rest on the gambit of the group, and a weight of 0.625 between A and B means “seen in the same group five times out of eight”, not “socially bonded”. Second, the whole thing depends on how a group was defined in the field, and that definition is a decision you made, not a fact the animals handed you. The checking post takes both of these apart.
With the network in hand, the next question is whether any of its structure is real or whether it is what random mixing would produce anyway. That needs a null model, and for social networks the choice of null is the difference between a sound result and a confident mistake.
Where to go next
Build the network first, then interrogate it. The permutation post shows how to ask whether the associations you measured are more than chance, and why the obvious shuffle gives the wrong answer. The centrality post turns the matrix into per-individual scores, and the checking post returns to the two assumptions flagged above.
References
Cairns SJ, Schwager SJ 1987 Animal Behaviour 35(5):1454-1469 (10.1016/S0003-3472(87)80018-0)
Whitehead H 2008 Analyzing Animal Societies: Quantitative Methods for Vertebrate Social Analysis. University of Chicago Press. ISBN 978-0226895215
Farine DR, Whitehead H 2015 Journal of Animal Ecology 84(5):1144-1163 (10.1111/1365-2656.12418)