Analyzing Two-Mode Networks

This lecture deals with the network analysis of two-mode networks. Note that in the literature there is some terminological slippage. Two-mode networks are a type of social network. By definition two-mode networks can be represented using rectangular adjacency matrices (sometimes called affiliation matrices in sociology).

In this case, two-mode networks fall under the general category of “two-mode data.” Any data set that has information on two types of objects (e.g., people and variables) is two-mode data so two-mode networks are just a special case of two-mode data.

In this sense, there is a useful a distinction, due to Borgatti and Everett (1997). This is that between the “modes” and the “ways” of a data matrix. So most data matrices are two-ways, in that they have at least two dimensions (e.g., the row and column dimensions).

But some data matrices (like the usual adjacency matrix in regular network data) only collect information on a single type of entity, so they are “one mode, two ways.” But sometimes we have network data on two sets of objects, in which case, we use a data matrix that has “two-modes” (sets of nodes) and “two ways” (rows and columns).

So what makes a network a “two-mode network”? Well, a two-mode network is different from a regular network, because it has two sets of nodes not just one. So instead of \(V\) now we have \(V_1\) and \(V_2\). Moreover, the edges in a two-mode network only go from nodes in one set to nodes in the other set; there are no within-node-set edges.

Bipartite Graphs

This restriction makes the graph that represents a two-mode network a special kind of graph called a bipartite graph. A graph is bipartite if the set of nodes in the graph can be divided into two groups, such that relations go from nodes in one set to nodes in the other set.

Note that bipartite graphs can be be used to represent both two-mode and regular one mode networks, as long as the above condition holds. For instance, a dating network with 100% heterosexual people will yield a bipartite graph based on the dating relation, with men in one set and women on the other node set, even though it’s a one-mode network.

So whether or not a graph is bipartite is something you can check for.

Let’s see how that works. Let us load the most famous two-mode network data set (kind of the Drosophila of two-mode network analysis; one of the most repeatedly analyzed social structures in history: For a classic sampling of such analyses see here) a network composed of eighteen women from the social elite of a tiny town in the south in the 1930s who attended fourteen social events (Breiger 1974), otherwise know as the Southern Women (SW) data:

   library(igraph)
   library(networkdata)
   g <- southern_women

Now we already know this is a bipartite graph. However, let’s say you are new and you’ve never heard of these data. You can check whether the graph you loaded up is bipartite or not by using the igraph function is_bipartite:

   is_bipartite(g)
[1] TRUE

Which returns TRUE as an answer. Had we loaded up any old non-bipartite graph, the answer would have been:

   g.whatever <- movie_45
   is_bipartite(g.whatever)
[1] FALSE

Which makes sense because that’s just a regular old graph.

Note that if we check the bipartite graph object, it looks like any other igraph object:

   g
IGRAPH 1074643 UN-B 32 89 -- 
+ attr: type (v/l), name (v/c)
+ edges from 1074643 (vertex names):
 [1] EVELYN   --6/27 EVELYN   --3/2  EVELYN   --4/12 EVELYN   --9/26
 [5] EVELYN   --2/25 EVELYN   --5/19 EVELYN   --9/16 EVELYN   --4/8 
 [9] LAURA    --6/27 LAURA    --3/2  LAURA    --4/12 LAURA    --2/25
[13] LAURA    --5/19 LAURA    --3/15 LAURA    --9/16 THERESA  --3/2 
[17] THERESA  --4/12 THERESA  --9/26 THERESA  --2/25 THERESA  --5/19
[21] THERESA  --3/15 THERESA  --9/16 THERESA  --4/8  BRENDA   --6/27
[25] BRENDA   --4/12 BRENDA   --9/26 BRENDA   --2/25 BRENDA   --5/19
[29] BRENDA   --3/15 BRENDA   --9/16 CHARLOTTE--4/12 CHARLOTTE--9/26
+ ... omitted several edges

