Анализ текста: последняя часть
easy

Анализ текста: последняя часть

Смотрим на существительные, прилагательные и глаголы у Толстого

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

  • загрузили все тома «Войны и мира» в Python;
  • очистили текст от запятых, предлогов и переносов строк;
  • установили и настроили библиотеку NLTK для работы с текстом;
  • убрали из текста русские и французские стоп-слова (например, артикли и предлоги);
  • нашли самые частые слова в каждом томе;
  • нарисовали облако слов по популярности — чем чаще встречается слово, тем крупнее оно написано.

И во второй:

  • привели каждое слово в тексте к нормальной форме; 
  • построили облака слов для каждого тома;
  • нашли общие самые частые слова, которые есть в каждом томе;
  • убрали те, слова, что есть в каждом облаке, чтобы они не мешали найти суть происходящего в книгах;
  • нарисовали новые облака слов, которые отражают уникальный сюжет каждой книги.

В конце второй части мы дошли до сути первого тома «Войны и мира» — нашли самые частые слова, которые описывают сюжет:

Анализ текста: последняя часть

Что делаем сегодня

Сегодня мы поиграем с данными и извлечём ещё кое-какие знания из текстов Толстого:

  • разделим существительные, прилагательные и глаголы;
  • каждую такую группу слов сохраним в отдельном списке и построим для каждой своё облако тегов;
  • оптимизируем код так, чтобы всё это можно было сделать за один проход.

Смысл в том, что существительные нам покажут ключевые элементы, вокруг которых вращается сюжет, прилагательные покажут эмоциональную окраску и характеристики героев, а глаголы — основное действие, которое происходит в книге.

Оптимизируем код

Так как нам нужно будет за один прогон получить три картинки с облаками слов, поменяем код так, чтобы это было сделать проще всего. Для этого нам нужно сделать две вещи:

  1. Собрать в одном месте импорты нужных библиотек.
  2. Превратить в функцию тот фрагмент кода, где происходит подготовка и формирование облака слов.

Логика работы будет такая: формируем три списка слов и по очереди вызываем функцию создания облака. Благодаря функции нам не придётся делать спагетти-код и дублировать три раза подряд одно и то же, только с разными переменными.

Соберём все команды импорта в начало скрипта — просто найдём их в коде и перенесём в начало:

# подключаем встроенный модуль работы со строками
import string
# подключаем регулярные выражения
import re
# из библиотеки обработки текста подключаем модуль для токенизации слов
from nltk import word_tokenize
# подключаем библиотеку для нормализации слов
import pymorphy2
# подключаем библиотеку для работы с текстом
import nltk
# подключаем библиотеку для создания облака слов
from wordcloud import WordCloud
# и графический модуль, с помощью которого нарисуем это облако
import matplotlib.pyplot as plt
# подключаем статистику 
from nltk.probability import FreqDist

Теперь найдём в коде такую строчку:

# переводим токены в текстовый формат
text = nltk.Text(filtered_tokens)

Начиная с этой команды скрипт обрабатывает наш текст, чтобы потом на его основе сформировать готовую картинку с облаком. Так как нужные команды идут до конца скрипта, то мы сделаем так:

  1. Перед этой командой объявим новую функцию def text_cloud().
  2. В качестве параметра укажем переменную tokens — её у нас ещё нет, поэтому пусть будет как аргумент функции.
  3. В этой функции меняем везде filtered_tokens на tokens, чтобы функция могла универсально обработать любой список слов, который мы ей дадим.

В итоге должен получиться такой код:

def text_cloud(tokens):
    # переводим токены в текстовый формат
    text = nltk.Text(tokens)
    # считаем слова в тексте по популярности
    fdist = FreqDist(text)
    # выводим первые 5 популярных слов
    print(fdist.most_common(5))
    # подключаем модуль со стоп-словами
    from nltk.corpus import stopwords
    # добавляем русские и французские стоп-слова
    russian_stopwords = stopwords.words("russian")
    russian_stopwords += stopwords.words("french")
    # перестраиваем токены, не учитывая стоп-слова
    text_tokens = [token.strip() for token in tokens if token not in russian_stopwords]
    # снова приводим токены к текстовому виду
    text = nltk.Text(text_tokens)
    # считаем заново частоту слов
    fdist_sw = FreqDist(text)
    # показываем самые популярные
    print(fdist_sw.most_common(10))
    # добавляем свои слова в этот список
    russian_stopwords.extend(['это', 'что','всё','который', 'свой','говорить','сказать','думать','человек','ещё','весь','лицо','время','мочь','знать','видеть'])
    # перестраиваем токены, не учитывая стоп-слова
    text_tokens = [token.strip() for token in text_tokens if token not in russian_stopwords]
    # снова приводим токены к текстовому виду
    text = nltk.Text(text_tokens)
    # считаем заново частоту слов
    fdist_sw = FreqDist(text)
    # переводим всё в текстовый формат
    text_raw = " ".join(text)
    # готовим размер картинки
    wordcloud = WordCloud(width=1600, height=800).generate(text_raw)
    plt.figure( figsize=(20,10), facecolor='k')
    # добавляем туда облако слов
    plt.imshow(wordcloud)
    # выключаем оси и подписи
    plt.axis("off")
    # убираем рамку вокруг
    plt.tight_layout(pad=0)
    # выводим картинку на экран
    plt.show()

