Тема 18 Dracor

Мы продолжим исследовать возможности сетевого анализа с использованием корпуса Dracor. В дальнейшем я опираюсь на презентацию Ивана Позднякова, который разрабатывал DraCor Shiny app (https://shiny.dracor.org/).

18.1 О корпусе Dracor

DraCor — сокращение от drama corpora — это собрание размеченных по стандарту TEI драматических текстов на 11-ти языках. Здесь есть корпуса пьес на французском, немецком, испанском, русском, итальянском, шведском, португальском (только Кальдерон) и английском (только Шекспир), а также совсем небольшие коллекции эльзасских, татарских и башкирских пьес.

Два крупных корпуса пьес в составе собрания — немецкий и русский — были собраны и поддерживаются создателями проекта DraCor (часть команды из России — это Центр цифровых гуманитарных исследований Высшей школы экономики). Остальные корпуса были взяты из сторонних проектов, а затем адаптированы для совместимости с функционалом DraCor. (Отсюда).

На сайте проекта “Системный Блок” можно прочитать серию материалов о том, как возможности Dracor используются в литературоведении:

  • о “Ревизоре” тыц
  • о плотности сетей в трагедии и комедии тыц
  • о “зоне смерти” в “Гамлете” и др. тыц

18.2 Начало работы с Dracor

# remotes::install_github("Pozdniakov/rdracor")
library(rdracor)
library(tidyverse)

get_dracor_meta() %>%
  summary()
## DraCor hosts 16 corpora comprising 3132 plays.
## 
## The last updated corpus was German Drama Corpus (2023-12-06 00:03:42.47).

Извлекаем метаданные.

meta <- get_dracor_meta() %>%
  select(name, title, plays)

meta
## # A tibble: 16 × 3
##    name  title                           plays
##    <chr> <chr>                           <int>
##  1 fre   French Drama Corpus              1560
##  2 ger   German Drama Corpus               655
##  3 rus   Russian Drama Corpus              212
##  4 cal   Calderón Drama Corpus             205
##  5 ita   Italian Drama Corpus              139
##  6 swe   Swedish Drama Corpus               68
##  7 hun   Hungarian Drama Corpus             41
##  8 greek Greek Drama Corpus                 40
##  9 u     Ukrainian Drama Corpus             40
## 10 gersh German Shakespeare Drama Corpus    38
## 11 shake Shakespeare Drama Corpus           37
## 12 rom   Roman Drama Corpus                 36
## 13 als   Alsatian Drama Corpus              30
## 14 span  Spanish Drama Corpus               25
## 15 bash  Bashkir Drama Corpus                3
## 16 tat   Tatar Drama Corpus                  3
meta %>%
  plot()

shake <- get_dracor("shake")
summary(shake)
## 37 plays in Shakespeare Drama Corpus 
## Corpus id: shake, repository: https://github.com/dracor-org/shakedracor  
## Description: Derived from the [Folger Shakespeare Library](https://shakespeare.folger.edu/). Enhancements documented in our [README at GitHub](https://github.com/dracor-org/shakedracor).
## Written years (range): 1591–1613 
## No information on premiere years 
## Years of the first printing (range): 1597–1774
shake <- as_tibble(shake)
shake
## # A tibble: 37 × 55
##    corpus id          playName        yearNormalized title subtitle
##    <chr>  <chr>       <chr>                    <int> <chr> <lgl>   
##  1 shake  shake000001 the-tempest               1611 The … NA      
##  2 shake  shake000002 two-gentlemen-…           1591 Two … NA      
##  3 shake  shake000003 the-merry-wive…           1602 The … NA      
##  4 shake  shake000004 measure-for-me…           1604 Meas… NA      
##  5 shake  shake000005 the-comedy-of-…           1594 The … NA      
##  6 shake  shake000006 much-ado-about…           1600 Much… NA      
##  7 shake  shake000007 love-s-labor-s…           1595 Love… NA      
##  8 shake  shake000008 a-midsummer-ni…           1595 A Mi… NA      
##  9 shake  shake000009 the-merchant-o…           1600 The … NA      
## 10 shake  shake000010 as-you-like-it            1599 As Y… NA      
## # ℹ 27 more rows
## # ℹ 49 more variables: firstAuthorName <chr>, authors <list>,
## #   source <lgl>, sourceUrl <lgl>, writtenYearStart <lgl>,
## #   writtenYearFinish <int>, printYearStart <lgl>,
## #   printYearFinish <int>, premiereYearStart <lgl>,
## #   premiereYearFinish <lgl>, wikidataId <chr>, networkSize <int>,
## #   networkdataCsvUrl <chr>, normalizedGenre <lgl>, size <int>, …

Тут хранится очень много всего: размер сети, плотность сети и т.д. Вот так, например, выглядят самые длинные пьесы Шекспира:

shake %>%
  arrange(desc(wordCountText)) %>%
  select(corpus, firstAuthorName, title, wordCountText, yearWrittenMeta)
## # A tibble: 37 × 5
##    corpus firstAuthorName      title  wordCountText yearWrittenMeta
##    <chr>  <chr>                <chr>          <int>           <int>
##  1 shake  Shakespeare, William Hamlet         32539            1602
##  2 shake  Shakespeare, William Richa…         31765            1593
##  3 shake  Shakespeare, William Cymbe…         30141            1610
##  4 shake  Shakespeare, William Corio…         29948            1608
##  5 shake  Shakespeare, William Troil…         28502            1602
##  6 shake  Shakespeare, William Henry…         28461            1598
##  7 shake  Shakespeare, William Othel…         28452            1604
##  8 shake  Shakespeare, William King …         28192            1606
##  9 shake  Shakespeare, William Henry…         27982            1599
## 10 shake  Shakespeare, William Henry…         27902            1591
## # ℹ 27 more rows

Распределение мужских и женских персонажей по годам:

gender_ratio <- shake %>%
  select(title, yearWrittenMeta, numOfSpeakers, numOfSpeakersFemale, numOfSpeakersMale) %>% 
  mutate(male = numOfSpeakersMale / numOfSpeakers,
         female = numOfSpeakersFemale / numOfSpeakers) %>%
  select(-numOfSpeakers, -numOfSpeakersMale, -numOfSpeakersFemale) %>% 
  arrange(yearWrittenMeta) %>% 
  pivot_longer(cols = c(male, female), names_to = "gender") %>% 
  mutate(year = as.factor(yearWrittenMeta)) %>% 
  select(-yearWrittenMeta)

gender_ratio 
## # A tibble: 74 × 4
##    title                   gender  value year 
##    <chr>                   <chr>   <dbl> <fct>
##  1 Two Gentlemen of Verona male   0.824  1591 
##  2 Two Gentlemen of Verona female 0.176  1591 
##  3 The Taming of the Shrew male   0.5    1591 
##  4 The Taming of the Shrew female 0.0789 1591 
##  5 Henry VI, Part 1        male   0.238  1591 
##  6 Henry VI, Part 1        female 0.0159 1591 
##  7 Henry VI, Part 2        male   0.583  1591 
##  8 Henry VI, Part 2        female 0.0278 1591 
##  9 Henry VI, Part 3        male   0.531  1591 
## 10 Henry VI, Part 3        female 0.0408 1591 
## # ℹ 64 more rows
gender_ratio %>%
  group_by(year, gender) %>% 
  summarise(mean = mean(value)) %>% 
  ggplot(aes(year, mean, fill = gender)) + 
  geom_col(position = "dodge") 
## `summarise()` has grouped output by 'year'. You can override using
## the `.groups` argument.

18.3 Сети Dracor

Однако самое интересное – это построение и анализ сетей при помощи Dracor.

taming <- get_net_cooccur_igraph(corpus = "shake",
                                 play = "the-taming-of-the-shrew")

taming
## IGRAPH be72fe8 UNW- 38 222 -- 
## + attr: name (v/c), isGroup (v/l), gender (v/c),
## | numOfScenes (v/n), numOfSpeechActs (v/n), numOfWords
## | (v/n), degree (v/n), weightedDegree (v/n), closeness
## | (v/n), betweenness (v/n), eigenvector (v/n), weight (e/n)
## + edges from be72fe8 (vertex names):
## [1] Christopher Sly--characters in the Induction
## [2] Christopher Sly--characters in the Induction
## [3] Christopher Sly--characters in the Induction
## [4] Christopher Sly--characters in the Induction
## [5] Christopher Sly--characters in the Induction
## + ... omitted several edges
library(ggraph)

ggraph(taming, layout = "fr") + 
  geom_edge_link(edge_alpha = 0.5) +
  geom_node_point(fill = "steelblue", 
                  size = 4,
                  alpha = 0.5, 
                  shape = 21) + 
  geom_node_text(aes(label = name), vjust = 1, hjust = 1) + 
  theme_void()

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

library(igraph)
groups <- get.vertex.attribute(taming)$isGroup
groups
##  [1] FALSE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE
## [11]  TRUE  TRUE  TRUE  TRUE  TRUE FALSE FALSE FALSE FALSE FALSE
## [21] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
## [31] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
taming_sub <- induced_subgraph(taming, vids = -c(1:15))
ggraph(taming_sub, layout = "fr") + 
  geom_edge_link(edge_alpha = 0.5) +
  geom_node_point(aes(fill = gender),
                  size = 4,
                  alpha = 0.5, 
                  shape = 21) + 
  geom_node_text(aes(label = name), vjust = 1, hjust = 1) + 
  theme_void()

Теперь на примере этих данных исследуем такую характеристику сети, как модулярность.

18.4 Модулярность

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

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

Если все узлы принадлежат к одному классу, то модулярность равна нулю. Если разбиение на классы хорошее, то модулярность должна быть высокая.

Снова загружим датасет Moreno, где, как мы видели, ученики 4-го класса делятся на две сети в зависимости от пола.

library(UserNetR)
library(intergraph)

data(Moreno)
Moreno_graph <- intergraph::asIgraph(Moreno)
plot.igraph(Moreno_graph, vertex.color =1+ V(Moreno_graph)$gender)

Метрика модулярности подтверждает, что пол учеников действительно объясняет наблюдаемую кластеризацию.

modularity(Moreno_graph, V(Moreno_graph)$gender)
## [1] 0.4761342

В графе “Укрощения…” пол явно не является определяющим (он закодирован цветом на графике выше). Чтобы убедиться в этом сначала перекодируем гендер, так как функция modularity() принимает числовую переменную в качестве аргумента. Значение гендера 3 (unknown) в пьесе имеют групповые персонажи.

V(taming_sub)$gender
##  [1] "MALE"    "MALE"    "MALE"    "MALE"    "FEMALE"  "MALE"   
##  [7] "FEMALE"  "MALE"    "MALE"    "MALE"    "UNKNOWN" "MALE"   
## [13] "MALE"    "MALE"    "MALE"    "MALE"    "MALE"    "UNKNOWN"
## [19] "MALE"    "MALE"    "MALE"    "MALE"    "FEMALE"
## male = 1
idx <- V(taming_sub)$gender=="MALE"
V(taming_sub)$gender[idx] <- 1

## female = 2
idx <- V(taming_sub)$gender=="FEMALE"
V(taming_sub)$gender[idx] <- 2

## unknown for groups
idx <- V(taming_sub)$gender=="UNKNOWN"
V(taming_sub)$gender[idx] <- 3
gender <- as.numeric(V(taming_sub)$gender)
modularity(taming_sub, gender)
## [1] -0.01708984

При выделении сообществ в большинстве случаев наша задача – максимизировать модулярность.

18.5 Алгоритмы обнаружения сообществ

В пакете igraph реализовано множество алгоритмов обнаружения сообществ. Обычной практикой является применение нескольких алгоритмов и сравнение результатов.

У нас ненаправленная взвешенная сеть.

taming_sub
## IGRAPH fc698a7 UNW- 23 128 -- 
## + attr: name (v/c), isGroup (v/l), gender (v/c),
## | numOfScenes (v/n), numOfSpeechActs (v/n), numOfWords
## | (v/n), degree (v/n), weightedDegree (v/n), closeness
## | (v/n), betweenness (v/n), eigenvector (v/n), weight (e/n)
## + edges from fc698a7 (vertex names):
## [1] Lucentio--Tranio          Lucentio--Baptista Minola
## [3] Lucentio--Gremio          Lucentio--Katherine      
## [5] Lucentio--Hortensio       Lucentio--Bianca         
## [7] Lucentio--Biondello       Lucentio--Petruchio      
## [9] Lucentio--Grumio         
## + ... omitted several edges

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

cw <- cluster_walktrap(taming_sub)
membership(cw)
##                   Lucentio                     Tranio 
##                          2                          2 
##            Baptista Minola                     Gremio 
##                          2                          2 
##                  Katherine                  Hortensio 
##                          1                          2 
##                     Bianca                  Biondello 
##                          2                          2 
##                  Petruchio                     Grumio 
##                          1                          1 
##    SERVANTS.BAPTISTA.1_Shr                     Curtis 
##                          2                          3 
##                  Nathaniel                    Phillip 
##                          3                          3 
##                     Joseph                   Nicholas 
##                          3                          3 
##                      Peter SERVANTS.PETRUCHIO.0.4_Shr 
##                          3                          3 
##               Merchant_Shr            Haberdasher_Shr 
##                          2                          1 
##                 Tailor_Shr                  Vincentio 
##                          1                          2 
##                  Widow_Shr 
##                          2
plot(cw, taming_sub)

Значение модулярности положительное, но не очень высокое.

modularity(cw)
## [1] 0.1489342

Поищем другое разбиение.

csg <- cluster_spinglass(taming_sub)
membership(csg)
##                   Lucentio                     Tranio 
##                          1                          1 
##            Baptista Minola                     Gremio 
##                          1                          1 
##                  Katherine                  Hortensio 
##                          2                          1 
##                     Bianca                  Biondello 
##                          1                          1 
##                  Petruchio                     Grumio 
##                          2                          2 
##    SERVANTS.BAPTISTA.1_Shr                     Curtis 
##                          1                          2 
##                  Nathaniel                    Phillip 
##                          2                          2 
##                     Joseph                   Nicholas 
##                          2                          2 
##                      Peter SERVANTS.PETRUCHIO.0.4_Shr 
##                          2                          2 
##               Merchant_Shr            Haberdasher_Shr 
##                          1                          2 
##                 Tailor_Shr                  Vincentio 
##                          2                          1 
##                  Widow_Shr 
##                          1

На графе видно, что почти все слуги, портной и галантерейщик оказались в отдельном кластере, что вполне осмысленно. Но есть два кластера с одним элементом (вдова и слуги Баптисты), всего 4.

plot(csg, taming_sub)

Показатели модулярности чуть выше, чем для предыдущего разбиения.

modularity(csg)
## [1] 0.2373678

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

cev <- cluster_leading_eigen(taming_sub)
plot(cev, taming_sub)

modularity(cev)
## [1] 0.1664325

18.6 Двудольные графы (биграфы)

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

Будет дополнено позже.