Работа с графами

Компьютерный анализ текстов, занятие 19

Автор

Константин Сатдаров

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

2026.03.08

Импорт необходимых библиотек среды R

library(igraph)
library(ggplot2)
library(ggraph)
library(tidyverse)
library(udpipe)

Подготовка данных

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

# практикуемся в создании графов на латинском датасете
caesar <- udpipe_read_conllu('https://github.com/locusclassicus/text_analysis_2024/raw/main/files/bg_latinpipe.conllu')

# смотрим на состав лемм, в relevant отбираем только NOUN и VERB
co_occur <- cooccurrence(caesar$lemma,
                     relevant = caesar$upos %in% c('NOUN', 'VERB'),
                     skipgram = 1) |>
  as_tibble() |> 
  filter(cooc > 8)

В полученном tibble’е фиксируется 29 наблюдений, если filter(cooc > 8), что вполне поддаётся визуализации. Если задать filter(cooc > 5), получится 85 наблюдений, но при этом для визуализации это поддаётся существенно хуже.

Создание графа

Здесь я предлагаю доработать получившийся граф:

co_occur_g <- graph_from_data_frame(co_occur)
co_occur_g
IGRAPH 89dee5c DN-- 40 29 -- 
+ attr: name (v/c), cooc (e/n)
+ edges from 89dee5c (vertex names):
 [1] res       ->cognosco tribunus  ->miles    bellum    ->gero    
 [4] iter      ->facio    proelium  ->committo fero      ->possum  
 [7] bellum    ->infero   obses     ->do       legatus   ->mitto   
[10] res       ->gero     sustineo  ->possum   impetus   ->facio   
[13] castra    ->educo    locus     ->castra   consilium ->capio   
[16] hostis    ->impetus  praesidium->relinquo pars      ->flumen  
[19] locus     ->natura   flumen    ->transeo  hostis    ->castra  
[22] signum    ->do       impero    ->facio    castra    ->venio   
+ ... omitted several edges

Граф направленный (D), 40 вершин и 29 связей, что логично, так как в исходном co_occur 29 наблюдений.

В граф можно добавить информацию о том, какою частью речи является та или иная вершина. Список лемм и их частеречной принадлежности можно выделить из основной таблицы caesar. Оставляю только NOUN и VERB.

caesar_pos <- caesar |>
  filter(lemma %in% V(co_occur_g)$name) |>
  select(lemma, upos) |>
  unique() |>
  filter(upos %in% c('VERB', 'NOUN'))

При этом длины векторов caesar_pos$lemma и V(co_occur_g)$name отличаются:

length(caesar_pos$lemma)
[1] 41
length(V(co_occur_g)$name)
[1] 40

Дело в двойном прочтении mitto как глагола или существительного. Оно фигурирует в составе сочетания legatus + mitto. Из анализа я решился исключить менее вероятное прочтение: mitto будет представлено исключительно как VERB.

pos_tbl <- caesar_pos |>
  filter(!(lemma == 'mitto' & upos == 'NOUN'))

Сохраняем информацию о частях речи:

V(co_occur_g)$pos <- pos_tbl$upos

co_occur_g
IGRAPH 89dee5c DN-- 40 29 -- 
+ attr: name (v/c), pos (v/c), cooc (e/n)
+ edges from 89dee5c (vertex names):
 [1] res       ->cognosco tribunus  ->miles    bellum    ->gero    
 [4] iter      ->facio    proelium  ->committo fero      ->possum  
 [7] bellum    ->infero   obses     ->do       legatus   ->mitto   
[10] res       ->gero     sustineo  ->possum   impetus   ->facio   
[13] castra    ->educo    locus     ->castra   consilium ->capio   
[16] hostis    ->impetus  praesidium->relinquo pars      ->flumen  
[19] locus     ->natura   flumen    ->transeo  hostis    ->castra  
[22] signum    ->do       impero    ->facio    castra    ->venio   
+ ... omitted several edges
vertex_attr(co_occur_g)
$name
 [1] "res"        "tribunus"   "bellum"     "iter"       "proelium"  
 [6] "fero"       "obses"      "legatus"    "sustineo"   "impetus"   
[11] "castra"     "locus"      "consilium"  "hostis"     "praesidium"
[16] "pars"       "flumen"     "signum"     "impero"     "summa"     
[21] "aedificium" "cognosco"   "miles"      "gero"       "facio"     
[26] "committo"   "possum"     "infero"     "do"         "mitto"     
[31] "educo"      "capio"      "relinquo"   "natura"     "transeo"   
[36] "venio"      "imperium"   "incendo"    "recipio"    "copia"     

$pos
 [1] "NOUN" "NOUN" "NOUN" "VERB" "NOUN" "VERB" "VERB" "NOUN" "NOUN" "NOUN"
[11] "NOUN" "NOUN" "VERB" "VERB" "NOUN" "VERB" "NOUN" "VERB" "NOUN" "VERB"
[21] "VERB" "NOUN" "VERB" "NOUN" "VERB" "NOUN" "VERB" "VERB" "NOUN" "VERB"
[31] "NOUN" "NOUN" "VERB" "VERB" "VERB" "NOUN" "VERB" "NOUN" "NOUN" "NOUN"

Визуализируем!

# разными цветами закодирую анализируемые части речи
cols <- c('NOUN' = '#76EEC6', 'VERB' = '#CDC0B0')

set.seed(2026)
ggraph(co_occur_g, layout = 'fr', maxiter = 100) +
  geom_edge_link(aes(width = cooc, edge_alpha = cooc)) +
  geom_node_label(aes(label = name, 
                      fill = pos),
                  colour = 'grey22',
                  size = 3) +
  scale_fill_manual(values = cols) +
  theme_graph(base_family = 'sans') +
  theme(legend.position = 'bottom') +
  labs(title = 'Коллокации в латинском датасете', subtitle = 'VERBs & NOUNs')
Warning: The `trans` argument of `continuous_scale()` is deprecated as of ggplot2 3.5.0.
ℹ Please use the `transform` argument instead.