Компьютерный анализ стихов Андрея Вознесенского

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

Перед началом работы необходимо загрузить используемые библиотеки языка R:

library(rvest)
library(tidyverse)
library(stringr)
library(tidytext)
library(tokenizers)
library(udpipe)
library(igraph)
library(ggraph)
library(wordcloud)
library(wordcloud2)
library(RColorBrewer)
library(textplot)

Для начала необходимо загрузить тексты стихов Вознесенского. Я буду использовать библиотеку rvest для веб-скрапинга и загрузки нужных текстов с сайта “Библиотека русской поэзии”. Я хочу загрузить все 204 стихотворения, размещенные на этом сайте для проведения анализа.

url <- "https://libverse.ru/voznesenskii/list.html"
html <- read_html(url, encoding = "windows-1251")

# формирую таблицу с названиями стихов, ссылками, годами выхода
poemsV <-tibble(
  title = html %>%
    html_elements("#verses a") %>%
    html_text2(),
    stable_url = html %>%
      html_elements("#verses a") %>%
      html_attr("href") %>%
      paste0("https://libverse.ru/voznesenskii/", .)
)
# забираю тексты стихов по ссылкам с учётом кодировки
get_text <- function(url) {
  text <- read_html(url, encoding = "windows1251") %>%
    html_elements("pre") %>%
    html_text2()
  return(text)
}
poems_text <- poemsV %>%
  mutate(text = map_chr(stable_url, get_text))

# текст стиха содержит в конце дату - выделяю даты в отдельный столбец year
poems_text <- poems_text %>%
  separate(text, into = c("text", "year"),
           sep = "(?=\\d{4}[^.,])")

# удаляю из текстов римские номера разделов, указания кому посвящен стих (например Н.Андросовой)
poems_clean <- poems_text %>%
  mutate(text = str_replace_all(text, "[I-V]+", "")) %>%
  mutate(text = str_replace_all(text, "[А-Я]\\. [А-Я].*|[А-Я].* [А-Я]\\.[А-Я]. [А-Я].*", "")) %>%
  mutate(text = str_replace_all(text, "\\(.*\\)", "")) %>%
  mutate(text = str_squish(text)) %>%
  # посвящения из двух отдельных стихов вычищаю неэлегантным* способом
  mutate(text = str_replace(text, "Авиавступление Посвящается слушателям школы Ленина в Лонжюмо ", "")) %>%
  mutate(text = str_replace(text, "Памяти жертв фашизма", ""))
head(poems_clean, 10)
## # A tibble: 10 × 4
##    title                           stable_url                        text  year 
##    <chr>                           <chr>                             <chr> <chr>
##  1 Ода сплетникам                  https://libverse.ru/voznesenskii… "Я с… "195…
##  2 На плотах                       https://libverse.ru/voznesenskii… "Нас… "195…
##  3 Пожар в Архитектурном институте https://libverse.ru/voznesenskii… "Пож… "195…
##  4 Сидишь беременная, бледная...   https://libverse.ru/voznesenskii… "Сид… "195…
##  5 Гойя                            https://libverse.ru/voznesenskii… "Я -… "195…
##  6 Первый лед                      https://libverse.ru/voznesenskii… "Мер… "195…
##  7 Утиных крыльев переплеск...     https://libverse.ru/voznesenskii… "Ути… "195…
##  8 Параболическая баллада          https://libverse.ru/voznesenskii… "Суд… "195…
##  9 Кто мы — фишки или великие?..   https://libverse.ru/voznesenskii… "Кто… "195…
## 10 Туманная улица                  https://libverse.ru/voznesenskii… "Тум… "195…

“Давайте напишем это более элегантным способом”.

— Алиева О.В.

Количество выпущеных стихов по годам

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