But we can tell that the graph is a two-mode network because we have links starting with people with old southern lady names from the 1930s (which are also the names of a bunch of young girls in middle school today) and ending with events that have dates in them. So the (undirected) edge is \(person-event\).

The graph is undirected because the “membership” or “attendance” relation between a person and an organization/event doesn’t have a natural directionality:

   is_directed(g)
[1] FALSE

Another way of checking the “bipartiteness” of a graph in igraph is by using the bipartite_mapping function.

Let’s see what it does:

   bipartite_mapping(g)
$res
[1] TRUE

$type
   EVELYN     LAURA   THERESA    BRENDA CHARLOTTE   FRANCES   ELEANOR     PEARL 
    FALSE     FALSE     FALSE     FALSE     FALSE     FALSE     FALSE     FALSE 
     RUTH     VERNE     MYRNA KATHERINE    SYLVIA      NORA     HELEN   DOROTHY 
    FALSE     FALSE     FALSE     FALSE     FALSE     FALSE     FALSE     FALSE 
   OLIVIA     FLORA      6/27       3/2      4/12      9/26      2/25      5/19 
    FALSE     FALSE      TRUE      TRUE      TRUE      TRUE      TRUE      TRUE 
     3/15      9/16       4/8      6/10      2/23       4/7     11/21       8/3 
     TRUE      TRUE      TRUE      TRUE      TRUE      TRUE      TRUE      TRUE 

This function takes the candidate bipartite graph as input and returns two objects: res is just a check to see if the graph is actually bipartite (TRUE in this case), type is a logical vector of dimensions \(M + N\) (where \(M\) is the number of nodes in the person set and \(N\) is the number of nodes in the event set) dividing the nodes into two groups. Here people get FALSE and events get TRUE, but this designations are arbitrary (a kind of dummy coding; FALSE = 0 and TRUE = 1).

We can add this as a node attribute to our graph so that way we know which node is in which set:

   V(g)$type <- bipartite_mapping(g)$type

The Biadjacency (Affiliation) Matrix

Once you have your bipartite graph loaded up, you may want (if the graph is small enough) to check out the graph’s affiliation matrix \(A\).

This works just like before, except that now we use the as_biadjacency_matrix function:

   A <- as.matrix(as_biadjacency_matrix(g))
   A
          6/27 3/2 4/12 9/26 2/25 5/19 3/15 9/16 4/8 6/10 2/23 4/7 11/21 8/3
EVELYN       1   1    1    1    1    1    0    1   1    0    0   0     0   0
LAURA        1   1    1    0    1    1    1    1   0    0    0   0     0   0
THERESA      0   1    1    1    1    1    1    1   1    0    0   0     0   0
BRENDA       1   0    1    1    1    1    1    1   0    0    0   0     0   0
CHARLOTTE    0   0    1    1    1    0    1    0   0    0    0   0     0   0
FRANCES      0   0    1    0    1    1    0    1   0    0    0   0     0   0
ELEANOR      0   0    0    0    1    1    1    1   0    0    0   0     0   0
PEARL        0   0    0    0    0    1    0    1   1    0    0   0     0   0
RUTH         0   0    0    0    1    0    1    1   1    0    0   0     0   0
VERNE        0   0    0    0    0    0    1    1   1    0    0   1     0   0
MYRNA        0   0    0    0    0    0    0    1   1    1    0   1     0   0
KATHERINE    0   0    0    0    0    0    0    1   1    1    0   1     1   1
SYLVIA       0   0    0    0    0    0    1    1   1    1    0   1     1   1
NORA         0   0    0    0    0    1    1    0   1    1    1   1     1   1
HELEN        0   0    0    0    0    0    1    1   0    1    1   1     0   0
DOROTHY      0   0    0    0    0    0    0    1   1    0    0   0     0   0
OLIVIA       0   0    0    0    0    0    0    0   1    0    1   0     0   0
FLORA        0   0    0    0    0    0    0    0   1    0    1   0     0   0

