statnet / ergm

Fit, Simulate and Diagnose Exponential-Family Models for Networks
Other
94 stars 36 forks source link

Unexpected behavior of F() term #546

Closed benrosche closed 6 months ago

benrosche commented 6 months ago

Dear statnet developers,

I hope this is a bug and not me not understanding the F() term properly.

Here is a small network with three 3 edges between 3 nodes that have one attribute, socioeconomic status (ses), corresponding to their node id:

library(ergm)

a <- matrix(c(0,1,1,0,0,0,1,0,0), ncol=3, nrow=3, byrow=T)
g <- network(a, vertex.attr=data.frame(ses=c(1,2,3))) 
# network with one attribute: socioeconomic status (ses)
# vertex name corresponds to ses value

plot(g, displaylabels=T)

Using the F() term, I want to filter how many edges that bridge between socioeconomic groups (xses ties) are reciprocated. I also want to differentiate xses ties that connect lower->higher and edges that connect higher->lower. For that, I create the following mixing matrices:

# Mixing matrices 
mm_xses <- matrix(c(NA,"xses","xses","xses",NA,"xses","xses","xses",NA), ncol=3, byrow=T)
mm_xses.up <- matrix(c(NA,"up","up",NA,NA,"up",NA,NA,NA), ncol=3, byrow=T)
mm_xses.down <- matrix(c(NA,NA,NA,"down",NA,NA,"down","down",NA), ncol=3, byrow=T)

Looking at the edge counts, nodemix('ses', levels2=mm_xses)+nodemix('ses', levels2=mm_xses.up)+nodemix('ses', levels2=mm_xses.down) correctly counts that all three edges are xses ties, 2 go up and 1 goes down.

Strange is that, when I look at how many of those ties are reciprocated, F(~mutual, ~nodemix('ses', levels2=mm_xses)) correctly counts that 1 of them is reciprocated. However, F(~mutual, ~nodemix('ses', levels2=mm_xses.up))+F(~mutual, ~nodemix('ses', levels2=mm_xses.down)) both return 0 even though one of them should be 1, no?

Is there a better way to count how many ties that connect lower to higher nodes are reciprocated?

summary(
  g ~
    edges + # 3 edges
    nodemix('ses', levels2=mm_xses) + # 3 xses ties
    nodemix('ses', levels2=mm_xses.up) + # 2 xses ties that go up
    nodemix('ses', levels2=mm_xses.down) + # 1 xses tie that goes down
    F(~mutual, ~nodemix('ses', levels2=mm_xses)) + # 1 xses tie that is reciprocated
    F(~mutual, ~nodemix('ses', levels2=mm_xses.up)) + # 0 ?
    F(~mutual, ~nodemix('ses', levels2=mm_xses.down)) # 0 ?
)

Thank you very much and best wishes, Ben

krivit commented 6 months ago

Strange is that, when I look at how many of those ties are reciprocated, F(~mutual, ~nodemix('ses', levels2=mm_xses)) correctly counts that 1 of them is reciprocated. However, F(~mutual, ~nodemix('ses', levels2=mm_xses.up))+F(~mutual, ~nodemix('ses', levels2=mm_xses.down)) both return 0 even though one of them should be 1, no?

I think this is working as intended. mm_xses.up keeps only ties going up (or equal) on SES, and mm_xses.down only those going down (or equal). This means that if two nodes have differing SES, F() will drop ties in one of the directions, so mutuality is only possible if the SES of the two nodes is equal.

Since reciprocating ties are symmetric between the two nodes, which is up and which is down is not meaningful in the first place.

Is there a better way to count how many ties that connect lower to higher nodes are reciprocated?

You don't actually need the matrices:

# Going "up"
summary(g~F(~edges, ~diff("ses", dir="h-t")>0))
# Going "down"
summary(g~F(~edges, ~diff("ses", dir="h-t")<0))
# Reciprocated across SES
summary(g~F(~mutual, ~absdiff("ses")>0))