1 file changed, 106 insertions(+), 6 deletions(-)
final.py | 112 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----

modified   final.py
@@ -45,8 +45,12 @@ import nltk
# In[92]:

import nltk
-huvel = nltk.corpus.CategorizedPlaintextCorpusReader("/home/lukes/edu/python/huvel",r".*\.txt",
-                                                     encoding="utf8",cat_pattern=r".*-(.*),")
# Čistě formální drobnost: za čárkou píšeme v Pythonu mezeru.
huvel = nltk.corpus.CategorizedPlaintextCorpusReader("/home/lukes/edu/python/huvel", r".*\.txt",
                                                     encoding="utf8", cat_pattern=r".*-(.*),")
# Když je řádek před začátkem závorky hodně dlouhý, často se příkaz pro lepší čitelnost píše takto:
huvel = nltk.corpus.CategorizedPlaintextCorpusReader(
    "/home/lukes/edu/python/huvel", r".*\.txt", encoding="utf8", cat_pattern=r".*-(.*),")


# In[93]:
@@ -121,14 +125,32 @@ fd

# In[98]:

# Import stačí v rámci souboru provést jen jednou, takže když máte `import nltk` a `import csv` v
# jedné z předchozích buněk, které jste už vyhodnotila, není nutné je zde provádět znovu (taky to
# není problém, Python pozná, že už jste import provedla a nebude ho opakovat). Na druhou stranu,
# pokud buňky vyhodnocujete většinou spíš na přeskáčku, tak chápu, že chcete mít importy pro jistotu
# v každé buňce, která je využívá (když pak soubor otevřete znovu, můžete rovnou vyhodnotit
# kteroukoli buňku, nemusíte nejdřív hledat buňky s importy).

import nltk
import csv

# Nebál bych se v zájmu čitelnosti občas přidat prázdný řádek (typicky se tak oddělují jednotlivé
# funkce nebo importy) :)

def load_fdist(path):
    fdist = nltk.FreqDist()
    with open(path, newline='') as csvfile:
        csv_in = csv.reader(csvfile, delimiter=';', quotechar='"')
        for row in csv_in:
-            fdist[row[1]]=int(row[2]) #pozor, musim ulozit cislo a ne retezec!
            # Je to sice jen zvyklost, ne nutnost, ale `=` v Pythonu píšeme bez mezer, pokud je ve
            # volání funkce (`foo(bar="baz")`) a s mezerami pokud ne (`foo = "baz"`). (Tyhle
            # formální drobnosti, které nemají vliv na výstup programu, znějí jako hloupost, ale váš
            # kód se pak bude mnoheme lépe číst ostatním programátorům, a vám se bude mnohem lépe
            # číst zase cizí kód, pokud tyhle zvyklosti budeme všichni víceméně dodržovat :) A vím,
            # že jsme na ně v semestru nekladli příliš velký důraz, neberte to tedy vůbec jako výtku
            # nebo tak něco.)
            fdist[row[1]] = int(row[2]) #pozor, musim ulozit cislo a ne retezec!
    return fdist


@@ -269,10 +291,42 @@ fd.N()

from scipy.stats import chi2_contingency
def is_keyword_candidate(word, text_fd, ref_fd): #vratim vysledek statisticke vyznamnosti podle prikladu v zadani, zmenim jen hladinu z 0.05 na 0.001
    # Pozor, místo `text_fd.N()` je potřeba použít `text_fd.N() - text_fd[word.lower()]` (a obdobně
    # pro `ref_fd`), v kontingenční tabulce potřebujeme počet výskytů cílového slova a počet výskytů
    # ostatních slov, ne celkovou délku textu. (Byť u někdy -- třeba u velkého korpusu a malé
    # frekvence cílového slova -- může být rozdíl zanedbatelný.)
    #
    # Další věc je, že testujete pouze, zda se poměr v textu statisticky významně *liší* od poměru v
    # korpusu, takže vrátíte `True` i v případě, že bude *nižší*. O slově, které je v textu významně
    # méně zastoupeno než v korpusu, bychom jako o kandidátovi na klíčové asi uvažovat neměli.
    #
    # Také bych operaci kvůli přehlednosti rozepsal do více kroků a pojmenoval si mezivýsledky (byť
    # jinak je naprosto v pořádku). Viz níže funkce `is_keyword_candidate_upraveno()` pro představu,
    # jak by všechny výše navržené úpravy mohly vypadat.
    p=chi2_contingency([[text_fd[word.lower()], text_fd.N()], [ref_fd[word.lower()], ref_fd.N()]], lambda_="log-likelihood")[1]
    # Velmi dobře! Někdy mají začátečníci tendenci tento krok rozepisovat pomocí `if`, přitom
    # porovnávací operátory (`<`, `>=` a spol.) rovnou vracejí pravdivostní hodnoty `True` a
    # `False` (což zde správně využíváte).
    return p<0.001