In this matrix we list one set of nodes in the rows and the other set is in the columns. Each cell \(a_{ij} = 1\) if row node \(i\) is affiliated with column node \(j\), otherwise \(a_{ij} = 0\).

The Bipartite Adjacency Matrix

Note that if we were to use the regular as_adjacency_matrix function on a bipartite graph, we get a curious version of the adjacency matrix:

   B <- as.matrix(as_adjacency_matrix(g))
   B
          EVELYN LAURA THERESA BRENDA CHARLOTTE FRANCES ELEANOR PEARL RUTH
EVELYN         0     0       0      0         0       0       0     0    0
LAURA          0     0       0      0         0       0       0     0    0
THERESA        0     0       0      0         0       0       0     0    0
BRENDA         0     0       0      0         0       0       0     0    0
CHARLOTTE      0     0       0      0         0       0       0     0    0
FRANCES        0     0       0      0         0       0       0     0    0
ELEANOR        0     0       0      0         0       0       0     0    0
PEARL          0     0       0      0         0       0       0     0    0
RUTH           0     0       0      0         0       0       0     0    0
VERNE          0     0       0      0         0       0       0     0    0
MYRNA          0     0       0      0         0       0       0     0    0
KATHERINE      0     0       0      0         0       0       0     0    0
SYLVIA         0     0       0      0         0       0       0     0    0
NORA           0     0       0      0         0       0       0     0    0
HELEN          0     0       0      0         0       0       0     0    0
DOROTHY        0     0       0      0         0       0       0     0    0
OLIVIA         0     0       0      0         0       0       0     0    0
FLORA          0     0       0      0         0       0       0     0    0
6/27           1     1       0      1         0       0       0     0    0
3/2            1     1       1      0         0       0       0     0    0
4/12           1     1       1      1         1       1       0     0    0
9/26           1     0       1      1         1       0       0     0    0
2/25           1     1       1      1         1       1       1     0    1
5/19           1     1       1      1         0       1       1     1    0
3/15           0     1       1      1         1       0       1     0    1
9/16           1     1       1      1         0       1       1     1    1
4/8            1     0       1      0         0       0       0     1    1
6/10           0     0       0      0         0       0       0     0    0
2/23           0     0       0      0         0       0       0     0    0
4/7            0     0       0      0         0       0       0     0    0
11/21          0     0       0      0         0       0       0     0    0
8/3            0     0       0      0         0       0       0     0    0
          VERNE MYRNA KATHERINE SYLVIA NORA HELEN DOROTHY OLIVIA FLORA 6/27 3/2
EVELYN        0     0         0      0    0     0       0      0     0    1   1
LAURA         0     0         0      0    0     0       0      0     0    1   1
THERESA       0     0         0      0    0     0       0      0     0    0   1
BRENDA        0     0         0      0    0     0       0      0     0    1   0
CHARLOTTE     0     0         0      0    0     0       0      0     0    0   0
FRANCES       0     0         0      0    0     0       0      0     0    0   0
ELEANOR       0     0         0      0    0     0       0      0     0    0   0
PEARL         0     0         0      0    0     0       0      0     0    0   0
RUTH          0     0         0      0    0     0       0      0     0    0   0
VERNE         0     0         0      0    0     0       0      0     0    0   0
MYRNA         0     0         0      0    0     0       0      0     0    0   0
KATHERINE     0     0         0      0    0     0       0      0     0    0   0
SYLVIA        0     0         0      0    0     0       0      0     0    0   0
NORA          0     0         0      0    0     0       0      0     0    0   0
HELEN         0     0         0      0    0     0       0      0     0    0   0
DOROTHY       0     0         0      0    0     0       0      0     0    0   0
OLIVIA        0     0         0      0    0     0       0      0     0    0   0
FLORA         0     0         0      0    0     0       0      0     0    0   0
6/27          0     0         0      0    0     0       0      0     0    0   0
3/2           0     0         0      0    0     0       0      0     0    0   0
4/12          0     0         0      0    0     0       0      0     0    0   0
9/26          0     0         0      0    0     0       0      0     0    0   0
2/25          0     0         0      0    0     0       0      0     0    0   0
5/19          0     0         0      0    1     0       0      0     0    0   0
3/15          1     0         0      1    1     1       0      0     0    0   0
9/16          1     1         1      1    0     1       1      0     0    0   0
4/8           1     1         1      1    1     0       1      1     1    0   0
6/10          0     1         1      1    1     1       0      0     0    0   0
2/23          0     0         0      0    1     1       0      1     1    0   0
4/7           1     1         1      1    1     1       0      0     0    0   0
11/21         0     0         1      1    1     0       0      0     0    0   0
8/3           0     0         1      1    1     0       0      0     0    0   0
          4/12 9/26 2/25 5/19 3/15 9/16 4/8 6/10 2/23 4/7 11/21 8/3
