R Academy · Урок

Анализ тональности в R

Объединяйте токены со словарями тональности, чтобы измерять положительный и отрицательный тон

Урок 3 из 413 шагов

«Анализ тональности в R» — бесплатный урок R Academy на CoddyKit. Это урок 3 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения R Academy, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс R Academy содержит 4 уроков всего.

Обзор анализа тональности

Анализ тональности определяет эмоциональную окраску текста. Подход tidytext сопоставляет отдельные слова с лексиконом тональности и объединяет полученные оценки. Доступны три встроенных лексикона: AFINN (числовые оценки), Bing (положительная или отрицательная тональность) и NRC (категории эмоций).

library(tidytext)

# Available sentiment lexicons
# AFINN: -5 to +5 numeric score
# Bing:  binary positive/negative
# NRC:   emotion categories (joy, fear, anger, ...)

# Preview AFINN
afinn_sample <- get_sentiments('afinn')
cat('AFINN rows:', nrow(afinn_sample), '\n')
cat('Score range:', range(afinn_sample$value), '\n')
print(head(afinn_sample, 5))

get_sentiments('afinn')

Лексикон AFINN присваивает 2 477 английским словам целочисленные оценки от −5 (очень отрицательная тональность) до +5 (очень положительная). Его составил Финн Оруп Нильсен; лексикон хорошо подходит для текстов из социальных сетей и отзывов.

library(tidytext)
library(dplyr)

afinn <- get_sentiments('afinn')

# Most positive and most negative words
cat('Most positive words:\n')
print(afinn |> slice_max(value, n = 5))

cat('\nMost negative words:\n')
print(afinn |> slice_min(value, n = 5))

# Score distribution
cat('\nScore distribution:\n')
print(table(afinn$value))

get_sentiments('bing')

Лексикон Bing (Bing Liu и другие авторы) классифицирует 6 786 слов как положительные или отрицательные. Он больше AFINN и хорошо подходит для анализа отзывов о товарах и обратной связи от клиентов, когда достаточно простой полярности.

library(tidytext)
library(dplyr)

bing <- get_sentiments('bing')

cat('Bing lexicon size:', nrow(bing), '\n')
cat('Positive words:', sum(bing$sentiment == 'positive'), '\n')
cat('Negative words:', sum(bing$sentiment == 'negative'), '\n')

# Sample positive and negative words
cat('\nSample positive:', head(bing$word[bing$sentiment == 'positive'], 8), '\n')
cat('Sample negative:', head(bing$word[bing$sentiment == 'negative'], 8), '\n')

inner_join: оценка слов

inner_join(tokens, afinn, by = 'word') оставляет только токены, которые присутствуют в лексиконе AFINN, и добавляет к ним оценки. Слова, которых нет в лексиконе, молча удаляются — это одновременно преимущество (фокус на известных словах, выражающих настроение) и ограничение.

library(tidytext)
library(dplyr)

reviews <- tibble::tibble(
  review_id = 1:3,
  text = c(
    'The product is excellent and outstanding quality',
    'Terrible experience, broken and disappointing',
    'Good value but slow delivery, acceptable overall'
  )
)

scored <- reviews |>
  unnest_tokens(word, text) |>
  inner_join(get_sentiments('afinn'), by = 'word')

cat('Scored words:\n')
print(scored[, c('review_id', 'word', 'value')])

Итоговое настроение документа

Просуммируйте оценки AFINN для каждого документа с помощью group_by(document) |> summarise(sentiment = sum(value)), чтобы получить итоговую оценку настроения. Положительная сумма указывает на преимущественно положительное настроение, отрицательная — на отрицательное.

library(tidytext)
library(dplyr)

reviews <- tibble::tibble(
  review_id = 1:5,
  text = c(
    'excellent outstanding perfect love best amazing',
    'terrible broken horrible awful waste money',
    'good acceptable okay decent average',
    'brilliant fantastic outstanding love recommend',
    'poor bad disappointing slow useless'
  )
)

net_sentiment <- reviews |>
  unnest_tokens(word, text) |>
  inner_join(get_sentiments('afinn'), by = 'word') |>
  group_by(review_id) |>
  summarise(
    sentiment  = sum(value),
    word_count = n()
  ) |>
  mutate(polarity = ifelse(sentiment > 0, 'positive', 'negative'))

print(net_sentiment)

Настроение по Bing: количество положительных и отрицательных слов

С помощью лексикона Bing сравните количество положительных и отрицательных слов в каждом документе. Вычислите sentiment = positive_n - negative_n, чтобы получить итоговую оценку полярности, или изобразите оба количества сгруппированными столбцами, чтобы увидеть их соотношение.