poems_clean %>%
  group_by(year) %>%
  summarise(count = n()) %>%
  ggplot(aes(year, count)) +
  geom_col() +
  xlab(NULL) +
  theme(axis.text.x = element_text(angle = 90)) +
  ggtitle("График зависимости количества выпущенных стихов по годам")

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

Токенизация по словам

Далее разобью тексты стихов Вознесенского на отделльные слова и посмотрю какие 3 слова чаще встречаются в стихах.

tidy_poems <- poems_clean %>%
  mutate(text = str_to_lower(text)) %>%
  unnest_tokens(word, text) %>%
  count(word) %>%
  arrange(-n)
ngrams_AV <- poems_clean %>%
  unnest_tokens(ngrams, text, token = "ngrams", n = 3) %>%
  count(ngrams) %>%
  arrange(-n)
head(ngrams_AV, 10)
## # A tibble: 10 × 2
##    ngrams               n
##    <chr>            <int>
##  1 благодарю что не     7
##  2 не умер вчера        7
##  3 с того берега        7
##  4 что не умер          7
##  5 господи ещё лет      6
##  6 ещё лет десять       6
##  7 на море венки        6
##  8 ну что тебе          6
##  9 тебя никогда не      6
## 10 я тебя никогда       6

Лемматизация

Для дальнейшего анализа необходимо привести слова к начальной форме (лемме).

# лемматизация
p_length <- poems_clean$text
udpipe_download_model(language = "russian-syntagrus")
##            language
## 1 russian-syntagrus
##                                                                 file_model
## 1 /Users/dashatm/Desktop/R_language/russian-syntagrus-ud-2.5-191206.udpipe
##                                                                                                                                        url
## 1 https://raw.githubusercontent.com/jwijffels/udpipe.models.ud.2.5/master/inst/udpipe-ud-2.5-191206/russian-syntagrus-ud-2.5-191206.udpipe
##   download_failed download_message
## 1           FALSE               OK
russian_syntagrus <- udpipe_load_model(file = "russian-syntagrus-ud-2.5-191206.udpipe")
poems_ann <- udpipe_annotate(russian_syntagrus, p_length)
poems_df <- as_tibble(poems_ann) %>%
  select(-sentence, -paragraph_id, -xpos)

# выведем часть столбцов для первого стиха
poems_df %>%
  filter(doc_id == "doc1") %>%
  select(-sentence_id, -head_token_id, -deps, -dep_rel, -misc) %>%
  DT::datatable()

Морфологическая разметка

propn <- poems_df %>%
  filter(upos == "ADJ")
propn[,2:6]
## # A tibble: 2,211 × 5
##    sentence_id token_id token         lemma         upos 
##          <int> <chr>    <chr>         <chr>         <chr>
##  1           1 4        замочные      замочный      ADJ  
##  2           4 3        трехспальная  трехспальный  ADJ  
##  3           7 3        царственные   царственный   ADJ  
##  4           7 12       непогрешимы   непогрешимый  ADJ  
##  5           7 14       чисты         чистый        ADJ  
##  6           8 16       соседских     соседский     ADJ  
##  7          11 1        Междугородные междугородный ADJ  
##  8          12 17       толст         толстый       ADJ  
##  9          12 19       рыж           рыжий         ADJ  
## 10          13 6        тонкой        тонкий        ADJ  
## # ℹ 2,201 more rows

Посчитаем части речи

poems_df %>%
  group_by(upos) %>%
  count() %>%
  filter(upos != "ADJ") %>%
  arrange(-n)
## # A tibble: 16 × 2
## # Groups:   upos [16]
##    upos      n
##    <chr> <int>
##  1 PUNCT  8839
##  2 NOUN   7380
##  3 VERB   4005
##  4 ADP    2572
##  5 PRON   2041
##  6 ADV    1107
##  7 CCONJ  1021
##  8 PROPN   957
##  9 PART    880
## 10 SCONJ   817
## 11 DET     743
## 12 NUM     200
## 13 AUX     155
## 14 INTJ     48
## 15 X        35
## 16 SYM       5
poems_df %>%
  group_by(upos) %>%
  count() %>%
  filter(upos != "PUNCT") %>%
  ggplot(aes(x = reorder(upos, n), y = n, fill = upos)) +
  geom_bar(stat = "identity", show.legend = F) +
  coord_flip() +
  ggtitle("Части речи в стихах А.Вознесенского") +
  xlab(NULL) +
  ylab(NULL) +
  theme_bw()

