1 Введение

1.1 tidyverse

library(tidyverse)

Я ожидаю, что вы знакомы со следующими функциями из tidyverse

  • %>%

Подробнее

%>% – пайп, конвеер, велосипед (добавляется горячими клавишами Ctrl+Shift+M/Cmd+Shift+M). Этот оператор передает результат работы одной функции в другую, что повышает читаемость. Сравните:

sort(round(sqrt(abs(sin(1:15))), 2), decreasing = TRUE)
##  [1] 1.00 1.00 0.99 0.98 0.95 0.92 0.87 0.81 0.81 0.74 0.73 0.65 0.64 0.53 0.38

и

1:15 %>% 
  sin() %>% 
  abs() %>% 
  sqrt() %>% 
  round(2) %>% 
  sort(decreasing = TRUE)
##  [1] 1.00 1.00 0.99 0.98 0.95 0.92 0.87 0.81 0.81 0.74 0.73 0.65 0.64 0.53 0.38
  • filter()

Подробнее

Функция filter() позволяет фильтровать колонки по одному и более условию:

iris %>% 
  filter(Sepal.Length > 5, 
         Petal.Length < 3)
  • arrange()

Подробнее

Функция arrange() позволяет сортировать одну и более колонок:

iris %>% 
  arrange(Sepal.Width, Petal.Length)
  • select()

Подробнее

Функция select() позволяет выбирать колонки:

diamonds %>% 
  select(price, color)

Отрицательный индекс позволяет выкидывать колонки:

diamonds %>% 
  select(-price, -color, -cut)
  • distinct()

Подробнее

Функция distinct() позволяет фильтровать колонки по одному и более условию:

diamonds %>% 
  distinct(cut, color)
  • group_by() %>% summarise()

Подробнее

Функции group_by() и summarise() часто используют вместе. Для начала разберемся с функцией summarise(), эта функция агрегирует какое-то саммари по всей таблице:

iris %>% 
  summarise(slmn = mean(Sepal.Length), 
            swmd = median(Sepal.Width))

Функция group_by() позволяет создавать в датасете группировки на основании одной или нескольких перемнных, так что summurise() работает на каждой из групп:

iris %>% 
  group_by(Species) %>% 
  summarise(slmn = mean(Sepal.Length), 
            swmd = median(Sepal.Width))
## `summarise()` ungrouping output (override with `.groups` argument)

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

  • mutate()

Подробнее

Функция mutate() позволяет создавать новые переменные в датасете:

iris %>% 
  mutate(new_var = Petal.Length+Petal.Width)

В комбинации с функцией group_by() позовляет сохранить какую-то описательную статистику как отдельную колонку (сравни с summarise()):

iris %>% 
  group_by(Species) %>% 
  mutate(slmn = mean(Sepal.Length), 
         swmd = median(Sepal.Width))
  • count()

Подробнее

Функция count() позволяет создать поддатасет с уникальными комбинациями признака/признаков:

diamonds %>% 
  count(cut, color)

Иногда бывает полезно использовать аргумент sort = TRUE, чтобы получить отсортированный датасет.

diamonds %>% 
  count(cut, color, sort = TRUE)
  • top_n()

Подробнее

Функция top_n() полезно использовать с функций count(), так как она возвращает первые n строчек, где n – количество:

diamonds %>% 
  count(cut, color) %>% 
  top_n(10)
## Selecting by n
  • ggplot()
  • read_csv()

1.2 Работа со строками

В tidyverse входит пакет stringr, который позволяет работать со строками, мы посмотрим несколько функций, которые будут нам сегодня полезны:

  • str_length()
starwars %>% 
  mutate(name_length = str_length(name)) %>% 
  select(name, name_length)
  • str_detect()
starwars %>% 
  filter(str_detect(name, "-")) %>% 
  select(name)
starwars %>% 
  filter(str_detect(name, "(Luke)|(Darth)")) %>% 
  select(name)
