Кривые тональности художественного текста (английский)

Author

Пронин Дмитрий

Published

December 18, 2025

Введение

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

Чтобы лучше увидеть глобальную динамику, «сырую» кривую сглаживаем фильтром Савицкого—Голея. Похожий подход к анализу эмоциональной динамики текста используется в исследованиях (Dodds et al. 2015).

Подключение библиотек

library(tidyverse)
library(stringr)
library(gutenbergr)   # загрузка текста (импорт данных)
library(janeaustenr)  # запасной источник текста, если Gutenberg недоступен
library(syuzhet)      # готовый словарь/метод сентимент-анализа
library(signal)       # Savitzky–Golay
library(gt)

Загрузка текстовых данных

Иногда Гутенберг недоступен (с некоторых сетей без V*N), поэтому, если ничего не сработает, будем использовать “Годрость и Предубеждение”

book_id <- 1342

text_tbl <- tryCatch(
  gutenberg_download(book_id),
  error = function(e) tibble::tibble(text = character())
)
Determining mirror for Project Gutenberg from
https://www.gutenberg.org/robot/harvest.
Using mirror https://aleph.gutenberg.org.
Warning: ! Could not download a book at https://aleph.gutenberg.org/1/3/4/1342/1342.
ℹ The book may have been archived.
ℹ Alternatively, You may need to select a different mirror.
→ See https://www.gutenberg.org/MIRRORS.ALL for options.
text_raw <- if (nrow(text_tbl) > 0) {
  text_tbl |>
    dplyr::pull(text) |>
    paste(collapse = " ")
} else {
  message("Gutenberg недоступен — используем janeaustenr::austen_books() как запасной источник.")
  janeaustenr::austen_books() |>
    dplyr::filter(book == "Pride & Prejudice") |>
    dplyr::pull(text) |>
    paste(collapse = " ")
}
Gutenberg недоступен — используем janeaustenr::austen_books() как запасной источник.

Очистка текста (регулярные выражения)

text_clean <- text_raw |>
  str_replace_all("[[:digit:]]+", "") |>
  str_squish()

Разбиение на предложения

sentences_vec <- syuzhet::get_sentences(text_clean)

sentences <- tibble::tibble(
  id = seq_along(sentences_vec),
  sentence = sentences_vec
)

Анализ тональности предложений (готовый метод)

Используем syuzhet::get_sentiment() с методом nrc (готовый словарь/лексикон), который корректно работает для английского текста.

sentences <- sentences |>
  dplyr::mutate(sentiment = syuzhet::get_sentiment(sentence, method = "nrc"))

Таблица результатов

sentences |>
  dplyr::slice(1:10) |>
  gt::gt() |>
  gt::tab_header(title = "Пример значений тональности по предложениям")
Пример значений тональности по предложениям
id sentence sentiment
1 PRIDE AND PREJUDICE By Jane Austen Chapter It is a truth universally acknowledged, that a single man in possession of a good fortune, must be in want of a wife. 2
2 However little known the feelings or views of such a man may be on his first entering a neighbourhood, this truth is so well fixed in the minds of the surrounding families, that he is considered the rightful property of some one or other of their daughters. 2
3 "My dear Mr. Bennet," said his lady to him one day, "have you heard that Netherfield Park is let at last?" 1
4 Mr. Bennet replied that he had not. 0
5 "But it is," returned she; "for Mrs. Long has just been here, and she told me all about it." 0
6 Mr. Bennet made no answer. 0
7 "Do you not want to know who has taken it?" 0
8 cried his wife impatiently. 0
9 "_You_ want to tell me, and I have no objection to hearing it." -2
10 This was invitation enough. 1

Сглаживание кривой (Savitzky–Golay)

Важно: для Savitzky–Golay окно n должно быть нечётным и строго больше порядка полинома p. Здесь используется базовый вариант p = 2 и автоматически подбирается безопасное окно.

x <- sentences |>
  dplyr::pull(sentiment) |>
  as.numeric()

len <- length(x)

# Параметры Savitzky–Golay
p <- 2L

# Базовая идея: окно ~ len/15, но с безопасными ограничениями
window <- floor(len / 15)

# минимально разумное окно для p=2: хотя бы 7 (и нечётное)
window <- max(window, 7L)

# делаем окно нечётным
if (window %% 2L == 0L) window <- window + 1L

# если окно оказалось не меньше длины ряда — уменьшаем
if (window >= len) window <- len - 1L
if (window %% 2L == 0L) window <- window - 1L

# финальная страховка: окно должно быть > p
if (window <= p) {
  window <- p + 3L
  if (window %% 2L == 0L) window <- window + 1L
  if (window >= len) window <- max(3L, len - 1L - ((len - 1L) %% 2L == 0L))
}

sentiment_sg <- if (len >= 7L && window > p) {
  signal::sgolayfilt(x = x, p = p, n = window) |> as.numeric()
} else {
  # если ряд очень короткий — показываем «как есть»
  x
}

sentences <- sentences |>
  dplyr::mutate(sentiment_sg = sentiment_sg)

Обычная кривая тональности

ggplot(sentences, aes(x = id, y = sentiment)) +
  geom_line(color = "gray60") +
  labs(
    title = "Сырая кривая тональности",
    x = "Номер предложения",
    y = "Тональность"
  ) +
  theme_minimal()

Сглаженная кривая тональности

ggplot(sentences, aes(x = id, y = sentiment_sg)) +
  geom_line(color = "steelblue", linewidth = 1) +
  labs(
    title = "Сглаженная кривая тональности (Савицкий—Голей)",
    x = "Номер предложения",
    y = "Тональность"
  ) +
  theme_minimal()

Обсуждение результатов

Сырая кривая содержит много локальных колебаний (это нормально: отдельные предложения могут быть эмоционально яркими или наоборот нейтральными). После сглаживания становится проще увидеть «фон» и крупные изменения эмоциональной динамики по мере движения по тексту.

Именно идея смотреть на форму кривой как на характеристику текста (а не только на отдельные слова) используется в работах по эмоциональной динамике текстов и медиа-контента.

References

Dodds, Peter Sheridan, Andrew J. Reagan, Lewis Mitchell, and Christopher M. Danforth. 2015. “The Emotional Arcs of Stories Are Dominated by Six Basic Shapes.” PLOS ONE 10 (12): e0138379. https://arxiv.org/abs/1606.07772.