In [1]:
# Imports

import os
import string
import math
import re
from collections import Counter
from pprint import pprint
import html    

import numpy as np
from sklearn.feature_extraction.text import CountVectorizer

from cltk.corpus.latin import latinlibrary
from cltk.tokenize.word import WordTokenizer
from cltk.stem.latin.j_v import JVReplacer
In [2]:
# Setup CLTK tools

word_tokenizer = WordTokenizer('latin')
replacer = JVReplacer()
In [3]:
# Setup files

files = latinlibrary.fileids()

# Typical setup
#files = [file for file in files]

# Filter for classical texts
classical = []

remove = ["The Bible","Ius Romanum","Papal Bulls","Medieval Latin","Christian Latin","Christina Latin","Neo-Latin","The Miscellany","Contemporary Latin"]

for file in files:
    raw = latinlibrary.raw(file)
    if not any(x in raw for x in remove):
        classical.append(file)

files = classical
In [4]:
# Preprocess texts

def preprocess(text):    

    text = html.unescape(text) # Handle html entities
    text = re.sub(r' ?', ' ',text) #  stripped incorrectly in corpus?
    text = re.sub('\x00',' ',text) #Another space problem?
    
    text = text.lower()
    text = replacer.replace(text) #Normalize u/v & i/j
    
    punctuation ="\"#$%&\'()*+,-/:;<=>@[\]^_`{|}~.?!«»"
    translator = str.maketrans({key: " " for key in punctuation})
    text = text.translate(translator)
    
    translator = str.maketrans({key: " " for key in '0123456789'})
    text = text.translate(translator)

    remove_list = [r'\bthe latin library\b',
                   r'\bthe classics page\b',
                   r'\bneo-latin\b', 
                   r'\bmedieval latin\b',
                   r'\bchristian latin\b',
                   r'\bchristina latin\b',
                   r'\bpapal bulls\b',
                   r'\bthe miscellany\b',
                  ]

    for pattern in remove_list:
        text = re.sub(pattern, '', text)
    
    text = re.sub('[ ]+',' ', text) # Remove double spaces
    text = re.sub('\s+\n+\s+','\n', text) # Remove double lines and trim spaces around new lines
    
    return text
In [5]:
# Make list of texts

raw_files = []

for file in files:
    raw = latinlibrary.raw(file)
    raw = preprocess(raw)
    if len(raw) < 1000:
        pass
    else:
        raw_tokens = raw.split()
        raw = " ".join(raw_tokens[50:-50])
        raw_files.append(raw)

Following [Alajmi 2012]

In [6]:
# Make document-term matrix and vocabulary

vectorizer = CountVectorizer(input='content')
dtm = vectorizer.fit_transform(raw_files)
dtm = dtm.toarray()

vocab = vectorizer.get_feature_names()
vocab = np.array(vocab)
In [7]:
M = len(vocab)
N= len(raw_files)
In [8]:
# Make array of probabilities per book

raw_lengths = [len(tokens.split()) for tokens in raw_files]
l = np.array(raw_lengths)
ll = l.reshape(len(l),1)

probs = dtm/ll

P=probs
In [9]:
# Calculate mean probability
# i.e. Sum of probabilities for each word / number of documents

probsum = np.ravel(probs.sum(axis=0))
MP = probsum/N
In [10]:
# Make array of bar probability

length = sum(raw_lengths)
barprobs = dtm/length
bP=barprobs
In [11]:
variance = (P-bP) ** 2
varsum = np.ravel(variance.sum(axis=0))
VP = varsum/N
In [12]:
cutoff = 100
In [13]:
# Return top counts

freq = np.ravel(dtm.sum(axis=0))
wordfreq = list(zip(vocab,freq))
wordfreq.sort(key=lambda x: x[1], reverse=True)
wf = [item[0] for item in wordfreq]
wf = wf[:cutoff]
print(wf)
['et', 'in', 'non', 'est', 'ut', 'cum', 'ad', 'quod', 'qui', 'si', 'sed', 'quam', 'quae', 'esse', 'de', 'ex', 'nec', 'aut', 'quid', 'se', 'atque', 'hoc', 'me', 'etiam', 'ab', 'te', 'enim', 'per', 'sunt', 'ac', 'ne', 'quo', 'iam', 'mihi', 'haec', 'tamen', 'id', 'sit', 'neque', 'tibi', 'quidem', 'ego', 'ita', 'nam', 'autem', 'eius', 'nihil', 'nunc', 'erat', 'quoque', 'eo', 'ea', 'tu', 'modo', 'tum', 'quem', 'quibus', 'inter', 'ille', 'esset', 'qua', 'fuit', 'sic', 'ipse', 'pro', 'hic', 'omnia', 'nisi', 'uel', 'illa', 'ubi', 'ante', 'tam', 'res', 'sibi', 'sine', 'an', 'eum', 'his', 'uero', 'causa', 'quia', 'quos', 'quis', 'at', 'omnes', 'apud', 'magis', 'nos', 'post', 'rem', 're', 'dum', 'omnibus', 'igitur', 'potest', 'tantum', 'inquit', 'deinde', 'itaque']
In [14]:
# Return top mean prob