starwars %>% 
  filter(str_detect(name, "\\d")) %>% 
  select(name)

Подробнее смотрите материалы с мастерской АнДана на Летней Школе.

2 Где взять тексты?

2.1 Загрузить

В пакете readr (входит в tidyverse) для чтения текста есть функция read_lines(). В качестве первой переменной может выступать путь к файлу на компьютере или интернет ссылка:

t <- read_lines("https://raw.githubusercontent.com/agricolamz/2020.11.01_appcogn_text_analysis/master/data/Chang.txt")
head(t)
## [1] "Тед Чан"                                                                                 
## [2] "История твоей жизни"                                                                     
## [3] "Твой отец собирается задать мне вопрос. Это самый важный момент в нашей жизни, и я хочу" 
## [4] "запомнить все до малейшей детали. Уже за полночь, но мы только что вернулись домой после"
## [5] "ужина в ресторане и веселого шоу и сразу выходим в патио полюбоваться полной луной. Хочу"
## [6] "танцевать! — объявляю я, и твой отец подтрунивает надо мной, но мы начинаем скользить в"

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

t2 <- str_c(t, collapse = " ")
length(t2)
## [1] 1
str_length(t2)
## [1] 117398

При таком слиянии, стоит проверить, не было ли в анализируемом тексте знаков переноса, иначе они сольются неправильно:

str_c(c("... она запо-", "лучила ..."), collapse = " ")
## [1] "... она запо- лучила ..."

Проблемы с кодировкой

Ошибка с кодировкой бывает нескольких типов:

  • у вас что-то не печатается в консоли
  • у вас что-то не печатается в html-вьювере (после команды View())
  • обе

Лечение всегда находится, но оно частное для разных операционных систем

Sys.setlocale("LC_ALL","Russian") # windows
Sys.setlocale("LC_ALL", "ru_RU") # macOS, linux
Sys.setlocale("LC_ALL", "ru_RU.UTF-8") # linux
Sys.setlocale("LC_ALL", "ru_RU.utf8") # linux

2.2 gutenbergr

Пакет gutenbergr является API для очень старого проекта Gutenberg.

library(gutenbergr)

Все самое важное в этом пакете хранится в датасете gutenberg_metadata

str(gutenberg_metadata)
## tibble [51,997 × 8] (S3: tbl_df/tbl/data.frame)
##  $ gutenberg_id       : int [1:51997] 0 1 2 3 4 5 6 7 8 9 ...
##  $ title              : chr [1:51997] NA "The Declaration of Independence of the United States of America" "The United States Bill of Rights\r\nThe Ten Original Amendments to the Constitution of the United States" "John F. Kennedy's Inaugural Address" ...
##  $ author             : chr [1:51997] NA "Jefferson, Thomas" "United States" "Kennedy, John F. (John Fitzgerald)" ...
##  $ gutenberg_author_id: int [1:51997] NA 1638 1 1666 3 1 4 NA 3 3 ...
##  $ language           : chr [1:51997] "en" "en" "en" "en" ...
##  $ gutenberg_bookshelf: chr [1:51997] NA "United States Law/American Revolutionary War/Politics" "American Revolutionary War/Politics/United States Law" NA ...
##  $ rights             : chr [1:51997] "Public domain in the USA." "Public domain in the USA." "Public domain in the USA." "Public domain in the USA." ...
##  $ has_text           : logi [1:51997] TRUE TRUE TRUE TRUE TRUE TRUE ...
##  - attr(*, "date_updated")= Date[1:1], format: "2016-05-05"

Например, сейчас мы можем понять, сколько книг на разных языках можно скачать из проекта:

gutenberg_metadata %>% 
  count(language, sort = TRUE)

Как видно, в основном это тексты на английском. Сколько авторов в датасете?

gutenberg_metadata %>% 
  count(author, sort = TRUE)

Сколько произведений Джейн Остин (не перепутайте с другими Остин) есть в датасете?

