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

Author

Darya Galkina

Published

March 16, 2025

Как журнал Ходасевича стал математическим объектом

«Камер-фурьерский журнал» представляет собой краткие подневные записи за 17 лет (с 1922 по 1939) жизни В. Ходасевича за границей. Несмотря на то, что подобные личные заметки писателя не могут претендовать на объективность и полноту, журнал является важным источником сведений об истории эмигрантской литературы в целом и его социальной организации в частности.

Портрет В. Ходасевича

Этому документу посчастливилось стать предметом исследования Б. В. Орехова, П. Ф. Успенского и В. В. Файнберг (Орехов,Б.В., Успенский, П.Ф., Файнберг, В.В. 2018), которые прибегли к методу анализа сложных сетей, подсказанному им самой структурой материала. Действительно, журнал содержит единообразно оформленные записи Ходасевича о том, в чьём присутствии происходили его встречи с теми или иными людьми. Авторы структурировали данные журнала и подготовили набор данных о круге общения Ходасевича за 17 лет эмиграции. Работе с частью подготовленных для анализа документов посвящён наш исследовательский экзерсис.

Выбор и подготовка материала

Загрузим из папки months_data записи за один год — 1926 — и обобщим их. Выбор этого периода неслучаен. Весной 1925 года В. Ходасевич переезжает с Ниной Берберовой в Париж, что, как пишут (Орехов,Б.В., Успенский, П.Ф., Файнберг, В.В. 2018), вынуждает писателя “активно налаживать связи на новом месте”. Исследователи отмечают, что этап интенсивного общения продолжается и в течение 1926 года, что делает этот период ключевым для реконструкции круга контактов Ходасевича в эмиграции.

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

setwd(".\\dataverse_files\\months_data")
paths <- list.files(pattern = "^1926_.*\\‎.gexf$", full.names = TRUE) 
joined <- tibble (from_name = character(), to_name = character(), weight = character())

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

for (doc in paths){all_docs_to_df(doc)}

#есть повторяющиеся пары, объединим эти наблюдения, сложив их вес
joined_summarized <- joined |>
  group_by(from_name, to_name) |>
  summarize(weight = sum(weight))

joined_summarized
# A tibble: 1,216 × 3
# Groups:   from_name [138]
   from_name to_name weight
   <chr>     <chr>    <dbl>
 1 Адамович  Алданов      1
 2 Адамович  Бахрах       3
 3 Адамович  Бахтин       3
 4 Адамович  Бунин        1
 5 Адамович  Бунина       1
 6 Адамович  Вальтер      1
 7 Адамович  Вейдле       1
 8 Адамович  Винавер      1
 9 Адамович  Вишняк       1
10 Адамович  Гиппиус      6
# ℹ 1,206 more rows

Создание объекта графа и его описание

Общие сведения

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

Согласно сводке, граф направленный, именованный, взвешенный. Как можно понять из документации к набору данных, направленность рёбер не имеет смысла, ведь документы фиксируют только количество встреч каждого из упомянутых Ходасевичем лиц с каждым за каждый документированный месяц. Граф содержит 165 узлов, 1216 рёбер. У узлов есть атрибут name, а у рёбер – атрибут weight (количество встреч).

Плотность сети

edge_density(graph)
[1] 0.04493718

Количество и состав компонент

components(graph)$csize
[1] 158   3   2   2

Граф состоит из четырёх компонент, в наиболее крупной из них 158 узлов. Рассмотрим, кто из персон не попал в состав главной компоненты.

Узлы, не входящие в главную компоненту

which(components(graph)$membership !=1)
   Карякин   Карякина    Милиоти Поволоцкий     Наташа     Слоним    Цатуров 
        59         60         85        101        161        162        163 

Наиболее удалённые узлы и расстояние между ними (диаметр сети)

lgc <- largest_component(graph)
farthest_vertices(lgc)
$vertices
+ 2/158 vertices, named, from 5a14a85:
[1] Бурцев Чабров

$distance
[1] 30

Транзитивность сети

transitivity(graph)
[1] 0.5202918

Атрибуты рёбер и узлов

Рассчитаем взвешенную центральность узлов (она отражает количество и вес связей, в которые вовлечена персона) и сделаем её атрибутом. Также закодируем ширину рёбер количеством встреч – то есть присвоим атрибуту width значения атрибута weight.

