Обзор исследования

Цель настоящего исследования — проанализировать текст романа Ф.М. Достоевского “Преступление и наказание” и попытаться ответить на следующие вопросы:

  1. Существуют ли закономерности в распределении предложений определённой длины внутри глав художественного произведения?

  2. Возможна ли чёткая классификация предложений художественного произведения по наборам частей речи, из которых они состоят?

  3. Существует ли связь между развитием сюжета произведения и частотой употребления тех или иных частей речи по главам?

Получение и обработка текста

Получение текста

Текст произведения получен с сайта “Интернет-библиотека Алексея Комарова”. Текст, размещённый на сайте, взят из 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)`

Исследование

1. Длина предложений

Подсчёт распределения длин предложений внутри глав:

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") 

2. Рисунок частей речи в предложении

Поставленный вопрос: возможна ли чёткая классификация предложений художественного произведения по наборам частей речи, из которых они состоят?

Перед подсчётом отметим, что под набором частей речи мы понимаем список используемых в предложении частей речи (например, предложению “Он смотрел в окно” соответствует список “местоимение, глагол, предлог, существительное”).

"ОН СМОТРЕЛ В ОКНО" = "МЕСТОИМЕНИЕ ГЛАГОЛ ПРЕДЛОГ СУЩЕСТВИТЕЛЬНОЕ"

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

"СУЩЕСТВИТЕЛЬНОЕ ГЛАГОЛ МЕСТОИМЕНИЕ" == "МЕСТОИМЕНИЕ ГЛАГОЛ СУЩЕСТВИТЕЛЬНОЕ" -> 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).

3. Распределение частей речи по главам

В финальной части исследования мы попробовали установить связь между развитием сюжета произведения и частотой употребления основных частей речи в соответствующих главах. Под основными частями речи мы понимаем:

  • наречия;
  • прилагательные;
  • существительные;
  • глаголы.

Подсчёт распределения основных частей речи по главам.

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. Связь между сюжетом произведения и распределением основных частей речи внутри глав.

Предположение о том, что частота основных частей речи внутри главы может быть связана с интенсивностью развития сюжета, нашло своё подтверждение в ходе исследования. Было обнаружено, что на главы с большим содержанием глаголов (в рамках исследования использовался “коэффициент действия” – отношение числа глаголов к числу остальных неслужебных частей речи) приходятся наиболее важные части повествования, кульминационные моменты. Полученные результаты говорят о перспективности данного направления и о необходимости его дальнейшего развития.

Библиографический список

Достоевский, Фёдор Михайлович. 1989. Собрание Сочинений в Пятнадцати Томах, Том Пятый: Преступление и Наказание. Ленинград: НАУКА. ЛЕНИНГРАДСКОЕ ОТДЕЛЕНИЕ.
Захаров, Владимир Николаевич. 2009. “Текстология Как Технология: Проблемы Текстологии ф. М. Достоевского.”