Цель настоящего исследования — проанализировать текст романа Ф.М. Достоевского “Преступление и наказание” и попытаться ответить на следующие вопросы:
Существуют ли закономерности в распределении предложений определённой длины внутри глав художественного произведения?
Возможна ли чёткая классификация предложений художественного произведения по наборам частей речи, из которых они состоят?
Существует ли связь между развитием сюжета произведения и частотой употребления тех или иных частей речи по главам?
Текст произведения получен с сайта “Интернет-библиотека Алексея Комарова”. Текст, размещённый на сайте, взят из 15-томного издания 1989 года (Достоевский 1989).
Исследователи творчества Достоевского отмечают, что существующие издания (как прижизненные, так и посмертные) зачастую искажают авторскую волю писателя (Захаров 2009). На момент проведения исследования известно о единственной серьёзной попытке издать канонические, сверенные с рукописями тексты Ф.М. Достоевского. С 1995 года текстологическая группа филологического факультета Петрозаводского государственного университета работает над изданием полного собрания сочинений Достоевского с подлинными текстами. Однако данная работа на настоящий момент ещё не закончена.
Получение перечня глав романа и ссылок на страницы с их содержанием.
library(rvest)
library(purrr)
library(tidyverse)
## ── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
## ✔ dplyr 1.1.3 ✔ readr 2.1.4
## ✔ forcats 1.0.0 ✔ stringr 1.5.0
## ✔ ggplot2 3.4.3 ✔ tibble 3.2.1
## ✔ lubridate 1.9.2 ✔ tidyr 1.3.0
## ── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
## ✖ dplyr::filter() masks stats::filter()
## ✖ readr::guess_encoding() masks rvest::guess_encoding()
## ✖ dplyr::lag() masks stats::lag()
## ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors
library(stringr)
library(tidytext)
library(udpipe)
# ---------------------------------------------------
# СКАЧИВАЕМ И ОБРАБАТЫВАЕМ "ПРЕСТУПЛЕНИЕ И НАКАЗАНИЕ"
# ---------------------------------------------------
dost_url <- "https://ilibrary.ru/text/69/index.html"
# ПОЛУЧАЕМ СОДЕРЖАНИЕ СО ССЫЛКАМИ НА ГЛАВЫ
dost_contents <- read_html(dost_url, encoding = "windows-1251") %>%
html_elements(".t")
dost_contents <- dost_contents[2]
dost_contents <- dost_contents%>%
html_elements("a")
dost_tibble <- tibble(
parts = numeric(),
chapters = character(),
url = character(),
content = character(),
)
part <- 0
for (elem in seq_along(dost_contents)) {
current_element <- dost_contents[elem]
chapter <- html_text2(current_element)
if (html_text2(current_element) == "") {
next
}
if (grepl("»", chapter, fixed = TRUE)) {
next
}
if (grepl("Часть", chapter, fixed = TRUE)) {
part <- part + 1
next
}
if (grepl("Эпилог", chapter, fixed = TRUE)) {
part <- part + 1
next
}
link <- html_attr(current_element, "href")
dost_tibble <- add_row(dost_tibble, parts = part, chapters = chapter, url = link)
}
dost_tibble
## # A tibble: 41 × 4
## parts chapters url content
## <dbl> <chr> <chr> <chr>
## 1 1 I /text/69/p.1/index.html <NA>
## 2 1 II /text/69/p.2/index.html <NA>
## 3 1 III /text/69/p.3/index.html <NA>
## 4 1 IV /text/69/p.4/index.html <NA>
## 5 1 V /text/69/p.5/index.html <NA>
## 6 1 VI /text/69/p.6/index.html <NA>
## 7 1 VII /text/69/p.7/index.html <NA>
## 8 2 I /text/69/p.8/index.html <NA>
## 9 2 II /text/69/p.9/index.html <NA>
## 10 2 III /text/69/p.10/index.html <NA>
## # ℹ 31 more rows
Извлечение текста каждой главы.
# ИДЁМ ПО СОДЕРЖАНИЮ И СОБИРАЕМ ТЕКСТЫ ГЛАВ
get_text <- function(url) {
url <- paste0("https://ilibrary.ru", url)
print(paste("Парсим", url))
page <- read_html(url, encoding = "windows-1251")
paragraphs <- html_elements(page, ".p")
txt <- ""
for (p in (1 : (length(paragraphs)-1))) {
paragraph <- html_text2(paragraphs[p]) %>%
str_replace_all("\u0097", "--")
txt <- paste(txt, paragraph)
}
Sys.sleep(0.1)
return(txt)
}
dost_tibble <- dost_tibble %>%
mutate(content = map_chr(url, get_text))
## [1] "Парсим https://ilibrary.ru/text/69/p.1/index.html"
## [1] "Парсим https://ilibrary.ru/text/69/p.2/index.html"
## [1] "Парсим https://ilibrary.ru/text/69/p.3/index.html"
## [1] "Парсим https://ilibrary.ru/text/69/p.4/index.html"
## [1] "Парсим https://ilibrary.ru/text/69/p.5/index.html"
## [1] "Парсим https://ilibrary.ru/text/69/p.6/index.html"
## [1] "Парсим https://ilibrary.ru/text/69/p.7/index.html"
## [1] "Парсим https://ilibrary.ru/text/69/p.8/index.html"
## [1] "Парсим https://ilibrary.ru/text/69/p.9/index.html"
## [1] "Парсим https://ilibrary.ru/text/69/p.10/index.html"
## [1] "Парсим https://ilibrary.ru/text/69/p.11/index.html"
## [1] "Парсим https://ilibrary.ru/text/69/p.12/index.html"
## [1] "Парсим https://ilibrary.ru/text/69/p.13/index.html"
## [1] "Парсим https://ilibrary.ru/text/69/p.14/index.html"
## [1] "Парсим https://ilibrary.ru/text/69/p.15/index.html"
## [1] "Парсим https://ilibrary.ru/text/69/p.16/index.html"
## [1] "Парсим https://ilibrary.ru/text/69/p.17/index.html"
## [1] "Парсим https://ilibrary.ru/text/69/p.18/index.html"
## [1] "Парсим https://ilibrary.ru/text/69/p.19/index.html"
## [1] "Парсим https://ilibrary.ru/text/69/p.20/index.html"
## [1] "Парсим https://ilibrary.ru/text/69/p.21/index.html"
## [1] "Парсим https://ilibrary.ru/text/69/p.22/index.html"
## [1] "Парсим https://ilibrary.ru/text/69/p.23/index.html"
## [1] "Парсим https://ilibrary.ru/text/69/p.24/index.html"
## [1] "Парсим https://ilibrary.ru/text/69/p.25/index.html"
## [1] "Парсим https://ilibrary.ru/text/69/p.26/index.html"
## [1] "Парсим https://ilibrary.ru/text/69/p.27/index.html"
## [1] "Парсим https://ilibrary.ru/text/69/p.28/index.html"
## [1] "Парсим https://ilibrary.ru/text/69/p.29/index.html"
## [1] "Парсим https://ilibrary.ru/text/69/p.30/index.html"
## [1] "Парсим https://ilibrary.ru/text/69/p.31/index.html"
## [1] "Парсим https://ilibrary.ru/text/69/p.32/index.html"
## [1] "Парсим https://ilibrary.ru/text/69/p.33/index.html"
## [1] "Парсим https://ilibrary.ru/text/69/p.34/index.html"
## [1] "Парсим https://ilibrary.ru/text/69/p.35/index.html"
## [1] "Парсим https://ilibrary.ru/text/69/p.36/index.html"
## [1] "Парсим https://ilibrary.ru/text/69/p.37/index.html"
## [1] "Парсим https://ilibrary.ru/text/69/p.38/index.html"
## [1] "Парсим https://ilibrary.ru/text/69/p.39/index.html"
## [1] "Парсим https://ilibrary.ru/text/69/p.40/index.html"
## [1] "Парсим https://ilibrary.ru/text/69/p.41/index.html"
dost_tbl <- dost_tibble %>%
select(-url) %>%
mutate(id = 1:nrow(dost_tibble)) %>%
mutate(doc_num = paste0("doc", as.character(id))) %>%
select(-id)
Результат выполнения кода — таблица с содержанием глав романа.
dost_tbl
## # A tibble: 41 × 4
## parts chapters content doc_num
## <dbl> <chr> <chr> <chr>
## 1 1 I " В начале июля, в чрезвычайно жаркое время, под вече… doc1
## 2 1 II " Раскольников не привык к толпе и, как уже сказано, … doc2
## 3 1 III " Он проснулся на другой день уже поздно, после трево… doc3
## 4 1 IV " Письмо матери его измучило. Но относительно главней… doc4
## 5 1 V " «Действительно, я у Разумихина недавно еще хотел бы… doc5
## 6 1 VI " Впоследствии Раскольникову случилось как-то узнать,… doc6
## 7 1 VII " Дверь, как и тогда, отворилась на крошечную щелочку… doc7
## 8 2 I " Так пролежал он очень долго. Случалось, что он как … doc8
## 9 2 II " «А что, если уж и был обыск? Что, если их как раз у… doc9
## 10 2 III " Он, однако ж, не то чтоб уж был совсем в беспамятст… doc10
## # ℹ 31 more rows
Лемматизация текста проводится с помощью библиотеки udpipe, модель russian-gsd.
# ЛЕММАТИЗАЦИЯ
# udpipe_download_model(language = "russian-gsd")
russian_gsd <- udpipe_load_model(file = "russian-gsd-ud-2.5-191206.udpipe")
dost_ann <- udpipe_annotate(russian_gsd, dost_tbl$content) %>%
as_tibble()
dost_source <- dost_ann %>%
select(doc_id, sentence_id, upos) %>%
filter(upos != "PUNCT")
# ДОБАВЛЯЕМ ЧАСТИ И ГЛАВЫ
parts_and_chapters <- dost_tbl %>%
select(doc_num, parts, chapters)
colnames(parts_and_chapters) <- c("doc_id", "part", "chapter")
dost_source <- left_join(dost_source, parts_and_chapters) %>%
relocate(doc_id, .before = sentence_id) %>%
relocate(part, .before = sentence_id) %>%
relocate(chapter, .before = sentence_id) %>%
select(part, chapter, sentence_id, upos)
## Joining with `by = join_by(doc_id)`
Подсчёт распределения длин предложений внутри глав:
sent_length <- dost_source %>%
select(-upos) %>%
group_by(part, chapter) %>%
count(sentence_id) %>%
mutate(p_ch = paste0(part, "-", chapter))
ggplot(sent_length, aes(x=sentence_id, y=n)) +
geom_col() +
facet_wrap(~p_ch) +
theme_test()
На графике распределения длин предложений по главам не наблюдается каких-либо выраженных закономерностей.
Также для информации приводим распределение длин предложений по всему тексту романа.
total_sent_length <- dost_source %>%
select(-upos) %>%
group_by(part, chapter) %>%
count(sentence_id) %>%
ungroup() %>%
select(n)
ggplot(total_sent_length, aes(n)) +
geom_histogram(binwidth = 1, color="#9999CC", fill="#9999CC") +
theme_test() +
scale_x_continuous(breaks = seq(0, 200, by = 10)) +
ylab("Number of sentences") +
xlab("Words in a sentence")
Поставленный вопрос: возможна ли чёткая классификация предложений художественного произведения по наборам частей речи, из которых они состоят?
Перед подсчётом отметим, что под набором частей речи мы понимаем список используемых в предложении частей речи (например, предложению “Он смотрел в окно” соответствует список “местоимение, глагол, предлог, существительное”).
"ОН СМОТРЕЛ В ОКНО" = "МЕСТОИМЕНИЕ ГЛАГОЛ ПРЕДЛОГ СУЩЕСТВИТЕЛЬНОЕ"
При этом будем считать идентичными списки, состоящие из одних и тех же частей речи, независимо от порядка частей речи в предложении (т.е. будем относить к одному типу списки “существительное, глагол, местоимение” и “местоимение, глагол, существительное”).
"СУЩЕСТВИТЕЛЬНОЕ ГЛАГОЛ МЕСТОИМЕНИЕ" == "МЕСТОИМЕНИЕ ГЛАГОЛ СУЩЕСТВИТЕЛЬНОЕ" -> TRUE
Отдельно отметим, что в первой версии исследования учитывался порядок частей речи в предложении, однако, при таком подходе не формировалось достаточное количество сколь-нибудь многочисленных классов.
В результате подсчёта образуется таблица, в которой для каждого “рисунка частей речи” указано число предложений, соответствующих ему.
parts_order <- dost_source %>%
group_by(part, chapter, sentence_id) %>%
arrange(part, chapter, sentence_id, upos) %>%
mutate(order = paste0(upos, collapse = ", ")) %>%
ungroup() %>%
select(-upos) %>%
unique() %>%
select(order) %>%
count(order) %>%
arrange(-n)
parts_order
## # A tibble: 9,184 × 2
## order n
## <chr> <int>
## 1 PROPN 61
## 2 NOUN 45
## 3 PRON, VERB 31
## 4 ADV, PRON, VERB 28
## 5 DET, NOUN 26
## 6 ADJ, NOUN 25
## 7 NOUN, PROPN 24
## 8 NOUN, VERB 23
## 9 ADV, VERB 20
## 10 VERB 20
## # ℹ 9,174 more rows
ggplot(parts_order, aes(x=n)) +
geom_histogram(color="#9999CC", fill="#9999CC") +
scale_x_continuous(breaks = seq(0, 60, by = 5)) +
theme_test() +
ylab("Number of sentences") +
xlab("Number of occurences in the text")
## `stat_bin()` using `bins = 30`. Pick better value with `binwidth`.
Как видно из графика, повторяющиеся “рисунки частей речи” для “Преступления и наказания” редки. Большая часть произведения написана с помощью длинных и уникальных по своему составу предложений (10184 предложений из 10378).
В финальной части исследования мы попробовали установить связь между развитием сюжета произведения и частотой употребления основных частей речи в соответствующих главах. Под основными частями речи мы понимаем:
Подсчёт распределения основных частей речи по главам.
parts_of_speech_by_chapters <- dost_source %>%
filter(upos %in% c("ADV", "ADJ", "NOUN", "VERB")) %>%
group_by(part, chapter, upos) %>%
count(part, chapter, upos) %>%
mutate(p_ch = paste0(part, "-", chapter))
ggplot(parts_of_speech_by_chapters, aes(x=upos, y=n, fill=upos)) +
geom_col() +
facet_wrap(~p_ch) +
theme_test() +
theme(axis.text.x = element_text(angle = 90, vjust = 0.5, hjust=1)) +
scale_fill_manual(values=c("gray", "#9999CC", "#66CC99", "#c1121f"),
name = "Parts of speech")
Наиболее интересным на графике представляется распределение распределение существительных и глаголов. Можно заметить, что в большей части глав существительные являются наиболее распространённой частью речи, однако в некоторых главах число глаголов приближается к числу существительных, а в четвёртой главе пятой части даже превышает его. Такое соотношение может быть объяснено тем, что в данной главе описан диалог, в ходе которого Родион признаётся Соне в содеянном. Большое число глаголов в репликах Родиона создают ощущение отрывистости, напряжённой быстроты его речи.
А знаешь ли, Соня, что низкие потолки и тесные комнаты душу и ум теснят! О, как ненавидел я эту конуру! А все-таки выходить из нее не хотел. Нарочно не хотел! По суткам не выходил, и работать не хотел, и даже есть не хотел, всё лежал.
Попробуем выяснить, связано ли отношение числа глаголов к числу других слов с развитием сюжета. Подсчитаем “коэффициент действия” (отношение числа глаголов к числу других основных частей речи) по главам.
verb_coeff <- parts_of_speech_by_chapters %>%
ungroup() %>%
pivot_wider(names_from = upos, values_from = n) %>%
mutate(verb_c = VERB / (ADJ + ADV + NOUN)) %>%
select(p_ch, verb_c)
normalize <- function(value) {
z <- (value - min(verb_coeff$verb_c)) / (max(verb_coeff$verb_c) - min(verb_coeff$verb_c)) %>%
round(2)
return(z)
}
verb_coeff <- verb_coeff %>%
select(p_ch, verb_c) %>%
mutate(verb_c_normalized = map_dbl(verb_c, normalize)) %>%
left_join(parts_of_speech_by_chapters)
## Joining with `by = join_by(p_ch)`
Отобразим на графике распределения частей речи, приведённом выше, “коэффициент действия” с помощью слоя прозрачности (чем ярче график, тем выше “коэффициент” в соответствующей главе).
ggplot(verb_coeff, aes(x=upos, y=n, fill=upos, alpha = verb_c)) +
geom_col() +
facet_wrap(~p_ch) +
theme_test() +
scale_fill_manual(values=c("gray", "#9999CC", "#66CC99", "#c1121f"),
name = "Parts of speech") +
scale_alpha(guide = 'none') +
theme(axis.text.x = element_text(angle = 90, vjust = 0.5, hjust=1))
Для большей отчётливости добавим графику контрастности. Посчитаем средний для всего произведения “коэффициент действия” и разделим главы на три дискретные группы в зависимости от отношения их “коэффициентов” к общему.
# Считаем общий коэффициент действия
total_parts <- dost_source %>%
filter(upos %in% c("ADV", "ADJ", "NOUN", "VERB")) %>%
count(upos)
total_verb_coeff <- total_parts$n[4] / (total_parts$n[1] + total_parts$n[2] + total_parts$n[3])
contrast <- function(value) {
if (value < total_verb_coeff) {
return(0)}
if (value < total_verb_coeff + (1 - total_verb_coeff) / 2) {
return(0.5)}
if (value > total_verb_coeff + (1 - total_verb_coeff) / 2) {
return(1)}
}
v <- verb_coeff %>%
mutate(verb_c_contrast = map_dbl(verb_c_normalized, contrast))
ggplot(v, aes(x=upos, y=n, fill=upos, alpha = verb_c_contrast)) +
geom_col() +
facet_wrap(~p_ch) +
theme_test() +
scale_fill_manual(values=c("gray", "#9999CC", "#66CC99", "#c1121f"),
name = "Parts of speech") +
scale_alpha(guide = 'none') +
theme(axis.text.x = element_text(angle = 90, vjust = 0.5, hjust=1))
Как можно видеть из графика, наибольшим “коэффициентом действия” обладают следующие главы (расположены в хронологическом порядке, главы с наивысшим коэффициентом выделены полужирным начертанием, в таблице приводится нормализованное значение коэффициента):
| Глава | “Коэффициент действия” | Краткое содержание, основные события |
|---|---|---|
| Часть 1, глава VII | 0.746 | Раскольников убивает старуху-процентщицу и её сестру и сбегает с места преступления |
| Часть 4, глава II | 0.787 | Встреча Дуни, её матери, Раскольникова и Разумихина с Лужиным. Ссора Дуни с Лужиным |
| Часть 4, глава IV | 0.723 | Беседа Раскольникова и Сони, Раскольников поражён бедственным положением её семьи, обещает прийти на следующий день и рассказать, кто убил Лизавету |
| Часть 4, глава VI | 1.017 | Кульминация напряжённого разговора Раскольникова с Порфирием, самооговор одного из маляров, Раскольникова отпускают из конторы следователя |
| Часть V, глава III | 0.774 | Лужин устраивает скандал, обвиняя Соню в краже денег, обнаружение подброшенных денег, Мармеладовых прогоняют с квартиры |
| Часть V, глава IV | 1.003 | Раскольников признаётся Соне в убийствах |
| Часть VI, глава I | 0.838 | Раскольников в полузабытье. Пришедший Разумихин рассказывает о письме, взволновавшем Дуню, после Разумихина приходит Порфирий, который собирается сообщить, что подозревает Раскольникова в убийстве |
| Часть VI, глава V | 0.732 | Свидригайлов разговаривает с Дуней, предлагает спасти Раскольникова в обмен на её любовь. Дуня пытается уйти, несколько раз стреляет в Свидригайлова, не попадает, просит отпустить её и уходит |
Каждая из приведённых в таблице глав играет важную роль в сюжете произведения, способствует его развитию, содержит описание кульминационных моментов. Ещё две главы, обладающие на наш взгляд особой важностью для произведения — главы VI и VIII шестой части (самоубийство Свидригайлова и признание Раскольникова в убийствах соответственно) — не попали в таблицу, поскольку обладают меньшим “коэффициентом действия” (нормализованные значения 0.384 и 0.678 соответственно). Такое значение можно объяснить тем, что данные главы расположены уже после самой напряжённой и насыщенной действием части романа — четвёртой и пятой частей, и высокий “коэффициент действия” в них нарушал бы общую композицию романа.
Результаты анализа текста “Преступления и наказания” позволяют дать следующие ответы на поставленные исследовательские вопросы:
1. Закономерности в распределении предложений определённой длины внутри главы художественного произведения.
В ходе нашего исследования никаких значимых закономерностей подобного рода в соответствующем произведении Ф.М. Достоевского не выявлено. Это позволяет заключить, что распределение предложений по длинам не является элементом авторского стиля Ф.М. Достоевского, а также предположить, что, в целом, маловероятно встретить в художественной литературе какие-либо закономерности в распределении предложений по длинам в выразительных целях. Данное предположение, впрочем, нуждается в дальнейшей проверке.
2. Повторяемость “рисунка частей речи” в предложениях, а также возможность классификации предложений художественного произведения по такому “рисунку”.
В ходе исследования было установлено, что “Преступление и наказание” написано большей частью уникальными с т.з. состава частей речи предложениями. Соответственно, классификация предложений произведения по составу частей речи лишена смысла. Тем не менее, данное направление исследований остаётся перспективным: можно исследовать рисунок частей речи не всех предложений произведения, а только неких стандартных видов предложений или их частей (например, деепричастных оборотов или предложений, содержащих составные именные сказуемые). Мы полагаем, что такое исследование, а также сравнительный анализ нескольких произведений разных авторов может выявить интересные закономерности.
3. Связь между сюжетом произведения и распределением основных частей речи внутри глав.
Предположение о том, что частота основных частей речи внутри главы может быть связана с интенсивностью развития сюжета, нашло своё подтверждение в ходе исследования. Было обнаружено, что на главы с большим содержанием глаголов (в рамках исследования использовался “коэффициент действия” – отношение числа глаголов к числу остальных неслужебных частей речи) приходятся наиболее важные части повествования, кульминационные моменты. Полученные результаты говорят о перспективности данного направления и о необходимости его дальнейшего развития.