The signnet package brings together methods that have been developed to analyse signed networks. This includes:
The package includes two well known datasets:
- the tribes dataset is a signed social network of tribes of the Gahuku–Gama alliance structure of the Eastern Central Highlands of New Guinea. The network contains sixteen tribes connected by friendship (“rova”) and enmity (“hina”)
- the cowList dataset contains a list of 52 signed networks of inter-state relations over time (1946-1999). Two countries are connected by a positive tie if they form an alliance or have a peace treaty. A negative tie exists between countries who are at war or in other kinds of conflicts
library(igraph)
library(signnet)
library(ggplot2)
# generate a balanced signed network with two sets of 10 nodes
g = sample_islands_signed(islands.n = 2, islands.size = 10,
islands.pin = 0.8, n.inter = 5)
# visualize network
ggsigned(g)

# count signed triangles
count_signed_triangles(g)
## +++ ++- +-- ---
## 106 0 8 0
# print signed tringles (P is the number of positive signs)
head(signed_triangles(g))
## V1 V2 V3 P
## [1,] 10 1 2 3
## [2,] 10 1 5 3
## [3,] 10 1 6 3
## [4,] 10 1 7 3
## [5,] 10 1 8 3
## [6,] 10 1 9 3
# recall g is balanced
balance_score(g, method = "triangles")
## [1] 1
balance_score(g, method = "walk")
## [1] 1
# Note that the problem is NP complete and only a lower bound
# of the index is returned (based on simulated annealing,
# which is not deterministic)
balance_score(g, method = "frustration")
## [1] 0.8
# use tribes dataset
data("tribes")
## visualize graph
ggsigned(tribes)

# adj matrix
as_adj_signed(tribes)[1:5,1:5]
## Gavev Kotun Ove Alika Nagam
## Gavev 0 1 -1 -1 -1
## Kotun 1 0 -1 0 -1
## Ove -1 -1 0 1 0
## Alika -1 0 1 0 0
## Nagam -1 -1 0 0 0
# find signed triangles
count_signed_triangles(tribes)
## +++ ++- +-- ---
## 19 2 40 7
# degree of balance
balance_score(tribes, method = "triangles")
## [1] 0.8676471
balance_score(tribes, method = "walk")
## [1] 0.3575761
balance_score(tribes, method = "frustration")
## [1] 0.7586207
# number of friends
degree_signed(tribes, type="pos")
## Gavev Kotun Ove Alika Nagam Gahuk Masil Ukudz Notoh Kohik Geham Asaro Uheto
## 3 3 4 2 3 5 7 6 3 2 4 4 4
## Seuve Nagad Gama
## 2 3 3
# number of enemies
degree_signed(tribes, type="neg")
## Gavev Kotun Ove Alika Nagam Gahuk Masil Ukudz Notoh Kohik Geham Asaro Uheto
## 5 5 2 1 4 5 0 1 4 3 5 4 4
## Seuve Nagad Gama
## 3 6 6
# number of friends - number of enemies
degree_signed(tribes, type="net")
## Gavev Kotun Ove Alika Nagam Gahuk Masil Ukudz Notoh Kohik Geham Asaro Uheto
## -2 -2 2 1 -1 0 7 5 -1 -1 -1 0 0
## Seuve Nagad Gama
## -1 -3 -3
# number of friends / (number of friends + number of enemies)
degree_signed(tribes, type="ratio")
## Gavev Kotun Ove Alika Nagam Gahuk Masil Ukudz
## 0.3750000 0.3750000 0.6666667 0.6666667 0.4285714 0.5000000 1.0000000 0.8571429
## Notoh Kohik Geham Asaro Uheto Seuve Nagad Gama
## 0.4285714 0.4000000 0.4444444 0.5000000 0.5000000 0.4000000 0.3333333 0.3333333
# signed eigenvector centrality
# the function returns an error if the adjacency matrix has not a unique dominant eigenvalue
eig_cent_signed = eigen_centrality_signed(tribes)
names(eig_cent_signed) = V(tribes)$name
eig_cent_signed
## Gavev Kotun Ove Alika Nagam Gahuk Masil
## 1.00000000 0.88879594 0.71609511 0.36818769 0.74303157 0.95454966 0.76017717
## Ukudz Notoh Kohik Geham Asaro Uheto Seuve
## 0.67100494 0.21063522 0.23890028 0.69639725 0.91352983 0.23390770 0.05855799
## Nagad Gama
## 0.91193923 0.98724909
# strange results if compared with degree. Why?
deg_signed = degree_signed(tribes, type="net")
sort(deg_signed, decreasing = TRUE)
## Masil Ukudz Ove Alika Gahuk Asaro Uheto Nagam Notoh Kohik Geham Seuve Gavev
## 7 5 2 1 0 0 0 -1 -1 -1 -1 -1 -2
## Kotun Nagad Gama
## -2 -3 -3
sort(eig_cent_signed, decreasing = TRUE)
## Gavev Gama Gahuk Asaro Nagad Kotun Masil
## 1.00000000 0.98724909 0.95454966 0.91352983 0.91193923 0.88879594 0.76017717
## Nagam Ove Geham Ukudz Alika Kohik Uheto
## 0.74303157 0.71609511 0.69639725 0.67100494 0.36818769 0.23890028 0.23390770
## Notoh Seuve
## 0.21063522 0.05855799
cor(deg_signed, eig_cent_signed)
## [1] -0.07325702
# let's investigate further. Compute the leading eigenvector of the adj matrix manually
A = as_adj_signed(tribes)
eig = eigen(A)
max_val = which.max(abs(eig$values))
eig_cent_signed_manual = eig$vectors[, max_val]
names(eig_cent_signed_manual) = V(tribes)$name
# the manual and library vectors are quite different.
# Notice eig_cent_signed (library version) is fully positive and normalized
sort(eig_cent_signed, decreasing = TRUE)
## Gavev Gama Gahuk Asaro Nagad Kotun Masil
## 1.00000000 0.98724909 0.95454966 0.91352983 0.91193923 0.88879594 0.76017717
## Nagam Ove Geham Ukudz Alika Kohik Uheto
## 0.74303157 0.71609511 0.69639725 0.67100494 0.36818769 0.23890028 0.23390770
## Notoh Seuve
## 0.21063522 0.05855799
sort(eig_cent_signed_manual, decreasing = TRUE)
## Gahuk Asaro Masil Nagam Ove Geham
## 0.33303726 0.31872566 0.26522174 0.25923973 0.24984174 0.24296927
## Ukudz Alika Kohik Uheto Notoh Seuve
## 0.23411002 0.12845871 0.08335103 0.08160914 0.07348950 0.02043057
## Kotun Nagad Gama Gavev
## -0.31009614 -0.31817071 -0.34444591 -0.34889464
# in fact function eigen_centrality_signed took the absolute value of the resulting vector
# and then normalized by the maximim value. But this destroys the signs
x = abs(eig_cent_signed_manual)
x = x / max(x)
sort(x, decreasing = TRUE)
## Gavev Gama Gahuk Asaro Nagad Kotun Masil
## 1.00000000 0.98724909 0.95454966 0.91352983 0.91193923 0.88879594 0.76017717
## Nagam Ove Geham Ukudz Alika Kohik Uheto
## 0.74303157 0.71609511 0.69639725 0.67100494 0.36818769 0.23890028 0.23390770
## Notoh Seuve
## 0.21063522 0.05855799
sort(eig_cent_signed, decreasing = TRUE)
## Gavev Gama Gahuk Asaro Nagad Kotun Masil
## 1.00000000 0.98724909 0.95454966 0.91352983 0.91193923 0.88879594 0.76017717
## Nagam Ove Geham Ukudz Alika Kohik Uheto
## 0.74303157 0.71609511 0.69639725 0.67100494 0.36818769 0.23890028 0.23390770
## Notoh Seuve
## 0.21063522 0.05855799
# the manual version is now correlated with net degree.
cor(deg_signed, eig_cent_signed_manual)
## [1] 0.6173092
plot(deg_signed, eig_cent_signed_manual)