test = list(zip(vocab,MP))
test.sort(key=lambda x: x[1], reverse=True)
mp = [item[0] for item in test]
mp = mp[:cutoff]
print(mp)
['et', 'in', 'est', 'non', 'cum', 'ut', 'ad', 'quod', 'qui', 'sed', 'si', 'quae', 'quam', 'nec', 'ex', 'de', 'esse', 'se', 'aut', 'atque', 'per', 'quid', 'ab', 'sunt', 'me', 'hoc', 'te', 'etiam', 'quo', 'ne', 'mihi', 'iam', 'enim', 'ac', 'haec', 'tibi', 'tamen', 'eius', 'neque', 'nam', 'id', 'sit', 'ego', 'nunc', 'erat', 'quoque', 'ille', 'inter', 'ita', 'quidem', 'fuit', 'quem', 'autem', 'ipse', 'quibus', 'eo', 'tu', 'esset', 'qua', 'sic', 'nihil', 'hic', 'ea', 'tum', 'ante', 'modo', 'pro', 'sibi', 'eum', 'illa', 'his', 'nisi', 'omnia', 'uel', 'post', 'res', 'sine', 'tam', 'bellum', 'ubi', 'quos', 'at', 'apud', 'omnes', 'quis', 'dum', 'uero', 'causa', 'an', 'tantum', 'omnibus', 'cui', 'cuius', 'rem', 'deinde', 'sub', 'nos', 'primum', 'sua', 'igitur']
In [15]:
# Return top variance prob

test = list(zip(vocab,VP))
test.sort(key=lambda x: x[1], reverse=True)
vp = [item[0] for item in test]
vp = vp[:cutoff]
print(vp)
['et', 'in', 'est', 'non', 'cum', 'ut', 'ad', 'quod', 'qui', 'si', 'sed', 'quae', 'quam', 'de', 'nec', 'ex', 'me', 'esse', 'te', 'se', 'aut', 'per', 'atque', 'quid', 'sunt', 'ab', 'mihi', 'tibi', 'hoc', 'ac', 'enim', 'etiam', 'ne', 'iam', 'quo', 'eius', 'ego', 'neque', 'bellum', 'id', 'autem', 'haec', 'esset', 'erat', 'tamen', 'cos', 'sit', 'inter', 'quoque', 'tu', 'fuit', 'nam', 'quidem', 'nunc', 'eo', 'quem', 'ita', 'ille', 'senatu', 'qua', 'quibus', 'eum', 'ipse', 'ante', 'hic', 'aduersus', 'senatus', 'nihil', 'ea', 'res', 'sic', 'antiocho', 'sibi', 'post', 'tum', 'uel', 'deinde', 'his', 'rem', 'illa', 'causa', 'milia', 'apud', 'pro', 'cn', 'inquit', 'contra', 'modo', 'exercitu', 'omnia', 'nisi', 'gel', 'eorum', 'marius', 'bello', 'sine', 'philippo', 'regem', 'mea', 'fr']
In [16]:
with np.errstate(divide='ignore', invalid='ignore'):
    logprobs = np.where(probs != 0, np.log10(1/probs), 0)