Чтобы убедиться, что всё работает, добавим в самый конец скрипта команду:

text_cloud(filtered_tokens)

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

Сортируем слова

Чтобы разделить существительные, прилагательные и глаголы, нам понадобится дополнительный элемент в библиотеке pymorphy2 — свойство tag. В нём хранится дополнительная информация о слове — тип, число, род и другая информация. 

Например, если проанализированное слово хранится в переменной p, то проверить, существительное оно или нет, можно так:

if "NOUN" in p.tag:
# это — существительное

Проще говоря, нам нужно проверить, есть ли нужное свойство среди всех свойств внутри тега. Чтобы разобраться, как в библиотеке кодируются разные части речи, идём на сайт с документацией библиотеки и смотрим таблицу «Часть речи»:

Анализ текста: последняя часть
В таблице видно, что для прилагательного и глагола есть два служебных слова — используем оба при проверке

Чтобы мы сразу могли отправлять найденные части речи по своим спискам, сразу после добавления анализатора слов заведём новые переменные:

# новые переменные для существительных, прилагательных и глаголов 
noun_tokens = []
adjf_tokens = []
verb_tokens = []

Теперь переберём все слова в исходном тексте и сразу посмотрим, в какой список их отправлять. Для этого посмотрим, какие признаки слов есть в тегах, и в зависимости от этого примем решение:

# перебираем все слова в исходном тексте
for token in text_tokens:
    # получаем нормальную форму текущего слова
    p = morph.parse(str(token))[0]
    if "NOUN" in p.tag:
        # добавляем его в массив c существительными
        noun_tokens.append(p.normal_form)
    elif "ADJF" in p.tag or "ADJS" in p.tag:
        # добавляем его в массив c прилагательными
        adjf_tokens.append(p.normal_form)
    elif "VERB" in p.tag or "INFN" in p.tag:
        # добавляем его в массив c глаголами
        verb_tokens.append(p.normal_form)

Запускаем скрипт

Сейчас у нас есть отсортированные слова, но нет картинок — всё потому, что мы не отправили их на обработку в функцию text_cloud(). Исправим это и добавим в самый конец скрипта такие команды:

# вызываем по очереди функцию создания облака тегов для каждого списка
text_cloud(noun_tokens)
text_cloud(adjf_tokens)
text_cloud(verb_tokens)

Скрипт сначала сформирует первую картинку; когда мы её закроем, то покажет вторую, а когда закроем и это окно — появится третья. 

Вот что у нас получилось в детальном рассмотрении первого тома «Войны и мира». С существительными у библиотеки всё более-менее хорошо:

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

А вот с прилагательными модуль дал сбой: «ваш» и «наш» он посчитал тоже прилагательными, как и Болконского. Их можно добавить в стоп-лист, но для чистоты эксперимента мы ничего не трогали:

Анализ текста: последняя часть
В первом томе всё крутится вокруг молодости и чувства долга

С глаголами тоже всё хорошо — нет почти ни одного лишнего слова:

Анализ текста: последняя часть
Герои больше хотят, чем делают, а «стать» и «казаться» имеют практически одинаковую частоту

# подключаем встроенный модуль работы со строками
import string
# подключаем регулярные выражения
import re
# из библиотеки обработки текста подключаем модуль для токенизации слов
from nltk import word_tokenize
# подключаем библиотеку для нормализации слов
import pymorphy2
# подключаем библиотеку для работы с текстом
import nltk
# подключаем библиотеку для создания облака слов
from wordcloud import WordCloud
# и графический модуль, с помощью которого нарисуем это облако
import matplotlib.pyplot as plt
# подключаем статистику 
from nltk.probability import FreqDist