wDegree <- strength(graph)
sort(wDegree, decreasing = TRUE)[1:10]
   Терапиано       Познер Мережковский      Алданов      Гиппиус       Цетлин 
         145          116          107          103           95           91 
       Бунин         Кнут    Ладинский       Вишняк 
          88           85           78           76 
E(graph)$width <- E(graph)$weight
V(graph)$size <- sqrt(sqrt(wDegree))*5 # нормализуем размер узлов

V(graph)$label.cex = 0.5 # укажем размер подписей

E(graph)$width[1:10]
 [1] 1 3 3 1 1 1 1 1 1 6
V(graph)$size[1:10]
 [1] 14.615064 15.928663 12.494997  9.671682 11.602979  8.132883  5.000000
 [8] 14.664860 11.180340 12.803748

Визуализация графа

data <- toVisNetworkData(graph)

network_3d <- visNetwork(nodes = data$nodes, 
                        edges = data$edges, 
                        width = "100%", 
                        height = 600)

visOptions(network_3d, highlightNearest = list(enabled = TRUE, degree = 1, hover = TRUE), nodesIdSelection = FALSE)  |> 
  visPhysics(maxVelocity = 20, stabilization = list(enabled = TRUE), adaptiveTimestep = TRUE)  |>  
  visInteraction(dragNodes = TRUE) |>
  visLayout(randomSeed = 1603) 

Подграф: оставим узлы с наибольшей взвешенной центральностью

V(graph)$degree <- wDegree
vert <- which(V(graph)$degree >= 30)

subgr <- induced_subgraph(graph, vids = vert)

V(subgr)$color = "#FFC680"
V(subgr)$borderWidth <- 1  
V(subgr)$borderColor <- "coral" 

subdata <- toVisNetworkData(subgr)

subgr_3d <- visNetwork(nodes = subdata$nodes, 
                         edges = subdata$edges, 
                         width = "100%", 
                         height = 600) 

visOptions(subgr_3d, highlightNearest = list(enabled = TRUE, degree = 1, hover = TRUE), nodesIdSelection = FALSE)  |> 
  visPhysics(maxVelocity = 20, stabilization = list(enabled = TRUE), adaptiveTimestep = TRUE)  |>  
  visInteraction(dragNodes = TRUE) |>
  visLayout(randomSeed = 1603)  

Клики

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

graph_undir <- as_undirected(graph, mode = "each")
cliques(graph_undir, min = 20)[1:3]
[[1]]
+ 20/165 vertices, named, from 5a3e012:
 [1] Алданов    Алданова   Бунин      Бунина     Зайцев_Б   Зайцева_В 
 [7] Луцкий     Осоргины   Познер     Познер_С   Потемкин   Резников  
[13] Свидерский Сухомлин   Терапиано  Тэффи      Цветаева   Цетлин    
[19] Цетлина    Эфрон     

[[2]]
+ 20/165 vertices, named, from 5a3e012:
 [1] Алданова   Бунин      Бунина     Зайцев_Б   Зайцева_В  Луцкая    
 [7] Луцкий     Осоргины   Познер     Познер_С   Потемкин   Резников  
[13] Свидерский Сухомлин   Терапиано  Тэффи      Цветаева   Цетлин    
[19] Цетлина    Эфрон     

[[3]]
+ 21/165 vertices, named, from 5a3e012:
 [1] Алданов    Алданова   Бунин      Бунина     Зайцев_Б   Зайцева_В 
 [7] Луцкая     Луцкий     Осоргины   Познер     Познер_С   Потемкин  
[13] Резников   Свидерский Сухомлин   Терапиано  Тэффи      Цветаева  
[19] Цетлин     Цетлина    Эфрон     

Размер самой большой клики – 26 узлов. Выведем список её узлов.

clique_num(graph_undir)
[1] 26
largest_cliques(graph_undir)
[[1]]
+ 26/165 vertices, named, from 5a3e012:
 [1] Свидерский Алданов    Эфрон      Цетлина    Цетлин     Цветаева  
 [7] Тэффи      Терапиано  Сухомлин   Резников   Потемкин   Познер_С  
[13] Познер     Осоргины   Луцкий     Луцкая     Зайцева_В  Зайцев_Б  
[19] Гофман     Вишняк_Абр Винаверы   Бунина     Бунин      Бахрахи   
[25] Андреев    Алданова  

Точки сочленения