gutenberg_metadata %>% 
  filter(author == "Austen, Jane") %>% 
  distinct(gutenberg_id, title)

Давайте скачаем “Эмму”:

emma <- gutenberg_download(158)
## Determining mirror for Project Gutenberg from http://www.gutenberg.org/robot/harvest
## Using mirror http://aleph.gutenberg.org
emma

Можно скачивать сразу несколько книг. Давайте добавим еще “Леди Сьюзен”:

books <- gutenberg_download(c(158, 946), meta_fields = "title")
books
books %>% 
  count(title)

Сколько уникальных заголовков из базы данных содержит “Sherlock Holmes”?


2.3 Обкачать из интернета

Cмотрите материалы с мастерской АнДана на Летней Школе.

3 Как анализировать тексты?

3.1 Частотный анализ

3.1.1 Библиотека tidytext

Сейчас скачанные книги записаны в таблицу, где одна строка это один абзац. Хочется мочь посчитать слова. Для этого книги нужно привести в tidy формат и для этого написан пакет tidytext (онлайн книга доступна здесь). Основное “оружие” пакета tidytext функция unnest_tokens(), которая переводит текст в tidy формат. В аргумент output подается вектор с именем будущей переменной, а аргумент input принимает переменную с текстом.

library(tidytext)
books %>% 
  unnest_tokens(output = "word", input = text)

Теперь можно посчитать самые частотные слова в обоих произведениях:

books %>% 
  unnest_tokens(output = "word", input = text) %>% 
  count(title, word, sort = TRUE)

Ну… Это было ожидаемо. Нужно убрать стопслова. Английские стопслова встроены в пакет (переменная stop_words):

books %>% 
  unnest_tokens(word, text) %>% 
  count(title, word, sort = TRUE) %>% 
  anti_join(stop_words)
## Joining, by = "word"

Постройте следующий график, на котором представлены самые частотные 20 слов каждого из произведений.

3.1.2 reorder_within()

Как видно, на графике все не упорядочено, давайте начнем с такого примера:

books %>% 
  unnest_tokens(word, text) %>% 
  count(word, sort = TRUE) %>% 
  top_n(1:20) %>% 
  ggplot(aes(n, word))+
  geom_col()
## Selecting by n
## Warning in if (n > 0) {: the condition has length > 1 and only the first element
## will be used
## Warning in min_rank(desc(wt)) <= n: longer object length is not a multiple of
## shorter object length

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

books %>% 
  unnest_tokens(word, text) %>% 
  count(word, sort = TRUE) %>% 
  top_n(20) %>% 
  mutate(word = fct_reorder(word, n)) %>% 
  ggplot(aes(n, word))+
  geom_col()
## Selecting by n

Однако, если мы применим это к нашим данным, то получится неупорядочено:

books %>% 
  unnest_tokens(word, text) %>% 
  count(title, word, sort = TRUE) %>% 
  group_by(title) %>% 
  top_n(20) %>% 
  ungroup() %>%
  mutate(word = fct_reorder(word, n)) %>% 
  ggplot(aes(n, word))+
  geom_col()+
  facet_wrap(~title, scales = "free")
## Selecting by n

В пакете tidytext есть функция reorder_within(), которая позволяет упорядочить нужным образом:

books %>% 
  unnest_tokens(word, text) %>% 
  count(title, word, sort = TRUE) %>% 
  group_by(title) %>% 
  top_n(20) %>% 
  ungroup() %>%
  mutate(word = reorder_within(x = word, by = n, within = title)) %>% 
  ggplot(aes(n, word))+
  geom_col()+
  facet_wrap(~title, scales = "free")
## Selecting by n

Чтобы избавиться от дополнительной подписи нужно использовать scale_y_reordered() или scale_x_reordered():