# открываем текстовый файл
f = open('tom1.txt', "r", encoding="utf-8")
# закидываем его содержимое в переменную
text = f.read()
# выводим начало, чтобы убедиться, что всё считалось правильно
print(text[:300])
# переводим символы в нижний регистр, чтобы всё было одинаково
text = text.lower()
# добавляем к стандартным знакам пунктуации кавычки и многоточие
spec_chars = string.punctuation + '«»\t—…’'
# очищаем текст от знаков препинания
text = "".join([ch for ch in text if ch not in spec_chars])
# меняем переносы строк на пробелы
text = re.sub('\n', ' ', text)
# убираем из текста цифры
text = "".join([ch for ch in text if ch not in string.digits])
# смотрим на результат
print(text[:300])
# токенизируем текст
text_tokens = word_tokenize(text)
# добавляем анализатор слов
morph = pymorphy2.MorphAnalyzer()

# новые переменные для существительных, прилагательных и глаголов 
noun_tokens = []
adjf_tokens = []
verb_tokens = []

# перебираем все слова в исходном тексте
for token in text_tokens:
    # получаем нормальную форму текущего слова
    p = morph.parse(str(token))[0]
    if "NOUN" in p.tag:
        # добавляем его в массив c существительными
        noun_tokens.append(p.normal_form)
    elif "ADJF" in p.tag or "ADJS" in p.tag:
        # добавляем его в массив c прилагательными
        adjf_tokens.append(p.normal_form)
    elif "VERB" in p.tag or "INFN" in p.tag:
        # добавляем его в массив c глаголами
        verb_tokens.append(p.normal_form)



def text_cloud(tokens):
    # переводим токены в текстовый формат
    text = nltk.Text(tokens)
    # считаем слова в тексте по популярности
    fdist = FreqDist(text)
    # выводим первые 5 популярных слов
    print(fdist.most_common(5))
    # подключаем модуль со стоп-словами
    from nltk.corpus import stopwords
    # добавляем русские и французские стоп-слова
    russian_stopwords = stopwords.words("russian")
    russian_stopwords += stopwords.words("french")
    # перестраиваем токены, не учитывая стоп-слова
    text_tokens = [token.strip() for token in tokens if token not in russian_stopwords]
    # снова приводим токены к текстовому виду
    text = nltk.Text(text_tokens)
    # считаем заново частоту слов
    fdist_sw = FreqDist(text)
    # показываем самые популярные
    print(fdist_sw.most_common(10))
    # добавляем свои слова в этот список
    russian_stopwords.extend(['это', 'что','всё','который', 'свой','говорить','сказать','думать','человек','ещё','весь','лицо','время','мочь','знать','видеть'])
    # перестраиваем токены, не учитывая стоп-слова
    text_tokens = [token.strip() for token in text_tokens if token not in russian_stopwords]
    # снова приводим токены к текстовому виду
    text = nltk.Text(text_tokens)
    # считаем заново частоту слов
    fdist_sw = FreqDist(text)
    # переводим всё в текстовый формат
    text_raw = " ".join(text)
    # готовим размер картинки
    wordcloud = WordCloud(width=1600, height=800).generate(text_raw)
    plt.figure( figsize=(20,10), facecolor='k')
    # добавляем туда облако слов
    plt.imshow(wordcloud)
    # выключаем оси и подписи
    plt.axis("off")
    # убираем рамку вокруг
    plt.tight_layout(pad=0)
    # выводим картинку на экран
    plt.show()

# вызываем по очереди функцию создания облака тегов для каждого списка
text_cloud(noun_tokens)
text_cloud(adjf_tokens)
text_cloud(verb_tokens)

Что дальше

Мы поработали с текстом на самом начальном уровне, в жизни обычно всё сложнее и глубже. Но даже этих знаний достаточно, чтобы, например, проанализировать тексты любимого блогера за последние пару лет и посмотреть, изменилось ли что в них или нет. А если изменилось — то что? 

Если сделаете что-то подобное — поделитесь в комментариях, что у вас получилось и к каким выводам вы пришли.

Любите данные? Посмотрите вот это
Возможно, у вас получится построить карьеру в мире дата-сайенса. Это новое направление, в котором очень нужны люди. Изучите эту сферу и начните карьеру в ИТ: старт — бесплатно, а после обучения — помощь с трудоустройством.
Начать бесплатно
Любите данные? Посмотрите вот это Любите данные? Посмотрите вот это Любите данные? Посмотрите вот это Любите данные? Посмотрите вот это
Получите ИТ-профессию
В «Яндекс Практикуме» можно стать разработчиком, тестировщиком, аналитиком и менеджером цифровых продуктов. Первая часть обучения всегда бесплатная, чтобы попробовать и найти то, что вам по душе. Дальше — программы трудоустройства.
Начать карьеру в ИТ
Получите ИТ-профессию Получите ИТ-профессию Получите ИТ-профессию Получите ИТ-профессию
Еще по теме
easy