EVELYN       1    1    1    1    0    1   1    0    0   0     0   0
LAURA        1    0    1    1    1    1   0    0    0   0     0   0
THERESA      1    1    1    1    1    1   1    0    0   0     0   0
BRENDA       1    1    1    1    1    1   0    0    0   0     0   0
CHARLOTTE    1    1    1    0    1    0   0    0    0   0     0   0
FRANCES      1    0    1    1    0    1   0    0    0   0     0   0
ELEANOR      0    0    1    1    1    1   0    0    0   0     0   0
PEARL        0    0    0    1    0    1   1    0    0   0     0   0
RUTH         0    0    1    0    1    1   1    0    0   0     0   0
VERNE        0    0    0    0    1    1   1    0    0   1     0   0
MYRNA        0    0    0    0    0    1   1    1    0   1     0   0
KATHERINE    0    0    0    0    0    1   1    1    0   1     1   1
SYLVIA       0    0    0    0    1    1   1    1    0   1     1   1
NORA         0    0    0    1    1    0   1    1    1   1     1   1
HELEN        0    0    0    0    1    1   0    1    1   1     0   0
DOROTHY      0    0    0    0    0    1   1    0    0   0     0   0
OLIVIA       0    0    0    0    0    0   1    0    1   0     0   0
FLORA        0    0    0    0    0    0   1    0    1   0     0   0
6/27         0    0    0    0    0    0   0    0    0   0     0   0
3/2          0    0    0    0    0    0   0    0    0   0     0   0
4/12         0    0    0    0    0    0   0    0    0   0     0   0
9/26         0    0    0    0    0    0   0    0    0   0     0   0
2/25         0    0    0    0    0    0   0    0    0   0     0   0
5/19         0    0    0    0    0    0   0    0    0   0     0   0
3/15         0    0    0    0    0    0   0    0    0   0     0   0
9/16         0    0    0    0    0    0   0    0    0   0     0   0
4/8          0    0    0    0    0    0   0    0    0   0     0   0
6/10         0    0    0    0    0    0   0    0    0   0     0   0
2/23         0    0    0    0    0    0   0    0    0   0     0   0
4/7          0    0    0    0    0    0   0    0    0   0     0   0
11/21        0    0    0    0    0    0   0    0    0   0     0   0
8/3          0    0    0    0    0    0   0    0    0   0     0   0

This bipartite adjacency matrix \(\mathbf{B}\) is of dimensions \((M + N) \times (M + N)\), which is \((18 + 14) \times (18 + 14) = 32 \times 32\) in the SW data; it has the following block structure (Fouss, Saerens, and Shimbo 2016, 12):

\[ \mathbf{B} = \left[ \begin{matrix} \mathbf{O}_{M \times M} & \mathbf{A}_{M \times N} \\ \mathbf{A}^T_{N \times M} & \mathbf{O}_{N \times N} \end{matrix} \right] \]