Наиболее частотные существительные

nouns <- poems_df %>%
  filter(upos %in% c("NOUN", "PROPN")) %>%
  count(lemma) %>%
  arrange(-n)
head(nouns)
## # A tibble: 6 × 2
##   lemma       n
##   <chr>   <int>
## 1 душа       66
## 2 жизнь      60
## 3 человек    51
## 4 женщина    49
## 5 год        44
## 6 Россия     38
pal <- RColorBrewer::brewer.pal(7, "Dark2")
nouns %>%
  with(wordcloud(lemma, n, max.words = 50, colors = pal))

Личный архив А. Вознесенского содержит множество видеом. Будучи архитектором по образованию, Андрей Андреевич любил изображать слова в пространстве и висанными в различные формы, так он создавал свои знаменитые видеомы. Интересно посмотреть какие цвета и геометрические формы можно встретить в его стихах.

Какие цвета любит поэзия А. Вознесенского

colours_n <- c("белый", "чёрный", "черный", "синий", "красный", "желтый", "жёлтый", "зеленый", "зелёный", "оранжевый", "корричневый", "серый", "голубой", "фиолетовый", "лиловый", "пурпурный", "багряный", "фиалковый", "сиреневый", "бирюзоывый", "абрикосовый", "янтарный", "персиковый", "аквамариновый", "алый", "бежевый", "розовый", "бордовый", "бурый", "вересковый", "салатовый", "кобальтовый", "коралловый", "сапфировый", "золотой", "серебряый")
poems_df %>%
  filter(lemma %in% colours_n) %>%
  mutate(lemma = 
           case_when(lemma == "желтый" ~ "жёлтый",
                     lemma == "черный" ~ "чёрный",
                     lemma == "зеленый" ~ "зелёный",
                     TRUE ~ lemma)) %>%
  count(lemma) %>%
  ggplot(aes(x = reorder(lemma, n), y = n, colour = lemma)) +
  geom_bar(stat = "identity", show.legend = F) +
  labs(title = "Наиболее часто используемые цвета в стихах А. Вознесенского", x = "Цвет", y = "Частота") +
  coord_flip() +
  theme_bw()

Какие геометрические формы любит поэзия А. Вознесенского

forms_n <- c("круг", "круглый", "квадрат", "квадратный", "треугольник", "треугольный", "овал", "овальный", "прямоугольник", "прямоугольный", "трапеция", "трапециедальный", "ромб", "паралеллограмм", "шестиугольник", "шар", "пирамида", "пирамиедальный", "цилиндр", "цилиндрический", "сфера", "куб", "призма", "параллелепипед")
poems_df %>%
  filter(lemma %in% forms_n) %>%
  count(lemma) %>%
  wordcloud2(minRotation = -pi/6, maxRotation = -pi/6, minSize = 10, rotateRatio = 1)

Cовместная встречаемость слов

