Частотные словари рассказов из цикла “The Adventures of Sherlock Holmes” Артура Конан Дойла

Йолдуз Хаертдинова

08.12.2023

Считается, что все 12 рассказов о Шерлоке Холмсе из цикла “The Adventures of Sherlock Holmes” объединены идеей борьбы с социальной несправедливостью. Прослеживается ли это в лексике, которую выбирает автор? О чём вообще нам может рассказать лексический пласт текста?

Задачи

  1. Построить общий частотный словарь цикла
  2. Построить частотный словарь каждого отдельного рассказа
  3. Проанализировать сходства/различия, прибегая при необходимости к визуализации данных
  4. Описать черты текста, на которые нам может указать список наиболее часто употребляемых слов

Библиотеки, необходимые для работы с кодом

  library(rvest)
  library(tidyverse)
  library(udpipe)
  library(tidytext)
  library(wordcloud)
  library(RColorBrewer)

Загрузка текста для последующего анализа

Наиболее удобным способом получения текста в данном случае кажется его загрузка в html-формате с применением инструмента SelectorGadget:

  url <- "https://sherlock-holm.es/html/"
  html <- read_html(url)

  title <- html %>%
    html_elements(".story-list:nth-child(4) .story-list-left") %>%
    html_text2()
  
  stable_url <- html %>%
    html_elements(".story-list:nth-child(4) .story-list-left a") %>%
    html_attr("href") %>%
    paste0("https://sherlock-holm.es", .) 
  
  size <- html %>%
    html_elements(".story-list:nth-child(4) .story-list-right") %>%
    html_text2()
  
  adventures <- cbind(title, stable_url, size) %>%
    as_tibble()
  
  get_text <- function(url) {
    text <- read_html(url) %>%
      html_elements("p") %>%
      html_text2() %>%
      str_flatten()
    return(text)
  }

  Sherlock_Holmes_adventures <- adventures %>%
    mutate(text = map_chr(stable_url, get_text)) %>%
      separate(text, into = "text",
               sep = "This text is provided")

Код выше позволил нам построить целую таблицу со всей необходимой информацией (название, ссылка на рассказ, его вес и полный текст). Мы не будем приводить эту таблицу здесь, поскольку она не является целью проекта и не представляет собой возможный материал для анализа. Вместо этого рассмотрим подробнее построение частотных словарей, основывавшееся на этой таблице.

Построение частотных словарей

Было принято решение строить частотные словари по леммам, чтобы исключить повторение одного и того же слова в разных формах и сократить количество встретившихся слов. Для этого мы обращаемся к инструменту UDPipe (пакет мы привязали ранее), устанавливаем и распаковываем модель, по которой текст будет лемматизироваться, а затем приступаем непосредственно к обработке текста:

  english <- udpipe_download_model(language = "english-gum")

  english_gum <- udpipe_load_model(file = "english-gum-ud-2.5-191206.udpipe")

  SH_texts <- Sherlock_Holmes_adventures$text

  SH_ann <- udpipe_annotate(english_gum, SH_texts)

Результат возвращается нам в сложном формате CONLL-U, который мы для удобства переводим в обычную таблицу:

  SH_df <- as_tibble(SH_ann)

Перечисленные действия приводят нас к таблице, в которой перечислены все встретившиеся леммы с подробной информацией по каждой (номер документа, абзаца, часть речи и т.д.).

