Introduction

This document visualizes a signed network with both positive and negative relationships between actors involved in the Syrian Conflict. The visualization includes community detection, centrality metrics, and interactive features.

Data Loading and Preprocessing

# Load network data
network_data <- read.csv("~/SignedNetwork.csv")

# Create igraph object
g <- graph_from_data_frame(network_data, directed = FALSE)

# Add edge colors based on the sign
E(g)$color <- ifelse(network_data$Sign == "Positive", "green", "red")

# Assign weights for layout
E(g)$layout_weight <- ifelse(network_data$Sign == "Positive", 1, 0.5)

# Community detection
communities <- cluster_louvain(g)

# Calculate network metrics
betweenness_scores <- betweenness(g, normalized = TRUE)
closeness_scores <- closeness(g, normalized = TRUE)
eigenvector_scores <- eigen_centrality(g)$vector
degree_scores <- degree(g, normalized = TRUE)

Network Metrics Summary

# Create summary statistics
network_summary <- data.frame(
  Metric = c("Nodes", "Edges", "Density", "Communities", 
             "Positive Edges", "Negative Edges"),
  Value = c(
    vcount(g),
    ecount(g),
    graph.density(g),
    length(unique(membership(communities))),
    sum(E(g)$color == "green"),
    sum(E(g)$color == "red")
  )
)

# Display summary
knitr::kable(network_summary, caption = "Network Summary Statistics")
Network Summary Statistics
Metric Value
Nodes 194.0000000
Edges 439.0000000
Density 0.0234496
Communities 9.0000000
Positive Edges 214.0000000
Negative Edges 225.0000000

Network Visualization

# Prepare nodes dataframe
nodes <- unique(c(network_data$Actor.1, network_data$Actor.2))
nodes_df <- data.frame(
  id = nodes,
  label = nodes,
  stringsAsFactors = FALSE
)

# Create clean edge dataframe
edges_df <- network_data %>%
  mutate(
    from = Actor.1,
    to = Actor.2,
    # Create a consistent edge ID for undirected edges
    edge_id = apply(cbind(Actor.1, Actor.2), 1, function(x) paste(sort(x), collapse="--"))
  ) %>%
  distinct(edge_id, .keep_all = TRUE) %>%
  mutate(
    # Edge styling
    color = case_when(
      Sign == "Positive" ~ "#00FF00",  # Bright green
      Sign == "Negative" ~ "#FF0000",  # Bright red
      TRUE ~ "#CCCCCC"                 # Default gray
    ),
    # Edge width based on type
    width = case_when(
      Sign == "Positive" ~ 2,
      Sign == "Negative" ~ 1,
      TRUE ~ 1
    ),
    # Add edge labels
    label = Sign,
    # Add tooltips
    title = paste("From:", Actor.1, "<br>To:", Actor.2, "<br>Type:", Sign)
  ) %>%
  select(from, to, color, width, label, title)

# Define a color palette for communities
community_colors <- rainbow(length(unique(membership(communities))))

# Enhance nodes with metrics and styling
nodes_df <- nodes_df %>%
  mutate(
    # Add community group
    group = as.numeric(membership(communities)),
    # Calculate node sizes based on degree
    degree = degree(g),
    size = 10 + (degree/max(degree)) * 40,
    # Add node tooltips
    title = paste0(
      "Node: ", label, "<br>",
      "Degree: ", degree, "<br>",
      "Community: ", group
    ),
    # Assign colors based on community
    color = community_colors[group]
  )