library(tidytext)
library(dplyr)

corpus <- tibble::tibble(
  chapter = 1:4,
  text = c(
    'the hero was brave courageous strong and victorious in battle',
    'disaster struck terrible losses failure defeat mourning grief',
    'love joy happiness beautiful peaceful wonderful morning',
    'evil darkness corrupt wicked terrible pain suffering fear'
  )
)

bing_sentiment <- corpus |>
  unnest_tokens(word, text) |>
  inner_join(get_sentiments('bing'), by = 'word') |>
  count(chapter, sentiment) |>
  tidyr::pivot_wider(names_from = sentiment, values_from = n, values_fill = 0) |>
  mutate(net = positive - negative)

print(bing_sentiment)

Настроение по главам или разделам

Чтобы отслеживать настроение на протяжении сюжетной дуги, вычислите итоговое настроение для каждой главы или скользящего окна. Это позволяет увидеть эмоциональную структуру: завязку, кульминацию и развязку. Используйте ggplot2 с geom_line() или geom_bar() для визуализации дуги.

library(tidytext)
library(dplyr)
library(ggplot2)

# Simulate a 10-chapter story arc
set.seed(42)
chapters <- tibble::tibble(
  chapter = 1:10,
  text    = c(
    'peaceful beautiful joy love happy wonderful',
    'good friends happy carefree enjoyable fun',
    'worried anxious trouble fear uncertain dark',
    'danger terrible threat awful pain suffering',
    'fear horror disaster terrible devastation loss',
    'fight battle struggle hard difficult challenge',
    'hope courage determination brave strong resist',
    'triumph victory success celebrate joy love',
    'relief peace gratitude wonderful blessed happy',
    'love joy peace beautiful grateful wonderful'
  )
)

arc <- chapters |>
  unnest_tokens(word, text) |>
  inner_join(get_sentiments('afinn'), by = 'word') |>
  group_by(chapter) |>
  summarise(net_sentiment = sum(value))

ggplot(arc, aes(chapter, net_sentiment)) +
  geom_line(color = 'steelblue', linewidth = 1.2) +
  geom_hline(yintercept = 0, linetype = 'dashed') +
  labs(x = 'Chapter', y = 'Net Sentiment', title = 'Story Sentiment Arc') +
  theme_minimal()

Лексикон NRC: категории эмоций

Лексикон NRC (Saif Mohammad и Peter Turney) распределяет 13 901 слово по восьми эмоциям (гневу, ожиданию, отвращению, страху, радости, грусти, удивлению и доверию), а также по категориям «положительное» и «отрицательное». Отфильтруйте данные по эмоции с помощью filter(sentiment == 'joy').

library(tidytext)
library(dplyr)

nrc <- get_sentiments('nrc')
cat('NRC size:', nrow(nrc), '\n')
cat('Emotions:', paste(unique(nrc$sentiment), collapse = ', '), '\n')

# Words associated with 'joy'
joy_words <- nrc |> filter(sentiment == 'joy')
cat('\nJoy words:', nrow(joy_words), '\n')
cat('Sample:', head(joy_words$word, 10), '\n')

# Count words per emotion
nrc |>
  count(sentiment, sort = TRUE) |>
  print()

Применение NRC к корпусу

Объедините токенизированный текст с NRC, чтобы отдельно оценить каждую эмоцию. Сгруппируйте данные по документу и эмоции с помощью count(doc_id, sentiment), чтобы увидеть, какие эмоции преобладают в каждом документе.

library(tidytext)
library(dplyr)

docs <- tibble::tibble(
  doc_id = c(1, 1, 2, 2, 3, 3),
  text   = c(
    'wonderful joyful happy love peace',
    'excited surprise anticipation trust',
    'fear anger terrible hate disgust',
    'dark horrible awful suffering pain',
    'curious wonder discovery learning',
    'hope trust anticipation future growth'
  )
)

nrc_scores <- docs |>
  unnest_tokens(word, text) |>
  inner_join(get_sentiments('nrc'), by = 'word') |>
  filter(!sentiment %in% c('positive', 'negative')) |>  # keep only emotions
  count(doc_id, sentiment, sort = TRUE)

cat('Emotion counts per document:\n')
print(nrc_scores)

Принцип работы облака слов

Облако слов визуализирует частотность слов: размер текста пропорционален количеству употреблений. Пакеты wordcloud или wordcloud2 создают такие облака из таблицы данных со словами и их частотами. Окрасьте слова по настроению с помощью лексикона Bing, чтобы получить облако, цвет которого отражает настроение.