books %>% 
  unnest_tokens(word, text) %>% 
  count(title, word, sort = TRUE) %>% 
  group_by(title) %>% 
  top_n(20) %>% 
  ungroup() %>%
  mutate(word = reorder_within(x = word, by = n, within = title)) %>% 
  ggplot(aes(n, word))+
  geom_col()+
  facet_wrap(~title, scales = "free")+
  scale_y_reordered()
## Selecting by n

Функция unnest_tokens() позволяет работать не только со словами, но и, напрмиер, с биграммами:

books %>% 
  unnest_tokens(word, text, token = "ngrams", n = 2)

3.1.3 Распределение слов

Поиск самых частотных слов — не едиснственная задача, которую можно решать при работе с текстом. Иногда имеет смысл узнать распределение слов в произведении. Давайте посмотрим как распределены в романе “Эмма” фамилии главных героев:

books %>% 
  filter(title == "Emma") %>% 
  unnest_tokens(word, text) %>% 
  mutate(narrative_time = 1:n()) %>% 
  filter(str_detect(word, "knightley$|woodhouse$|churchill$|fairfax$")) %>%  
  ggplot()+
      geom_vline(aes(xintercept = narrative_time))+
  facet_wrap(~word, ncol = 1)

3.1.4 Пакет stopwords

Выше мы упомянули, что в пакет tidytext встроен список английских стопслов. Стопслова для других язков можно раздобыть списки для других языков, используя пакет stopwords. Вместо имени языка, функция принимает ISO код языыка:

library(stopwords)
stopwords("ru")
##   [1] "и"       "в"       "во"      "не"      "что"     "он"      "на"     
##   [8] "я"       "с"       "со"      "как"     "а"       "то"      "все"    
##  [15] "она"     "так"     "его"     "но"      "да"      "ты"      "к"      
##  [22] "у"       "же"      "вы"      "за"      "бы"      "по"      "только" 
##  [29] "ее"      "мне"     "было"    "вот"     "от"      "меня"    "еще"    
##  [36] "нет"     "о"       "из"      "ему"     "теперь"  "когда"   "даже"   
##  [43] "ну"      "вдруг"   "ли"      "если"    "уже"     "или"     "ни"     
##  [50] "быть"    "был"     "него"    "до"      "вас"     "нибудь"  "опять"  
##  [57] "уж"      "вам"     "сказал"  "ведь"    "там"     "потом"   "себя"   
##  [64] "ничего"  "ей"      "может"   "они"     "тут"     "где"     "есть"   
##  [71] "надо"    "ней"     "для"     "мы"      "тебя"    "их"      "чем"    
##  [78] "была"    "сам"     "чтоб"    "без"     "будто"   "человек" "чего"   
##  [85] "раз"     "тоже"    "себе"    "под"     "жизнь"   "будет"   "ж"      
##  [92] "тогда"   "кто"     "этот"    "говорил" "того"    "потому"  "этого"  
##  [99] "какой"   "совсем"  "ним"     "здесь"   "этом"    "один"    "почти"  
## [106] "мой"     "тем"     "чтобы"   "нее"     "кажется" "сейчас"  "были"   
## [113] "куда"    "зачем"   "сказать" "всех"    "никогда" "сегодня" "можно"  
## [120] "при"     "наконец" "два"     "об"      "другой"  "хоть"    "после"  
## [127] "над"     "больше"  "тот"     "через"   "эти"     "нас"     "про"    
## [134] "всего"   "них"     "какая"   "много"   "разве"   "сказала" "три"    
## [141] "эту"     "моя"     "впрочем" "хорошо"  "свою"    "этой"    "перед"  
## [148] "иногда"  "лучше"   "чуть"    "том"     "нельзя"  "такой"   "им"     
## [155] "более"   "всегда"  "конечно" "всю"     "между"

Пакет предоставляет несколько источников списков:

stopwords_getsources()
## [1] "snowball"      "stopwords-iso" "misc"          "smart"        
## [5] "marimo"        "ancient"       "nltk"

Давайте посмотрем какие языки сейчас доступны:

