Анализ сетевых данных «Камер-фурьерского журнала» Ходасевича

Автор

Елена Тарбокова

Дата публикации

20.03.2025

Задачей для данной лабораторной работы является анализ сетевых данных с использованием различных инструментов и алгоритмов. Данное домашнее задание будет проведено на материале Камер-фурьерского журнала Ходасевича.

Данные собраны по материалам за 1927 год.

1 Подготовка и сбор данных

Загрузим все необходимые библиотеки и обобщим материал за 12 месяцев. Сделаем это с помощью регулярных выражений, функции, а затем для удобства суммируем данные для каждой пары взаимосвязей.

library(rgexf)
library(igraph)
library(dplyr)
library(visNetwork)
library(ggraph)
library(paletteer)

files <- list.files(pattern = "\\.gexf$", full.names = TRUE) 
merged <- tibble(from_name = character(), to_name = character(), weight = character())

all_docs <- function(doc){
  doc1 <- read.gexf(doc) |>
    gexf.to.igraph() |>
    as_long_data_frame () |>
    select(from_name, to_name, weight)
  merged <<-  merge(doc1, merged, all=TRUE)
}

for (doc in files){all_docs(doc)}

summarised_data <- merged |>
  group_by(from_name, to_name) |>
  summarize(weight = sum(weight))

summarised_data
# A tibble: 1,281 × 3
# Groups:   from_name [131]
   from_name to_name  weight
   <chr>     <chr>     <dbl>
 1 Адамович  Алданов       3
 2 Адамович  Алданова      1
 3 Адамович  Бахрах        1
 4 Адамович  Бахтин        4
 5 Адамович  Блох_А        1
 6 Адамович  Бунин         3
 7 Адамович  Бунина        2
 8 Адамович  Вейдле        3
 9 Адамович  Вишняк        1
10 Адамович  Вишнячка      1
# ℹ 1,271 more rows

2 Построение и описание графа

Теперь получившийся тиббл преобразуем в граф.

graph <- graph_from_data_frame(summarised_data)
graph
IGRAPH b920ff2 DNW- 151 1281 -- 
+ attr: name (v/c), weight (e/n)
+ edges from b920ff2 (vertex names):
 [1] Адамович->Алданов    Адамович->Алданова   Адамович->Бахрах    
 [4] Адамович->Бахтин     Адамович->Блох_А     Адамович->Бунин     
 [7] Адамович->Бунина     Адамович->Вейдле     Адамович->Вишняк    
[10] Адамович->Вишнячка   Адамович->Волконский Адамович->Гиппиус   
[13] Адамович->Горный     Адамович->Демидов    Адамович->Женя      
[16] Адамович->Зайцев_Б   Адамович->Зайцева_В  Адамович->Иванов    
[19] Адамович->Кантор     Адамович->Каплун     Адамович->Карякин   
[22] Адамович->Кнут       Адамович->Ладинский  Адамович->Маковский 
+ ... omitted several edges

Данный граф является:

  • Ориентированным: так как ребра имеют направление

  • Взвешенным: так как у ребер есть атрибут weight

  • Именованным: так как у узлов есть атрибут name

У графа 151 узел и 1281 ребро.

Для вычисления числа компонент используем функцию components и также посмотрим, какие узлы не связаны с основной компонентой.

components(graph)$no
[1] 2
components(graph)$csize
[1] 148   3
which(components(graph)$membership !=1)
Вяземский   Завриев  Струве_М 
       27        43       145 

Итого: 2 компоненты, 148 узлов – в первой, 3 – во второй.

Вычислим плотность графа.

edge_density(graph)
[1] 0.05655629

Диаметр сети.

lgc <- largest_component(graph)
diameter(lgc, directed = TRUE)
[1] 14

И транзитивность (т.е. тенденция к замыканию).

transitivity(graph)
[1] 0.5193865

Посчитаем общую центральность по степени, определяему количеством связей, и взвешенную центральность ребер для узла “Адамович”.