# Create the visualization
visNetwork(nodes_df, edges_df, width = "100%", height = "600px") %>%
  visIgraphLayout(layout = "layout_with_fr") %>%
  visOptions(
    highlightNearest = list(enabled = TRUE, degree = 1),
    selectedBy = list(variable = "group", multiple = TRUE),
    manipulation = FALSE
  ) %>%
  visLayout(randomSeed = 123) %>%
  visPhysics(
    solver = "forceAtlas2Based",
    forceAtlas2Based = list(
      gravitationalConstant = -100,
      centralGravity = 0.01,
      springLength = 150,
      springConstant = 0.08,
      damping = 0.4,
      avoidOverlap = 0.5
    ),
    stabilization = list(
      enabled = TRUE,
      iterations = 1000
    )
  ) %>%
  visInteraction(
    hideEdgesOnDrag = TRUE,
    hover = TRUE
  ) %>%
  visLegend(
    addNodes = data.frame(
      label = paste("Community", sort(unique(nodes_df$group))),
      shape = "dot",
      color = community_colors[sort(unique(nodes_df$group))]
    ),
    addEdges = data.frame(
      label = c("Positive", "Negative"),
      color = c("#00FF00", "#FF0000")
    ),
    useGroups = FALSE,
    position = "left",
    width = 0.1,
    zoom = FALSE
  )

Community Analysis

# Analyze communities
community_summary <- data.frame(
  Community = 1:max(nodes_df$group),
  Size = as.numeric(table(nodes_df$group))
) %>%
  mutate(Percentage = round(Size / sum(Size) * 100, 2))

# Display community summary
knitr::kable(community_summary, 
             caption = "Community Size Distribution",
             col.names = c("Community", "Number of Nodes", "% of Network"))
Community Size Distribution
Community Number of Nodes % of Network
1 24 12.37
2 48 24.74
3 28 14.43
4 11 5.67
5 27 13.92
6 29 14.95
7 24 12.37
8 1 0.52
9 2 1.03
# Visualize community sizes
ggplot(community_summary, aes(x = factor(Community), y = Size)) +
  geom_bar(stat = "identity", fill = "steelblue") +
  theme_minimal() +
  labs(
    title = "Community Size Distribution",
    x = "Community ID",
    y = "Number of Nodes"
  )

Centrality Analysis

# First, add all centrality metrics to nodes_df if not already present
nodes_df <- nodes_df %>%
  mutate(
    betweenness = betweenness_scores[id],
    closeness = closeness_scores[id],
    eigenvector = eigenvector_scores[id],
    value = degree_scores[id]
  )

# Create centrality summary
centrality_summary <- nodes_df %>%
  select(label, betweenness, closeness, eigenvector, value) %>%
  rename(
    Node = label,
    Betweenness = betweenness,
    Closeness = closeness,
    Eigenvector = eigenvector,
    Degree = value
  ) %>%
  arrange(desc(Betweenness)) %>%
  head(10)

# Display top 10 nodes by betweenness centrality
knitr::kable(centrality_summary,
             caption = "Top 10 Nodes by Centrality Metrics",
             digits = 3)
Top 10 Nodes by Centrality Metrics
Node Betweenness Closeness Eigenvector Degree
Syrian Government 0.464 0.571 1.000 0.482
Turkish Government 0.207 0.529 0.920 0.228
Peshmerga 0.176 0.428 0.354 0.155
HTS (Hayat Tahrir al-Sham) 0.145 0.515 0.868 0.264
Hezbollah 0.142 0.482 0.863 0.207
SDF (Syrian Democratic Forces) 0.078 0.497 0.841 0.187
Russian Forces 0.069 0.492 0.826 0.181
FSA (Free Syrian Army) 0.057 0.487 0.663 0.124
USA 0.053 0.442 0.650 0.150
Qatar 0.040 0.415 0.149 0.041

Network Density by Community

# Calculate density within communities
community_density <- sapply(1:max(nodes_df$group), function(comm) {
  nodes_in_comm <- nodes_df$id[nodes_df$group == comm]
  subg <- induced_subgraph(g, nodes_in_comm)
  graph.density(subg)
})

density_df <- data.frame(
  Community = 1:length(community_density),
  Density = community_density
)

# Visualize community densities
ggplot(density_df, aes(x = factor(Community), y = Density)) +
  geom_bar(stat = "identity", fill = "darkgreen") +
  theme_minimal() +
  labs(
    title = "Network Density by Community",
    x = "Community ID",
    y = "Density"
  )