Саму по себе её анализировать сложно, поэтому мы разделим её на отдельные рассказы и сразу переведём в частотные словари, удалив стоп-слова и пунктуацию:

  Bohemia <- SH_df %>%
    filter(doc_id == "doc1") %>%
    count(lemma) %>%
    arrange(-n) %>%
    filter(!grepl("[[:punct:]]", lemma)) %>%
    anti_join(stop_words, by = c("lemma" = "word"))
  League <- SH_df %>%
    filter(doc_id == "doc2") %>%
    count(lemma) %>%
    arrange(-n) %>%
    filter(!grepl("[[:punct:]]", lemma)) %>%
    anti_join(stop_words, by = c("lemma" = "word"))
  Identity <- SH_df %>%
    filter(doc_id == "doc3") %>%
    count(lemma) %>%
    arrange(-n) %>%
    filter(!grepl("[[:punct:]]", lemma)) %>%
    anti_join(stop_words, by = c("lemma" = "word"))
  Valley_Mystery <- SH_df %>%
    filter(doc_id == "doc4") %>%
    count(lemma) %>%
    arrange(-n) %>%
    filter(!grepl("[[:punct:]]", lemma)) %>%
    anti_join(stop_words, by = c("lemma" = "word"))
  Orange_Pips <- SH_df %>%
    filter(doc_id == "doc5") %>%
    count(lemma) %>%
    arrange(-n) %>%
    filter(!grepl("[[:punct:]]", lemma)) %>%
    anti_join(stop_words, by = c("lemma" = "word"))
  Twisted_Lip <- SH_df %>%
    filter(doc_id == "doc6") %>%
    count(lemma) %>%
    arrange(-n) %>%
    filter(!grepl("[[:punct:]]", lemma)) %>%
    anti_join(stop_words, by = c("lemma" = "word"))
  Blue_Carbuncle <- SH_df %>%
    filter(doc_id == "doc7") %>%
    count(lemma) %>%
    arrange(-n) %>%
    filter(!grepl("[[:punct:]]", lemma)) %>%
    anti_join(stop_words, by = c("lemma" = "word"))
  Speckled_Band <- SH_df %>%
    filter(doc_id == "doc8") %>%
    count(lemma) %>%
    arrange(-n) %>%
    filter(!grepl("[[:punct:]]", lemma)) %>%
    anti_join(stop_words, by = c("lemma" = "word"))
  Thumb <- SH_df %>%
    filter(doc_id == "doc9") %>%
    count(lemma) %>%
    arrange(-n) %>%
    filter(!grepl("[[:punct:]]", lemma)) %>%
    anti_join(stop_words, by = c("lemma" = "word"))
  Bachelor <- SH_df %>%
    filter(doc_id == "doc10") %>%
    count(lemma) %>%
    arrange(-n) %>%
    filter(!grepl("[[:punct:]]", lemma)) %>%
    anti_join(stop_words, by = c("lemma" = "word"))
  Beryl_Coronet <- SH_df %>%
    filter(doc_id == "doc11") %>%
    count(lemma) %>%
    arrange(-n) %>%
    filter(!grepl("[[:punct:]]", lemma)) %>%
    anti_join(stop_words, by = c("lemma" = "word"))
  Copper_Beeches <- SH_df %>%
    filter(doc_id == "doc12") %>%
    count(lemma) %>%
    arrange(-n) %>%
    filter(!grepl("[[:punct:]]", lemma)) %>%
    anti_join(stop_words, by = c("lemma" = "word"))

Код, представленный выше, позволяет нам вывести таблицы абсолютной частотности слов. Выглядят они достаточно просто, приведём в пример часть одного частотного словаря (первые 10 вхождений) по рассказу “A Scandal in Bohemia”, чтобы не занимать всё только ими.

lemma n
I 277
Holmes 41
Majesty 18
hand 17
photograph 16
woman 16
King 15
cry 15
matter 15
Irene 13

Как видим, получился материал, который уже можно анализировать.

Анализ результатов

Наиболее часто встречающимся во всех текстах словом оказывается “I”. Это неудивительно: повествование в рассказах этого цикла ведётся от первого лица, от лица Ватсона. Что странно, только в 8 рассказах из 12 вторым по частоте словом является “Holmes”. Казалось бы логичным, если бы Ватсон, рассказывающий о приключениях своего друга, постоянно упоминал его имя, однако где-то эта система ломается (“The Five Orange Pips”, “The Adventure of the Engineer’s Thumb”, “The Adventure of the Beryl Coronet”, “The Adventure of the Copper Beeches”).

