Для анализа я выбрала сборник рассказов “Beasts and Super-Beasts” английского писателя Гектора Хью Манро, использовавшего псевдоним Саки. Источником будет Wikisource.
“Beasts and Super-Beasts” – это последний сборник рассказов автора, опубликованный незадолго до его гибели в Первой мировой войне. В некоторый степени является пародией на “Man and Superman” Бернарда Шоу. Саки был известен своим цинизмом и черным юмором, и в рассказах этого сборника он изображает призраков, обман, жуткие пейзажи и демонических детей, создавая тревожную атмосферу, полную неожиданных поворотов.
Манро воспитывался под плотным и душным надзором двух своих теток, поэтому в его рассказах тетушки разных персонажей — весьма гнусные и противные особы. Дети часто обводят их вокруг пальца, запугиванием или манипуляциями добиваясь желаемого.
Одним из моих интересов в работе над данным сборником является описание детей и взрослых в рассказах.
Итак, загружаем все нужные в работе библиотеки и отдаем ссылку:
library(tidyverse)
library(tidytext)
library(rvest)
library(udpipe)
library(tokenizers)
library(wordcloud2)
library(igraph)
library(ggraph)
html <- read_html("https://en.wikisource.org/wiki/Beasts_and_Super-Beasts")
Я хочу создать тиббл, в котором будут столбцы с названием рассказа и ссылкой на него. Сначала создаю отдельные столбцы:
title <- html %>%
html_elements("#mw-content-text li a") %>%
html_text2() %>%
as_tibble_col(column_name = "title")
stable_url <- html %>%
html_elements("#mw-content-text li a") %>%
html_attr("href") %>%
paste0("https://en.wikisource.org/", .) %>%
as_tibble_col(column_name = "stable_url")
А затем сшиваю их, удаляя рассказ, который мне не нужен, и забираю ссылки на оставшиеся:
beasts_tbl <- cbind(title, stable_url) %>%
filter(row_number() != 31)
urls <- beasts_tbl %>%
pull(stable_url)
Я хочу записать каждый рассказ в отдельный txt-файл специально созданной папки. Пишу две функции, которые смогут дать мне название и текст каждого рассказа:
get_title <- function(url) {
read_html(url) %>%
html_element("#header_section_text") %>%
html_text2()
}
get_text <- function(url) {
read_html(url) %>%
html_elements("p") %>%
html_text2() %>%
paste(collapse = "\n")
}
А теперь запускаю цикл, который соберет рассказы и запишет их в нужную папку под соответствующим названием:
for (url in urls) {
path_to_file <- get_title(url) %>%
paste0("./Munro/", .) %>%
paste0(., ".txt")
# файл будет называться так же как и рассказ
text <- get_text(url) %>%
str_remove(., pattern = "From Beasts and Super-Beasts\n") # убираю из текста не нужную мне отсылку на сборник
write_lines(text, file = path_to_file)
}
Для лемматизации я буду использовать модель english-gum.
udpipe_download_model(language = "english-gum")
eng_gum <- udpipe_load_model(file = "english-gum-ud-2.5-191206.udpipe")
Захожу в свою папку с рассказами, собираю список файлов для анализа и создаю новый оператор, который мне вскоре понадобится.
stories <- list.files(path = "./Munro", pattern = ".txt")
`%notin%` <- Negate(`%in%`)
Создаю функцию для аннотирования и применяю ее к файлам из своего списка:
get_ann <- function(story) {
read_file(story) %>%
udpipe_annotate(eng_gum, ., doc_id = str_remove(story, pattern = ".txt")) %>%
# doc_id будет равен названию рассказа
as_tibble() %>%
select(-sentence, -paragraph_id)
}
beasts_anns <- map_df(stories, get_ann)
В результате получается вот такой тиббл:
| doc_id | sentence_id | token_id | token | lemma | upos | xpos | feats | head_token_id | dep_rel | deps | misc |
|---|---|---|---|---|---|---|---|---|---|---|---|
| A Defensive Diamond | 1 | 1 | Treddleford | Treddleford | PROPN | NNP | Number=Sing | 2 | nsubj | NA | NA |
| A Defensive Diamond | 1 | 2 | sat | sit | VERB | VBD | Mood=Ind|Tense=Past|VerbForm=Fin | 0 | root | NA | NA |
| A Defensive Diamond | 1 | 3 | in | in | ADP | IN | NA | 6 | case | NA | NA |
| A Defensive Diamond | 1 | 4 | an | a | DET | DT | Definite=Ind|PronType=Art | 6 | det | NA | NA |
| A Defensive Diamond | 1 | 5 | easeful | easeful | ADJ | JJ | Degree=Pos | 6 | amod | NA | NA |
| A Defensive Diamond | 1 | 6 | arm-chair | arm-chair | NOUN | NN | Number=Sing | 2 | obl | NA | NA |
| A Defensive Diamond | 1 | 7 | in | in | ADP | IN | NA | 8 | case | NA | NA |
| A Defensive Diamond | 1 | 8 | front | front | NOUN | NN | Number=Sing | 2 | obl | NA | NA |
| A Defensive Diamond | 1 | 9 | of | of | ADP | IN | NA | 12 | case | NA | NA |
| A Defensive Diamond | 1 | 10 | a | a | DET | DT | Definite=Ind|PronType=Art | 12 | det | NA | NA |
| A Defensive Diamond | 1 | 11 | slumberous | slumberous | ADJ | JJ | Degree=Pos | 12 | amod | NA | NA |
| A Defensive Diamond | 1 | 12 | fire | fire | NOUN | NN | Number=Sing | 8 | nmod | NA | SpaceAfter=No |
Я хочу узнать количественное соотношение частей речи, использованных в сборнике, и создать столбиковую диаграмму для представления результата:
beasts_anns %>%
group_by(upos) %>%
count() %>%
filter(upos %notin% c("PUNCT", "X", "SYM")) %>% # здесь и пригождается новый оператор
ggplot(aes(x = reorder(upos, n), y = n, fill = upos)) +
geom_bar(stat = "identity", show.legend = F) +
labs(x = "Part of Speech", title = "Parts of Speech", subtitle = "in Short Stories by H.H. Munro") +
coord_flip() +
theme_bw()
Как выясняется, существительные сильно доминируют. Глаголы отстают от них больше чем на 3000 единиц. Прилагательных и наречий не так много, как мне казалось при прочтении рассказов. Числительных не слишком много, и при более близком изучении становится понятно, что большая часть из них обозначает возраст персонажей-детей. Самый большой - пятнадцать лет.
Какие леммы чаще всего встречаются в сборнике? Давайте выясним, используя следующий код:
top_lemmas <- beasts_anns %>%
filter(upos %in% c("NOUN", "ADJ")) %>%
# оставляю только прилагательные и существительные
count(lemma) %>%
arrange(-n) %>%
head(100)
Для наглядности сразу же создаю облако:
pal <- c('#522E75', '#107050', '#D50B53', '#05328E', '#F05837')
shuffled_pal <- sample(pal)
wordcloud2(top_lemmas,
size = 0.7,
fontFamily = 'Monotype Corsiva',
color = rep(shuffled_pal, 20), backgroundColor = '#E6EFF3',
shape = 'diamond',
minRotation = -pi/8, maxRotation = -pi/8)
Теперь посмотрим, какие слова чаще встречаются в одном предложении:
x <- subset(beasts_anns, upos %in% c("NOUN", "ADJ"))
cooc <- x %>%
cooccurrence(term = "lemma", group = c("doc_id", "sentence_id")) %>%
head(50)
wordnetwork <- graph_from_data_frame(cooc)
ggraph(wordnetwork, layout = "fr") +
geom_edge_link(aes(width = cooc, edge_alpha = cooc), edge_colour = "lightgreen") +
geom_node_text(aes(label = name), col = "darkblue", size = 4) +
theme(legend.position = "none") +
labs(title = "Сooccurrence", subtitle = "Adjectives and Nouns")
Ожидаемо, в центре внимания у нас дети, взаимодействующие с молодыми и не очень людьми. Примечательно, что ниточка к story тянется именно от girl. По моим наблюдениям, именно девочки сочиняют истории, часто загадочные или жутковатые, в рассказах Саки. Отдельно можно заметить слово aunt, встречающееся вместе с gooseberry garden (предполагаю, благодаря рассказу “The Lumber Room”) и garden party – любимые вещи тетушек Манро.
Напоследок я решила взглянуть на биграммы со словом beast, не зря же оно вынесено в название сборника. Собираю текст всех историй в один тиббл, делю их на биграммы и с помощью str_detect() выбираю только те, в которых есть интересующее слово:
get_story <- function(story) {
read_file(story) %>%
as_tibble()
}
beasts_ngrams <- map_df(stories, get_story) %>%
unnest_tokens(output = ngram, input = value, token = "ngrams", n = 2) %>%
filter(str_detect(ngram, pattern = "beast") == T) %>%
count(ngram) %>%
arrange(-n)
Как выяснилось, beasts часто little, и, судя по общему настроению сборника, являются хитрыми детьми, любящими поиздеваться над некоторыми взрослыми.
Beasts and Super-Beasts. (2023, February 25). In Wikisource . Retrieved 21:37, December 7, 2023, from https://en.wikisource.org/w/index.php?title=Beasts_and_Super-Beasts&oldid=13024323