Анализ сетевых сообществ персонажей романа ‘Война и мир’

Автор

Линев Никита

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

20 марта 2026 г.

Cоциальная сеть персонажей романа Л. Н. Толстого «Война и мир».

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


Сначала импортируем библиотеки

library(xml2)
library(dplyr)
library(purrr)
library(tidyr)
library(igraph)
library(ggraph)
library(ggplot2)

Парсим XML

doc <- read_xml("War_and_Peace.xml")
doc <- xml_ns_strip(doc) #избавляемся от проблем с namespace

volumes <- xml_find_all(doc, ".//div[@type='volume'][@n='1' or @n='2']")
chapters <- xml_find_all(volumes, ".//div[@type='chapter']")

Теперь надо извлечь всех персонажей и их связи из романа

get_characters <- function(chapter) {
  
  # персонажи из rs
  rs_nodes <- xml_find_all(chapter, ".//rs[@ref]")
  rs_chars <- xml_attr(rs_nodes, "ref")
  
  # персонажи из said
  said_nodes <- xml_find_all(chapter, ".//said[@who]")
  said_chars <- xml_attr(said_nodes, "who")
  
  # объединяем и чистим
  chars <- unique(c(rs_chars, said_chars))
  chars <- chars[!is.na(chars)]
  
  return(chars)
}

chapter_characters <- map(chapters, get_characters)

Далее, с помощью следующей функции получим непосредственно ПАРЫ

get_edges <- function(characters) {
  if (length(characters) < 2) return(NULL)
  
  combn(characters, 2, simplify = FALSE) |> 
    map(~ tibble(
      source = .x[1],
      target = .x[2]
    )) |> 
    bind_rows()
}

Наконец, посчитаем, сколько раз встречается каждая пара в тексте

edges_list <- map(chapter_characters, get_edges)

edges <- bind_rows(edges_list)

edges_weighted <- edges_list |>
  bind_rows() |>
  group_by(source, target) |>
  summarise(weight = n(), .groups = "drop")

Теперь займемся построением графов и посчитаем основные характеристики

g <- graph_from_data_frame(
  edges_weighted,
  directed = FALSE
)

num_nodes <- vcount(g)
num_edges <- ecount(g)
density <- edge_density(g)

components <- components(g)
num_components <- components$no

Итак, на основе извлечённых данных мы построили неориентированный граф с помощью пакета igraph. Узлы графа представляют персонажей романа, а рёбра — факт их совместного появления в одной главе текста. Вес ребра отражает количество глав, в которых персонажи встречаются вместе.

Построенный граф включает 256 узлов и 2340 рёбер, отражающих совместные появления персонажей в главах. Плотность графа составляет 0.072, что указывает на достаточно плотную сеть взаимодействий. Граф состоит из 2 компонент связности, что, видимо, свидетельствует о высокой связности структуры и наличии одной доминирующей компоненты.


На следующем этапе мы рассчитаем показатели центральности. Предполагаем, что персонажи с наибольшей степенью являются наиболее социально активными, тогда как персонажи с высоким посредничеством выступают в роли «мостов» между различными группами.

# степень
V(g)$degree <- degree(g)
V(g)$degree_scaled <- scale(V(g)$degree)

# посредничество
V(g)$betweenness <- betweenness(g, normalized = TRUE)

# близость
V(g)$closeness <- closeness(g, normalized = TRUE)

as_data_frame(g, what = "vertices") |>
  arrange(desc(degree)) |>
  head(10)
                                                     name degree degree_scaled