Where \(\mathbf{O}\) is just the all zeros matrix of the relevant dimensions, and \(\mathbf{A}\) is the biadjacency (affiliation) matrix as defined earlier. Thus, the bipartite adjacency matrix necessarily has two big diagonal “zero blocks” in it (upper-left and lower-right) corresponding to where the links between nodes in the same set would be (but necessarily aren’t because this is a two-mode network). The non-zero blocks are just the affiliation matrix (upper-right) and its transpose(lower-left).

Bipartiteness as “Anti-Community”

Recall from the community structure lecture notes, that community structure is defined by clusters of nodes that have more connections among themselves than they do with outsiders. If you think about it, a bipartite graph has the opposite of this going on. Nodes of the same type have zero connections among themselves, and they have all their connections with nodes of the other group!

So that means that bipartite structure is the mirror image of community structure (in the two group case). This also means that if we were to compute the modularity of a bipartite graph, using the node type as the grouping variable we should get the theoretical minimum of this measure (which you may recall is \(Q = -\frac{1}{2}\)).

Let’s try it out, by computing the modularity from the bipartite adjacency matrix of the SW data, using node type as the grouping variable:

   V(g)$comm <- as.numeric(bipartite_mapping(g)$type) + 1
   modularity(g, V(g)$comm)
[1] -0.5

And indeed, we recover the theoretical minimum value of the modularity (Brandes et al. 2007, 173)! This also means that this method can be used to test whether a graph is bipartite, or whether any network approximates bipartiteness (Newman 2006, 13). Values that are close to \(-0.5\) would indicate that the network in question has bipartite structure.

Basic Two-Mode Network Statistics

We can calculate some basic network statistics from the affiliation (biadjacency) matrix. We have two number of nodes to calculate, but only one quantity for the number of edges.

Number of Nodes and Edges

The number of nodes on the people side \(N\) is just the number of rows of \(A\):

   nrow(A)
[1] 18

And the number of events/groups \(M\) is just the number of columns:

   ncol(A)
[1] 14

Finally, the number of edges \(E\) is just the sum of all the entries of \(A\):

   sum(A)
[1] 89

Note that if you were to use the igraph function vcount on the original graph object, you get the wrong answer:

   vcount(g)
[1] 32

That’s because vcount is working with the \(32 \times 32\) regular adjacency matrix, not the biadjacency matrix. Here, vcount is returning the total number of nodes in the graph summing across the two sets, which is \(M + N\).

If you wanted to get the right answer for each set of edges from the regular igraph graph object, you could use the type node attribute we defined earlier along with the subgraph function:

   vcount(subgraph(g, V(g)$type == FALSE))
[1] 18

Which gives us the number of women. For the events we do the same thing:

   vcount(subgraph(g, V(g)$type == TRUE))
[1] 14

However, because there’s only one set of edges, ecount still gives us the right answer:

   ecount(g)
[1] 89

Which is the same as:

   sum(A)
[1] 89

Density

As we saw in the case of one-mode networks, one of the most basic network statistics that can be derived from the above quantities is the density (observed number of edges divided by maximum possible number of edges in the graph).

In a two-mode network, density is given by:

\[ d = \frac{|E|}{N \times M} \]

Where \(|E|\) is the number of edges in the network. In our case we can compute the density as follows:

   d <- sum(A)/(nrow(A) * ncol(A))
   d
[1] 0.3531746

Degree-Based Statistics

Because we have two sets of degrees, all the basic degree statistics in the network double up. So we have two mean degrees, two maximum degrees, and two minimum degree to take care of:

   mean.d.p <- mean(rowSums(A))
   mean.d.g <- mean(colSums(A))
   max.d.p <- max(rowSums(A))
   max.d.g <- max(colSums(A))
   min.d.p <- min(rowSums(A))
   min.d.g <- min(colSums(A))

So we have:

   round(mean.d.p, 1)