-
def is_keyword_candidate_upravene(word, text_fd, ref_fd):
    word = word.lower()
    # je-li frekvence v textu menší než v ref. korpusu, můžeme
    # rovnou rozhodnout, že slovo nebude klíčové; musíme porovnávat
    # relativní frekvence, protože text a korpus budou skoro jistě
    # jinak dlouhé
    if text_fd.freq(word) < ref_fd.freq(word):
        return False
    # nyní dopočítáme hodnoty pro kontingenční tabulku
    # počet výskytů slova `word` v textu a ref. korpusu
    text_freq = text_fd[word]
    ref_freq = ref_fd[word]
    # počet slov jiných než `word` v textu a v ref. korpusu
    text_zbytek = text_fd.N() - text_freq
    ref_zbytek = ref_fd.N() - ref_freq
    p = chi2_contingency([[text_freq, text_zbytek], [ref_freq, ref_zbytek]],
                         lambda_="log-likelihood")[1]
    return p < 0.001

# **OVĚŘENÍ:** Otestujte si, zda vaše funkce dobře funguje. V následující buňce jsou proporce v textu i v referenčním korpusu stejné, výsledek by tedy měl být `False`.

@@ -296,8 +350,13 @@ is_keyword_candidate("kočka",text_fd_test,ref_fd_test )
text_fd_test = nltk.FreqDist()
ref_fd_test = nltk.FreqDist()

-text_fd_test["kočka"] = 90
-text_fd_test["zbytek"] = 1910
# Zkuste zde schválně upravit hodnoty tak, aby relativní frekvence v textu byla *nižší* než v
# referenčním korpusu; uvidíte, že původní verze funkce `is_keyword_candidate()` vrátí `True` i v
# tomto případě (bude-li rozdíl ve frekvencích dostatečně velký na to, aby byl statisticky
# významný).

text_fd_test["kočka"] = 10
text_fd_test["zbytek"] = 1990
ref_fd_test["kočka"] = 3000
ref_fd_test["zbytek"] = 197000

@@ -321,7 +380,11 @@ is_keyword_candidate("kočka", text_fd_test, ref_fd_test)
#DIN je hodnota ziskana jako 100x podil rozdilu a souctu relativnich frekvenci v testovanem textu a korpusu
#relativni frekvence je pocet vyskytu v textu / celkovy pocet slov
def din(word, text_fd, ref_fd):
    # Jen bych přidal kvůli čitelnosti mezi operátory pár mezer, ale jinak skvěle! :)
    word2=word.lower()    #ulozim si slovo word jako lower case
    # Případně mají ještě objekty typu `nltk.FreqDist` metodu `.freq()`, která relativní frekvenci
    # spočítá rovnou:
    # Rel_ft = text_fd.freq(word2)
    Rel_ft=text_fd[word2]/text_fd.N() #relativni cetnost slova v textu
    Rel_fc=ref_fd[word2]/ref_fd.N() #relativni cetnost slova v referencnim korpusu
    DIN=(Rel_ft-Rel_fc)/(Rel_ft+Rel_fc)*100   #spocitam DIN