map(stopwords_getsources(), stopwords_getlanguages)
## [[1]]
##  [1] "da" "de" "en" "es" "fi" "fr" "hu" "ir" "it" "nl" "no" "pt" "ro" "ru" "sv"
## 
## [[2]]
##  [1] "af" "ar" "hy" "eu" "bn" "br" "bg" "ca" "zh" "hr" "cs" "da" "nl" "en" "eo"
## [16] "et" "fi" "fr" "gl" "de" "el" "ha" "he" "hi" "hu" "id" "ga" "it" "ja" "ko"
## [31] "ku" "la" "lt" "lv" "ms" "mr" "no" "fa" "pl" "pt" "ro" "ru" "sk" "sl" "so"
## [46] "st" "es" "sw" "sv" "th" "tl" "tr" "uk" "ur" "vi" "yo" "zu"
## 
## [[3]]
## [1] "ar" "ca" "el" "gu" "zh"
## 
## [[4]]
## [1] "en"
## 
## [[5]]
## [1] "en"    "ja"    "ar"    "he"    "zh_tw" "zh_cn"
## 
## [[6]]
## [1] "grc" "la" 
## 
## [[7]]
##  [1] "ar" "az" "da" "nl" "en" "fi" "fr" "de" "el" "hu" "id" "it" "kk" "ne" "no"
## [16] "pt" "ro" "ru" "sl" "es" "sv" "tg" "tr"

Мы видим, что есть несколько источников для русского языка:

length(stopwords("ru", source = "snowball"))
## [1] 159
length(stopwords("ru", source = "stopwords-iso"))
## [1] 559

3.2 Пакет udpipe

Пакет udpipe представляет лемматизацию, морфологический и синтаксический анализ разных языков. Туториал можно найти здесь, там же есть список доступных языков.

library(udpipe)

Модели качаются очень долго.

enmodel <- udpipe_download_model(language = "english")
## Downloading udpipe model from https://raw.githubusercontent.com/jwijffels/udpipe.models.ud.2.5/master/inst/udpipe-ud-2.5-191206/english-ewt-ud-2.5-191206.udpipe to /home/agricolamz/work/materials/2020.11.01_appcogn_text_analysis/english-ewt-ud-2.5-191206.udpipe
##  - This model has been trained on version 2.5 of data from https://universaldependencies.org
##  - The model is distributed under the CC-BY-SA-NC license: https://creativecommons.org/licenses/by-nc-sa/4.0
##  - Visit https://github.com/jwijffels/udpipe.models.ud.2.5 for model license details.
##  - For a list of all models and their licenses (most models you can download with this package have either a CC-BY-SA or a CC-BY-SA-NC license) read the documentation at ?udpipe_download_model. For building your own models: visit the documentation by typing vignette('udpipe-train', package = 'udpipe')
## Downloading finished, model stored at '/home/agricolamz/work/materials/2020.11.01_appcogn_text_analysis/english-ewt-ud-2.5-191206.udpipe'

Теперь можно распарсить какое-нибудь предложение:

udpipe("The want of Miss Taylor would be felt every hour of every day.", object = enmodel)

Скачаем русскую модель:

rumodel <- udpipe_download_model(language = "russian-syntagrus")
## Downloading udpipe model from https://raw.githubusercontent.com/jwijffels/udpipe.models.ud.2.5/master/inst/udpipe-ud-2.5-191206/russian-syntagrus-ud-2.5-191206.udpipe to /home/agricolamz/work/materials/2020.11.01_appcogn_text_analysis/russian-syntagrus-ud-2.5-191206.udpipe
##  - This model has been trained on version 2.5 of data from https://universaldependencies.org
##  - The model is distributed under the CC-BY-SA-NC license: https://creativecommons.org/licenses/by-nc-sa/4.0
##  - Visit https://github.com/jwijffels/udpipe.models.ud.2.5 for model license details.
##  - For a list of all models and their licenses (most models you can download with this package have either a CC-BY-SA or a CC-BY-SA-NC license) read the documentation at ?udpipe_download_model. For building your own models: visit the documentation by typing vignette('udpipe-train', package = 'udpipe')
## Downloading finished, model stored at '/home/agricolamz/work/materials/2020.11.01_appcogn_text_analysis/russian-syntagrus-ud-2.5-191206.udpipe'
udpipe("Жила-была на свете крыса в морском порту Вальпараисо, на складе мяса и маиса, какао и вина.", object = rumodel)

