library(tidyverse)
library(stringr)
library(gutenbergr) # загрузка текста (импорт данных)
library(janeaustenr) # запасной источник текста, если Gutenberg недоступен
library(syuzhet) # готовый словарь/метод сентимент-анализа
library(signal) # Savitzky–Golay
library(gt)Кривые тональности художественного текста (английский)
Введение
В данном проекте проводится базовый анализ тональности художественного текста. Мы считаем тональность по предложениям, а затем строим кривую тональности (по оси X — номер предложения, по оси Y — численная тональность).
Чтобы лучше увидеть глобальную динамику, «сырую» кривую сглаживаем фильтром Савицкого—Голея. Похожий подход к анализу эмоциональной динамики текста используется в исследованиях (Dodds et al. 2015).
Подключение библиотек
Загрузка текстовых данных
Иногда Гутенберг недоступен (с некоторых сетей без 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()Обсуждение результатов
Сырая кривая содержит много локальных колебаний (это нормально: отдельные предложения могут быть эмоционально яркими или наоборот нейтральными). После сглаживания становится проще увидеть «фон» и крупные изменения эмоциональной динамики по мере движения по тексту.
Именно идея смотреть на форму кривой как на характеристику текста (а не только на отдельные слова) используется в работах по эмоциональной динамике текстов и медиа-контента.