convert directed affiliation matrix to edge list

162 views Asked by At

I have a directed affiliation matrix which I want to convert to an edge list. The matrix looks like this:

State   WarID    Initiator
A        1       1
B        1       0
A        2       1
C        2       0
D        2       0
B        3       1
C        3       1
D        3       0

where "State" is the name of a country, "WarID" is a unique identifier for war, and "Initiator" is a dummy variable which equals 1 if the state initiated the war. There is an edge between two states if they share the same "WarID" but have different value of "Initiator."

I want to change the affiliation matrix above into an edge list like this:

Initiator   Target  WarID
A              B    1
A              C    2
A              D    2
B              D    3
C              D    3

I know how to change a basic affiliation matrix into an edge list, but I struggled with keeping the "directed network" component. I'll be very grateful if someone could tell me how to do this in R efficiently (I have a pretty large affiliation matrix).

3

There are 3 answers

4
jay.sf On BEST ANSWER

You could group the data by WarID and Initiator using tapply and make an expand.grid for each WarID. Just rbind the results.

FUN <- function(d) {
  r <- with(d, tapply(State, list(WarID, Initiator), I))
  r <- lapply(1:nrow(r), function(i) cbind(expand.grid(rev(r[i, ])), i))
  r <- setNames(do.call(rbind, r), c("Initiator", "Target", "WarID"))
  r
}
FUN(d)
#   Initiator Target WarID
# 1         A      B     1
# 2         A      C     2
# 3         A      D     2
# 4         B      D     3
# 5         C      D     3

Notice that I used consecutive WarIDs as specified by you.


Data:

d <- structure(list(State = c("A", "B", "A", "C", "D", "B", "C", "D"
), WarID = c(1L, 1L, 2L, 2L, 2L, 3L, 3L, 3L), Initiator = c(1L, 
0L, 1L, 0L, 0L, 1L, 1L, 0L)), class = "data.frame", row.names = c(NA, 
-8L))
0
Karthik S On

Does this work:

> library(dplyr)
> df %>% group_by(WarID) %>% filter(Initiator == 1) %>% 
+   inner_join(df %>% group_by(WarID) %>% filter(Initiator == 0), by = ('WarID')) %>% rename(Target = State.y, Initiator = State.x ) %>% 
+   select(1,4,2)
# A tibble: 5 x 3
# Groups:   WarID [3]
  Initiator Target WarID
  <chr>     <chr>  <dbl>
1 A         B          1
2 A         C          2
3 A         D          2
4 B         D          3
5 C         D          3
> 

Data used:

> dput(df)
structure(list(State = c("A", "B", "A", "C", "D", "B", "C", "D"
), WarID = c(1, 1, 2, 2, 2, 3, 3, 3), Initiator = c(1, 0, 1, 
0, 0, 1, 1, 0)), class = c("spec_tbl_df", "tbl_df", "tbl", "data.frame"
), row.names = c(NA, -8L), spec = structure(list(cols = list(
    State = structure(list(), class = c("collector_character", 
    "collector")), WarID = structure(list(), class = c("collector_double", 
    "collector")), Initiator = structure(list(), class = c("collector_double", 
    "collector"))), default = structure(list(), class = c("collector_guess", 
"collector")), skip = 1), class = "col_spec"))
> 
2
Onyambu On

Using tidyverse you could do:

library(tidyverse)
df %>%
   group_by(WarID) %>%
   summarise(Target = list(State[Initiator==0]),
             Initiator = list(State[Initiator==1]), .groups='drop') %>% 
   unnest(c(Initiator, Target)) %>%
   rev() # Just to reverse the ordering, otherwise not necessary 

    # A tibble: 5 x 3
  Initiator Target WarID
  <chr>     <chr>  <int>
1 A         B          1
2 A         C          2
3 A         D          2
4 B         D          3
5 C         D          3