wDegree <- strength(graph)
sort(wDegree, decreasing = T)[1:10]
Мережковский      Гиппиус       Иванов       Цетлин         Фохт    Терапиано 
         245          224          209          202          166          143 
       Бунин    Одоевцева         Кнут      Алданов 
         137          137          132          128 
idx <- incident(graph, "Адамович")


sum(E(graph)[idx]$weight)
[1] 112

Теперь, осмыслив то, что в теории представляет собой наш граф, можем наконец посмотреть на него наглядно.

set.seed(123)
nodes <- data.frame(id = V(graph)$name, label = V(graph)$name)
edges <- get.data.frame(graph, what = "edges")

edges$width <- edges$weight * 2 
edges$color <- ifelse(edges$weight > quantile(edges$weight, 0.75), "red", "gray")


nodes$value <- degree(graph)  
nodes$color <- ifelse(nodes$value > quantile(nodes$value, 0.75), "orange", "lightblue")


visNetwork(nodes, edges) |>
  visPhysics(
    stabilization = TRUE,
    solver = "barnesHut",
    barnesHut = list(
      gravitationalConstant = -2000,
      centralGravity = 0.3,
      springLength = 95,
      springConstant = 0.04,
      damping = 0.09,
      avoidOverlap = 0.1
    )
  ) |>
  visIgraphLayout(layout = "layout_with_fr") |>
  visOptions(highlightNearest = TRUE, nodesIdSelection = TRUE) |>
  visLegend()

С помощью VisNetwork можно сделать акцент на более плотных связях (например, увеличить размер ребер в зависимости от их веса, а также использовать разную цветовую палитру).

Получается немного аляпистая и хаотичная картинка, но отражает плотность сети.

3 Построение подграфа

Метод: Ego-граф

В нашем графе есть узлы, представляющие людей, соответственно, сеть в некотором смысле представляет собой моделирование человеческих вазимоотношений. Поэтому кажется удобным взять ego-graph, который формирует взаимосвязи вокруг одного определенного интересного нам узла в качестве подграфа и выбрать один из таких узлов (например, “Адамович”), чтобы изучить его непосредственное окружение.

set.seed(123)
ego_node <- "Адамович"
ego_graph <- make_ego_graph(graph, order = 1, nodes = ego_node, mode = "out")[[1]]

ego_nodes <- data.frame(id = V(ego_graph)$name, label = V(ego_graph)$name)
ego_edges <- get.data.frame(ego_graph, what = "edges")


visNetwork(ego_nodes, ego_edges) |>
  visPhysics(
    stabilization = TRUE,
    solver = "barnesHut",
    barnesHut = list(
      gravitationalConstant = -2000,
      centralGravity = 0.3,
      springLength = 95,
      springConstant = 0.04,
      damping = 0.09,
      avoidOverlap = 0.1
    )
  ) |>
  visIgraphLayout(layout = "layout_with_fr") |>
  visOptions(highlightNearest = TRUE, nodesIdSelection = TRUE) |>
  visLegend()

Итого, рядом с Адамовичем узлы Иванов, Познер, Алданов, Одоевцева, Мережковский, Вейдле и др. На довольно приметной дистанции оказался Ховин. В статье Б.В. Орехова “Цифровые подходы к «Камер-фурьерскому журналу» В. Ф. Ходасевича” описывается интересная особенность связей Адамовича, который, будучи представителем “старшего” поколения, имел тендецию сближения с кругом более молодых пистелей, поэтов и художников, став, своего рода, “литературным авторитетом” для юных творцов в эмиграции. Некоторых из них мы можем наблюдать на графе, а также Иванова – наставника, близкого к молодому поколению, из круга “старших”.

4 Модулярность и обнаружение сообществ

Теперь попробуем несколько алгоритмов обнаружения сообществ.

Применим алгоритм “случайного блуждания”.

set.seed(123)
cw <- cluster_walktrap(graph)
membership(cw) |> head()
Адамович  Алданов Алданова  Аминадо Апостолы      Ася 
       5        3        3        3        3        3 
set.seed(123)
par(mar = rep(0, 4))
plot(cw, graph)