После того, как модель скачана можно уже к ней обращаться просто по имени файла:

udpipe("Жила-была на свете крыса в морском порту Вальпараисо, на складе мяса и маиса, какао и вина.", object = "russian-syntagrus-ud-2.5-191206.udpipe")

3.3 Еще данные

Для работы мы воспользуемся двумя датасетами:

  • Рассказы М. Зощенко
zo <- read_csv("https://raw.githubusercontent.com/agricolamz/2020.11.01_appcogn_text_analysis/master/data/zoshenko.csv")
zo
  • Курс начертательной геометрии под редакцией В.Гордона
geom <- read_csv("https://raw.githubusercontent.com/agricolamz/2020.11.01_appcogn_text_analysis/master/data/gordon_geometry.csv")

Для начала лемматизируем полуичвшиеся тексты:

library(udpipe)
rus <- udpipe_load_model("russian-syntagrus-ud-2.5-191206.udpipe")
geom_tokenized <- udpipe(geom, object = rus)
zo_tokenized <- udpipe(zo, object = rus)

Уберем стопслова и леммы, содержащие цифры и знаки препинания

library(stopwords)
sw <- tibble(lemma = stopwords(language = "ru"))

geom_tokenized %>% 
  bind_rows(zo_tokenized) %>% 
  filter(!str_detect(lemma, "\\W|\\d")) %>% 
  anti_join(sw) %>% 
  select(doc_id, sentence_id, lemma) ->
  all_texts
## Joining, by = "lemma"
all_texts

Используйте библиотеку gutenbergr и скачайте “Чувство и чувствительность”(Sense and Sensibility, gutenberg_id = 161) и “Гордость и предубеждение” (“Pride and Prejudice”, gutenberg_id = 1342). Приведите тексты к tidy формату и уберите стопслова (английские стопслова есть в переменной stop_words пакета tidytext).

  • Приведите, сколько получилось слов в романе “Чувство и чувствительность” после удаления стопслов:

  • Приведите, сколько получилось слов в романе “Гордость и предубеждение” после удаления стопслов:

3.4 tf-idf

tf-idf — важная мера, которая позволяет выделять важные для текста слова.

\[tf = \frac{количество\ употреблений\ единицы\ в\ тексте}{количество\ уникальных\ единиц\ в тексте}\] \[idf = log\left(\frac{количество\ документов\ в\ корпусе}{количество\ документов\ с\ исследуемой\ единицей}\right)\] \[TfIdf = tf \times idf\]

library(tidytext)
all_texts %>% 
  count(doc_id, lemma) %>% 
  bind_tf_idf(lemma, doc_id, n) %>% 
  arrange(tf_idf) %>% 
  group_by(doc_id) %>% 
  top_n(5) %>% 
  ungroup() %>% 
  mutate(lemma = reorder_within(lemma, tf_idf, doc_id)) %>% 
  ggplot(aes(tf_idf, lemma))+
  geom_col()+
  facet_wrap(~doc_id, scales = "free")+
  scale_y_reordered()

Давайте попробуем посчитать всего Зощенко одним корпусом:

all_texts %>% 
  mutate(doc_id = ifelse(doc_id != "gordon_geometry", "zoshenko", "gordon_geometry")) %>% 
  count(doc_id, lemma) %>% 
  bind_tf_idf(lemma, doc_id, n) %>% 
  arrange(tf_idf) %>% 
  group_by(doc_id) %>% 
  top_n(20) %>% 
  ungroup() %>% 
  mutate(lemma = reorder_within(lemma, tf_idf, doc_id)) %>% 
  ggplot(aes(tf_idf, lemma))+
  geom_col()+
  facet_wrap(~doc_id, scales = "free")+
  scale_y_reordered()
## Selecting by tf_idf

Используя созданную ранее переменную с текстами Джейн Остин без стопслов, выделите по 20 слов, важных для каждого романа.

3.5 Предиктивный ввод текста

На прошлом занятии мы разобрались, что пакет tidytext позволяет делить не только на отдльные слова, но и смотреть на биграммы. Частотность биграмм можно использовать в подсказке слова, которую мы видим в наших телефонах:

zo %>% 
  unnest_tokens("bigram", text, token = "ngrams", n = 2) %>% 
  separate(bigram, into = c("word_1", "word_2"), sep = " ") %>% 
  count(word_1, word_2, sort = TRUE) ->
  bigrams

Теперь у нас есть биграмы:

bigrams %>% 
  filter(word_1 == "однажды")
bigrams %>% 
  filter(word_1 == "днем")
bigrams %>% 
  filter(word_1 == "присела")
bigrams %>% 
  filter(word_1 == "ждет")
bigrams %>% 
  filter(word_1 == "а") %>% 
  head()
bigrams %>% 
  filter(word_1 == "я") %>% 
  head()
bigrams %>% 
  filter(word_1 == "говорю") %>% 
  head()
bigrams %>% 
  filter(word_1 == "не") %>% 
  head()
bigrams %>% 
  filter(word_1 == "могу") %>% 
  head()

Вот мы и получили предложение “Однажды днем присела ждет, а я говорю: ‘не могу’”. На большом корпусе результаты будут лучше, но легко предсатвить, как сделать из этого рабочую функцию. Можно переиначить задачу и работать с символами, тогда это будет ближе к T9 на современных телефонах.

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


3.6 Анализ тональности

  • Linis Crowd
    • лемма
    • значение
    • среднеквадратичное отклонение
  • РуСентиЛекс:
    • слово или словосочетание,
    • часть речи или синтаксический тип группы,
    • слово или словосочетание в лемматизированной форме,
    • тональность: позитивная (positive), негативная(negative), нейтральная (neutral) или неопределеная оценка, зависит от контекста (positive/negative),
    • источник: оценка (opinion), чувство (feeling), факт (fact),
    • если тональность отличается для разных значений многозначного слова, то перечисляются все значения слова по тезаурусу РуТез и дается отсылка на сооветствующее понятие - имя понятия в кавычках.

Мы будем использовать датасет, составленный на базе Linis Crowd

ru_sentiments <- read_csv("https://raw.githubusercontent.com/agricolamz/2020_HSE_DPO/master/data/ru_sentiment_linis-crowd.csv")
## 
## ── Column specification ────────────────────────────────────────────────────────
## cols(
##   words = col_character(),
##   value = col_double()
## )
all_texts %>% 
  group_by(doc_id) %>% 
  left_join(ru_sentiments, by = c("lemma" = "words")) %>% 
  mutate(value = ifelse(is.na(value), 0, value)) %>% 
  group_by(doc_id, sentence_id) %>% 
  summarise(value = sum(value)) %>% 
  mutate(color = ifelse(value >= 0, "positive", "negative")) %>% 
  ggplot(aes(sentence_id, value, fill = color))+
  geom_col()+
  facet_wrap(~doc_id, scales = "free")
## `summarise()` regrouping output by 'doc_id' (override with `.groups` argument)

4 Что дальше?

NLP имеет достаточно много областей применения и мы не покрыли достаточно много тем, так что если вам будет интересно, поищите следующие ключевые слова:

  • тематическое моделирование
  • векторное представление слов
  • извлечение именованных сущностей
  • определение авторства