[1] 4.9
   round(mean.d.g, 1)
[1] 6.4
   max.d.p
[1] 8
   max.d.g
[1] 14
   min.d.p
[1] 2
   min.d.g
[1] 3

However, note that because there’s only one set of undirected edges, the total number of edges incident to each node in each of the two sets is always going to be the same.

That means that there’s only one sum of degrees. So the sum of degrees for people:

   sum(rowSums(A))
[1] 89

Is the same as the sum of degrees of events:

   sum(colSums(A))
[1] 89

Note that in a bipartite graph, therefore, the sum of degrees of nodes in each node set is equal to the \(|E|\), the number of edges in the graph!

Degree Centrality

In a two-mode network, there are two degree sets, each corresponding to one set of nodes. For the people, in this case, their degree (centrality) is just the number of events they attend, and for the groups, it’s just the number of people that attend each event.

As we have already seen, we can get each from the affiliation matrix. The degree of the people are just the row sums:

   rowSums(A)
   EVELYN     LAURA   THERESA    BRENDA CHARLOTTE   FRANCES   ELEANOR     PEARL 
        8         7         8         7         4         4         4         3 
     RUTH     VERNE     MYRNA KATHERINE    SYLVIA      NORA     HELEN   DOROTHY 
        4         4         4         6         7         8         5         2 
   OLIVIA     FLORA 
        2         2 

And the degree of the events are just the column sums:

   colSums(A)
 6/27   3/2  4/12  9/26  2/25  5/19  3/15  9/16   4/8  6/10  2/23   4/7 11/21 
    3     3     6     4     8     8    10    14    12     5     4     6     3 
  8/3 
    3 

The igraph function degree will also give us the right answer, but in the form of a single vector including both people and events:

   degree(g)
   EVELYN     LAURA   THERESA    BRENDA CHARLOTTE   FRANCES   ELEANOR     PEARL 
        8         7         8         7         4         4         4         3 
     RUTH     VERNE     MYRNA KATHERINE    SYLVIA      NORA     HELEN   DOROTHY 
        4         4         4         6         7         8         5         2 
   OLIVIA     FLORA      6/27       3/2      4/12      9/26      2/25      5/19 
        2         2         3         3         6         4         8         8 
     3/15      9/16       4/8      6/10      2/23       4/7     11/21       8/3 
       10        14        12         5         4         6         3         3 

As Borgatti and Everett (1997) note, if we want normalized degree centrality measures, we need to divide by either \(M\) (for people) or \(N\) (for events). That is, for people we use the number of events as the norm (as this is the theoretical maximum) and for events the number of people.

So for people, normalized degree is:

   round(rowSums(A)/ncol(A), 3)
   EVELYN     LAURA   THERESA    BRENDA CHARLOTTE   FRANCES   ELEANOR     PEARL 
    0.571     0.500     0.571     0.500     0.286     0.286     0.286     0.214 
     RUTH     VERNE     MYRNA KATHERINE    SYLVIA      NORA     HELEN   DOROTHY 
    0.286     0.286     0.286     0.429     0.500     0.571     0.357     0.143 
   OLIVIA     FLORA 
    0.143     0.143 

And for events:

   round(colSums(A)/nrow(A), 3)
 6/27   3/2  4/12  9/26  2/25  5/19  3/15  9/16   4/8  6/10  2/23   4/7 11/21 
0.167 0.167 0.333 0.222 0.444 0.444 0.556 0.778 0.667 0.278 0.222 0.333 0.167 
  8/3 
0.167 