library(tidytext)
library(dplyr)

# Prepare word frequencies coloured by Bing sentiment
corpus_text <- tibble::tibble(
  text = c(
    'excellent performance amazing results love beautiful',
    'terrible failure poor broken horrible waste',
    'brilliant outstanding success happy wonderful joy',
    'awful disappointing bad slow useless frustrating'
  )
)

word_freq <- corpus_text |>
  unnest_tokens(word, text) |>
  count(word, sort = TRUE) |>
  left_join(get_sentiments('bing'), by = 'word') |>
  mutate(
    sentiment = replace(sentiment, is.na(sentiment), 'neutral'),
    colour    = case_when(
      sentiment == 'positive' ~ '#2196F3',
      sentiment == 'negative' ~ '#F44336',
      TRUE                    ~ '#9E9E9E'
    )
  )

print(word_freq)
# In practice: wordcloud2(word_freq[, c('word','n')], color = word_freq$colour)

Ограничения анализа настроения на основе лексикона

Анализ настроения на основе лексикона имеет известные проблемные случаи: отрицание («not good» получает положительную оценку), сарказм («oh great, another bug»), несоответствие предметной области («sick» в сленге имеет положительное значение) и слова, отсутствующие в словаре. Учитывайте эти ограничения при интерпретации результатов.

library(tidytext)
library(dplyr)

# Negation problem
neg_examples <- tibble::tibble(
  text = c(
    'not good at all',   # negative, but 'good' scores +3
    'not bad',           # positive, but 'bad' scores -3
    'sick beats bro'     # slang positive, 'sick' is negative in AFINN
  )
)

scored <- neg_examples |>
  unnest_tokens(word, text) |>
  inner_join(get_sentiments('afinn'), by = 'word')

cat('Lexicon scores (naive - ignores negation):\n')
print(scored[, c('word', 'value')])
cat('\nNote: "not" is a stop word - the negation is lost!\n')

Быстрая проверка

Вы используете inner_join(tokens, afinn, by = 'word') для оценки настроения. Что происходит со словами из tokens, которых НЕТ в лексиконе AFINN?

Итоги: анализ настроения

Основные выводы:

  • Три основных лексикона: AFINN (оценки от −5 до +5), Bing (положительные и отрицательные слова), NRC (8 эмоций)
  • get_sentiments('afinn') / 'bing' / 'nrc' извлекает лексикон в виде тиббла
  • inner_join(tokens, lexicon, by = 'word') оценивает только совпавшие слова (несовпавшие удаляются)
  • Итоговое настроение = group_by(doc) |> summarise(sentiment = sum(value))
  • Bing: сравнивайте количество positive - negative слов в каждом документе или разделе
  • NRC: фильтруйте по названию эмоции, чтобы выделить страх, радость, гнев и другие эмоции
  • Основное ограничение: методы на основе лексикона не учитывают отрицание, сарказм и сленг предметной области
library(tidytext)
library(dplyr)

tibble::tibble(text = 'excellent amazing love joy beautiful happy') |>
  unnest_tokens(word, text) |>
  inner_join(get_sentiments('afinn'), by = 'word') |>
  summarise(net_sentiment = sum(value)) |>
  print()
Можно начать бесплатно

Изучай R с ИИ-репетитором — бесплатно

Пиши и запускай код прямо в браузере, получай мгновенную помощь от ИИ-репетитора 24/7 и продолжи учиться на сайте или в приложении.

Курсы
43
Уроки
159

Часто задаваемые вопросы

Урок «Анализ тональности в R» бесплатный?

Да — полный текст урока «Анализ тональности в R» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс R Academy, подпишись на CoddyKit PRO. Курс R Academy содержит 4 уроков всего.

Чему я научусь в уроке «Анализ тональности в R»?

Объединяйте токены со словарями тональности, чтобы измерять положительный и отрицательный тон Ты практикуешь R Academy с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.

Нужен ли мне опыт, чтобы начать R Academy?

Предыдущий опыт не требуется. R Academy на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 3 из 4.

Сколько времени занимает урок «Анализ тональности в R»?

Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.

Можно ли писать и запускать код в этом уроке R Academy?

Да. Каждый урок R Academy включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.

Все уроки этого курса

  1. Токенизация и удаление стоп-слов
  2. TF-IDF и анализ частотности терминов
  3. Анализ тональности в R
  4. Тематическое моделирование с LDA
← Назад к R Academy