Это легко объясняется образами или героями, который выходят на первый план в соответствии с сюжетом (например, “coronet” в “The Adventure of the Beryl Coronet” упоминается 25 раз, а имя главного героя – 22 раза). Кроме того, “в игру” вступает омонимия, которую тяжело учитывать при лемматизации. Этим, по-видимому, может объясняться относительно высокая частота слова “paper” (26 словоупотреблений) в рассказе “The Five Orange Pips”: хотя конечно, нельзя не признать роль бумаги в сюжете, оказывается, что “papers” (газеты) в результате лемматизации было приведено к единственному числу, из-за чего границы между словами стёрлись. Тем не менее, всё упомянутое всё же закономерно и объясняется сюжетом отдельных рассказов, чего нельзя сказать о “The Adventure of the Engineer’s Thumb” (см. график ниже).

  Thumb_frequent <- Thumb %>%
    filter(n >= 10, n < 100)
    
  ggplot(aes(lemma, n, fill = lemma), 
       data = Thumb_frequent) + 
    geom_bar(stat = "identity") +
    coord_flip()

Как видим, если убрать из списка “I”, самым упоминаемым становится слово “time”, после него идут “door”, “hand”, которые соотносятся с сюжетом. Частота употреблений слова “time” может быть связана с различными устойчивыми выражениями (например, from time to time, in time) или стёршейся омонимией (“this time”), но они не занимают весь текст. Действительно, оказывается, что в рассказе периодически возникает мотив ценного времени (фразы вроде “I shall take up as little of your valuable time as possible”, “my time is of value”). Возможно, это связано с торопливостью преступников или же, наоборот, со спешкой раскрыть дело, однако интересно в первую очередь, что на такую деталь указывает количественный анализ текста.

Также, пока мы обсуждаем этот график нельзя не заметить, что слова “Colonel” и “colonel” воспринимаются программой как разные, из-за чего можно легко пропустить их частотность. Вместе с тем, это один из ключевых героев рассказа, поэтому, как уже говорилось ранее, вполне ожидаемо, что это слово будет часто появляться в статистике.

Таковы небольшие результаты анализа отдельных рассказов. А что мы можем сказать о цикле в целом?

Частотный словарь всего цикла рассказов

Для построения частотного словаря цикла “The Adventures of Sherlock Holmes” мы воспользовались уже известной схемой:

  SH_freq_lemmas <- SH_df %>%
    count(lemma) %>%
    arrange(-n) %>%
    filter(!grepl("[[:punct:]]", lemma)) %>%
    anti_join(stop_words, by = c("lemma" = "word"))

Если мы выберем наиболее часто встречающиеся во всех рассказах слова и построим облако слов, то получим интересные результаты.

  pal <- c("#383838", "#2d5867", "#1e2e4d", "#000d11", "#2c003a")
  wordcloud(SH_freq_lemmas$lemma, SH_freq_lemmas$n,
            min.freq = 60, 
            scale = c(3, 0.8),
            colors = pal,
            random.color = TRUE,
            random.order = TRUE,
            rot.per = 0.3)

Конечно, во всём корпусе текстов самым частотным опять оказывается слово “I”, о причинах мы уже говорили ранее. На первые места выходят имена главных героев и слово “friend”, объединяющее их. Различиные обозначения людей, по-видимому, относятся к обращавшимся за помощью или иным героям произведений, поскольку слова довольно универсальны (“lady”, “Mr”). Важными оказываются обозначения времени суток, что, возможно, связано с жанровой спецификой детективов. Глаголы вроде “cry”, “throw”, “leave” могут говорить о наиболее частых реакциях героев на слова/действия Шерлока Холмса или на события, разворачивающиеся вокруг них.

Наиболее загадочным сейчас кажется слово “hand”: слово встречается в корпусе 185 раз. Само по себе это число, вероятно, небольшое, но это слово – 4 в топе часто встречающихся слов. Почему – нам ещё предстоит ответить.

Выводы

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

Библиография

Doyle (1982)

Doyle, Arthur Conan. 1982. The Adventures of Sherlock Holmes. https://sherlock-holm.es/html/.