Or with igraph:

   round(degree(g)/c(rep(14, 18), rep(18, 14)), 3)
   EVELYN     LAURA   THERESA    BRENDA CHARLOTTE   FRANCES   ELEANOR     PEARL 
    0.571     0.500     0.571     0.500     0.286     0.286     0.286     0.214 
     RUTH     VERNE     MYRNA KATHERINE    SYLVIA      NORA     HELEN   DOROTHY 
    0.286     0.286     0.286     0.429     0.500     0.571     0.357     0.143 
   OLIVIA     FLORA      6/27       3/2      4/12      9/26      2/25      5/19 
    0.143     0.143     0.167     0.167     0.333     0.222     0.444     0.444 
     3/15      9/16       4/8      6/10      2/23       4/7     11/21       8/3 
    0.556     0.778     0.667     0.278     0.222     0.333     0.167     0.167 

Average Nearest Neighbor Degree

For each person (group) we may also be interested in whether they connect to more or less central groups (persons). As such, we can compute the average nearest neighbor degree for persons and groups.

For people this is equivalent to multiplying the vector of group degrees by the entries of the affiliation matrix, and then dividing by the degrees of each person:

   knn.p <- A * colSums(A)
   knn.p
          6/27 3/2 4/12 9/26 2/25 5/19 3/15 9/16 4/8 6/10 2/23 4/7 11/21 8/3
EVELYN       3   8   12    3    6   10    0    3   8    0    0   0     0   0
LAURA        3   8    5    0    4   14    6    3   0    0    0   0     0   0
THERESA      0  10    4    3    8   12    3    6  10    0    0   0     0   0
BRENDA       4   0    6    3    8    5    3    4   0    0    0   0     0   0
CHARLOTTE    0   0    3    6   10    0    3    0   0    0    0   0     0   0
FRANCES      0   0    3    0   14    6    0    8   0    0    0   0     0   0
ELEANOR      0   0    0    0   12    3    6   10   0    0    0   0     0   0
PEARL        0   0    0    0    0    3    0   14   6    0    0   0     0   0
RUTH         0   0    0    0    4    0    8   12   3    0    0   0     0   0
VERNE        0   0    0    0    0    0    8    5   3    0    0   6     0   0
MYRNA        0   0    0    0    0    0    0    4   3    8    0   3     0   0
KATHERINE    0   0    0    0    0    0    0    6   3    8    0   3     4  14
SYLVIA       0   0    0    0    0    0   12    3   6   10    0   3     8  12
NORA         0   0    0    0    0    8    5    0   4   14    6   3     8   5
HELEN        0   0    0    0    0    0    4    3   0   12    3   6     0   0
DOROTHY      0   0    0    0    0    0    0    3   8    0    0   0     0   0
OLIVIA       0   0    0    0    0    0    0    0  10    0    3   0     0   0
FLORA        0   0    0    0    0    0    0    0  14    0    3   0     0   0
   knn.p <- rowSums(knn.p)/rowSums(A)
   round(knn.p, 2)
   EVELYN     LAURA   THERESA    BRENDA CHARLOTTE   FRANCES   ELEANOR     PEARL 
     6.62      6.14      7.00      4.71      5.50      7.75      7.75      7.67 
     RUTH     VERNE     MYRNA KATHERINE    SYLVIA      NORA     HELEN   DOROTHY 
     6.75      5.50      4.50      6.33      7.71      6.62      5.60      5.50 
   OLIVIA     FLORA 
     6.50      8.50 

We can see that for Flora, the average number of members of the groups she connects to is very high, while the opposite is the case for Myrna.

We can do the same for groups:

   knn.g <- t(A) * rowSums(A)
   knn.g
      EVELYN LAURA THERESA BRENDA CHARLOTTE FRANCES ELEANOR PEARL RUTH VERNE
6/27       8     5       0      4         0       0       0     0    0     0
3/2        7     2       6      0         0       0       0     0    0     0
4/12       8     2       7      4         4       8       0     0    0     0
9/26       7     0       8      4         4       0       0     0    0     0
2/25       4     8       5      4         4       8       2     0    4     0
5/19       4     7       2      6         0       7       2     8    0     0
3/15       0     8       2      7         4       0       8     0    4     4
9/16       3     7       2      8         0       4       7     2    6     3
4/8        4     0       8      0         0       0       0     2    7     4
6/10       0     0       0      0         0       0       0     0    0     0
2/23       0     0       0      0         0       0       0     0    0     0
4/7        0     0       0      0         0       0       0     0    0     6
11/21      0     0       0      0         0       0       0     0    0     0
8/3        0     0       0      0         0       0       0     0    0     0
      MYRNA KATHERINE SYLVIA NORA HELEN DOROTHY OLIVIA FLORA
