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