Это последняя статья из цикла про лексический анализ текста. Вот краткое содержание того, что мы уже сделали в первой части:
- загрузили все тома «Войны и мира» в Python;
- очистили текст от запятых, предлогов и переносов строк;
- установили и настроили библиотеку NLTK для работы с текстом;
- убрали из текста русские и французские стоп-слова (например, артикли и предлоги);
- нашли самые частые слова в каждом томе;
- нарисовали облако слов по популярности — чем чаще встречается слово, тем крупнее оно написано.
И во второй:
- привели каждое слово в тексте к нормальной форме;
- построили облака слов для каждого тома;
- нашли общие самые частые слова, которые есть в каждом томе;
- убрали те, слова, что есть в каждом облаке, чтобы они не мешали найти суть происходящего в книгах;
- нарисовали новые облака слов, которые отражают уникальный сюжет каждой книги.
В конце второй части мы дошли до сути первого тома «Войны и мира» — нашли самые частые слова, которые описывают сюжет:
Что делаем сегодня
Сегодня мы поиграем с данными и извлечём ещё кое-какие знания из текстов Толстого:
- разделим существительные, прилагательные и глаголы;
- каждую такую группу слов сохраним в отдельном списке и построим для каждой своё облако тегов;
- оптимизируем код так, чтобы всё это можно было сделать за один проход.
Смысл в том, что существительные нам покажут ключевые элементы, вокруг которых вращается сюжет, прилагательные покажут эмоциональную окраску и характеристики героев, а глаголы — основное действие, которое происходит в книге.
Оптимизируем код
Так как нам нужно будет за один прогон получить три картинки с облаками слов, поменяем код так, чтобы это было сделать проще всего. Для этого нам нужно сделать две вещи:
- Собрать в одном месте импорты нужных библиотек.
- Превратить в функцию тот фрагмент кода, где происходит подготовка и формирование облака слов.
Логика работы будет такая: формируем три списка слов и по очереди вызываем функцию создания облака. Благодаря функции нам не придётся делать спагетти-код и дублировать три раза подряд одно и то же, только с разными переменными.
Соберём все команды импорта в начало скрипта — просто найдём их в коде и перенесём в начало:
# подключаем встроенный модуль работы со строками
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)
Начиная с этой команды скрипт обрабатывает наш текст, чтобы потом на его основе сформировать готовую картинку с облаком. Так как нужные команды идут до конца скрипта, то мы сделаем так:
- Перед этой командой объявим новую функцию
def text_cloud()
. - В качестве параметра укажем переменную
tokens
— её у нас ещё нет, поэтому пусть будет как аргумент функции. - В этой функции меняем везде
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)
Что дальше
Мы поработали с текстом на самом начальном уровне, в жизни обычно всё сложнее и глубже. Но даже этих знаний достаточно, чтобы, например, проанализировать тексты любимого блогера за последние пару лет и посмотреть, изменилось ли что в них или нет. А если изменилось — то что?
Если сделаете что-то подобное — поделитесь в комментариях, что у вас получилось и к каким выводам вы пришли.