6/27      0         0      0    0     0       0      0     0
3/2       0         0      0    0     0       0      0     0
4/12      0         0      0    0     0       0      0     0
9/26      0         0      0    0     0       0      0     0
2/25      0         0      0    0     0       0      0     0
5/19      0         0      0    3     0       0      0     0
3/15      0         0      7    4     4       0      0     0
9/16      7         2      8    0     4       7      0     0
4/8       4         8      5    4     0       8      2     7
6/10      4         7      2    6     3       0      0     0
2/23      0         0      0    7     4       0      8     5
4/7       3         7      2    8     4       0      0     0
11/21     0         4      8    5     0       0      0     0
8/3       0         4      7    2     0       0      0     0
   knn.g <- rowSums(knn.g)/colSums(A)
   round(knn.g, 2)
 6/27   3/2  4/12  9/26  2/25  5/19  3/15  9/16   4/8  6/10  2/23   4/7 11/21 
 5.67  5.00  5.50  5.75  4.88  4.88  5.20  5.00  5.25  4.40  6.00  5.00  5.67 
  8/3 
 4.33 

Degree Correlation

Finally, we can also compute the degree correlation between the nodes in each mode. This tell us whether people with more memberships connect to larger (positive correlation) or smaller (negative correlation) groups.

Here’s a function to compute the degree correlation in a two mode network from the bipartite matrix:

   tm.deg.corr <- function(x) {
      d <- data.frame(e = as.vector(x), 
                      rd = rep(rowSums(x), ncol(x)), 
                      cd = rep(colSums(x), each = nrow(x)),
                      rn = rep(rownames(x), ncol(x)),
                      cn = rep(colnames(x), each = nrow(x))
                      )
      r <- cor(d[d$e == 1, ]$rd, d[d$e == 1, ]$cd)
      return(list(r = r, d = d))
   }

The tm.deg.corr function creates a data frame set with as many rows as there are entries in the bipartite matrix, and three columns: e recording whether there is a one or a zero for that particular combination of nodes (line 2), rd recording the degree of that node (line 3), and cd recording other node (line 4); lines 5 and 6 record the node labels for that dyad. Then in line 8 the function computes the Pearson correlations between the degrees of persons and groups that are connected in the data frame (e.g., e = 1).

We can now apply our function to the SW data:

   tm.deg.corr(B)$r
[1] -0.3369979

Which tells us that there is degree anti-correlation in this network: People with more memberships tend to belong to smaller groups, and people with less memberships connect to bigger groups.

References

Borgatti, Stephen P, and Martin G Everett. 1997. “Network Analysis of 2-Mode Data.” Social Networks 19 (3): 243–69.
Brandes, Ulrik, Daniel Delling, Marco Gaertler, Robert Gorke, Martin Hoefer, Zoran Nikoloski, and Dorothea Wagner. 2007. “On Modularity Clustering.” IEEE Transactions on Knowledge and Data Engineering 20 (2): 172–88.
Breiger, Ronald L. 1974. “The Duality of Persons and Groups.” Social Forces 53 (2): 181–90.
Fouss, François, Marco Saerens, and Masashi Shimbo. 2016. Algorithms and Models for Network Data and Link Analysis. Cambridge University Press.
Newman, Mark EJ. 2006. “Finding Community Structure in Networks Using the Eigenvectors of Matrices.” Physical Review E—Statistical, Nonlinear, and Soft Matter Physics 74 (3): 036104.