Без дополнительных настроек выглядит довольно грустно.

Попробуем алгоритм “главный собственный вектор” и немножко его подкрутим.

set.seed(123)
cev <- cluster_leading_eigen(graph)
modularity(cev)
[1] 0.2871387

Модулярность 0.287 указывает на умеренную структуру сообществ в графе. Это означает, что сообщества существуют, но они не сильно изолированы друг от друга, а также отражает сложные взаимодействия между группами, где ключевые фигуры связывают разные группы людей.

Посмотрим, так ли это выглядит.

set.seed(123)

par(mar = rep(0, 4))
plot(cev, graph,
     vertex.size = 8, 
     vertex.label.cex = 0.8, 
     vertex.label.dist = 1.5,
     vertex.label.color = "black",
     edge.arrow.size = 0.3,
      avoidOverlap = 0.1)

Как и ожидалось, плотность сети, к сожалению, слишком высокая, что мешает восприяютию отдельных узлов даже с дополнительными настройками, однако можно различить несколько выделяющихся групп. Например, очень четко выделяются Завриев, Струве, Вяземский.

Зато изолированность сообществ действительно невелика.

5 Анализ ключевых узлов / структур

Попробуем вычислить точки сочленения.

articulation_points(graph)
+ 13/151 vertices, named, from b920ff2:
 [1] Нина         Рысс         Поволоцкий   Терапиано    Раевский    
 [6] Мережковский Маковский    Женя         Берберов_Л   Вейдле      
[11] Алданов      Адамович     Струве_М    

В нашем случае это 13 точек. Таким образом, мы можем видеть, что 13 перечисленных персон играют важную роль в поддержании структуры сети, и их удаление может привести к ее фрагментированию.

Теперь посчитаем клики.

cliques(graph, min=20)[1:3]
[[1]]
+ 20/151 vertices, named, from b920ff2:
 [1] Адамович     Алданов      Бахрах       Бунин        Бунина      
 [6] Вишняк       Зайцев_Б     Зайцева_В    Иванов       Кнут        
[11] Нидермиллер  Осоргин      Познер       Познер_С     Скрябина    
[16] Степун       Фохт         Цетлин       Цетлина      жена_Степуна

[[2]]
+ 20/151 vertices, named, from b920ff2:
 [1] Адамович     Бахрах       Бунин        Бунина       Вишняк      
 [6] Зайцев_Б     Зайцева_В    Иванов       Кнут         Наташа      
[11] Нидермиллер  Осоргин      Познер       Познер_С     Скрябина    
[16] Степун       Фохт         Цетлин       Цетлина      жена_Степуна

[[3]]
+ 21/151 vertices, named, from b920ff2:
 [1] Адамович     Алданов      Бахрах       Бунин        Бунина      
 [6] Вишняк       Зайцев_Б     Зайцева_В    Иванов       Кнут        
[11] Наташа       Нидермиллер  Осоргин      Познер       Познер_С    
[16] Скрябина     Степун       Фохт         Цетлин       Цетлина     
[21] жена_Степуна
clique_num(graph)
[1] 23

Размер самой большой клики для сети – 23 узла. Можно посмотреть, что это за узлы.

largest_cliques(graph)
[[1]]
+ 23/151 vertices, named, from b920ff2:
 [1] Цетлина      Адамович     жена_Степуна Цетлин       Фохт        
 [6] Степун       Скрябина     Познер_С     Познер       Осоргин     
[11] Нидермиллер  Наташа       Кнут         Карякин      Иванов      
[16] Зайцева_В    Зайцев_Б     Женя         Вишняк       Бунина      
[21] Бунин        Бахрах       Алданов     

6 Вывод

Таким образом, анализ социальных сетей и графов позволяет нам выявить ключевые фигуры и их роль в сети, обнаружить взаимодействия отдельных сообществ, понять, как исторические события повлияли на структуру сети и визуализировать связи для наглядности.

Сеть может послужить своеобразной “картой” интеллектуальной жизни эпохи, показывая, как люди и идеи перемещались между группами и странами.