@@ -432,13 +495,38 @@ def keywords(text, ref_fd):
    for token in text: #projdu oznackovany text
        #fdist[token[0].lower()]+=1 # prictu jednicku ke kazdemu vyskytu slova a ulozim do distribuce
        fdist[token[0]]+=1 #pokud chci prevod na mala pismena, zakomentuji tento radek a odkomentuji ten nad nim a naopak

    # Výsledek je správně, ale obecně tento způsob inicializace frekvenční distribuce používáme spíš
    # v případě, že máme k dispozici předpočítaná data. Pokud máme k dispozici seznam tokenů, je
    # výhodnější (rychlejší, kratší, přehlednější...) předat tento seznam rovnou konstruktoru:
    # `fdist = nltk.FreqDist(text)`.

    #uWords={token[0].lower():token[2] for token in text} #ulozim si unikatni slova s jejich znackami
    uWords={token[0]:token[2] for token in text}#pokud chci prevod na mala pismena, zakomentuji tento radek a odkomentuji ten nad nim a naopak

    # To je hezký nápad jak se vypořádat s omezením klíčových slov na autosémantika! Jen pozor:
    # pokud se nějaký slovní tvar vyskytuje v textu s více značkami, tak ve slovníku `uWords`
    # zachováme jen tu poslední odpovídající značku (pokaždé když narazíme na nějaký slovní tvar,
    # tak pokud už ve slovníku je, jeho záznam přepíšeme tím novým výskytem).

    # Takže teoreticky vám může utéct potenciální klíčové slovo, které se v textu objevuje homonymně
    # jako autosémantikum i synsémantikum, pokud je poslední výskyt synsémantický. Na druhou stranu,
    # je otázka, jestli takové případy vůbec existují (záleží na použitém systému morfologického
    # značkování), a pokud bychom chtěli být opravdu důslední, museli bychom homonymii ošetřit i na
    # straně referenčních frekvenčních hodnot (a rozlišit tak např. frekvenci tvaru "stát" jako
    # slovesa vs. jako substantiva).

    # Jinak je vaše řešení obecně moc pěkné, stručné a přehledné.

    for word in fdist: #projdu distribuci
        if fdist[word]>2: #zajimaji me slova, ktera se vysytnou alespon 3x
            if re.match("[NAVD]", uWords[word]):  #zajimaji me jen substantiva, adjektiva, adverbia, verba
                if is_keyword_candidate(word,fdist,ref_fd): #zjistim, jestli je frekvence slova statisticky vyznamne vyssi
                    kw.append((word,din(word,fdist,ref_fd),fdist[word])) #pokud ano, pridam si do seznamu slovo, DIN a pocet vyskytu v textu
    # Funkce `sorted()` vytvoří nový seznam, do něhož vloží stejné položky, jen seřazené, takže po
    # tu dobu, co existují oba, zabírají společně dvakrát tolik místa v paměti. Pokud původní pořadí
    # už k ničemu nepotřebujete, bývá výhodnější použít metodu `.sort()` (takže `kw.sort(...)`),
    # která položky "zpřehází" úsporněji v rámci původního seznamu.
    return sorted(kw, key=lambda podle: podle[1], reverse=True) #seradim vysledny seznam podle DIN

huvel = nltk.corpus.CategorizedPlaintextCorpusReader("/home/lukes/edu/python/huvel",r".*\.txt",
@@ -496,6 +584,14 @@ sorted(seznam, key=itemgetter(0, 2))
# Pricemz nektera slova slova se v referencnim korpusu po takove uprave ani nevyskytuji a tudiz je DIN=100 (napr. KSC vs. ksc)
# Musel by se upravit referencni korpus (sloucit slova a jejich vyskyty)

# Ano, velmi dobře! Při analýze klíčových slov většinou právě začínáme od case sensitive analýzy
# slovních tvarů, protože se stává, že specifický je konkrétní zápis (např. relativně běžné slovo
# může být i jméno firmy, pokud je psané s velkým počátečním písmenem) nebo tvar (viz Husákovy
# projevy a tvar "minulém", který se vyskytuje v kolokaci "v minulém roce"). Teprve pak případně
# přistupujeme ke case insensitive analýze, případně analýze lemmat (kde by myslím např. ve vztahu k
# těm Husákovým projevům lemma "minulý" jako nijak zvlášť klíčové nevyvstalo, jde tu skutečně o
# klíčovost onoho specifického tvaru). V té chvíli je pak pochopitelně třeba příslušně upravit
# referenční data.

with open("/home/lukes/edu/python/huvel/1975-Husák,_Gustáv.txt") as file:
    txt = file.read()
@@ -574,3 +670,7 @@ print_din(kw,90)
# do parametru funkce is_keyword_candidate by slo doplnit:
#   hladina alfa
# otazkou je, zda vyhodnocovat slova jako takova, nebo pracovat s lemmaty - vysledky by byly objektivnejsi

# Pěkné nápady! Ohledně lemmat viz výše -- ale kdybyste psala celou vypilovanou knihovnu na práci s
# klíčovými slovy, rozhodně by bylo šikovné mít možnost jednoduše (jedním parametrem) přepínat mezi
# case sensitive analýzou, case insensitive a lemmaty :)

Generated by David Lukeš using scpaste at Mon Mar 6 15:14:37 2017. CET. (original)