ent = probs * logprobs
In [17]:
ents = np.ravel(ent.sum(axis=0))
entrank = list(zip(vocab,ents))
entrank.sort(key=lambda x: x[1], reverse=True)
e = [item[0] for item in entrank]
e = e[:cutoff]
print(e)
['et', 'in', 'est', 'non', 'cum', 'ut', 'ad', 'quod', 'qui', 'sed', 'si', 'quae', 'quam', 'nec', 'ex', 'de', 'esse', 'se', 'aut', 'per', 'atque', 'quid', 'ab', 'sunt', 'hoc', 'quo', 'etiam', 'ne', 'me', 'te', 'iam', 'enim', 'ac', 'mihi', 'haec', 'tamen', 'tibi', 'nam', 'eius', 'neque', 'sit', 'nunc', 'id', 'erat', 'ille', 'quem', 'quoque', 'inter', 'ita', 'fuit', 'quidem', 'ego', 'ipse', 'quibus', 'autem', 'qua', 'eo', 'sic', 'esset', 'tu', 'nihil', 'hic', 'ea', 'tum', 'modo', 'ante', 'pro', 'sibi', 'omnia', 'his', 'nisi', 'illa', 'uel', 'post', 'res', 'eum', 'sine', 'tam', 'quos', 'ubi', 'omnes', 'at', 'dum', 'quis', 'apud', 'uero', 'causa', 'bellum', 'tantum', 'an', 'cui', 'omnibus', 'cuius', 'primum', 'nos', 'sua', 'sub', 'rem', 'magis', 'deinde']
In [18]:
def borda_sort(lists):
    ### From http://stackoverflow.com/a/30259368/1816347 ###
    scores = {}
    for l in lists:
        for idx, elem in enumerate(reversed(l)):
            if not elem in scores:
                scores[elem] = 0
            scores[elem] += idx
    return sorted(scores.keys(), key=lambda elem: scores[elem], reverse=True)

lists = [wf, mp, vp, e]
In [19]:
print(borda_sort(lists))
['et', 'in', 'est', 'non', 'cum', 'ut', 'ad', 'quod', 'qui', 'sed', 'si', 'quae', 'quam', 'nec', 'de', 'ex', 'esse', 'se', 'aut', 'atque', 'quid', 'per', 'me', 'ab', 'sunt', 'te', 'hoc', 'etiam', 'ne', 'quo', 'enim', 'mihi', 'ac', 'iam', 'tibi', 'haec', 'tamen', 'neque', 'eius', 'id', 'sit', 'ego', 'nam', 'erat', 'nunc', 'quoque', 'autem', 'quidem', 'ita', 'inter', 'ille', 'quem', 'fuit', 'eo', 'esset', 'tu', 'quibus', 'ipse', 'qua', 'nihil', 'ea', 'sic', 'hic', 'tum', 'ante', 'modo', 'pro', 'sibi', 'eum', 'illa', 'uel', 'res', 'his', 'omnia', 'nisi', 'bellum', 'post', 'sine', 'tam', 'ubi', 'causa', 'apud', 'quos', 'cos', 'at', 'omnes', 'quis', 'uero', 'an', 'senatu', 'rem', 'dum', 'aduersus', 'senatus', 'deinde', 'antiocho', 'tantum', 'omnibus', 'nos', 'quia', 'milia', 'cui', 'inquit', 'cn', 'cuius', 'contra', 'magis', 'exercitu', 're', 'primum', 'gel', 'sub', 'eorum', 'marius', 'sua', 'igitur', 'bello', 'potest', 'philippo', 'regem', 'mea', 'fr', 'itaque']

Other Latin stopword lists

In [20]:
tesserae_base = ['qui', 'quis', 'et', 'sum', 'in', 'is', 'non', 'hic', 'ego', 'ut']
In [22]:
# Cf. http://www.perseus.tufts.edu/hopper/stopwords
# Same as the list w. the following:
# from cltk.stop.latin.stops import STOPS_LIST
perseus = ['ab', 'ac', 'ad', 'adhic', 'aliqui', 'aliquis', 'an', 'ante', 'apud', 'at', 'atque', 'aut', 'autem', 'cum', 'cur', 'de', 'deinde', 'dum', 'ego', 'enim', 'ergo', 'es', 'est', 'et', 'etiam', 'etsi', 'ex', 'fio', 'haud', 'hic', 'iam', 'idem', 'igitur', 'ille', 'in', 'infra', 'inter', 'interim', 'ipse', 'is', 'ita', 'magis', 'modo', 'mox', 'nam', 'ne', 'nec', 'necque', 'neque', 'nisi', 'non', 'nos', 'o', 'ob', 'per', 'possum', 'post', 'pro', 'quae', 'quam', 'quare', 'qui', 'quia', 'quicumque', 'quidem', 'quilibet', 'quis', 'quisnam', 'quisquam', 'quisque', 'quisquis', 'quo', 'quoniam', 'sed', 'si', 'sic', 'sive', 'sub', 'sui', 'sum', 'super', 'suus', 'tam', 'tamen', 'trans', 'tu', 'tum', 'ubi', 'uel', 'uero', 'unus', 'ut']

References

  • Alajmi, A., Saad, E.M., and R.R. Darwish. 2012. "Toward an Arabic Stop-Words List Generation," International Journal of Computer Applications 48(8): 8-13.