{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Анализа текстуалних података - припрема књиге за анализу" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "У овој радној свесци настављамо са преузимањем и припремом текстуалних података тако да можемо да се бавимо анализом како смо и до сада навикли. Велика база књига које су бесплатне и доступне онлајн у txt формату налази се на [платформи пројекта Гутенберг](https://www.gutenberg.org/), и ми ћемо у наставку преузети и припремити податке за анализу по књизи Ана Карењина.\n", "\n", "Као и радна свеска са преузимањем текстова са веб сајта и ова превазилази оквире планираних тема у домену обраде података и није неопходна за разумевање садржаја приказаних у свесци која обрађује податке о књизи Ана Карењина. Свеска је остављена овде у случају да вас занима како се дошло до података који се обрађују у централној радној свесци или да послужи за инспирацију за неко даље анализирање неких других књига." ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [], "source": [ "import pandas as pd\n", "import numpy as np\n", "import re\n", "import string" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Текстуални фајл преузет са платформе Гутенберг пројекта (https://www.gutenberg.org/ebooks/1661) налази се у фолдеру са подацима и учитаћемо га комплетног. Користићемо функцију **open** да отворимо фајл, **readlines** да прочитамо све линије текста из фајла, након чега ћемо затворити фајл **close**:" ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [], "source": [ "f = open('data/tekst data/Anna.txt', 'r', encoding='UTF-8')\n", "lines = f.readlines()\n", "f.close()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Прегледамо првих 5 линија текста да кренемо у упознавање са подацима које смо преузели:" ] }, { "cell_type": "code", "execution_count": 3, "metadata": { "scrolled": true }, "outputs": [ { "data": { "text/plain": [ "['\\ufeff\\n',\n", " 'The Project Gutenberg EBook of Anna Karenina, by Leo Tolstoy\\n',\n", " '\\n',\n", " 'This eBook is for the use of anyone anywhere at no cost and with\\n',\n", " 'almost no restrictions whatsoever. You may copy it, give it away or\\n']" ] }, "execution_count": 3, "metadata": {}, "output_type": "execute_result" } ], "source": [ "lines[:5]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Уочвавамо да се свака линија завшава ознаком за нови ред '\\n' али и да има елемената листе који не садрже ништа више од тога, њих ћемо одмах уклонити:" ] }, { "cell_type": "code", "execution_count": 4, "metadata": { "scrolled": false }, "outputs": [], "source": [ "lines=[l for l in lines if l!='\\n']" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Уочавамо такође и да почетак фајла садржи податке о доступности књиге, верзијама и сличне техничке информације које нису део текста који желимо да анализирамо. Погледајмо мало више линија текста да детектујемо у којој линији креће текст књиге:" ] }, { "cell_type": "code", "execution_count": 5, "metadata": { "scrolled": false }, "outputs": [ { "data": { "text/plain": [ "['\\ufeff\\n',\n", " 'The Project Gutenberg EBook of Anna Karenina, by Leo Tolstoy\\n',\n", " 'This eBook is for the use of anyone anywhere at no cost and with\\n',\n", " 'almost no restrictions whatsoever. You may copy it, give it away or\\n',\n", " 're-use it under the terms of the Project Gutenberg License included\\n',\n", " 'with this eBook or online at www.gutenberg.org\\n',\n", " 'Title: Anna Karenina\\n',\n", " 'Author: Leo Tolstoy\\n',\n", " 'Release Date: July 01, 1998 [EBook #1399]\\n',\n", " 'Last Updated: July 28, 2019\\n',\n", " 'Language: English\\n',\n", " 'Character set encoding: UTF-8\\n',\n", " '*** START OF THIS PROJECT GUTENBERG EBOOK ANNA KARENINA ***\\n',\n", " 'Produced by David Brannan, Andrew Sly and David Widger.\\n',\n", " ' ANNA KARENINA \\n',\n", " ' by Leo Tolstoy \\n',\n", " ' Translated by Constance Garnett \\n',\n", " 'Contents\\n',\n", " ' PART ONE\\n',\n", " ' PART TWO\\n',\n", " ' PART THREE\\n',\n", " ' PART FOUR\\n',\n", " ' PART FIVE\\n',\n", " ' PART SIX\\n',\n", " ' PART SEVEN\\n',\n", " ' PART EIGHT\\n',\n", " 'PART ONE\\n',\n", " 'Chapter 1\\n',\n", " 'Happy families are all alike; every unhappy family is unhappy in its\\n',\n", " 'own way.\\n',\n", " 'Everything was in confusion in the Oblonskys’ house. The wife had\\n',\n", " 'discovered that the husband was carrying on an intrigue with a French\\n',\n", " 'girl, who had been a governess in their family, and she had announced\\n',\n", " 'to her husband that she could not go on living in the same house with\\n',\n", " 'him. This position of affairs had now lasted three days, and not only\\n',\n", " 'the husband and wife themselves, but all the members of their family\\n',\n", " 'and household, were painfully conscious of it. Every person in the\\n',\n", " 'house felt that there was no sense in their living together, and that\\n',\n", " 'the stray people brought together by chance in any inn had more in\\n',\n", " 'common with one another than they, the members of the family and\\n']" ] }, "execution_count": 5, "metadata": {}, "output_type": "execute_result" } ], "source": [ "lines[:40]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Први елемент од интереса је наслов књиге. Користећи **index** можемо издвојити индекс елемента листе који садржи наслов:" ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "14" ] }, "execution_count": 6, "metadata": {}, "output_type": "execute_result" } ], "source": [ "lines.index(' ANNA KARENINA \\n')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Док за почетак књиге можемо прескочити садржај и кренути од почетка првог дела:" ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "26" ] }, "execution_count": 7, "metadata": {}, "output_type": "execute_result" } ], "source": [ "pocetak = lines.index('PART ONE\\n')\n", "pocetak" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Слично као што је почетак фајла означен информацијама о пројекту Гутенберг, постоји и ознака за крај текста, након које следе детаљи о пројекту, правима и слично. Последњу линију од интереса налазимо на следећи начин:" ] }, { "cell_type": "code", "execution_count": 8, "metadata": { "scrolled": false }, "outputs": [ { "data": { "text/plain": [ "31877" ] }, "execution_count": 8, "metadata": {}, "output_type": "execute_result" } ], "source": [ "kraj = lines.index('End of The Project Gutenberg Etext of Anna Karenina by Leo Tolstoy\\n')\n", "kraj" ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [], "source": [ "knjiga = lines[pocetak:kraj]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Као део припреме текста у наставку ће нам бити потрбна и функција за уклањање знака интерпункције:" ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [], "source": [ "znaci_interpunkcije = string.punctuation + '”“’‘—'\n", "def ukloni_znake_interpunkcije(text):\n", " for ch in znaci_interpunkcije:\n", " text=text.replace(ch,' ')\n", " return text" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Раздвајање листе редова на делове и поглавља:" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "У нашем кратком прегледу текста уочили смо да сваком делу књиге претходи линија која садржи стринг \"PART\", као и да поглавља садрже стринг \"Chapter\", што ћемо искористити за идентификацију одговарајућих сегмената текста у наставку:" ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [ { "ename": "NameError", "evalue": "name 'knjiga' is not defined", "output_type": "error", "traceback": [ "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[1;31mNameError\u001b[0m Traceback (most recent call last)", "\u001b[1;32m\u001b[0m in \u001b[0;36m\u001b[1;34m\u001b[0m\n\u001b[0;32m 5\u001b[0m \u001b[0mi\u001b[0m \u001b[1;33m=\u001b[0m \u001b[1;36m0\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 6\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m----> 7\u001b[1;33m \u001b[1;32mfor\u001b[0m \u001b[0mline\u001b[0m \u001b[1;32min\u001b[0m \u001b[0mknjiga\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m 8\u001b[0m \u001b[1;32mif\u001b[0m \u001b[1;34m'PART'\u001b[0m \u001b[1;32min\u001b[0m \u001b[0mline\u001b[0m\u001b[1;33m:\u001b[0m \u001b[1;31m# провера да ли је разматрана линија почетак новог дела књиге\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 9\u001b[0m \u001b[1;32mif\u001b[0m \u001b[0mi\u001b[0m\u001b[1;33m>\u001b[0m\u001b[1;36m0\u001b[0m\u001b[1;33m:\u001b[0m \u001b[1;31m# бројач i расте са сваким новим поглављем, тако да изузев у првмом делу књиге овај услов ће бити задовољен\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", "\u001b[1;31mNameError\u001b[0m: name 'knjiga' is not defined" ] } ], "source": [ "partlens = [] # листа у којој ћемо сачувати број полавља у оквиру сваког дела књиге\n", "chapters = [] # листа у којој ћемо сачувати текстове свих поглавља\n", "\n", "tempchapter = ' ' # овде ћемо чувати садржај поглавља, додајући ред по ред \n", "i = 0\n", "\n", "for line in knjiga:\n", " if 'PART' in line: # провера да ли је разматрана линија почетак новог дела књиге\n", " if i>0: # бројач i расте са сваким новим поглављем, тако да изузев у првмом делу књиге овај услов ће бити задовољен\n", " partlens.append(i) # у листу partlens додајемо укупан број поглавља у окивру претходног дела књиге\n", " i = 0 # ресетујемо бројач\n", " elif 'Chapter' in line: # провера да ли је у питању почетак новог поглавља\n", " i += 1\n", " if (tempchapter != ' '): # ако текст поглавља није празан (што је тачно за све осим за прво поглавље)\n", " chapters.append(tempchapter) # додајемо текст поглавља у листу \n", " tempchapter = ' ' # ресетујемо променљиву у којој чувамо текст поглавља\n", " else: # нова линија текста која није наслов поглавља или дела и треба је додати у текст поглавља\n", " templine = line.replace('\\n',' ')# у наредна два реда изацујемо \\n и \\t који су нам непотребни за даљу анализу\n", " templine = templine.replace('\\t',' ')\n", " tempchapter += templine # додавање очишћене линије у текст поглавља\n", "chapters.append(tempchapter) # додавање текста последњег поглавља у листу\n", "partlens.append(i) # додавање броја поглавља последњег дела у листу" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Кратка провера да смо претходним делом кода урадили шта смо желели је да проверимо дужину листе поглавља, као и суму дужина делова књиге:" ] }, { "cell_type": "code", "execution_count": 12, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "(239, 239)" ] }, "execution_count": 12, "metadata": {}, "output_type": "execute_result" } ], "source": [ "(len(chapters),sum(partlens))" ] }, { "cell_type": "code", "execution_count": 13, "metadata": {}, "outputs": [], "source": [ "chapters = [ukloni_znake_interpunkcije(chapter) for chapter in chapters]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Детекција јунака" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "У овом делу наш циљ је да препознамо ко су јунаци у тексту и да припремимо сет података о броју појављивања сваког од јунака не бисмо ли успели да само на основу анализе текста наслутимо о коме је реч у тексту. Томе ћемо приступити користећи чињеницу да се властита имена пишу великим почетним словом. За почетак, спојићемо целокупан текст и од њега направити листу речи:" ] }, { "cell_type": "code", "execution_count": 14, "metadata": {}, "outputs": [], "source": [ "razmak=' '\n", "ceotekst = razmak.join(chapters)" ] }, { "cell_type": "code", "execution_count": 15, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "' Happy families are all alike every unhappy family is unhappy in its own way Everything was in con'" ] }, "execution_count": 15, "metadata": {}, "output_type": "execute_result" } ], "source": [ "ceotekst[:100]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Дужина текста мерена у карактерима:" ] }, { "cell_type": "code", "execution_count": 16, "metadata": { "scrolled": true }, "outputs": [ { "data": { "text/plain": [ "1954438" ] }, "execution_count": 16, "metadata": {}, "output_type": "execute_result" } ], "source": [ "len(ceotekst)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "До листе појединачних речи доћи ћемо користећи функцију split(), поређења ради, излистаћемо и првих 10 речи:" ] }, { "cell_type": "code", "execution_count": 17, "metadata": { "scrolled": false }, "outputs": [ { "data": { "text/plain": [ "['Happy',\n", " 'families',\n", " 'are',\n", " 'all',\n", " 'alike',\n", " 'every',\n", " 'unhappy',\n", " 'family',\n", " 'is',\n", " 'unhappy']" ] }, "execution_count": 17, "metadata": {}, "output_type": "execute_result" } ], "source": [ "reci = ceotekst.split()\n", "reci[:10] " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Сачуваћемо и листу речи као још један једноставан сет информација о овој књизи." ] }, { "cell_type": "code", "execution_count": 18, "metadata": {}, "outputs": [], "source": [ "reci = [rec for rec in reci if rec.isalpha()]\n", "pd.DataFrame(reci,columns={'Rec'}).to_csv('Ana_df.csv',index=False)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Међутим, у листи речи коју смо овако направили, пуно речи се понавља (што ће бити део анализе у следећој радној свесци) те ћемо припремити и једну листу јединствених речи из овог текста:" ] }, { "cell_type": "code", "execution_count": 19, "metadata": { "scrolled": false }, "outputs": [ { "data": { "text/plain": [ "['undoubted',\n", " 'scold',\n", " 'divorces',\n", " 'track',\n", " 'cat',\n", " 'Slavophiles',\n", " 'village',\n", " 'bounces',\n", " 'dolls',\n", " 'quack']" ] }, "execution_count": 19, "metadata": {}, "output_type": "execute_result" } ], "source": [ "jedinstvenereci = list(set(reci))\n", "jedinstvenereci[:10]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Да бисмо из листе јединствених речи издвојили потенцијалне кандидате за јунаке, користићемо се регуларним изразима. Правилност (енг. pattern) који ћемо тражити је реч почиње великим словом [A-Z] а затим је прати једно или више малих слова [a-z]+ (плус је ту да означи да очекујемо 1 или више слова из угласте заграде).\n", "\n", "О регуларним изразима можете прочитати више [овде](https://docs.python.org/3/library/re.html), али суштински, када дефинишемо *pattern* само филтрирамо листу да добијемо речи које одговарају нашим захтевима:" ] }, { "cell_type": "code", "execution_count": 20, "metadata": {}, "outputs": [], "source": [ "pattern = re.compile(r'\\b[A-Z][a-z]+\\b')\n", "potencijalni_junaci = list(filter(pattern.search, jedinstvenereci))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "У листи *potencijalni_junaci* сада се налазе сви кандидати за име јунака, можемо видети колико их је и осмотрити првих 10: " ] }, { "cell_type": "code", "execution_count": 21, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "1711" ] }, "execution_count": 21, "metadata": {}, "output_type": "execute_result" } ], "source": [ "len(potencijalni_junaci)" ] }, { "cell_type": "code", "execution_count": 22, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "['Slavophiles',\n", " 'Ivanov',\n", " 'Nothing',\n", " 'Surely',\n", " 'Elements',\n", " 'Buslaev',\n", " 'Raphaelite',\n", " 'Striving',\n", " 'Casting',\n", " 'Principles']" ] }, "execution_count": 22, "metadata": {}, "output_type": "execute_result" } ], "source": [ "potencijalni_junaci[:10]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Међу ових 10 речи, видимо неке речи које нису властита имена. То је зато што наше правило описује и све речи које се налазе на почетку реченице! Међутим, када се нека честа реч нашла на почетку реченице, очекујемо да се иста реч појављује у тексту и на неким другим местима у реченици, па ће се у листи јединствених речи наћи и у верзији исписа малим словима. То није ситуација у којој се може наћи властито име, па ћемо пробати да на тај начин умањимо листу кандидата за ликове. На пример, из претходне листе речи, можете одабрати неку од речи и да испитате да ли се она у тексту налази и када је цела написана малим словима:" ] }, { "cell_type": "code", "execution_count": 23, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "'Nothing'" ] }, "execution_count": 23, "metadata": {}, "output_type": "execute_result" } ], "source": [ "i = 2\n", "potencijalni_junaci[i]" ] }, { "cell_type": "code", "execution_count": 24, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "True" ] }, "execution_count": 24, "metadata": {}, "output_type": "execute_result" } ], "source": [ "potencijalni_junaci[i].lower() in jedinstvenereci" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Сада ћемо овај метод применити на целу листу речи потенцијалних јунака, чувајући само речи које се не налазе у остатку текста написане малим словима:" ] }, { "cell_type": "code", "execution_count": 25, "metadata": {}, "outputs": [], "source": [ "potencijalni_junaci = [rec for rec in potencijalni_junaci if rec.lower() not in jedinstvenereci]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Нова дужина листе је:" ] }, { "cell_type": "code", "execution_count": 26, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "810" ] }, "execution_count": 26, "metadata": {}, "output_type": "execute_result" } ], "source": [ "len(potencijalni_junaci)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Успели смо да преполовимо листу потенцијалних јунака овим једноставним резоновањем!" ] }, { "cell_type": "code", "execution_count": 27, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "['Slavophiles',\n", " 'Ivanov',\n", " 'Buslaev',\n", " 'Raphaelite',\n", " 'Gospel',\n", " 'Serbia',\n", " 'Sorokina',\n", " 'Pervozvanny',\n", " 'Slav',\n", " 'Shrinking']" ] }, "execution_count": 27, "metadata": {}, "output_type": "execute_result" } ], "source": [ "potencijalni_junaci[:10]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Међутим и даље нису све речи у нашој потенцијалној листи заиста јунаци, стога ћемо осмислити још пар начина да смањимо листу речи које ћемо пратити на даље. На пример, сетићемо се да се на енглеском и називи дана и називи месеци пишу првим великим словом, тако да очекујемо да су се и они нашли у листи наших потенцијалних јунака:" ] }, { "cell_type": "code", "execution_count": 28, "metadata": { "scrolled": true }, "outputs": [], "source": [ "days = ['Monday','Tuesday','Wednesday','Thursday','Friday','Saturday','Sunday']\n", "months = ['January','February','March','April','May','June','July','August','September','October','November','December']" ] }, { "cell_type": "code", "execution_count": 29, "metadata": { "scrolled": true }, "outputs": [ { "data": { "text/plain": [ "True" ] }, "execution_count": 29, "metadata": {}, "output_type": "execute_result" } ], "source": [ "months[1] in potencijalni_junaci" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Cа друге стране, нису ни сви ликови једнако битни, па је један од начина да усмеримо нашу пажњу само на ликове који се појављују више пута у текст. Погледајмо за почетак како изгледа број понављања различитих потенцијалних јунака у целом тексту." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Сада ћемо ипак претраживати целокупан текст књиге исписан малим словима (и имена потенцијалних јунака ћемо исписивати на исти начин) за случај да се неки од издвојених јунака помиње у књизи и у комбинацији када су сва слова велика (нпр. неки узвик)." ] }, { "cell_type": "code", "execution_count": 30, "metadata": {}, "outputs": [], "source": [ "zastupljenostjunaka=dict()\n", "reci_malaslova = [rec.lower() for rec in reci]\n", "for junak in potencijalni_junaci:\n", " zastupljenostjunaka[junak]=reci_malaslova.count(junak.lower())" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Да испитамо исход ове процедуре, сортираћемо и погледати првих 20 фреквентних потенцијалних јунака:" ] }, { "cell_type": "code", "execution_count": 31, "metadata": { "scrolled": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Levin 1616\n", "Vronsky 862\n", "Anna 822\n", "Kitty 673\n", "Alexey 632\n", "Alexandrovitch 571\n", "Stepan 548\n", "Arkadyevitch 548\n", "Dolly 307\n", "Ivanovitch 302\n", "Sergey 301\n", "Alexandrovna 215\n", "Darya 209\n", "Moscow 173\n", "Varenka 155\n", "Sviazhsky 137\n", "Petersburg 127\n", "Seryozha 122\n", "Oblonsky 115\n", "Konstantin 112\n" ] } ], "source": [ "sorted_counts = sorted(list(zastupljenostjunaka.items()), key=lambda x:x[1], reverse=True)\n", "for word, count in sorted_counts[:20]:\n", " print(word, count)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Уколико сте читали књигу, сада вас сигурно радује чињеница да смо дошли до главних јунака (чак и локација на којима се радња дешава) без пуно муке! " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "У посебну листу убацити само она имена која се у целој књизи појављују 5 или мање пута и елиминисаћемо их из наставка анализе. *Ово је параметар који можемо мењати и последично мењати дужину листе потенцијалних ликова.*" ] }, { "cell_type": "code", "execution_count": 32, "metadata": {}, "outputs": [], "source": [ "rarenames = [junak for junak in zastupljenostjunaka.keys() if zastupljenostjunaka[junak]<=5]" ] }, { "cell_type": "code", "execution_count": 33, "metadata": { "scrolled": false }, "outputs": [ { "data": { "text/plain": [ "643" ] }, "execution_count": 33, "metadata": {}, "output_type": "execute_result" } ], "source": [ "len(rarenames)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Има чак 643 јунака који се појављују само пар пута у књизи, што је згодно пошто ћемо фокусирати анализу на пар десетина најактивнијих јунака. Дакле, речи које желимо да уклонимо из листе потенцијалних јунака су називи дана и месеци, као и листу ретко помињаних имена:" ] }, { "cell_type": "code", "execution_count": 34, "metadata": {}, "outputs": [], "source": [ "wordstoremove = rarenames + days + months" ] }, { "cell_type": "code", "execution_count": 35, "metadata": {}, "outputs": [], "source": [ "potencijalni_junaci = [junak for junak in potencijalni_junaci if junak not in wordstoremove]" ] }, { "cell_type": "code", "execution_count": 36, "metadata": { "scrolled": true }, "outputs": [ { "data": { "text/plain": [ "164" ] }, "execution_count": 36, "metadata": {}, "output_type": "execute_result" } ], "source": [ "len(potencijalni_junaci)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Сачуваћемо сада и нову верзију речника заступљености јунака, која садржи само изабране јунаке које ћемо пратити у наставку:" ] }, { "cell_type": "code", "execution_count": 37, "metadata": {}, "outputs": [], "source": [ "zastupljenostjunaka = {junak: zastupljenostjunaka[junak] for junak in potencijalni_junaci}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Направити табеле јунака и њиховог укупног броја појављивања (именом) у тексту:" ] }, { "cell_type": "code", "execution_count": 38, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
0
Gospel6
Sorokina11
Kouzma22
Katya9
Oblonsky115
\n", "
" ], "text/plain": [ " 0\n", "Gospel 6\n", "Sorokina 11\n", "Kouzma 22\n", "Katya 9\n", "Oblonsky 115" ] }, "execution_count": 38, "metadata": {}, "output_type": "execute_result" } ], "source": [ "junaci_df = pd.DataFrame.from_dict(zastupljenostjunaka,orient='index')\n", "junaci_df.head()" ] }, { "cell_type": "code", "execution_count": 39, "metadata": {}, "outputs": [], "source": [ "junaci_df = junaci_df.rename(columns={0:'Broj pojavljivanja'})" ] }, { "cell_type": "code", "execution_count": 40, "metadata": { "scrolled": true }, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
Broj pojavljivanja
Gospel6
Sorokina11
\n", "
" ], "text/plain": [ " Broj pojavljivanja\n", "Gospel 6\n", "Sorokina 11" ] }, "execution_count": 40, "metadata": {}, "output_type": "execute_result" } ], "source": [ "junaci_df.head(2)" ] }, { "cell_type": "code", "execution_count": 41, "metadata": {}, "outputs": [], "source": [ "junaci_df = junaci_df.reset_index()" ] }, { "cell_type": "code", "execution_count": 42, "metadata": {}, "outputs": [], "source": [ "junaci_df = junaci_df.rename(columns={'index':'Ime'})" ] }, { "cell_type": "code", "execution_count": 43, "metadata": {}, "outputs": [], "source": [ "#junaci_df.to_csv('Ana_likovi_ukupan_br_pojavljivanja.csv')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Јунаци у различитим поглављима" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Да би смо могли у даљој анализи да пратимо да ли су исти ликови популарни све време, или се неки јунаци појављују епизодно, или повремено, забележићемо на следећи начин и број понављања имена сваког од јунака у сваком поглављу појединачно, као и позицију у оквиру поглавља у којој је дати јунак поменут (ово ћемо у будућности користити као меру блискости два јунака - за јунаке који се помињу у истој реченици ћемо претпоставити да су блиски за разлику од јунака који се помињу на различитим странама)." ] }, { "cell_type": "code", "execution_count": 44, "metadata": {}, "outputs": [], "source": [ "lista_junaka_malaslova = [junak.lower() for junak in potencijalni_junaci]\n", "\n", "pojavljivanja_junaka = []\n", "for chapter in chapters:\n", " pojavljivanja_temp = []\n", " reciupoglavlju = ukloni_znake_interpunkcije(chapter).lower().split()\n", " for junak in lista_junaka_malaslova:\n", " pojavljivanja_temp.append(reciupoglavlju.count(junak))\n", " pojavljivanja_junaka.append(pojavljivanja_temp)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Користећи ове податке, направићемо одговарајућу табелу:" ] }, { "cell_type": "code", "execution_count": 45, "metadata": { "scrolled": true }, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
GospelSorokinaKouzmaKatyaOblonskyAlexandrovnaLizavetaLvovAlexanderLvova...LilyVredeMihailovPavaVozdvizhenskoeAgafeaLidiaFlerovMahotinGagin
00000100000...0000000000
10000040000...0000000000
20000100000...0000000000
30000040000...0000000000
400002100000...0000000000
\n", "

5 rows × 164 columns

\n", "
" ], "text/plain": [ " Gospel Sorokina Kouzma Katya Oblonsky Alexandrovna Lizaveta Lvov \\\n", "0 0 0 0 0 1 0 0 0 \n", "1 0 0 0 0 0 4 0 0 \n", "2 0 0 0 0 1 0 0 0 \n", "3 0 0 0 0 0 4 0 0 \n", "4 0 0 0 0 21 0 0 0 \n", "\n", " Alexander Lvova ... Lily Vrede Mihailov Pava Vozdvizhenskoe Agafea \\\n", "0 0 0 ... 0 0 0 0 0 0 \n", "1 0 0 ... 0 0 0 0 0 0 \n", "2 0 0 ... 0 0 0 0 0 0 \n", "3 0 0 ... 0 0 0 0 0 0 \n", "4 0 0 ... 0 0 0 0 0 0 \n", "\n", " Lidia Flerov Mahotin Gagin \n", "0 0 0 0 0 \n", "1 0 0 0 0 \n", "2 0 0 0 0 \n", "3 0 0 0 0 \n", "4 0 0 0 0 \n", "\n", "[5 rows x 164 columns]" ] }, "execution_count": 45, "metadata": {}, "output_type": "execute_result" } ], "source": [ "pojavljivanj_df = pd.DataFrame(pojavljivanja_junaka,columns=list(junaci_df.Ime))\n", "pojavljivanj_df.head()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "У овој табели свака колона одговара једном јунаку, док различити редови садрже информације о броју појављивања сваког од јунака у датом поглављу." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Сачуваћемо ову табелу у csv фајлу како бисмо јој лакше приступали и анализирали даље овај текст." ] }, { "cell_type": "code", "execution_count": 46, "metadata": {}, "outputs": [], "source": [ "pojavljivanj_df.to_csv('Ana_likovi_pojavljivanja.csv',index=False)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "А сада ћемо направити табелу у којој се налазе сва имена јунака и локације у тексту на којима се појављују:" ] }, { "cell_type": "code", "execution_count": 47, "metadata": {}, "outputs": [], "source": [ "junak = []\n", "pozicija = []\n", "for i in range(len(reci_malaslova)):\n", " if reci_malaslova[i] in lista_junaka_malaslova:\n", " junak.append(reci_malaslova[i])\n", " pozicija.append(i)" ] }, { "cell_type": "code", "execution_count": 48, "metadata": {}, "outputs": [], "source": [ "pozicije_junaka = pd.DataFrame()" ] }, { "cell_type": "code", "execution_count": 49, "metadata": { "scrolled": true }, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
Ime junakaPozicija junaka
0oblonskys20
1french36
2oblonskys142
3english170
4stepan220
\n", "
" ], "text/plain": [ " Ime junaka Pozicija junaka\n", "0 oblonskys 20\n", "1 french 36\n", "2 oblonskys 142\n", "3 english 170\n", "4 stepan 220" ] }, "execution_count": 49, "metadata": {}, "output_type": "execute_result" } ], "source": [ "pozicije_junaka['Ime junaka'] = junak\n", "pozicije_junaka['Pozicija junaka'] = pozicija\n", "pozicije_junaka.head()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Kако смо у овом процесу све преписали малим словима - овде ћемо то исправити користећи функцију **capitalize**:" ] }, { "cell_type": "code", "execution_count": 50, "metadata": { "scrolled": true }, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
Ime junakaPozicija junaka
0Oblonskys20
1French36
\n", "
" ], "text/plain": [ " Ime junaka Pozicija junaka\n", "0 Oblonskys 20\n", "1 French 36" ] }, "execution_count": 50, "metadata": {}, "output_type": "execute_result" } ], "source": [ "pozicije_junaka['Ime junaka'] = pozicije_junaka['Ime junaka'].apply(str.capitalize)\n", "pozicije_junaka.head(2)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Коначно сачуваћемо ове податке за даљу анализу:" ] }, { "cell_type": "code", "execution_count": 51, "metadata": {}, "outputs": [], "source": [ "pozicije_junaka.to_csv('Ana_pozicije_junaka.csv',index=False)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "У овој радној свесци:\n", "- учитали смо и пречистили текст књиге\n", "- користили смо регуларне изразе да издвојимо јунаке\n", "- избројали смо појављивања јунака и позиције појављивања у оквиру књиге.\n", "\n", "Детекција ликова у тексту није једноставан задатак зато што су властита имена рановрсна, мењају се с временом и врло често могу бити у питању речи које имају своје значење, те се појављују у тексту и мимо именовања ликова. Међутим, када нам није потребна изузетна прецизност, једноставне процедуре попут ових приказаних могу бити довољне. Данас, задатак детекције јунака је најчешће намењен алгоритмима заснованим на машинском учењу за које је потребно припремити изузетно добре тренинг сетове у којима су детекцију ликова урадили људи." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Задатак" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "1. Одлука да се фокусирамо само на ликове који се појављују више од 5 пута у тексту је помоћна процена. Са једне стране, желимо да смањимо скуп ликова које ћемо анализирати у наставку, са друге стране не желимо да изгубимо неке ликове који иако се појављују мало, могу играти битну улогу у повезивању јунака. Стога предлажемо да варирате број појављивања у тексту који користите као филтер и избројте колико потенцијалних јунака остаје у којој од опција. Нацртајте график броја потенцијалних јунака за различита филтрирања од 0 (без филтрирања по броју појављивања имена) до 10 (где елиминишете сва имена која су поменута мање од 10 пута). Можете сачувати и неке од табела које су настале овим другачијим филтрирањем да видите како се резултати у наредној радној свесци разликују када користите њих у односу на ове које смо припремили заједно." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.9.1" } }, "nbformat": 4, "nbformat_minor": 2 }