x <-  subset(poems_df, upos %in% c("NOUN", "ADJ"))
cooc <- cooccurrence(x, term = "lemma", group = c("doc_id", "sentence_id"))
head(cooc, 20)  
##        term1   term2 cooc
## 1       друг спасибо   23
## 2      берег  родной   20
## 3      берег   чужой   20
## 4     вечный  память   17
## 5        дом спасибо   14
## 6       пора спасибо   14
## 7        год   новый   12
## 8    женщина спасибо    9
## 9      крона спасибо    9
## 10    мобель  черный    9
## 11    родной   чужой    8
## 12     венка    море    7
## 13     живой    небо    7
## 14 аккордеон спасибо    7
## 15      балл спасибо    7
## 16    басить спасибо    7
## 17   блатный спасибо    7
## 18   бульдик спасибо    7
## 19  вагонный спасибо    7
## 20      враг спасибо    7
wordnetwork <- head(cooc, 20)
wordnetwork <- graph_from_data_frame(wordnetwork)
ggraph(wordnetwork, layout = "fr") +
  geom_edge_link(aes(width = cooc, edge_alpha = cooc), edge_colour = "lightblue") +
  geom_node_text(aes(label = name), col = "darkblue", size = 4) +
  theme_graph(base_family = "Arial Narrow") +
  theme(legend.position = "none") +
  labs(title = "Совместная встречаемость слов", subtitle = "Существительные и прилагательные")
## Warning: Using the `size` aesthetic in this geom was deprecated in ggplot2 3.4.0.
## ℹ Please use `linewidth` in the `default_aes` field and elsewhere instead.
## This warning is displayed once every 8 hours.
## Call `lifecycle::last_lifecycle_warnings()` to see where this warning was
## generated.

Какие слова чаще всего стоят рядом

cooc <- cooccurrence(x$lemma, relevant = x$upos %in% c("NOUN", "ADJ"), skipgram = 1)
head(cooc) # тут лучше только визуалку
##    term1  term2 cooc
## 1  берег  берег   11
## 2 вечный память    9
## 3   боль   боль    8
## 4   луна   луна    8
## 5   друг   друг    7
## 6  новый    год    7
wordnetwork <- head(cooc, 30)
wordnetwork <- graph_from_data_frame(wordnetwork)

ggraph(wordnetwork, layout = "fr") +
  geom_edge_link(aes(width = cooc, edge_colour = "salmon", edge_alpha=0.7), show.legend = F) +
  geom_node_text(aes(label = name), col = "darkgreen", size = 4, angle=15, repel = T) +
  theme_graph(base_family = "Arial Narrow") +
  labs(title = "Слова, стоящие рядом в тексте", subtitle = "Существительные и прилагательные")
## Warning: ggrepel: Repulsion works correctly only for rotation angles multiple
## of 90 degrees

Cинтаксическая разметка

poems_synt <- poems_ann %>% 
  as.data.frame() 

poems_synt_sel <- poems_synt %>% 
  filter(doc_id == "doc17", sentence_id == 12) %>% 
  filter(token != "-")

poems_synt_sel[,c("token", "token_id", "head_token_id", "dep_rel")]
##         token token_id head_token_id   dep_rel
## 1           И        1             2        cc
## 2        было        2             0      root
## 3  величайшей        3             4      amod
## 4       ложью        4             2     nsubj
## 5         Все        5             4     appos
## 6           ,        6             8     punct
## 7         что        7             8     nsubj
## 8    игралось        8             5 acl:relcl
## 9          до        9            10      case
## 10       него       10             8       obl
## 11          !       11             2     punct
textplot_dependencyparser(poems_synt_sel)

Выводы

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

Литература:

Всем заинтересованным в цифровых методах для гуманитарных наук хочу порекомендовать хорошую книгу (Антопольский et al. 2023)

Антопольский, Александр Борисович, Анастасия Александровна Бонч-Осмоловская, Леонид Иосифович Бородкин, Андрей Юрьевич Володин, Динара Амировна Гагарина, Евгений Сергеевич Гришин, Инна Александровна Кижнер, Борис Валерьевич Орехов, Максим Валерьевич Румянцев, and Андрей Владимирович Сметанин. 2023. “Цифровые Гуманитарные Исследования.” СФУ.
Блатт, Бен. 2018. Любимое Слово Набокова–Лиловый. Что Может Рассказать Статистика о Наших Любимых Авторах. Litres.