Точка сочленения – это узел, при удалении которого увеличивается число компонент связности. Таким образом, они соединяют разные части сети. Определим точки сочленения нашего графа (отмечены коралловым цветом).

ap <- articulation_points(graph)
ap
+ 18/165 vertices, named, from 59fb72f:
 [1] Каплун    Манухин   Тумаркин  Цетлин    Фохт      Рысс      Сев      
 [8] Терапиано Иванов    Вольфсон  Папоушек  Вейдле    Ульпе     Кан_С    
[15] Женя      Бахрах    Аминадо   Наташа   
vcolor <- rep("gray", vcount(graph))
vcolor[ap] <- "coral"

set.seed(123)
par(mar = c(0.5, 0.5, 0.5, 0.5))
plot(graph, vertex.color = vcolor,
     vertex.label.cex = 1, vertex.size = 5)  

В главной компоненте ключевыми точками сочленения являтся Женя, Каплун, Аминадо, Рысс, Вольфсон и др.

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

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

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

set.seed(123)
cfg <- cluster_fast_greedy(graph_undir)
modularity(cfg)
[1] 0.3180159
cl <- cluster_louvain(graph_undir)
modularity(cl)
[1] 0.3405547
cw <- cluster_walktrap(graph_undir)
modularity(cw)
[1] 0.2540698
clp <- cluster_label_prop(graph_undir)
modularity(clp)
[1] 0.01750361

Алгоритм Louvain наилучшим образом определяет сообщества на нашем материале, поэтому остановимся на нём для дальнейшего анализа. Теперь каждому узлу графа присвоен номер его группы. Отобразим группы на графе закрашенными областями и оставим подписи только для периферийных групп, чтобы не перегружать изображение.

set.seed(123)
membership(cl) |> head()
Адамович  Алданов Алданова  Аминадо  Андреев      Ася 
       1        2        3        4        3        5 
nodes_to_label <- V(graph_undir)[membership(cl) %in% c(4, 5, 6, 8, 9, 10, 11) ]

labels <- ifelse(V(graph_undir) %in% nodes_to_label, V(graph_undir)$name, "")

V(graph_undir)$size <- 5

par(mar = c(0.5, 0.5, 0.5, 0.5))
plot(cl, graph_undir, vertex.label = labels, vertex.label.color = "black", vertex.label.cex = 1.1)

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

Так, легко выделяются: зелёное сообщество слева (Кук, Зайцев, Таубе, Нина, Женя, Ася и др.), голубая область справа (Сев, Каплун, Липницкий, Скрябнев и др.), салатовая область снизу (Аминадо, Бенедиктов, Вакар, Керенский, Сосинский, Поляков, Цвибак, Португейс и др.), а также сообщества, не принадлежащие главной компоненте (Карякины и Наташа; Поволоцкий и Цатуров; Слоним и Милоти).

К-ядра

Другим способом определения социальной сплоченности является k-ядро (k-core). Это максимальный подграф, в котором каждая вершина связана минимум с k другими вершинами этого же подграфа. Визуализируем ядерную структуру графа, отобразив только k-ядра 10-й степени и выше.

cores <- coreness(graph)
head(cores)
Адамович  Алданов Алданова  Аминадо  Андреев      Ася 
      18       25       25        8       25        3 
table(cores)
cores
 1  2  3  4  5  6  7  8  9 10 11 12 14 16 17 18 25 
22 16 20  8  4 13  4  5  5  4  6  8  3  2  7 12 26 
V(graph)$core <- cores

graph_from_10 <- induced_subgraph(graph, vids=V(graph)[core > 10])

cols <- paletteer_d("nbapalettes::hawks_statement")
ggraph(graph_from_10, layout = "kk", maxiter = 500) + 
  geom_edge_link(color = cols[3],
                 alpha = 0.3,
                 width = 0.6) +

  geom_node_point(aes(color = as.factor(core)),
                  size = 10, 
                  show.legend = TRUE) +
  geom_node_text(aes(label = name),
                 color = cols[3],
                 repel = TRUE) +
  scale_color_brewer("k-ядра", type = "qual") +
  theme_void()

Литература

Орехов,Б.В., Успенский, П.Ф., Файнберг, В.В. 2018. “’Цифровые Подходы к "Камер-Фурьерскому Журналу" в.ф. Ходасевича’.” "Русская Литература", no. 3: 19–53.