# End of the story: either use the manual version or install the development version
# at https://github.com/schochastics/signnet which in the meantime has been updated
# a graph with two dominant eigenvalues
# all nodes have two friends and two foes
A = matrix(c( 0, 1, 1, -1, 0, 0, -1, 0, 0,
1, 0, 1, 0, -1, 0, 0, -1, 0,
1, 1, 0, 0, 0, -1, 0, 0, -1,
-1, 0, 0, 0, 1, 1, -1, 0, 0,
0, -1, 0, 1, 0, 1, 0, -1, 0,
0, 0, -1, 1, 1, 0, 0, 0, -1,
-1, 0, 0, -1, 0, 0, 0, 1, 1,
0, -1, 0, 0, -1, 0, 1, 0, 1,
0, 0, -1, 0, 0, -1, 1, 1, 0), 9, 9)
g = graph_from_adjacency_matrix(A, "undirected", weighted = "sign")
plot(g, edge.label = E(g)$sign)

# eigenvalues
eig = eigen(A)
round(eig$values, 6)
## [1] 3 3 0 0 0 0 0 -3 -3
# we have 4 possible independent rankings!
M = eig$vectors
M = M[, c(1,2,8,9)]
round(M, 6)
## [,1] [,2] [,3] [,4]
## [1,] -0.471405 0.000000 0.000000 0.471405
## [2,] -0.471405 0.000000 -0.408248 -0.235702
## [3,] -0.471405 0.000000 0.408248 -0.235702
## [4,] 0.235702 -0.408248 0.000000 0.471405
## [5,] 0.235702 -0.408248 -0.408248 -0.235702
## [6,] 0.235702 -0.408248 0.408248 -0.235702
## [7,] 0.235702 0.408248 0.000000 0.471405
## [8,] 0.235702 0.408248 -0.408248 -0.235702
## [9,] 0.235702 0.408248 0.408248 -0.235702
round(cor(M), 6)
## [,1] [,2] [,3] [,4]
## [1,] 1 0 0 0
## [2,] 0 1 0 0
## [3,] 0 0 1 0
## [4,] 0 0 0 1
# returns an error
#eigen_centrality_signed(g)