AndreyBolkonsky                           AndreyBolkonsky    210      5.796512
Tsar_Alexander_I_of_Russia     Tsar_Alexander_I_of_Russia    193      5.264107
Nikolai_Rostov                             Nikolai_Rostov    160      4.230615
Pierre_Bezukhov                           Pierre_Bezukhov    135      3.447666
NatashaRostova                             NatashaRostova    133      3.385030
Mikhail_Ilarionovich_Kutuzov Mikhail_Ilarionovich_Kutuzov    131      3.322394
Fedor_Ivanovich_Dolokhov         Fedor_Ivanovich_Dolokhov    130      3.291076
Napoleon_Bonaparte                     Napoleon_Bonaparte    130      3.291076
Sonya_Rostova                               Sonya_Rostova    114      2.789989
Count_Ilya_Rostov                       Count_Ilya_Rostov    111      2.696035
                             betweenness closeness
AndreyBolkonsky               0.13827724 0.6000000
Tsar_Alexander_I_of_Russia    0.11526402 0.6129808
Nikolai_Rostov                0.09494513 0.5916473
Pierre_Bezukhov               0.04515313 0.5172414
NatashaRostova                0.05060278 0.5579869
Mikhail_Ilarionovich_Kutuzov  0.04069718 0.5616740
Fedor_Ivanovich_Dolokhov      0.05804491 0.5717489
Napoleon_Bonaparte            0.04730317 0.5666667
Sonya_Rostova                 0.02978550 0.5460385
Count_Ilya_Rostov             0.01703175 0.4990215

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

g_filtered <- g |>
  delete_edges(E(g)[weight < 3])

ego_graph <- make_ego_graph(
  g_filtered,
  order = 1,
  nodes = "Tsar_Alexander_I_of_Russia"
)[[1]]

V(ego_graph)$is_main <- V(ego_graph)$name == "Tsar_Alexander_I_of_Russia"

ggraph(ego_graph, layout = "fr") +
  geom_edge_link(aes(width = weight), alpha = 0.5) +
  geom_node_point(aes(size = degree, color = is_main)) +
  geom_node_text(aes(label = name), repel = TRUE, size = 3) +
  scale_color_manual(values = c("steelblue", "red")) +
  theme_void()
Warning: The `trans` argument of `continuous_scale()` is deprecated as of ggplot2 3.5.0.
ℹ Please use the `transform` argument instead.
Warning: ggrepel: 5 unlabeled data points (too many overlaps). Consider
increasing max.overlaps

Да, мы действительно получили эго-сеть Александра I. Данный выбор обусловлен высокой степенью центральности персонажа (или просто потому что автору так захотелось). Построенный эго-граф первого порядка отражает ближайшее окружение персонажа (Андрей Болконский тут как тут) и демонстрирует его взаимодействие с ключевыми фигурами романа.

Больше визуализации!

# поиск сообществ
communities <- cluster_louvain(g)

# добавляем в граф
V(g)$community <- membership(communities)

sizes(communities)
Community sizes
  1   2   3   4   5 
 44  41  64 101   6 
ggraph(g, layout = "fr") +
  geom_edge_link(alpha = 0.2) +
  geom_node_point(aes(color = factor(community), size = degree)) +
  theme_void() +
  guides(color = "none")

modularity_value <- modularity(communities)

Для выявления структуры сети мы применяем алгоритм Louvain, чтобы обнаружить сообщества персонажей. В результате выделено 19 сообществ различного размера.

Наиболее крупные сообщества включают 73, 43 и 27 узлов, что говорит о наличии крупных кластеров взаимодействий в соответствии с сюжетными линиями произведения (кластер для военной линии, кластер для семьи Ростовых и тд). При этом присутствуют также малые сообщества и одиночные узлы, отражающие периферийных или эпизодических персонажей.


Наконец, попробуем посчитать точки сочленения

art_points <- articulation_points(g)

length(art_points)
[1] 0
V(g)$name[art_points]
character(0)

В результате анализа удалось выявить целую одну точку сочленения — Андрея Болконского. Значит, удаление князя приводит к распаду сети на несколько компонент, что подчеркивает его ключевую роль в поддержании связности графа.


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

Наш построенный граф включает 256 узлов и 2340 рёбер, с умеренной плотностью (0.072) и двумя компонентами связности, поэтому можем сделать вывод о высокой интеграции персонажей в повествовании.